mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-09-04 23:18:44 +08:00
perf(glass): complete phase three optimization (#580)
This commit is contained in:
@@ -22,15 +22,20 @@ describe('glass optics geometry', () => {
|
||||
expect(getGlassCoverScale(900, 1600, 2400, 1600)).toEqual({ x: 0.375, y: 1 })
|
||||
})
|
||||
|
||||
it('keeps high quality optics while reducing the media-dense recommendation budget', () => {
|
||||
it('keeps the selected optical quality on every route', () => {
|
||||
expect(getGlassOpticalRenderProfile('high', '/dashboard')).toEqual({
|
||||
bufferQuality: 'high',
|
||||
textureLimit: 3072,
|
||||
textureSource: 'wallpaper',
|
||||
})
|
||||
expect(getGlassOpticalRenderProfile('high', '/recommend?source=tmdb')).toEqual({
|
||||
bufferQuality: 'balanced',
|
||||
textureLimit: 2048,
|
||||
bufferQuality: 'high',
|
||||
textureLimit: 3072,
|
||||
textureSource: 'wallpaper',
|
||||
})
|
||||
expect(getGlassOpticalRenderProfile('high', '/subscribe/movie')).toEqual({
|
||||
bufferQuality: 'high',
|
||||
textureLimit: 3072,
|
||||
textureSource: 'wallpaper',
|
||||
})
|
||||
expect(getGlassOpticalRenderProfile('balanced', '/dashboard')).toEqual({
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import { findNearestScrollTarget, invalidateScrollTargetCache } from '@/utils/scrollTarget'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
describe('findNearestScrollTarget', () => {
|
||||
beforeEach(() => {
|
||||
invalidateScrollTargetCache()
|
||||
document.body.innerHTML = ''
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks()
|
||||
})
|
||||
|
||||
it('returns the nearest scrollable ancestor', () => {
|
||||
const scrollable = document.createElement('div')
|
||||
const wrapper = document.createElement('div')
|
||||
const grid = document.createElement('div')
|
||||
scrollable.style.overflowY = 'auto'
|
||||
scrollable.append(wrapper)
|
||||
wrapper.append(grid)
|
||||
document.body.append(scrollable)
|
||||
|
||||
expect(findNearestScrollTarget(grid)).toBe(scrollable)
|
||||
})
|
||||
|
||||
it('reuses ancestor results across sibling grids', () => {
|
||||
const getComputedStyle = vi.spyOn(window, 'getComputedStyle')
|
||||
const wrapper = document.createElement('div')
|
||||
const firstGrid = document.createElement('div')
|
||||
const secondGrid = document.createElement('div')
|
||||
wrapper.append(firstGrid, secondGrid)
|
||||
document.body.append(wrapper)
|
||||
|
||||
expect(findNearestScrollTarget(firstGrid)).toBe(window)
|
||||
const callsAfterFirstGrid = getComputedStyle.mock.calls.length
|
||||
expect(findNearestScrollTarget(secondGrid)).toBe(window)
|
||||
|
||||
expect(getComputedStyle).toHaveBeenCalledTimes(callsAfterFirstGrid)
|
||||
})
|
||||
|
||||
it('recomputes targets after cache invalidation', () => {
|
||||
const wrapper = document.createElement('div')
|
||||
const grid = document.createElement('div')
|
||||
wrapper.append(grid)
|
||||
document.body.append(wrapper)
|
||||
|
||||
expect(findNearestScrollTarget(grid)).toBe(window)
|
||||
|
||||
wrapper.style.overflowY = 'auto'
|
||||
invalidateScrollTargetCache()
|
||||
|
||||
expect(findNearestScrollTarget(grid)).toBe(wrapper)
|
||||
})
|
||||
})
|
||||
@@ -24,7 +24,7 @@ export interface GlassOpticalBufferSize {
|
||||
}
|
||||
|
||||
export interface GlassOpticalRenderProfile {
|
||||
/** 实际内部缓冲档位;媒体密集场景可保留高质量光学但降低合成分辨率。 */
|
||||
/** 光学层内部缓冲使用的质量档位。 */
|
||||
bufferQuality: GlassOpticalQuality
|
||||
/** 活动壁纸进入 GPU 前的最长边限制。 */
|
||||
textureLimit: number
|
||||
@@ -32,16 +32,14 @@ export interface GlassOpticalRenderProfile {
|
||||
textureSource: 'auto' | 'procedural' | 'wallpaper'
|
||||
}
|
||||
|
||||
/** 按质量与场景分配合成预算,避免推荐页海报解码与高分辨率光学层争抢资源。 */
|
||||
/** 质量决定合成缓冲与纹理上限;路由只切换纹理来源,不改变质量档位。 */
|
||||
export function getGlassOpticalRenderProfile(
|
||||
quality: GlassOpticalQuality,
|
||||
routeKey: string,
|
||||
): GlassOpticalRenderProfile {
|
||||
const mediaDenseRoute = routeKey.startsWith('/recommend')
|
||||
|
||||
return {
|
||||
bufferQuality: quality === 'high' && !mediaDenseRoute ? 'high' : 'balanced',
|
||||
textureLimit: quality === 'high' && !mediaDenseRoute ? 3072 : 2048,
|
||||
bufferQuality: quality,
|
||||
textureLimit: quality === 'high' ? 3072 : 2048,
|
||||
textureSource: routeKey.startsWith('/login') ? 'auto' : 'wallpaper',
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
export type ScrollTarget = Window | HTMLElement
|
||||
|
||||
let targetCache = new WeakMap<HTMLElement, ScrollTarget>()
|
||||
|
||||
/**
|
||||
* 清除祖先滚动容器缓存。响应式布局或 overlay 状态改变后必须重新解析。
|
||||
*/
|
||||
export function invalidateScrollTargetCache() {
|
||||
targetCache = new WeakMap<HTMLElement, ScrollTarget>()
|
||||
}
|
||||
|
||||
function isScrollableOverflow(overflowY: string) {
|
||||
return overflowY === 'auto' || overflowY === 'scroll' || overflowY === 'overlay'
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析元素最近的纵向滚动容器,并让同一祖先链上的网格复用样式查询结果。
|
||||
*/
|
||||
export function findNearestScrollTarget(element: HTMLElement | null): ScrollTarget {
|
||||
if (!element) return window
|
||||
|
||||
let parent = element.parentElement
|
||||
const visited: HTMLElement[] = []
|
||||
let target: ScrollTarget = window
|
||||
|
||||
while (parent && parent !== document.body && parent !== document.documentElement) {
|
||||
const cachedTarget = targetCache.get(parent)
|
||||
if (cachedTarget) {
|
||||
target = cachedTarget
|
||||
break
|
||||
}
|
||||
|
||||
visited.push(parent)
|
||||
|
||||
if (isScrollableOverflow(window.getComputedStyle(parent).overflowY)) {
|
||||
target = parent
|
||||
break
|
||||
}
|
||||
|
||||
parent = parent.parentElement
|
||||
}
|
||||
|
||||
visited.forEach(ancestor => targetCache.set(ancestor, target))
|
||||
|
||||
return target
|
||||
}
|
||||
Reference in New Issue
Block a user