mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-09-07 16:56:40 +08:00
fix(glass): stabilize frosted fixed shells (#602)
This commit is contained in:
@@ -1,11 +1,13 @@
|
||||
<script lang="ts">
|
||||
import { useDisplay } from 'vuetify'
|
||||
import VerticalNav from '@layouts/components/VerticalNav.vue'
|
||||
import GlassFixedShellBackplate from '@/components/theme/GlassFixedShellBackplate.vue'
|
||||
import {
|
||||
readThemeCustomizerSettings,
|
||||
THEME_CUSTOMIZER_CHANGE_EVENT,
|
||||
type ThemeCustomizerSettings,
|
||||
} from '@/composables/useThemeCustomizer'
|
||||
import { useGlassFixedShellBackplate } from '@/composables/useGlassFixedShellBackplate'
|
||||
import { usePWA } from '@/composables/usePWA'
|
||||
|
||||
export default defineComponent({
|
||||
@@ -17,6 +19,7 @@ export default defineComponent({
|
||||
const route = useRoute()
|
||||
const { mdAndDown } = useDisplay()
|
||||
const { appMode } = usePWA()
|
||||
const fixedShellBackplate = useGlassFixedShellBackplate()
|
||||
const themeLayout = ref(readThemeCustomizerSettings().layout)
|
||||
const canUseDesktopLayout = computed(() => !mdAndDown.value && !appMode.value)
|
||||
const isCollapsedLayout = computed(() => canUseDesktopLayout.value && themeLayout.value === 'collapsed')
|
||||
@@ -85,6 +88,16 @@ 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
|
||||
|
||||
// 👉 Navbar
|
||||
const navbar = h(
|
||||
'header',
|
||||
@@ -149,7 +162,12 @@ export default defineComponent({
|
||||
!isHorizontalLayout.value && isNavbarScrolled && 'window-scrolled',
|
||||
],
|
||||
},
|
||||
[verticalNav, h('div', { class: 'layout-content-wrapper' }, [navbar, main, footer]), layoutOverlay],
|
||||
[
|
||||
fixedShellBackplateNode,
|
||||
verticalNav,
|
||||
h('div', { class: 'layout-content-wrapper' }, [navbar, main, footer]),
|
||||
layoutOverlay,
|
||||
],
|
||||
)
|
||||
}
|
||||
},
|
||||
|
||||
+30
@@ -28,6 +28,11 @@ import { configureApexChartsTheme } from '@/utils/apexCharts'
|
||||
import { useGlobalOfflineStatus, type ConnectionFailureReason } from '@/composables/useOfflineStatus'
|
||||
import { useAppActivityLifecycle } from '@/composables/useAppActivityLifecycle'
|
||||
import { useGlassWallpaperTransaction } from '@/composables/useGlassWallpaperTransaction'
|
||||
import {
|
||||
provideGlassFixedShellBackplate,
|
||||
shouldUseGlassFixedShellBackplate,
|
||||
type GlassFixedShellBackplateLayer,
|
||||
} from '@/composables/useGlassFixedShellBackplate'
|
||||
import {
|
||||
BACKGROUND_ROTATION_GRACE_MS,
|
||||
createBackgroundCandidateOrderResolver,
|
||||
@@ -303,6 +308,31 @@ function getBackgroundLayerStyle(layer: LoginBackgroundLayer) {
|
||||
}
|
||||
}
|
||||
|
||||
const fixedShellBackplateLayers = computed<readonly GlassFixedShellBackplateLayer[]>(() => {
|
||||
const hasWallpaper = renderedBackgroundLayers.value.some(layer => Boolean(layer.url))
|
||||
if (
|
||||
!shouldUseGlassFixedShellBackplate({
|
||||
appearance: effectiveGlassSettings.value.glassAppearance,
|
||||
hasWallpaper,
|
||||
isAuthenticated: Boolean(isLogin.value),
|
||||
quality: effectiveGlassSettings.value.glassQuality,
|
||||
themeName: globalTheme.name.value,
|
||||
})
|
||||
) {
|
||||
return []
|
||||
}
|
||||
|
||||
return renderedBackgroundLayers.value.map(layer => ({
|
||||
...layer,
|
||||
style: getBackgroundLayerStyle(layer),
|
||||
}))
|
||||
})
|
||||
|
||||
provideGlassFixedShellBackplate({
|
||||
layers: fixedShellBackplateLayers,
|
||||
transitionDurationMs: BACKGROUND_CROSSFADE_DURATION_MS,
|
||||
})
|
||||
|
||||
applyTransparentBackgroundSettings()
|
||||
|
||||
void router.isReady().then(() => {
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
<script lang="ts" setup>
|
||||
import type { GlassFixedShellBackplateLayer } from '@/composables/useGlassFixedShellBackplate'
|
||||
|
||||
interface Props {
|
||||
/** 移动端 overlay 导航当前是否可见。 */
|
||||
isOverlayNavActive: boolean
|
||||
/** 当前布局是否使用移动端 overlay 导航。 */
|
||||
isOverlayNav: boolean
|
||||
/** App 壁纸状态机提供的稳定双槽位。 */
|
||||
layers: readonly GlassFixedShellBackplateLayer[]
|
||||
/** 与全局壁纸事务一致的交叉淡化时长。 */
|
||||
transitionDurationMs: number
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
const transitionStyle = computed(() => ({
|
||||
'--glass-fixed-shell-transition-duration': `${Math.max(0, props.transitionDurationMs)}ms`,
|
||||
}))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="glass-fixed-shell-backplate glass-fixed-shell-backplate--main"
|
||||
data-backplate-surface="main"
|
||||
:style="transitionStyle"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div
|
||||
v-for="layer in layers"
|
||||
:key="layer.key"
|
||||
class="glass-fixed-shell-backplate__layer"
|
||||
:class="`is-${layer.role}`"
|
||||
:data-backplate-slot="layer.key"
|
||||
>
|
||||
<div class="glass-fixed-shell-backplate__wallpaper" :style="layer.style" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="isOverlayNav"
|
||||
class="glass-fixed-shell-backplate glass-fixed-shell-backplate--overlay-nav"
|
||||
:class="{ 'is-visible': isOverlayNavActive }"
|
||||
data-backplate-surface="overlay-nav"
|
||||
:style="transitionStyle"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div
|
||||
v-for="layer in layers"
|
||||
:key="layer.key"
|
||||
class="glass-fixed-shell-backplate__layer"
|
||||
:class="`is-${layer.role}`"
|
||||
:data-backplate-slot="layer.key"
|
||||
>
|
||||
<div class="glass-fixed-shell-backplate__wallpaper" :style="layer.style" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@use '@configured-variables' as variables;
|
||||
@use '@layouts/styles/mixins';
|
||||
|
||||
.glass-fixed-shell-backplate {
|
||||
position: fixed;
|
||||
overflow: hidden;
|
||||
contain: strict;
|
||||
inset: 0;
|
||||
isolation: isolate;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.glass-fixed-shell-backplate--main {
|
||||
--glass-fixed-shell-nav-inline-size: #{variables.$layout-vertical-nav-width};
|
||||
|
||||
z-index: variables.$layout-vertical-nav-layout-navbar-z-index - 1;
|
||||
clip-path: polygon(
|
||||
0 0,
|
||||
calc(100% - 0.5rem) 0,
|
||||
calc(100% - 0.5rem) var(--layout-navbar-block-size),
|
||||
var(--glass-fixed-shell-nav-inline-size) var(--layout-navbar-block-size),
|
||||
var(--glass-fixed-shell-nav-inline-size) 100%,
|
||||
0 100%
|
||||
);
|
||||
transition: clip-path 0.25s ease-in-out;
|
||||
}
|
||||
|
||||
.layout-wrapper.layout-vertical-nav-collapsed > .glass-fixed-shell-backplate--main {
|
||||
--glass-fixed-shell-nav-inline-size: #{variables.$layout-vertical-nav-collapsed-width};
|
||||
}
|
||||
|
||||
.layout-wrapper:is(.layout-horizontal-nav-active, .layout-overlay-nav) > .glass-fixed-shell-backplate--main {
|
||||
clip-path: inset(0 0 calc(100% - var(--layout-navbar-block-size)) 0);
|
||||
}
|
||||
|
||||
[dir='rtl'] .layout-wrapper > .glass-fixed-shell-backplate--main {
|
||||
clip-path: polygon(
|
||||
0.5rem 0,
|
||||
100% 0,
|
||||
100% 100%,
|
||||
calc(100% - var(--glass-fixed-shell-nav-inline-size)) 100%,
|
||||
calc(100% - var(--glass-fixed-shell-nav-inline-size)) var(--layout-navbar-block-size),
|
||||
0.5rem var(--layout-navbar-block-size)
|
||||
);
|
||||
}
|
||||
|
||||
[dir='rtl']
|
||||
.layout-wrapper:is(.layout-horizontal-nav-active, .layout-overlay-nav)
|
||||
> .glass-fixed-shell-backplate--main {
|
||||
clip-path: inset(0 0 calc(100% - var(--layout-navbar-block-size)) 0);
|
||||
}
|
||||
|
||||
.glass-fixed-shell-backplate--overlay-nav {
|
||||
z-index: variables.$layout-vertical-nav-z-index - 1;
|
||||
clip-path: inset(0 100% 0 0);
|
||||
transition: clip-path 0.25s ease-in-out;
|
||||
|
||||
&.is-visible {
|
||||
clip-path: inset(0 calc(100% - #{variables.$layout-vertical-nav-width}) 0 0);
|
||||
}
|
||||
|
||||
@include mixins.rtl {
|
||||
clip-path: inset(0 0 0 100%);
|
||||
|
||||
&.is-visible {
|
||||
clip-path: inset(0 0 0 calc(100% - #{variables.$layout-vertical-nav-width}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.glass-fixed-shell-backplate__layer {
|
||||
position: absolute;
|
||||
filter: var(--glass-fixed-shell-backplate-filter);
|
||||
inset: 0;
|
||||
opacity: 0;
|
||||
transition: opacity var(--glass-fixed-shell-transition-duration, 1500ms) ease;
|
||||
will-change: opacity;
|
||||
|
||||
&.is-active {
|
||||
z-index: 2;
|
||||
opacity: 0.92;
|
||||
}
|
||||
|
||||
&.is-previous {
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.glass-fixed-shell-backplate__wallpaper {
|
||||
position: absolute;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
filter: brightness(var(--glass-wallpaper-brightness, 0.82)) saturate(0.9);
|
||||
inset: 0;
|
||||
|
||||
&::after {
|
||||
position: absolute;
|
||||
background: linear-gradient(rgba(6, 10, 19, 24%) 0%, rgba(6, 10, 19, 48%) 100%), rgba(11, 19, 34, 8%);
|
||||
content: '';
|
||||
inset: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.glass-fixed-shell-backplate,
|
||||
.glass-fixed-shell-backplate__layer {
|
||||
transition-duration: 0.01ms !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-transparency: reduce) {
|
||||
.glass-fixed-shell-backplate {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,90 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import GlassFixedShellBackplate from '@/components/theme/GlassFixedShellBackplate.vue'
|
||||
import type { GlassFixedShellBackplateLayer } from '@/composables/useGlassFixedShellBackplate'
|
||||
|
||||
const initialLayers: readonly GlassFixedShellBackplateLayer[] = [
|
||||
{
|
||||
key: 'front',
|
||||
role: 'active',
|
||||
style: {
|
||||
'backgroundImage': 'url(/wallpaper-current.jpg)',
|
||||
'--glass-wallpaper-brightness': '0.82',
|
||||
},
|
||||
url: '/wallpaper-current.jpg',
|
||||
},
|
||||
{
|
||||
key: 'back',
|
||||
role: 'standby',
|
||||
style: {
|
||||
'backgroundImage': 'url(/wallpaper-next.jpg)',
|
||||
'--glass-wallpaper-brightness': '0.76',
|
||||
},
|
||||
url: '/wallpaper-next.jpg',
|
||||
},
|
||||
]
|
||||
|
||||
describe('GlassFixedShellBackplate', () => {
|
||||
it('renders the App-owned slots once for the shared desktop shell', () => {
|
||||
const wrapper = mount(GlassFixedShellBackplate, {
|
||||
props: {
|
||||
isOverlayNav: false,
|
||||
isOverlayNavActive: false,
|
||||
layers: initialLayers,
|
||||
transitionDurationMs: 1500,
|
||||
},
|
||||
})
|
||||
|
||||
expect(wrapper.findAll('[data-backplate-surface="main"] [data-backplate-slot]')).toHaveLength(2)
|
||||
expect(wrapper.find('[data-backplate-slot="front"]').classes()).toContain('is-active')
|
||||
expect(wrapper.find('[data-backplate-slot="back"]').classes()).toContain('is-standby')
|
||||
expect(
|
||||
(wrapper.find('[data-backplate-slot="front"] > div').element as HTMLElement).style.backgroundImage,
|
||||
).toContain('/wallpaper-current.jpg')
|
||||
expect(wrapper.find('[data-backplate-surface="main"]').attributes('style')).toContain(
|
||||
'--glass-fixed-shell-transition-duration: 1500ms',
|
||||
)
|
||||
expect(wrapper.find('[data-backplate-surface="overlay-nav"]').exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('preserves slot nodes while their active and previous roles swap', async () => {
|
||||
const wrapper = mount(GlassFixedShellBackplate, {
|
||||
props: {
|
||||
isOverlayNav: false,
|
||||
isOverlayNavActive: false,
|
||||
layers: initialLayers,
|
||||
transitionDurationMs: 1500,
|
||||
},
|
||||
})
|
||||
const frontSlot = wrapper.find('[data-backplate-surface="main"] [data-backplate-slot="front"]').element
|
||||
const backSlot = wrapper.find('[data-backplate-surface="main"] [data-backplate-slot="back"]').element
|
||||
|
||||
await wrapper.setProps({
|
||||
layers: [
|
||||
{ ...initialLayers[0], role: 'previous' },
|
||||
{ ...initialLayers[1], role: 'active' },
|
||||
],
|
||||
})
|
||||
|
||||
expect(wrapper.find('[data-backplate-surface="main"] [data-backplate-slot="front"]').element).toBe(frontSlot)
|
||||
expect(wrapper.find('[data-backplate-surface="main"] [data-backplate-slot="back"]').element).toBe(backSlot)
|
||||
expect(wrapper.find('[data-backplate-slot="front"]').classes()).toContain('is-previous')
|
||||
expect(wrapper.find('[data-backplate-slot="back"]').classes()).toContain('is-active')
|
||||
})
|
||||
|
||||
it('adds a separately clipped surface only for mobile overlay navigation', () => {
|
||||
const wrapper = mount(GlassFixedShellBackplate, {
|
||||
props: {
|
||||
isOverlayNav: true,
|
||||
isOverlayNavActive: true,
|
||||
layers: initialLayers,
|
||||
transitionDurationMs: 1500,
|
||||
},
|
||||
})
|
||||
|
||||
const overlay = wrapper.find('[data-backplate-surface="overlay-nav"]')
|
||||
expect(overlay.exists()).toBe(true)
|
||||
expect(overlay.classes()).toContain('is-visible')
|
||||
expect(overlay.findAll('[data-backplate-slot]')).toHaveLength(2)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,23 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { shouldUseGlassFixedShellBackplate } from '@/composables/useGlassFixedShellBackplate'
|
||||
|
||||
describe('shouldUseGlassFixedShellBackplate', () => {
|
||||
const eligible = {
|
||||
appearance: 'frosted' as const,
|
||||
hasWallpaper: true,
|
||||
isAuthenticated: true,
|
||||
quality: 'css' as const,
|
||||
themeName: 'glass',
|
||||
}
|
||||
|
||||
it('enables the direct wallpaper backplate only for authenticated frosted CSS rendering', () => {
|
||||
expect(shouldUseGlassFixedShellBackplate(eligible)).toBe(true)
|
||||
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)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,57 @@
|
||||
import { inject, provide, shallowRef, type InjectionKey, type Ref, type StyleValue } from 'vue'
|
||||
import type { ThemeCustomizerGlassAppearance, ThemeCustomizerGlassQuality } from '@/composables/useThemeCustomizer'
|
||||
import type { LoginBackgroundLayer } from '@/utils/loginPresentation'
|
||||
|
||||
export interface GlassFixedShellBackplateLayer extends LoginBackgroundLayer {
|
||||
/** 当前槽位直接采样壁纸所需的背景图与色调变量。 */
|
||||
style: StyleValue
|
||||
}
|
||||
|
||||
export interface GlassFixedShellBackplateContext {
|
||||
/** App 持有的稳定双槽位;布局只消费,不建立第二套壁纸生命周期。 */
|
||||
layers: Readonly<Ref<readonly GlassFixedShellBackplateLayer[]>>
|
||||
/** 双槽位角色交换时使用的统一交叉淡化时长。 */
|
||||
transitionDurationMs: number
|
||||
}
|
||||
|
||||
interface GlassFixedShellBackplateEligibility {
|
||||
/** 当前玻璃材质。 */
|
||||
appearance: ThemeCustomizerGlassAppearance
|
||||
/** 稳定双槽位中是否已经存在可显示的壁纸。 */
|
||||
hasWallpaper: boolean
|
||||
/** 当前页面是否处于认证后的主布局。 */
|
||||
isAuthenticated: boolean
|
||||
/** 当前玻璃渲染质量。 */
|
||||
quality: ThemeCustomizerGlassQuality
|
||||
/** Vuetify 当前解析后的主题名。 */
|
||||
themeName: string
|
||||
}
|
||||
|
||||
const GLASS_FIXED_SHELL_BACKPLATE_KEY: InjectionKey<GlassFixedShellBackplateContext> =
|
||||
Symbol('glass-fixed-shell-backplate')
|
||||
const EMPTY_FIXED_SHELL_LAYERS = shallowRef<readonly GlassFixedShellBackplateLayer[]>([])
|
||||
const EMPTY_FIXED_SHELL_CONTEXT: GlassFixedShellBackplateContext = {
|
||||
layers: EMPTY_FIXED_SHELL_LAYERS,
|
||||
transitionDurationMs: 0,
|
||||
}
|
||||
|
||||
/** 只有磨砂标准使用直接壁纸背板;其他材质和 GPU 质量继续走既有呈现路径。 */
|
||||
export function shouldUseGlassFixedShellBackplate(options: GlassFixedShellBackplateEligibility) {
|
||||
return (
|
||||
options.isAuthenticated &&
|
||||
options.themeName === 'glass' &&
|
||||
options.appearance === 'frosted' &&
|
||||
options.quality === 'css' &&
|
||||
options.hasWallpaper
|
||||
)
|
||||
}
|
||||
|
||||
/** 从 App 向认证后布局提供唯一的 fixed-shell 壁纸双槽位。 */
|
||||
export function provideGlassFixedShellBackplate(context: GlassFixedShellBackplateContext) {
|
||||
provide(GLASS_FIXED_SHELL_BACKPLATE_KEY, context)
|
||||
}
|
||||
|
||||
/** 读取 fixed-shell 双槽位;不在 App 树下时返回稳定空上下文。 */
|
||||
export function useGlassFixedShellBackplate() {
|
||||
return inject(GLASS_FIXED_SHELL_BACKPLATE_KEY, EMPTY_FIXED_SHELL_CONTEXT)
|
||||
}
|
||||
@@ -33,13 +33,19 @@ describe('glass overlay material styles', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('keeps the fixed navigation backdrop isolated from route content and its scrollbar', () => {
|
||||
it('keeps frosted CSS 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')
|
||||
|
||||
expect(styles).toContain('--glass-fixed-shell-backdrop-filter: blur(min(var(--glass-blur-raised), 60px))')
|
||||
expect(styles).toContain('--glass-fixed-shell-backplate-filter: blur(min(var(--glass-blur-raised), 60px))')
|
||||
expect(styles).toMatch(
|
||||
/\.layout-vertical-nav\s*\{[\s\S]*?isolation:\s*isolate;[\s\S]*?backdrop-filter:\s*none;[\s\S]*?&::before\s*\{[\s\S]*?backdrop-filter:\s*var\(--glass-fixed-shell-backdrop-filter\);/,
|
||||
/&\[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;/,
|
||||
)
|
||||
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%')
|
||||
expect(backplate).toContain('.layout-wrapper:is(.layout-horizontal-nav-active, .layout-overlay-nav)')
|
||||
expect(backplate).toContain('.glass-fixed-shell-backplate--overlay-nav')
|
||||
expect(styles).toMatch(/\.layout-vertical-nav \.ps__rail-y\s*\{[\s\S]*?inset-inline-end:\s*0\.5rem !important;/)
|
||||
})
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ html[data-theme='glass'] {
|
||||
--glass-surface-backdrop-filter: var(--glass-native-surface-backdrop-filter);
|
||||
--glass-raised-backdrop-filter: brightness(var(--glass-transmission-brightness));
|
||||
--glass-fixed-shell-backdrop-filter: var(--glass-raised-backdrop-filter);
|
||||
--glass-fixed-shell-backplate-filter: brightness(var(--glass-transmission-brightness));
|
||||
--glass-control-backdrop-filter: none;
|
||||
--glass-control-prominent-backdrop-filter: none;
|
||||
--glass-navbar-backdrop-filter: var(--glass-raised-backdrop-filter);
|
||||
@@ -210,6 +211,8 @@ html[data-theme='glass'] {
|
||||
// 固定全高导航限制采样半径,避免页面重绘扩大其 backdrop 栅格化区域。
|
||||
--glass-fixed-shell-backdrop-filter: blur(min(var(--glass-blur-raised), 60px)) saturate(var(--glass-saturate))
|
||||
brightness(var(--glass-transmission-brightness));
|
||||
--glass-fixed-shell-backplate-filter: blur(min(var(--glass-blur-raised), 60px)) saturate(var(--glass-saturate))
|
||||
brightness(var(--glass-transmission-brightness));
|
||||
--glass-control-prominent-backdrop-filter: blur(24px) saturate(150%);
|
||||
--glass-overlay-surface: rgba(var(--v-theme-background), calc(0.24 + var(--glass-surface-density, 0.86) * 0.12));
|
||||
--glass-overlay-blur: min(var(--glass-blur-raised), 36px);
|
||||
@@ -443,7 +446,7 @@ html[data-theme='glass'] {
|
||||
background: transparent !important;
|
||||
box-shadow: none !important;
|
||||
|
||||
// 导航材质与可变菜单内容分层,路由激活态切换不应重栅格化整块全高背板。
|
||||
// 导航色层与可变菜单内容分离,路由激活态切换不应重建整块材质。
|
||||
&::before {
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
@@ -459,6 +462,16 @@ html[data-theme='glass'] {
|
||||
}
|
||||
}
|
||||
|
||||
// 磨砂标准直接渲染稳定壁纸背板,固定功能层不得读取随页面重绘的 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 {
|
||||
-webkit-backdrop-filter: none !important;
|
||||
backdrop-filter: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
// 滚动条与导航材质边界保持间距,避免窄轨道参与全高背板的合成。
|
||||
.layout-vertical-nav .ps__rail-y {
|
||||
inset-inline-end: 0.5rem !important;
|
||||
|
||||
Reference in New Issue
Block a user