九宫格优化
This commit is contained in:
@@ -38,19 +38,27 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 九宫格:未 activate 用透明占位撑布局;全入后再挂载真图 + 方向性 v-reveal -->
|
||||
<!-- 九宫格:透明占位撑布局;activate 后按行分批挂载真图,减轻主线程尖峰 -->
|
||||
<div v-else-if="page.type === 'nine-grid'" ref="nineGridRef" class="nine-grid grid grid-cols-3 gap-1.5 px-4 mt-6">
|
||||
<template v-if="activate">
|
||||
<div v-for="(img, imgIdx) in page.images" :key="imgIdx"
|
||||
v-reveal="getRevealConfig(imgIdx)"
|
||||
class="aspect-square relative" :class="frameClass">
|
||||
<img :src="img" class="nine-grid-img preview-img" @click.stop="previewImage(img)" />
|
||||
<div
|
||||
v-for="(img, imgIdx) in (page.images || [])"
|
||||
:key="imgIdx"
|
||||
class="aspect-square relative"
|
||||
:class="[frameClass, imgIdx < nineShownCount ? '' : 'nine-grid-placeholder']"
|
||||
>
|
||||
<div
|
||||
v-if="imgIdx < nineShownCount"
|
||||
v-reveal="getRevealConfig(imgIdx)"
|
||||
class="absolute inset-0"
|
||||
>
|
||||
<img
|
||||
:src="img"
|
||||
:data-nine-idx="imgIdx"
|
||||
class="nine-grid-img preview-img"
|
||||
@click.stop="previewImage(img)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div v-for="(_, imgIdx) in (page.images || [])" :key="`ph-${imgIdx}`"
|
||||
class="aspect-square relative nine-grid-placeholder" :class="frameClass" aria-hidden="true" />
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 瀑布流 -->
|
||||
@@ -92,7 +100,9 @@
|
||||
import { ref, computed, watch, nextTick } from 'vue'
|
||||
import { borderClass } from '@/composables/borderStyles'
|
||||
|
||||
const DECODE_TIMEOUT_MS = 4000
|
||||
const BATCH_SIZE = 3
|
||||
const BATCH_GAP_MS = 140
|
||||
const BATCH_DECODE_TIMEOUT_MS = 2500
|
||||
|
||||
const props = defineProps({
|
||||
page: { type: Object, required: true },
|
||||
@@ -102,37 +112,65 @@ const props = defineProps({
|
||||
const emit = defineEmits(['preview-image', 'ready'])
|
||||
|
||||
const nineGridRef = ref(null)
|
||||
const nineShownCount = ref(0)
|
||||
let readyEmitted = false
|
||||
|
||||
const frameClass = computed(() => borderClass(props.page.borderStyle))
|
||||
|
||||
const waitImagesReady = async () => {
|
||||
await nextTick()
|
||||
const root = nineGridRef.value
|
||||
if (!root) return
|
||||
const imgs = [...root.querySelectorAll('img[src]')]
|
||||
if (!imgs.length) return
|
||||
const decodeAll = Promise.allSettled(
|
||||
imgs.map((img) => {
|
||||
if (typeof img.decode === 'function') return img.decode().catch(() => {})
|
||||
if (img.complete) return Promise.resolve()
|
||||
return new Promise((resolve) => {
|
||||
img.addEventListener('load', resolve, { once: true })
|
||||
img.addEventListener('error', resolve, { once: true })
|
||||
})
|
||||
})
|
||||
)
|
||||
await Promise.race([
|
||||
decodeAll,
|
||||
new Promise((resolve) => setTimeout(resolve, DECODE_TIMEOUT_MS)),
|
||||
])
|
||||
const decodeImg = (img) => {
|
||||
if (!img) return Promise.resolve()
|
||||
if (typeof img.decode === 'function') return img.decode().catch(() => {})
|
||||
if (img.complete) return Promise.resolve()
|
||||
return new Promise((resolve) => {
|
||||
img.addEventListener('load', resolve, { once: true })
|
||||
img.addEventListener('error', resolve, { once: true })
|
||||
})
|
||||
}
|
||||
|
||||
const yieldToMain = () => new Promise((resolve) => {
|
||||
if (typeof requestAnimationFrame === 'function') {
|
||||
requestAnimationFrame(() => resolve())
|
||||
} else {
|
||||
setTimeout(resolve, 0)
|
||||
}
|
||||
})
|
||||
|
||||
const runBatchedReveal = async () => {
|
||||
const images = props.page.images || []
|
||||
const total = images.length
|
||||
if (!total) return
|
||||
|
||||
for (let start = 0; start < total; start += BATCH_SIZE) {
|
||||
nineShownCount.value = Math.min(total, start + BATCH_SIZE)
|
||||
await nextTick()
|
||||
await yieldToMain()
|
||||
|
||||
const root = nineGridRef.value
|
||||
const end = nineShownCount.value
|
||||
const imgs = root
|
||||
? [...root.querySelectorAll('img[data-nine-idx]')].filter((el) => {
|
||||
const i = Number(el.getAttribute('data-nine-idx'))
|
||||
return i >= start && i < end
|
||||
})
|
||||
: []
|
||||
|
||||
await Promise.race([
|
||||
Promise.allSettled(imgs.map(decodeImg)),
|
||||
new Promise((resolve) => setTimeout(resolve, BATCH_DECODE_TIMEOUT_MS)),
|
||||
])
|
||||
|
||||
if (end < total) {
|
||||
await new Promise((resolve) => setTimeout(resolve, BATCH_GAP_MS))
|
||||
await yieldToMain()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.activate && props.page?.type === 'nine-grid',
|
||||
async (on) => {
|
||||
if (!on || readyEmitted) return
|
||||
await waitImagesReady()
|
||||
await runBatchedReveal()
|
||||
if (readyEmitted) return
|
||||
readyEmitted = true
|
||||
emit('ready')
|
||||
|
||||
@@ -117,7 +117,7 @@
|
||||
<div class="formal-copy flex justify-center gap-9 h-[22rem] text-[#3A3A3A]">
|
||||
<span class="vertical-text formal-main calligraphy-strong">{{ data.formalText1 }}</span>
|
||||
<span class="vertical-text formal-main formal-main-offset calligraphy-strong">{{ data.formalText2 }}</span>
|
||||
<span class="vertical-text formal-side calligraphy ml-4 border-l-[0.5px] border-[#a88c6b]/30 pl-6 h-64 mt-8 tracking-[0.28em]">见证我们的幸福</span>
|
||||
<span class="vertical-text formal-side calligraphy ml-4 border-l-[0.5px] border-[#a88c6b]/30 pl-6 h-64 mt-28 tracking-[0.28em]">见证我们的幸福</span>
|
||||
<span class="vertical-text formal-side calligraphy ml-4 h-64 mt-8 tracking-[0.28em]">诚挚邀请您</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -353,7 +353,8 @@ let nineGridRevealTimer = null
|
||||
const nineGridDone = new Set()
|
||||
const BOTTOM_RETURN_DELAY = 30000
|
||||
const FREE_SCROLL_BASE_STEP = 1.2
|
||||
const NINE_GRID_REVEAL_HOLD_MS = 900
|
||||
/** 分批入场已占时间,收尾再略停一下即可 */
|
||||
const NINE_GRID_REVEAL_HOLD_MS = 400
|
||||
const NINE_GRID_FULL_RATIO = 0.9
|
||||
|
||||
const freeInterval = computed(() => {
|
||||
|
||||
Reference in New Issue
Block a user