From 985dafe45ff43da3c59810f6ccf3995d8c5a97bf Mon Sep 17 00:00:00 2001 From: InfinityPacer <160988576+InfinityPacer@users.noreply.github.com> Date: Fri, 31 Jul 2026 18:16:53 +0800 Subject: [PATCH] fix(glass): align frosted fixed and scroll materials (#617) --- src/App.vue | 48 +++++++++---------- .../useGlassFixedShellBackplate.spec.ts | 6 +-- .../__tests__/useGlassOpticalRenderer.spec.ts | 41 ++++++++++++++++ .../useGlassFixedShellBackplate.ts | 3 +- src/composables/useGlassOpticalRenderer.ts | 5 +- .../__tests__/glassOverlayMaterial.spec.ts | 11 ++++- src/styles/themes/glass.scss | 13 ++++- 7 files changed, 95 insertions(+), 32 deletions(-) diff --git a/src/App.vue b/src/App.vue index 1bb65d34..0d4e4a17 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1141,35 +1141,35 @@ onUnmounted(() => {
- + diff --git a/src/composables/__tests__/useGlassFixedShellBackplate.spec.ts b/src/composables/__tests__/useGlassFixedShellBackplate.spec.ts index a4e9bdf6..a333e148 100644 --- a/src/composables/__tests__/useGlassFixedShellBackplate.spec.ts +++ b/src/composables/__tests__/useGlassFixedShellBackplate.spec.ts @@ -14,13 +14,13 @@ describe('shouldUseGlassFixedShellBackplate', () => { themeName: 'glass', } - it('enables the direct wallpaper backplate only for affected authenticated Chromium rendering', () => { + it('enables the direct wallpaper backplate for every frosted Chromium quality', () => { expect(shouldUseGlassFixedShellBackplate(eligible)).toBe(true) + expect(shouldUseGlassFixedShellBackplate({ ...eligible, quality: 'balanced' })).toBe(true) + expect(shouldUseGlassFixedShellBackplate({ ...eligible, quality: 'high' })).toBe(true) expect(shouldUseGlassFixedShellBackplate({ ...eligible, needsStableFixedBackdrop: false })).toBe(false) expect(shouldUseGlassFixedShellBackplate({ ...eligible, appearance: 'clear' })).toBe(false) expect(shouldUseGlassFixedShellBackplate({ ...eligible, appearance: 'tinted' })).toBe(false) - expect(shouldUseGlassFixedShellBackplate({ ...eligible, quality: 'balanced' })).toBe(false) - expect(shouldUseGlassFixedShellBackplate({ ...eligible, quality: 'high' })).toBe(false) expect(shouldUseGlassFixedShellBackplate({ ...eligible, themeName: 'transparent' })).toBe(false) expect(shouldUseGlassFixedShellBackplate({ ...eligible, isAuthenticated: false })).toBe(false) expect(shouldUseGlassFixedShellBackplate({ ...eligible, hasWallpaper: false })).toBe(false) diff --git a/src/composables/__tests__/useGlassOpticalRenderer.spec.ts b/src/composables/__tests__/useGlassOpticalRenderer.spec.ts index bf2bc0ab..2b07de48 100644 --- a/src/composables/__tests__/useGlassOpticalRenderer.spec.ts +++ b/src/composables/__tests__/useGlassOpticalRenderer.spec.ts @@ -456,6 +456,47 @@ describe('glass optical surface discovery', () => { ]) }) + it('keeps frosted fixed surfaces on dynamics-only GPU composition', async () => { + const three = await import('three') + const render = vi.spyOn(three.WebGLRenderer.prototype, 'render') + const canvas = document.createElement('canvas') + const appearance = ref<'clear' | 'frosted' | 'tinted'>('clear') + appendOpticalSurface('layout-vertical-nav', { height: 800, width: 260, x: 0, y: 0 }) + const scope = effectScope() + const renderer = scope.run(() => + useGlassOpticalRenderer({ + active: ref(true), + appearance, + canvas: ref(canvas), + quality: ref('balanced'), + routeKey: ref('/dashboard'), + surfaceSpace: 'fixed', + tintColor: ref('#8D51F9'), + wallpaperUrl: ref('https://example.com/wallpaper.jpg'), + }), + ) + + await vi.waitFor(() => expect(renderer?.state.value).toBe('ready')) + const scene = render.mock.calls.at(-1)?.[0] as unknown as { + children: Array<{ + material: { + uniforms: { + uDynamicsOnly: { value: number } + } + } + }> + } + const dynamicsOnly = scene.children[0].material.uniforms.uDynamicsOnly + expect(dynamicsOnly.value).toBe(0) + + appearance.value = 'frosted' + await vi.waitFor(() => expect(dynamicsOnly.value).toBe(1)) + + appearance.value = 'tinted' + await vi.waitFor(() => expect(dynamicsOnly.value).toBe(0)) + scope.stop() + }) + it('re-samples scroll surfaces and material weight on a shared page motion revision', async () => { vi.spyOn(window, 'innerWidth', 'get').mockReturnValue(1200) vi.spyOn(window, 'innerHeight', 'get').mockReturnValue(800) diff --git a/src/composables/useGlassFixedShellBackplate.ts b/src/composables/useGlassFixedShellBackplate.ts index a2269c2c..39667798 100644 --- a/src/composables/useGlassFixedShellBackplate.ts +++ b/src/composables/useGlassFixedShellBackplate.ts @@ -56,14 +56,13 @@ export function isChromiumFixedShellBackplateBrowser(browserIdentity: GlassFixed return /\b(?:Chrome|Chromium)\/\d+/u.test(browserIdentity.userAgent) } -/** 只有需要稳定 fixed 背板的 Chromium 磨砂标准布局使用直接壁纸采样。 */ +/** Chromium 磨砂 fixed 层使用稳定壁纸背板,避免读取随页面重绘的 document backdrop。 */ export function shouldUseGlassFixedShellBackplate(options: GlassFixedShellBackplateEligibility) { return ( options.isAuthenticated && options.needsStableFixedBackdrop && options.themeName === 'glass' && options.appearance === 'frosted' && - options.quality === 'css' && options.hasWallpaper ) } diff --git a/src/composables/useGlassOpticalRenderer.ts b/src/composables/useGlassOpticalRenderer.ts index 5771c1ad..6f8db45f 100644 --- a/src/composables/useGlassOpticalRenderer.ts +++ b/src/composables/useGlassOpticalRenderer.ts @@ -1399,6 +1399,8 @@ export function useGlassOpticalRenderer(options: UseGlassOpticalRendererOptions) let resumePromise: Promise | null = null let resumeVersion = 0 const presentationSpace = options.surfaceSpace ?? 'fixed' + const usesDynamicsOnly = () => + presentationSpace === 'scroll' || (presentationSpace === 'fixed' && toValue(options.appearance) === 'frosted') const wallpaperSourceCache = options.wallpaperSourceCache ?? createGlassWallpaperSourceCache() /** 滚动期间由原生 backdrop 接管壁纸;稳定态恢复完整纹理折射与流体反馈。 */ @@ -3664,7 +3666,7 @@ export function useGlassOpticalRenderer(options: UseGlassOpticalRendererOptions) uBackgroundVisibility: { value: materialResponse.backgroundVisibility }, uCoverScale: { value: new three.Vector2(1, 1) }, uDeformationStrength: { value: getDeformationStrengthScale() }, - uDynamicsOnly: { value: presentationSpace === 'scroll' ? 1 : 0 }, + uDynamicsOnly: { value: usesDynamicsOnly() ? 1 : 0 }, uFlowTexture: { value: null }, uFlowStrength: { value: getFlowStrengthScale() }, uHasFlowTexture: { value: 0 }, @@ -3818,6 +3820,7 @@ export function useGlassOpticalRenderer(options: UseGlassOpticalRendererOptions) ) resources.uniforms.uAppearance.value = getGlassAppearanceUniformValue(appearance) resources.uniforms.uBackgroundVisibility.value = materialResponse.backgroundVisibility + resources.uniforms.uDynamicsOnly.value = usesDynamicsOnly() ? 1 : 0 resources.uniforms.uFrostDetailLevel.value = materialResponse.frostDetailLevel resources.uniforms.uSurfaceDensity.value = materialResponse.surfaceDensity resources.uniforms.uTintDensity.value = materialResponse.tintDensity diff --git a/src/styles/__tests__/glassOverlayMaterial.spec.ts b/src/styles/__tests__/glassOverlayMaterial.spec.ts index e4e83e9f..276084d8 100644 --- a/src/styles/__tests__/glassOverlayMaterial.spec.ts +++ b/src/styles/__tests__/glassOverlayMaterial.spec.ts @@ -33,7 +33,7 @@ describe('glass overlay material styles', () => { ) }) - it('keeps frosted CSS fixed shells on the stable wallpaper backplate', () => { + it('keeps Chromium frosted fixed shells on the stable wallpaper backplate', () => { const styles = readFileSync(resolve(cwd(), 'src/styles/themes/glass.scss'), 'utf8') const backplate = readFileSync(resolve(cwd(), 'src/components/theme/GlassFixedShellBackplate.vue'), 'utf8') @@ -41,6 +41,12 @@ describe('glass overlay material styles', () => { expect(styles).toMatch( /&\[data-glass-appearance='frosted'\]\[data-glass-quality='css'\][\s\S]*?\.layout-wrapper\.layout-fixed-shell-backplate-active \.layout-vertical-nav::before,[\s\S]*?\.layout-wrapper\.layout-fixed-shell-backplate-active \.layout-navbar,[\s\S]*?backdrop-filter:\s*none\s*!important;/, ) + expect(styles).toMatch( + /\[data-glass-appearance='frosted'\]\[data-glass-quality='balanced'\]\s*\{[\s\S]*?--glass-fixed-shell-backplate-filter:\s*var\(--glass-native-surface-backdrop-filter\);/, + ) + expect(styles).toMatch( + /\[data-glass-appearance='frosted'\]\[data-glass-quality='high'\]\s*\{[\s\S]*?--glass-fixed-shell-backplate-filter:\s*var\(--glass-native-surface-backdrop-filter\);/, + ) expect(backplate).toContain('filter: var(--glass-fixed-shell-backplate-filter)') expect(backplate).not.toContain('backdrop-filter') expect(backplate).toContain('var(--glass-fixed-shell-nav-inline-size) 100%') @@ -121,6 +127,9 @@ describe('glass overlay material styles', () => { expect(styles).toMatch( /\[data-glass-renderer-state='ready'\][\s\S]*?\.app-hover-lift-card:not\(\.media-card--image-loaded\)[\s\S]*?backdrop-filter:\s*var\(--glass-native-surface-backdrop-filter\)\s*!important;/, ) + expect(styles).toMatch( + /\.layout-wrapper:not\(\.layout-fixed-shell-backplate-active\) \.layout-vertical-nav::before,[\s\S]*?backdrop-filter:\s*var\(--glass-native-surface-backdrop-filter\)\s*!important;/, + ) expect(styles).toMatch( /\.settings-section-card\.app-grouped-list\s*\{[\s\S]*?backdrop-filter:\s*var\(--glass-native-surface-backdrop-filter\)\s*!important;/, ) diff --git a/src/styles/themes/glass.scss b/src/styles/themes/glass.scss index c2efc7dc..f7c5dc8d 100644 --- a/src/styles/themes/glass.scss +++ b/src/styles/themes/glass.scss @@ -1,6 +1,8 @@ /* stylelint-disable no-descending-specificity */ /* stylelint-disable scss/at-rule-no-unknown */ +@use '@configured-variables' as variables; + // 材质只覆盖统一表面 token;质量档只替换光学层,不改变业务组件契约。 html[data-theme='glass'] { --glass-surface: rgba(11, 19, 34, calc(0.02 + var(--glass-surface-density, 0.62) * 0.3)); @@ -462,7 +464,7 @@ html[data-theme='glass'] { } } - // Chromium 磨砂标准直接渲染稳定壁纸背板,固定功能层不得读取随页面重绘的 document backdrop。 + // Chromium 磨砂 fixed 层直接渲染稳定壁纸背板,固定功能层不得读取随页面重绘的 document backdrop。 &[data-glass-appearance='frosted'][data-glass-quality='css'] body[data-theme='glass'] { .layout-wrapper.layout-fixed-shell-backplate-active .layout-vertical-nav::before, .layout-wrapper.layout-fixed-shell-backplate-active .layout-navbar, @@ -1193,6 +1195,7 @@ html[data-theme='glass'] { .glass-optical-layer--fixed { position: fixed; + z-index: variables.$layout-vertical-nav-layout-navbar-z-index; } // 文档表面与该呈现层共享滚动坐标,canvas backing buffer 仍由 renderer 的质量预算约束。 @@ -1256,6 +1259,12 @@ html[data-glass-appearance='frosted']:is( backdrop-filter: none !important; } + .layout-wrapper:not(.layout-fixed-shell-backplate-active) .layout-vertical-nav::before, + .layout-wrapper:not(.layout-fixed-shell-backplate-active) .layout-navbar { + -webkit-backdrop-filter: var(--glass-native-surface-backdrop-filter) !important; + backdrop-filter: var(--glass-native-surface-backdrop-filter) !important; + } + .layout-wrapper.window-scrolled.layout-navbar-fixed .layout-navbar, .layout-wrapper.layout-horizontal-nav-active.layout-horizontal-nav-scrolled.layout-navbar-fixed .layout-navbar { -webkit-backdrop-filter: var(--glass-navbar-scrolled-backdrop-filter) !important; @@ -1280,12 +1289,14 @@ html[data-glass-appearance='frosted'][data-glass-quality='balanced'] { --glass-dashboard-backdrop-filter: var(--glass-native-surface-backdrop-filter); --glass-native-surface-backdrop-filter: blur(calc(16px * var(--glass-frost-blur-scale, 1))) saturate(162%) brightness(var(--glass-transmission-brightness)); + --glass-fixed-shell-backplate-filter: var(--glass-native-surface-backdrop-filter); } html[data-glass-appearance='frosted'][data-glass-quality='high'] { --glass-dashboard-backdrop-filter: var(--glass-native-surface-backdrop-filter); --glass-native-surface-backdrop-filter: blur(calc(10px * var(--glass-frost-blur-scale, 1))) saturate(154%) brightness(var(--glass-transmission-brightness)); + --glass-fixed-shell-backplate-filter: var(--glass-native-surface-backdrop-filter); } // 异步滚动期间只暂停 GPU 动态层;原生壁纸基底不发生材质切换。