mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-08-11 00:23:37 +08:00
Fix mobile clear glass surfaces
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
collectGlassOpticalRects,
|
||||
containsGlassOpticalSurface,
|
||||
setGlassRendererState,
|
||||
useGlassOpticalRenderer,
|
||||
@@ -35,11 +36,33 @@ vi.mock('three', async importOriginal => {
|
||||
}
|
||||
})
|
||||
|
||||
/** 提供 renderer 单元测试所需的最小 ResizeObserver 实现。 */
|
||||
class ResizeObserverMock {
|
||||
/** 断开观察时无需执行额外逻辑。 */
|
||||
disconnect() {}
|
||||
|
||||
/** 测试不需要追踪具体被观察元素。 */
|
||||
observe() {}
|
||||
}
|
||||
|
||||
/** 创建带稳定视口边界的光学表面元素。 */
|
||||
function appendOpticalSurface(className: string, bounds: Pick<DOMRect, 'height' | 'width' | 'x' | 'y'>) {
|
||||
const surface = document.createElement('section')
|
||||
surface.className = className
|
||||
surface.getBoundingClientRect = () =>
|
||||
({
|
||||
...bounds,
|
||||
bottom: bounds.y + bounds.height,
|
||||
left: bounds.x,
|
||||
right: bounds.x + bounds.width,
|
||||
top: bounds.y,
|
||||
toJSON: () => ({}),
|
||||
}) as DOMRect
|
||||
document.body.append(surface)
|
||||
|
||||
return surface
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
vi.stubGlobal('ResizeObserver', ResizeObserverMock)
|
||||
vi.stubGlobal('WebGLRenderingContext', class {})
|
||||
@@ -49,6 +72,7 @@ beforeEach(() => {
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks()
|
||||
vi.unstubAllGlobals()
|
||||
document.body.replaceChildren()
|
||||
delete document.documentElement.dataset.glassRendererState
|
||||
})
|
||||
|
||||
@@ -89,6 +113,32 @@ describe('glass optical surface discovery', () => {
|
||||
expect(document.documentElement.dataset.glassRendererState).toBe('fallback')
|
||||
})
|
||||
|
||||
it('excludes the navbar from mobile clear optical surfaces', () => {
|
||||
appendOpticalSurface('layout-navbar', { height: 64, width: 390, x: 0, y: 0 })
|
||||
appendOpticalSurface('dashboard-grid-item-content', { height: 200, width: 350, x: 20, y: 120 })
|
||||
|
||||
const rects = collectGlassOpticalRects(390, 800, 'clear')
|
||||
|
||||
expect(rects).toHaveLength(1)
|
||||
expect(rects[0]).toMatchObject({ height: 200, width: 350, x: 20, y: 120 })
|
||||
})
|
||||
|
||||
it('keeps the navbar in mobile frosted optical surfaces', () => {
|
||||
appendOpticalSurface('layout-navbar', { height: 64, width: 390, x: 0, y: 0 })
|
||||
|
||||
expect(collectGlassOpticalRects(390, 800, 'frosted')).toEqual([
|
||||
expect.objectContaining({ height: 64, width: 390, x: 0, y: 0 }),
|
||||
])
|
||||
})
|
||||
|
||||
it('keeps the navbar in desktop clear optical surfaces', () => {
|
||||
appendOpticalSurface('layout-navbar', { height: 64, width: 1000, x: 0, y: 0 })
|
||||
|
||||
expect(collectGlassOpticalRects(1000, 800, 'clear')).toEqual([
|
||||
expect.objectContaining({ height: 64, width: 1000, x: 0, y: 0 }),
|
||||
])
|
||||
})
|
||||
|
||||
it('recovers after consecutive WebGL context loss cycles', async () => {
|
||||
const canvas = document.createElement('canvas')
|
||||
const scope = effectScope()
|
||||
|
||||
@@ -231,11 +231,18 @@ function isVisibleSurface(element: HTMLElement, bounds: DOMRect) {
|
||||
}
|
||||
|
||||
/** 将活动界面中的高价值材质面集中转换为 renderer 矩形预算。 */
|
||||
export function collectGlassOpticalRects(viewportWidth: number, viewportHeight: number) {
|
||||
export function collectGlassOpticalRects(
|
||||
viewportWidth: number,
|
||||
viewportHeight: number,
|
||||
appearance: ThemeCustomizerGlassAppearance,
|
||||
) {
|
||||
const candidates: GlassOpticalRect[] = []
|
||||
const seen = new Set<HTMLElement>()
|
||||
|
||||
for (const { rank, selector } of SURFACE_SELECTORS) {
|
||||
// 移动端透明顶栏只使用稳定的 CSS 表面,避免滚动重扫时再次叠加壁纸折射。
|
||||
if (viewportWidth <= 600 && appearance === 'clear' && selector === '.layout-navbar') continue
|
||||
|
||||
for (const element of document.querySelectorAll<HTMLElement>(selector)) {
|
||||
if (seen.has(element)) continue
|
||||
seen.add(element)
|
||||
@@ -315,7 +322,7 @@ export function useGlassOpticalRenderer(options: UseGlassOpticalRendererOptions)
|
||||
|
||||
const viewportWidth = window.innerWidth
|
||||
const viewportHeight = window.innerHeight
|
||||
const rects = collectGlassOpticalRects(viewportWidth, viewportHeight)
|
||||
const rects = collectGlassOpticalRects(viewportWidth, viewportHeight, toValue(options.appearance))
|
||||
currentRects = rects
|
||||
const normalized = rects.map(rect => normalizeGlassOpticalRect(rect, viewportWidth, viewportHeight))
|
||||
const uniformRects = resources.uniforms.uRects.value
|
||||
@@ -742,7 +749,7 @@ export function useGlassOpticalRenderer(options: UseGlassOpticalRendererOptions)
|
||||
if (!resources) return
|
||||
|
||||
resources.uniforms.uAppearance.value = getGlassAppearanceUniformValue(appearance)
|
||||
scheduleFrame()
|
||||
scheduleSurfaceUpdate()
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -41,8 +41,6 @@ html[data-theme='glass'] {
|
||||
--glass-control-backdrop-filter: none;
|
||||
--glass-control-prominent-backdrop-filter: none;
|
||||
--glass-navbar-backdrop-filter: var(--glass-raised-backdrop-filter);
|
||||
--glass-clear-overlay-surface: rgba(11, 19, 34, 72%);
|
||||
--glass-clear-mobile-nav-surface: rgba(11, 19, 34, 68%);
|
||||
--glass-control-icon-color: rgba(242, 245, 250, 70%);
|
||||
--glass-control-placeholder-color: rgba(242, 245, 250, 54%);
|
||||
--glass-control-shortcut-background: rgba(255, 255, 255, 8%);
|
||||
@@ -490,7 +488,8 @@ html[data-theme='glass'] {
|
||||
.v-overlay__content {
|
||||
border-radius: var(--app-overlay-radius);
|
||||
|
||||
> :where(.v-card, .v-sheet, .v-list) {
|
||||
> :where(.v-card, .v-sheet, .v-list),
|
||||
> form > :where(.v-card, .v-sheet) {
|
||||
-webkit-backdrop-filter: var(--glass-raised-backdrop-filter) !important;
|
||||
backdrop-filter: var(--glass-raised-backdrop-filter) !important;
|
||||
background-color: var(--glass-surface-raised) !important;
|
||||
@@ -499,7 +498,7 @@ html[data-theme='glass'] {
|
||||
}
|
||||
}
|
||||
|
||||
// 透明弹层沿用普通玻璃的色相,只提高 alpha,不引入独立底色。
|
||||
// 透明弹层与顶栏共用同一静态表面,不额外增加模糊或深色覆盖。
|
||||
&[data-glass-appearance='clear'] {
|
||||
.v-overlay__content > :where(.v-card, .v-sheet, .v-list),
|
||||
.v-overlay__content > form > :where(.v-card, .v-sheet),
|
||||
@@ -507,7 +506,7 @@ html[data-theme='glass'] {
|
||||
.agent-assistant-panel {
|
||||
-webkit-backdrop-filter: none !important;
|
||||
backdrop-filter: none !important;
|
||||
background-color: var(--glass-clear-overlay-surface) !important;
|
||||
background-color: var(--glass-surface-raised) !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -810,15 +809,6 @@ html[data-theme='glass'] {
|
||||
}
|
||||
|
||||
@media (width <= 960px) {
|
||||
&[data-glass-appearance='clear'] {
|
||||
// 底部主导航和旁侧动态按钮共用 footer-nav-card,提高 alpha 后仍保持透明材质色相。
|
||||
.footer-nav-card.v-card {
|
||||
-webkit-backdrop-filter: none !important;
|
||||
backdrop-filter: none !important;
|
||||
background-color: var(--glass-clear-mobile-nav-surface) !important;
|
||||
}
|
||||
}
|
||||
|
||||
// 全屏快捷弹窗释放外层玻璃背景,让定时服务卡片直接采样全局壁纸。
|
||||
&[data-glass-appearance='frosted'] .scheduler-shortcut-dialog-card {
|
||||
-webkit-backdrop-filter: none !important;
|
||||
|
||||
Reference in New Issue
Block a user