fix(glass): align frosted fixed and scroll materials (#617)

This commit is contained in:
InfinityPacer
2026-07-31 18:16:53 +08:00
committed by GitHub
parent 21558372d5
commit 985dafe45f
7 changed files with 95 additions and 32 deletions
+24 -24
View File
@@ -1141,35 +1141,35 @@ onUnmounted(() => {
<!-- 全局磨砂层 --> <!-- 全局磨砂层 -->
<div v-if="shouldRenderGlobalBlurLayer" class="global-blur-layer"></div> <div v-if="shouldRenderGlobalBlurLayer" class="global-blur-layer"></div>
</div> </div>
<GlassOpticalLayer
v-if="shouldRenderGlassOpticalLayer"
:appearance="effectiveGlassSettings.glassAppearance"
:deformation-strength="opticalDeformationStrength"
:flow-strength="opticalFlowStrength"
:quality="opticalQuality === 'high' ? 'high' : 'balanced'"
:reflection-strength="opticalReflectionStrength"
:transparency-strength="opticalTransparencyStrength"
:transmission-strength="opticalTransmissionStrength"
:translation-strength="opticalTranslationStrength"
:route-key="route.fullPath"
:tint-color="globalTheme.current.value.colors.primary"
:transition-duration="BACKGROUND_CROSSFADE_DURATION_MS"
:transition-started-at="backgroundCrossfadeStartedAt"
:wallpaper-url="activeOpticalBackgroundImage"
:previous-wallpaper-url="previousOpticalBackgroundImage"
:pending-wallpaper-url="pendingOpticalBackgroundImage"
:pending-wallpaper-revision="pendingOpticalWallpaperRevision"
:activate-wallpaper-revision="activateOpticalWallpaperRevision"
@wallpaper-activation-failed="handleOpticalWallpaperActivationFailed"
@wallpaper-activated="handleOpticalWallpaperActivated"
@wallpaper-preparation-failed="handleOpticalWallpaperPreparationFailed"
@wallpaper-prepared="handleOpticalWallpaperPrepared"
/>
<!-- 页面内容 --> <!-- 页面内容 -->
<VApp <VApp
:class="{ 'app-shell--login-wallpaper': isLoginWallpaperRoute }" :class="{ 'app-shell--login-wallpaper': isLoginWallpaperRoute }"
:data-login-visual-profile="isLoginWallpaperRoute ? loginVisualProfile : undefined" :data-login-visual-profile="isLoginWallpaperRoute ? loginVisualProfile : undefined"
> >
<GlassOpticalLayer
v-if="shouldRenderGlassOpticalLayer"
:appearance="effectiveGlassSettings.glassAppearance"
:deformation-strength="opticalDeformationStrength"
:flow-strength="opticalFlowStrength"
:quality="opticalQuality === 'high' ? 'high' : 'balanced'"
:reflection-strength="opticalReflectionStrength"
:transparency-strength="opticalTransparencyStrength"
:transmission-strength="opticalTransmissionStrength"
:translation-strength="opticalTranslationStrength"
:route-key="route.fullPath"
:tint-color="globalTheme.current.value.colors.primary"
:transition-duration="BACKGROUND_CROSSFADE_DURATION_MS"
:transition-started-at="backgroundCrossfadeStartedAt"
:wallpaper-url="activeOpticalBackgroundImage"
:previous-wallpaper-url="previousOpticalBackgroundImage"
:pending-wallpaper-url="pendingOpticalBackgroundImage"
:pending-wallpaper-revision="pendingOpticalWallpaperRevision"
:activate-wallpaper-revision="activateOpticalWallpaperRevision"
@wallpaper-activation-failed="handleOpticalWallpaperActivationFailed"
@wallpaper-activated="handleOpticalWallpaperActivated"
@wallpaper-preparation-failed="handleOpticalWallpaperPreparationFailed"
@wallpaper-prepared="handleOpticalWallpaperPrepared"
/>
<RouterView /> <RouterView />
<!-- 全局共享弹窗入口列表与卡片按需在这里挂载业务弹窗 --> <!-- 全局共享弹窗入口列表与卡片按需在这里挂载业务弹窗 -->
<SharedDialogHost /> <SharedDialogHost />
@@ -14,13 +14,13 @@ describe('shouldUseGlassFixedShellBackplate', () => {
themeName: 'glass', 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)).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, needsStableFixedBackdrop: false })).toBe(false)
expect(shouldUseGlassFixedShellBackplate({ ...eligible, appearance: 'clear' })).toBe(false) expect(shouldUseGlassFixedShellBackplate({ ...eligible, appearance: 'clear' })).toBe(false)
expect(shouldUseGlassFixedShellBackplate({ ...eligible, appearance: 'tinted' })).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, themeName: 'transparent' })).toBe(false)
expect(shouldUseGlassFixedShellBackplate({ ...eligible, isAuthenticated: false })).toBe(false) expect(shouldUseGlassFixedShellBackplate({ ...eligible, isAuthenticated: false })).toBe(false)
expect(shouldUseGlassFixedShellBackplate({ ...eligible, hasWallpaper: false })).toBe(false) expect(shouldUseGlassFixedShellBackplate({ ...eligible, hasWallpaper: false })).toBe(false)
@@ -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 () => { 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, 'innerWidth', 'get').mockReturnValue(1200)
vi.spyOn(window, 'innerHeight', 'get').mockReturnValue(800) vi.spyOn(window, 'innerHeight', 'get').mockReturnValue(800)
@@ -56,14 +56,13 @@ export function isChromiumFixedShellBackplateBrowser(browserIdentity: GlassFixed
return /\b(?:Chrome|Chromium)\/\d+/u.test(browserIdentity.userAgent) return /\b(?:Chrome|Chromium)\/\d+/u.test(browserIdentity.userAgent)
} }
/** 只有需要稳定 fixed 背板的 Chromium 磨砂标准布局使用直接壁纸采样。 */ /** Chromium 磨砂 fixed 层使用稳定壁纸背板,避免读取随页面重绘的 document backdrop。 */
export function shouldUseGlassFixedShellBackplate(options: GlassFixedShellBackplateEligibility) { export function shouldUseGlassFixedShellBackplate(options: GlassFixedShellBackplateEligibility) {
return ( return (
options.isAuthenticated && options.isAuthenticated &&
options.needsStableFixedBackdrop && options.needsStableFixedBackdrop &&
options.themeName === 'glass' && options.themeName === 'glass' &&
options.appearance === 'frosted' && options.appearance === 'frosted' &&
options.quality === 'css' &&
options.hasWallpaper options.hasWallpaper
) )
} }
+4 -1
View File
@@ -1399,6 +1399,8 @@ export function useGlassOpticalRenderer(options: UseGlassOpticalRendererOptions)
let resumePromise: Promise<void> | null = null let resumePromise: Promise<void> | null = null
let resumeVersion = 0 let resumeVersion = 0
const presentationSpace = options.surfaceSpace ?? 'fixed' const presentationSpace = options.surfaceSpace ?? 'fixed'
const usesDynamicsOnly = () =>
presentationSpace === 'scroll' || (presentationSpace === 'fixed' && toValue(options.appearance) === 'frosted')
const wallpaperSourceCache = options.wallpaperSourceCache ?? createGlassWallpaperSourceCache() const wallpaperSourceCache = options.wallpaperSourceCache ?? createGlassWallpaperSourceCache()
/** 滚动期间由原生 backdrop 接管壁纸;稳定态恢复完整纹理折射与流体反馈。 */ /** 滚动期间由原生 backdrop 接管壁纸;稳定态恢复完整纹理折射与流体反馈。 */
@@ -3664,7 +3666,7 @@ export function useGlassOpticalRenderer(options: UseGlassOpticalRendererOptions)
uBackgroundVisibility: { value: materialResponse.backgroundVisibility }, uBackgroundVisibility: { value: materialResponse.backgroundVisibility },
uCoverScale: { value: new three.Vector2(1, 1) }, uCoverScale: { value: new three.Vector2(1, 1) },
uDeformationStrength: { value: getDeformationStrengthScale() }, uDeformationStrength: { value: getDeformationStrengthScale() },
uDynamicsOnly: { value: presentationSpace === 'scroll' ? 1 : 0 }, uDynamicsOnly: { value: usesDynamicsOnly() ? 1 : 0 },
uFlowTexture: { value: null }, uFlowTexture: { value: null },
uFlowStrength: { value: getFlowStrengthScale() }, uFlowStrength: { value: getFlowStrengthScale() },
uHasFlowTexture: { value: 0 }, uHasFlowTexture: { value: 0 },
@@ -3818,6 +3820,7 @@ export function useGlassOpticalRenderer(options: UseGlassOpticalRendererOptions)
) )
resources.uniforms.uAppearance.value = getGlassAppearanceUniformValue(appearance) resources.uniforms.uAppearance.value = getGlassAppearanceUniformValue(appearance)
resources.uniforms.uBackgroundVisibility.value = materialResponse.backgroundVisibility resources.uniforms.uBackgroundVisibility.value = materialResponse.backgroundVisibility
resources.uniforms.uDynamicsOnly.value = usesDynamicsOnly() ? 1 : 0
resources.uniforms.uFrostDetailLevel.value = materialResponse.frostDetailLevel resources.uniforms.uFrostDetailLevel.value = materialResponse.frostDetailLevel
resources.uniforms.uSurfaceDensity.value = materialResponse.surfaceDensity resources.uniforms.uSurfaceDensity.value = materialResponse.surfaceDensity
resources.uniforms.uTintDensity.value = materialResponse.tintDensity resources.uniforms.uTintDensity.value = materialResponse.tintDensity
@@ -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 styles = readFileSync(resolve(cwd(), 'src/styles/themes/glass.scss'), 'utf8')
const backplate = readFileSync(resolve(cwd(), 'src/components/theme/GlassFixedShellBackplate.vue'), 'utf8') const backplate = readFileSync(resolve(cwd(), 'src/components/theme/GlassFixedShellBackplate.vue'), 'utf8')
@@ -41,6 +41,12 @@ describe('glass overlay material styles', () => {
expect(styles).toMatch( 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;/, /&\[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).toContain('filter: var(--glass-fixed-shell-backplate-filter)')
expect(backplate).not.toContain('backdrop-filter') expect(backplate).not.toContain('backdrop-filter')
expect(backplate).toContain('var(--glass-fixed-shell-nav-inline-size) 100%') expect(backplate).toContain('var(--glass-fixed-shell-nav-inline-size) 100%')
@@ -121,6 +127,9 @@ describe('glass overlay material styles', () => {
expect(styles).toMatch( 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;/, /\[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( expect(styles).toMatch(
/\.settings-section-card\.app-grouped-list\s*\{[\s\S]*?backdrop-filter:\s*var\(--glass-native-surface-backdrop-filter\)\s*!important;/, /\.settings-section-card\.app-grouped-list\s*\{[\s\S]*?backdrop-filter:\s*var\(--glass-native-surface-backdrop-filter\)\s*!important;/,
) )
+12 -1
View File
@@ -1,6 +1,8 @@
/* stylelint-disable no-descending-specificity */ /* stylelint-disable no-descending-specificity */
/* stylelint-disable scss/at-rule-no-unknown */ /* stylelint-disable scss/at-rule-no-unknown */
@use '@configured-variables' as variables;
// 材质只覆盖统一表面 token质量档只替换光学层不改变业务组件契约 // 材质只覆盖统一表面 token质量档只替换光学层不改变业务组件契约
html[data-theme='glass'] { html[data-theme='glass'] {
--glass-surface: rgba(11, 19, 34, calc(0.02 + var(--glass-surface-density, 0.62) * 0.3)); --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'] { &[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-vertical-nav::before,
.layout-wrapper.layout-fixed-shell-backplate-active .layout-navbar, .layout-wrapper.layout-fixed-shell-backplate-active .layout-navbar,
@@ -1193,6 +1195,7 @@ html[data-theme='glass'] {
.glass-optical-layer--fixed { .glass-optical-layer--fixed {
position: fixed; position: fixed;
z-index: variables.$layout-vertical-nav-layout-navbar-z-index;
} }
// 文档表面与该呈现层共享滚动坐标canvas backing buffer 仍由 renderer 的质量预算约束 // 文档表面与该呈现层共享滚动坐标canvas backing buffer 仍由 renderer 的质量预算约束
@@ -1256,6 +1259,12 @@ html[data-glass-appearance='frosted']:is(
backdrop-filter: none !important; 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.window-scrolled.layout-navbar-fixed .layout-navbar,
.layout-wrapper.layout-horizontal-nav-active.layout-horizontal-nav-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; -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-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%) --glass-native-surface-backdrop-filter: blur(calc(16px * var(--glass-frost-blur-scale, 1))) saturate(162%)
brightness(var(--glass-transmission-brightness)); 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'] { html[data-glass-appearance='frosted'][data-glass-quality='high'] {
--glass-dashboard-backdrop-filter: var(--glass-native-surface-backdrop-filter); --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%) --glass-native-surface-backdrop-filter: blur(calc(10px * var(--glass-frost-blur-scale, 1))) saturate(154%)
brightness(var(--glass-transmission-brightness)); brightness(var(--glass-transmission-brightness));
--glass-fixed-shell-backplate-filter: var(--glass-native-surface-backdrop-filter);
} }
// 异步滚动期间只暂停 GPU 动态层原生壁纸基底不发生材质切换 // 异步滚动期间只暂停 GPU 动态层原生壁纸基底不发生材质切换