mirror of
https://github.com/LifeArchiveProject/WeChatDataAnalysis.git
synced 2026-02-19 22:30:49 +08:00
improvement(wrapped-ui): 下线 DOS 主题并优化 Wrapped 多主题体验
- 移除 DOS 主题入口、切换器组件与相关样式逻辑,统一主题为 Modern / GameBoy / Win98。 - 新增 WrappedGameboyDither 组件,并在背景与 CRT 叠加层中引入 GameBoy 噪点效果。 - 优化 wrapped 页面视口高度与背景同步逻辑(含 ResizeObserver 与 100dvh 适配),提升桌面容器显示稳定性。 - 调整封面标题与预览位移、回复速度卡片滚动行为等细节,提升主题下视觉与交互一致性。
This commit is contained in:
98
frontend/components/wrapped/shared/WrappedGameboyDither.vue
Normal file
98
frontend/components/wrapped/shared/WrappedGameboyDither.vue
Normal file
@@ -0,0 +1,98 @@
|
||||
<template>
|
||||
<canvas
|
||||
ref="grainRef"
|
||||
class="pointer-events-none absolute inset-0 h-full w-full"
|
||||
:style="canvasStyle"
|
||||
aria-hidden="true"
|
||||
></canvas>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
patternRefreshInterval: {
|
||||
type: Number,
|
||||
default: 2
|
||||
},
|
||||
patternAlpha: {
|
||||
type: Number,
|
||||
default: 20
|
||||
},
|
||||
mixBlendMode: {
|
||||
type: String,
|
||||
default: 'multiply'
|
||||
},
|
||||
patternSize: {
|
||||
type: Number,
|
||||
default: 512
|
||||
}
|
||||
})
|
||||
|
||||
const grainRef = ref(null)
|
||||
|
||||
const canvasStyle = computed(() => `image-rendering: pixelated; mix-blend-mode: ${props.mixBlendMode};`)
|
||||
|
||||
let animationId = 0
|
||||
let frame = 0
|
||||
let noiseData
|
||||
let noise32
|
||||
|
||||
const clamp = (value, min, max) => Math.min(max, Math.max(min, value))
|
||||
|
||||
const resize = () => {
|
||||
const canvas = grainRef.value
|
||||
if (!canvas) return
|
||||
const size = Math.max(64, Math.round(props.patternSize))
|
||||
canvas.width = size
|
||||
canvas.height = size
|
||||
}
|
||||
|
||||
const initImageData = (ctx) => {
|
||||
const canvas = grainRef.value
|
||||
if (!canvas) return
|
||||
noiseData = ctx.createImageData(canvas.width, canvas.height)
|
||||
noise32 = new Uint32Array(noiseData.data.buffer)
|
||||
}
|
||||
|
||||
const drawGrain = () => {
|
||||
if (!noise32) return
|
||||
const alpha = clamp(Math.round(props.patternAlpha), 0, 255) << 24
|
||||
for (let i = 0; i < noise32.length; i++) {
|
||||
const value = (Math.random() * 255) | 0
|
||||
noise32[i] = alpha | (value << 16) | (value << 8) | value
|
||||
}
|
||||
}
|
||||
|
||||
const loop = (ctx) => {
|
||||
const refreshEvery = Math.max(1, Math.round(props.patternRefreshInterval))
|
||||
if (frame % refreshEvery === 0) {
|
||||
drawGrain()
|
||||
ctx.putImageData(noiseData, 0, 0)
|
||||
}
|
||||
|
||||
frame++
|
||||
animationId = window.requestAnimationFrame(() => loop(ctx))
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
const canvas = grainRef.value
|
||||
if (!canvas) return
|
||||
|
||||
const ctx = canvas.getContext('2d', { alpha: true })
|
||||
if (!ctx) return
|
||||
|
||||
resize()
|
||||
initImageData(ctx)
|
||||
drawGrain()
|
||||
ctx.putImageData(noiseData, 0, 0)
|
||||
loop(ctx)
|
||||
|
||||
window.addEventListener('resize', resize)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('resize', resize)
|
||||
window.cancelAnimationFrame(animationId)
|
||||
})
|
||||
</script>
|
||||
Reference in New Issue
Block a user