mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-08-10 08:03:45 +08:00
fix(glass): reuse CORS wallpaper sources (#640)
This commit is contained in:
47
src/App.vue
47
src/App.vue
@@ -154,6 +154,7 @@ const globalSettingsStore = useGlobalSettingsStore()
|
||||
const backgroundImages = ref<string[]>([])
|
||||
const backgroundLayers = ref(createLoginBackgroundLayers())
|
||||
const backgroundDisplayImages = ref<Record<string, string>>({})
|
||||
const backgroundCorsReady = ref<Record<string, boolean>>({})
|
||||
const backgroundToneProfiles = ref<Record<string, GlassWallpaperToneProfile>>({})
|
||||
const activeImageIndex = ref(0)
|
||||
const previousImageIndex = ref<number | null>(null)
|
||||
@@ -302,13 +303,25 @@ function getBackgroundLayerStyle(layer: LoginBackgroundLayer) {
|
||||
const appearance = effectiveGlassSettings.value.glassAppearance
|
||||
const materialExposure = appearance === 'frosted' ? 0.82 : appearance === 'tinted' ? 0.85 : 0.86
|
||||
const displayUrl = isGlassTheme.value ? getPreparedBackgroundImage(layer.url) : layer.url
|
||||
const usesCorsImageElement = isGlassTheme.value && Object.hasOwn(backgroundDisplayImages.value, layer.url)
|
||||
|
||||
return {
|
||||
'backgroundImage': displayUrl ? `url(${displayUrl})` : undefined,
|
||||
'backgroundImage': !usesCorsImageElement && displayUrl ? `url(${displayUrl})` : undefined,
|
||||
'--glass-wallpaper-brightness': String(materialExposure * profile.exposure),
|
||||
}
|
||||
}
|
||||
|
||||
/** 玻璃可见层与 tone/WebGL 使用同一图片请求模式,避免 CSS 再创建无 Origin 的缓存变体。 */
|
||||
function getBackgroundLayerImageSource(layer: LoginBackgroundLayer) {
|
||||
if (!isGlassTheme.value || !Object.hasOwn(backgroundDisplayImages.value, layer.url)) return ''
|
||||
|
||||
return getPreparedBackgroundImage(layer.url)
|
||||
}
|
||||
|
||||
function getBackgroundLayerCrossOrigin(layer: LoginBackgroundLayer) {
|
||||
return backgroundCorsReady.value[layer.url] ? 'anonymous' : undefined
|
||||
}
|
||||
|
||||
const needsStableFixedBackdrop = isChromiumFixedShellBackplateBrowser()
|
||||
const fixedShellBackplateLayers = computed<readonly GlassFixedShellBackplateLayer[]>(() => {
|
||||
const hasWallpaper = renderedBackgroundLayers.value.some(layer => Boolean(layer.url))
|
||||
@@ -327,6 +340,8 @@ const fixedShellBackplateLayers = computed<readonly GlassFixedShellBackplateLaye
|
||||
|
||||
return renderedBackgroundLayers.value.map(layer => ({
|
||||
...layer,
|
||||
crossOrigin: getBackgroundLayerCrossOrigin(layer),
|
||||
src: getBackgroundLayerImageSource(layer),
|
||||
style: getBackgroundLayerStyle(layer),
|
||||
}))
|
||||
})
|
||||
@@ -711,6 +726,10 @@ async function preloadBackgroundCandidate(imageUrl: string) {
|
||||
...backgroundDisplayImages.value,
|
||||
[imageUrl]: opticalUrl,
|
||||
}
|
||||
backgroundCorsReady.value = {
|
||||
...backgroundCorsReady.value,
|
||||
[imageUrl]: true,
|
||||
}
|
||||
recordGlassLaunchTiming('wallpaper-source-ready', imageUrl)
|
||||
return true
|
||||
}
|
||||
@@ -719,6 +738,10 @@ async function preloadBackgroundCandidate(imageUrl: string) {
|
||||
...backgroundDisplayImages.value,
|
||||
[imageUrl]: imageUrl,
|
||||
}
|
||||
backgroundCorsReady.value = {
|
||||
...backgroundCorsReady.value,
|
||||
[imageUrl]: false,
|
||||
}
|
||||
|
||||
const ready = await preloadImage(imageUrl)
|
||||
recordGlassLaunchTiming(ready ? 'wallpaper-source-ready' : 'wallpaper-source-failed', imageUrl)
|
||||
@@ -1137,7 +1160,17 @@ onUnmounted(() => {
|
||||
class="background-image"
|
||||
:class="layer.role"
|
||||
:style="getBackgroundLayerStyle(layer)"
|
||||
/>
|
||||
>
|
||||
<img
|
||||
v-if="getBackgroundLayerImageSource(layer)"
|
||||
class="background-image__source"
|
||||
:crossorigin="getBackgroundLayerCrossOrigin(layer)"
|
||||
:src="getBackgroundLayerImageSource(layer)"
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
draggable="false"
|
||||
/>
|
||||
</div>
|
||||
<!-- 全局磨砂层 -->
|
||||
<div v-if="shouldRenderGlobalBlurLayer" class="global-blur-layer"></div>
|
||||
</div>
|
||||
@@ -1238,6 +1271,16 @@ onUnmounted(() => {
|
||||
}
|
||||
}
|
||||
|
||||
.background-image__source {
|
||||
position: absolute;
|
||||
display: block;
|
||||
block-size: 100%;
|
||||
inline-size: 100%;
|
||||
inset: 0;
|
||||
object-fit: cover;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.background-container.is-transparent-theme .background-image.active {
|
||||
opacity: var(--transparent-background-poster-opacity, 1);
|
||||
}
|
||||
|
||||
@@ -103,6 +103,7 @@ async function goPlay() {
|
||||
@keyup.enter="goPlay"
|
||||
@keyup.space="goPlay"
|
||||
>
|
||||
<!-- 媒体服务器图片统一采用匿名 CORS,避免同一代理资源分裂为两种请求与缓存模式;Safari 普通 dev 可能重复请求,PWA 下由图片缓存路由统一处理。 -->
|
||||
<VImg
|
||||
:src="imageUrl"
|
||||
crossorigin="anonymous"
|
||||
|
||||
@@ -63,6 +63,7 @@ async function goPlay(isHovering: boolean | null = false) {
|
||||
'ring-1': isImageLoaded,
|
||||
}"
|
||||
>
|
||||
<!-- 媒体服务器图片统一采用匿名 CORS,避免同一代理资源分裂为两种请求与缓存模式;Safari 普通 dev 可能重复请求,PWA 下由图片缓存路由统一处理。 -->
|
||||
<VImg
|
||||
aspect-ratio="2/3"
|
||||
:src="getImgUrl"
|
||||
|
||||
@@ -32,7 +32,17 @@ const transitionStyle = computed(() => ({
|
||||
:class="`is-${layer.role}`"
|
||||
:data-backplate-slot="layer.key"
|
||||
>
|
||||
<div class="glass-fixed-shell-backplate__wallpaper" :style="layer.style" />
|
||||
<div class="glass-fixed-shell-backplate__wallpaper" :style="layer.style">
|
||||
<img
|
||||
v-if="layer.src"
|
||||
class="glass-fixed-shell-backplate__source"
|
||||
:crossorigin="layer.crossOrigin"
|
||||
:src="layer.src"
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
draggable="false"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -51,7 +61,17 @@ const transitionStyle = computed(() => ({
|
||||
:class="`is-${layer.role}`"
|
||||
:data-backplate-slot="layer.key"
|
||||
>
|
||||
<div class="glass-fixed-shell-backplate__wallpaper" :style="layer.style" />
|
||||
<div class="glass-fixed-shell-backplate__wallpaper" :style="layer.style">
|
||||
<img
|
||||
v-if="layer.src"
|
||||
class="glass-fixed-shell-backplate__source"
|
||||
:crossorigin="layer.crossOrigin"
|
||||
:src="layer.src"
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
draggable="false"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -160,6 +180,16 @@ const transitionStyle = computed(() => ({
|
||||
}
|
||||
}
|
||||
|
||||
.glass-fixed-shell-backplate__source {
|
||||
position: absolute;
|
||||
display: block;
|
||||
block-size: 100%;
|
||||
inline-size: 100%;
|
||||
inset: 0;
|
||||
object-fit: cover;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.glass-fixed-shell-backplate,
|
||||
.glass-fixed-shell-backplate__layer {
|
||||
|
||||
@@ -7,8 +7,9 @@ const initialLayers: readonly GlassFixedShellBackplateLayer[] = [
|
||||
{
|
||||
key: 'front',
|
||||
role: 'active',
|
||||
crossOrigin: 'anonymous',
|
||||
src: '/wallpaper-current.jpg',
|
||||
style: {
|
||||
'backgroundImage': 'url(/wallpaper-current.jpg)',
|
||||
'--glass-wallpaper-brightness': '0.82',
|
||||
},
|
||||
url: '/wallpaper-current.jpg',
|
||||
@@ -16,8 +17,9 @@ const initialLayers: readonly GlassFixedShellBackplateLayer[] = [
|
||||
{
|
||||
key: 'back',
|
||||
role: 'standby',
|
||||
crossOrigin: 'anonymous',
|
||||
src: '/wallpaper-next.jpg',
|
||||
style: {
|
||||
'backgroundImage': 'url(/wallpaper-next.jpg)',
|
||||
'--glass-wallpaper-brightness': '0.76',
|
||||
},
|
||||
url: '/wallpaper-next.jpg',
|
||||
@@ -38,9 +40,10 @@ describe('GlassFixedShellBackplate', () => {
|
||||
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-slot="front"] img').attributes()).toMatchObject({
|
||||
crossorigin: 'anonymous',
|
||||
src: '/wallpaper-current.jpg',
|
||||
})
|
||||
expect(wrapper.find('[data-backplate-surface="main"]').attributes('style')).toContain(
|
||||
'--glass-fixed-shell-transition-duration: 1500ms',
|
||||
)
|
||||
|
||||
@@ -3,7 +3,11 @@ import type { ThemeCustomizerGlassAppearance, ThemeCustomizerGlassQuality } from
|
||||
import type { LoginBackgroundLayer } from '@/utils/loginPresentation'
|
||||
|
||||
export interface GlassFixedShellBackplateLayer extends LoginBackgroundLayer {
|
||||
/** 当前槽位直接采样壁纸所需的背景图与色调变量。 */
|
||||
/** 与 tone/WebGL 一致的图片请求模式;CORS 不可用时省略并使用普通图片回退。 */
|
||||
crossOrigin?: 'anonymous'
|
||||
/** 已完成首图准备、可直接进入稳定背板槽位的图片地址。 */
|
||||
src: string
|
||||
/** 当前槽位直接采样壁纸时叠加的色调变量。 */
|
||||
style: StyleValue
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user