fix(glass): scope fixed shell backplate (#603)

This commit is contained in:
InfinityPacer
2026-07-30 22:47:22 +08:00
committed by GitHub
parent bf77c8ee04
commit e67edd9feb
6 changed files with 105 additions and 17 deletions

View File

@@ -76,6 +76,8 @@ export default defineComponent({
})
return () => {
const hasFixedShellBackplate = fixedShellBackplate.layers.value.length > 0
// 👉 Vertical nav
const verticalNav = h(
VerticalNav,
@@ -88,15 +90,14 @@ export default defineComponent({
},
)
const fixedShellBackplateNode =
fixedShellBackplate.layers.value.length > 0
? h(GlassFixedShellBackplate, {
isOverlayNav: mdAndDown.value,
isOverlayNavActive: isOverlayNavActive.value,
layers: fixedShellBackplate.layers.value,
transitionDurationMs: fixedShellBackplate.transitionDurationMs,
})
: null
const fixedShellBackplateNode = hasFixedShellBackplate
? h(GlassFixedShellBackplate, {
isOverlayNav: mdAndDown.value,
isOverlayNavActive: isOverlayNavActive.value,
layers: fixedShellBackplate.layers.value,
transitionDurationMs: fixedShellBackplate.transitionDurationMs,
})
: null
// 👉 Navbar
const navbar = h(
@@ -158,6 +159,7 @@ export default defineComponent({
isCollapsedLayout.value && 'layout-vertical-nav-collapsed',
isHorizontalLayout.value && 'layout-horizontal-nav-active',
isHorizontalLayout.value && isNavbarScrolled && 'layout-horizontal-nav-scrolled',
hasFixedShellBackplate && 'layout-fixed-shell-backplate-active',
route.meta.layoutWrapperClasses,
!isHorizontalLayout.value && isNavbarScrolled && 'window-scrolled',
],

View File

@@ -29,6 +29,7 @@ import { useGlobalOfflineStatus, type ConnectionFailureReason } from '@/composab
import { useAppActivityLifecycle } from '@/composables/useAppActivityLifecycle'
import { useGlassWallpaperTransaction } from '@/composables/useGlassWallpaperTransaction'
import {
isChromiumFixedShellBackplateBrowser,
provideGlassFixedShellBackplate,
shouldUseGlassFixedShellBackplate,
type GlassFixedShellBackplateLayer,
@@ -308,6 +309,7 @@ function getBackgroundLayerStyle(layer: LoginBackgroundLayer) {
}
}
const needsStableFixedBackdrop = isChromiumFixedShellBackplateBrowser()
const fixedShellBackplateLayers = computed<readonly GlassFixedShellBackplateLayer[]>(() => {
const hasWallpaper = renderedBackgroundLayers.value.some(layer => Boolean(layer.url))
if (
@@ -315,6 +317,7 @@ const fixedShellBackplateLayers = computed<readonly GlassFixedShellBackplateLaye
appearance: effectiveGlassSettings.value.glassAppearance,
hasWallpaper,
isAuthenticated: Boolean(isLogin.value),
needsStableFixedBackdrop,
quality: effectiveGlassSettings.value.glassQuality,
themeName: globalTheme.name.value,
})

View File

@@ -1,17 +1,22 @@
import { describe, expect, it } from 'vitest'
import { shouldUseGlassFixedShellBackplate } from '@/composables/useGlassFixedShellBackplate'
import {
isChromiumFixedShellBackplateBrowser,
shouldUseGlassFixedShellBackplate,
} from '@/composables/useGlassFixedShellBackplate'
describe('shouldUseGlassFixedShellBackplate', () => {
const eligible = {
appearance: 'frosted' as const,
hasWallpaper: true,
isAuthenticated: true,
needsStableFixedBackdrop: true,
quality: 'css' as const,
themeName: 'glass',
}
it('enables the direct wallpaper backplate only for authenticated frosted CSS rendering', () => {
it('enables the direct wallpaper backplate only for affected authenticated Chromium rendering', () => {
expect(shouldUseGlassFixedShellBackplate(eligible)).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)
@@ -21,3 +26,58 @@ describe('shouldUseGlassFixedShellBackplate', () => {
expect(shouldUseGlassFixedShellBackplate({ ...eligible, hasWallpaper: false })).toBe(false)
})
})
describe('isChromiumFixedShellBackplateBrowser', () => {
it('uses the Chromium engine brand for Chrome and Edge', () => {
expect(
isChromiumFixedShellBackplateBrowser({
userAgent: 'Mozilla/5.0 Chrome/140.0.0.0 Safari/537.36',
userAgentData: {
brands: [{ brand: 'Chromium' }, { brand: 'Google Chrome' }],
},
}),
).toBe(true)
expect(
isChromiumFixedShellBackplateBrowser({
userAgent: 'Mozilla/5.0 Chrome/140.0.0.0 Safari/537.36 Edg/140.0.0.0',
userAgentData: {
brands: [{ brand: 'Chromium' }, { brand: 'Microsoft Edge' }],
},
}),
).toBe(true)
expect(
isChromiumFixedShellBackplateBrowser({
userAgent: 'Mozilla/5.0 Version/26.4 Safari/605.1.15',
userAgentData: {
brands: [{ brand: 'Safari' }],
},
}),
).toBe(false)
})
it.each([
['Chrome', 'Mozilla/5.0 Chrome/140.0.0.0 Safari/537.36'],
['Edge', 'Mozilla/5.0 Chrome/140.0.0.0 Safari/537.36 Edg/140.0.0.0'],
['Opera', 'Mozilla/5.0 Chrome/140.0.0.0 Safari/537.36 OPR/121.0.0.0'],
['Vivaldi', 'Mozilla/5.0 Chrome/140.0.0.0 Safari/537.36 Vivaldi/7.6.0.0'],
['Yandex Browser', 'Mozilla/5.0 Chrome/140.0.0.0 Safari/537.36 YaBrowser/25.8.0.0'],
['Samsung Internet', 'Mozilla/5.0 Chrome/140.0.0.0 Mobile Safari/537.36 SamsungBrowser/28.0'],
['Android WebView', 'Mozilla/5.0; wv) Version/4.0 Chrome/140.0.0.0 Mobile Safari/537.36'],
['Chromium', 'Mozilla/5.0 Chromium/140.0.0.0 Safari/537.36'],
])('falls back to Chromium UA tokens for %s', (_browser, userAgent) => {
expect(isChromiumFixedShellBackplateBrowser({ userAgent })).toBe(true)
})
it('leaves Safari and iOS Chrome on the native backdrop path', () => {
expect(
isChromiumFixedShellBackplateBrowser({
userAgent: 'Mozilla/5.0 Version/26.4 Safari/605.1.15',
}),
).toBe(false)
expect(
isChromiumFixedShellBackplateBrowser({
userAgent: 'Mozilla/5.0 CriOS/140.0.0.0 Mobile/15E148 Safari/604.1',
}),
).toBe(false)
})
})

View File

@@ -21,12 +21,25 @@ interface GlassFixedShellBackplateEligibility {
hasWallpaper: boolean
/** 当前页面是否处于认证后的主布局。 */
isAuthenticated: boolean
/** 当前浏览器是否需要绕开 Chromium fixed document backdrop 的重合成路径。 */
needsStableFixedBackdrop: boolean
/** 当前玻璃渲染质量。 */
quality: ThemeCustomizerGlassQuality
/** Vuetify 当前解析后的主题名。 */
themeName: string
}
interface GlassFixedShellBrowserIdentity {
/** User-Agent Client Hints 暴露的浏览器品牌。 */
userAgentData?: {
brands?: readonly {
brand: string
}[]
}
/** Client Hints 不可用时使用的传统浏览器标识。 */
userAgent: string
}
const GLASS_FIXED_SHELL_BACKPLATE_KEY: InjectionKey<GlassFixedShellBackplateContext> =
Symbol('glass-fixed-shell-backplate')
const EMPTY_FIXED_SHELL_LAYERS = shallowRef<readonly GlassFixedShellBackplateLayer[]>([])
@@ -35,10 +48,19 @@ const EMPTY_FIXED_SHELL_CONTEXT: GlassFixedShellBackplateContext = {
transitionDurationMs: 0,
}
/** 只有磨砂标准使用直接壁纸背板;其他材质和 GPU 质量继续走既有呈现路径。 */
/** Chromium 使用独立 fixed 背板规避 document backdrop 重合成WebKit 保留原生采样路径。 */
export function isChromiumFixedShellBackplateBrowser(browserIdentity: GlassFixedShellBrowserIdentity = navigator) {
const brands = browserIdentity.userAgentData?.brands
if (brands?.length) return brands.some(({ brand }) => brand === 'Chromium')
return /\b(?:Chrome|Chromium)\/\d+/u.test(browserIdentity.userAgent)
}
/** 只有需要稳定 fixed 背板的 Chromium 磨砂标准布局使用直接壁纸采样。 */
export function shouldUseGlassFixedShellBackplate(options: GlassFixedShellBackplateEligibility) {
return (
options.isAuthenticated &&
options.needsStableFixedBackdrop &&
options.themeName === 'glass' &&
options.appearance === 'frosted' &&
options.quality === 'css' &&

View File

@@ -39,7 +39,7 @@ describe('glass overlay material styles', () => {
expect(styles).toContain('--glass-fixed-shell-backplate-filter: blur(min(var(--glass-blur-raised), 60px))')
expect(styles).toMatch(
/&\[data-glass-appearance='frosted'\]\[data-glass-quality='css'\][\s\S]*?\.layout-vertical-nav::before,[\s\S]*?\.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(backplate).toContain('filter: var(--glass-fixed-shell-backplate-filter)')
expect(backplate).not.toContain('backdrop-filter')

View File

@@ -462,11 +462,12 @@ html[data-theme='glass'] {
}
}
// 磨砂标准直接渲染稳定壁纸背板,固定功能层不得读取随页面重绘的 document backdrop。
// Chromium 磨砂标准直接渲染稳定壁纸背板,固定功能层不得读取随页面重绘的 document backdrop。
&[data-glass-appearance='frosted'][data-glass-quality='css'] body[data-theme='glass'] {
.layout-vertical-nav::before,
.layout-navbar,
.layout-wrapper.layout-horizontal-nav-active.layout-horizontal-nav-scrolled.layout-navbar-fixed .layout-navbar {
.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-horizontal-nav-active.layout-horizontal-nav-scrolled.layout-navbar-fixed
.layout-navbar {
-webkit-backdrop-filter: none !important;
backdrop-filter: none !important;
}