fix: ios页面抽搐
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="wrapper relative w-full min-h-[100vh] bg-[#Fdfbf7] overflow-x-hidden text-[#2c2c2c]">
|
||||
<div class="wrapper relative w-full min-h-[100dvh] bg-[#Fdfbf7] overflow-x-hidden text-[#2c2c2c]">
|
||||
|
||||
<!-- 入场揭幕帘幕(请柬封面) -->
|
||||
<div v-show="showIntro" class="curtain-root" :class="[{ 'is-opening': introOpening }, introExitClass]" @click="openInvitation">
|
||||
@@ -256,7 +256,7 @@
|
||||
|
||||
<!-- 主滚动区域(注意:不要加 scroll-smooth,它会劫持 JS 滚动赋值导致卡顿) -->
|
||||
<div ref="scrollContainerRef"
|
||||
class="w-full min-h-[100vh] no-scrollbar relative"
|
||||
class="w-full min-h-[100dvh] no-scrollbar relative"
|
||||
@touchstart="handleUserInteraction" @touchmove="handleUserInteraction"
|
||||
@wheel="handleUserInteraction" @mousedown="handleUserInteraction">
|
||||
|
||||
@@ -642,7 +642,7 @@ const DEFAULTS = {
|
||||
freeScrollInterval: 50, // 自由滚动速度:滚动间隔 ms(越小越快)
|
||||
pageSwitchDuration: 800, // 页面切换速度:翻页动画时长 ms
|
||||
firstScreenDuration: 4500, // 首屏停留时间:首屏效果结束后再开始自动滚动
|
||||
formalPageHeight: '100vh',
|
||||
formalPageHeight: '100dvh',
|
||||
calendarDate: '2026-09-04',
|
||||
introExitEffect: 'wind',
|
||||
}
|
||||
@@ -653,6 +653,7 @@ const data = ref({"date": "2026年9月4日 星期五", "bride": "李逸烁", "gr
|
||||
const audio = useAudio()
|
||||
const isAutoScroll = ref(true), isDrawerOpen = ref(false), isSubmitted = ref(false)
|
||||
const isInteracting = ref(false), scrollContainerRef = ref(null), progress = ref(0)
|
||||
const isSnapping = ref(false)
|
||||
const showIntro = ref(false), introOpening = ref(false), showTrackList = ref(false), showMapOptions = ref(false)
|
||||
const showOpenInBrowser = ref(false)
|
||||
const showBlessingDrawer = ref(false)
|
||||
@@ -790,6 +791,8 @@ const scheduleBlessingAutoClose = () => {
|
||||
|
||||
let rafId = null, snapTimer = null, pauseTimer = null, introTimer = null, freeTimer = null, animId = null, scrollStartTimer = null, bottomReturnTimer = null
|
||||
let freeScrollLastTs = 0
|
||||
/** free 模式自维护的滚动偏移,避免每帧读 scrollY 触发 iOS 同步重排/撕裂 */
|
||||
let freeScrollOffset = -1
|
||||
let galleryObserver = null
|
||||
let galleryRevealTimer = null
|
||||
const galleryDone = new Set()
|
||||
@@ -855,8 +858,11 @@ const calendarDays = computed(() => {
|
||||
})
|
||||
|
||||
const sectionHeightStyle = (height) => {
|
||||
const h = height || '100vh'
|
||||
return h === 'auto' ? { minHeight: 'auto' } : { minHeight: h }
|
||||
let h = height || '100dvh'
|
||||
if (h === 'auto') return { minHeight: 'auto' }
|
||||
// 后端配置可能是 '100vh',统一升级为 dvh(iOS 上 vh 会因地址栏伸缩抖动)
|
||||
if (/^\d+(\.\d+)?vh$/.test(h)) h = h.replace('vh', 'dvh')
|
||||
return { minHeight: h }
|
||||
}
|
||||
const formalPageHeightStyle = computed(() => sectionHeightStyle(data.value.formalPageHeight))
|
||||
const pageHeightStyle = (page) => sectionHeightStyle(page.height)
|
||||
@@ -958,7 +964,11 @@ const handleUserInteraction = () => {
|
||||
cancelAnimationFrame(animId)
|
||||
clearTimeout(pauseTimer)
|
||||
if (isAtBottom()) scheduleBottomReturn(true)
|
||||
pauseTimer = setTimeout(() => (isInteracting.value = false), 3000)
|
||||
pauseTimer = setTimeout(() => {
|
||||
isInteracting.value = false
|
||||
// 恢复自动滚动前,让下一帧重新同步真实滚动位置
|
||||
freeScrollOffset = -1
|
||||
}, 3000)
|
||||
}
|
||||
|
||||
const sectionEls = () => Array.from(scrollContainerRef.value?.querySelectorAll('.page-section') || [])
|
||||
@@ -991,6 +1001,8 @@ const smoothScrollTo = (target, duration) => {
|
||||
cancelAnimationFrame(animId)
|
||||
const start = pageScrollTop(), dist = target - start
|
||||
if (!dist) return
|
||||
// 标记 offset 失效,下一次 free 滚动重新从真实位置开始
|
||||
freeScrollOffset = -1
|
||||
if (duration <= 0) { window.scrollTo(0, target); return }
|
||||
const t0 = performance.now()
|
||||
const tick = (now) => {
|
||||
@@ -1003,18 +1015,22 @@ const smoothScrollTo = (target, duration) => {
|
||||
}
|
||||
|
||||
const goNextSection = () => {
|
||||
if (isSnapping.value) return
|
||||
const top = pageScrollTop()
|
||||
const next = sectionEls().find((e) => e.offsetTop > top + 12)
|
||||
if (next) {
|
||||
smoothScrollTo(next.offsetTop, switchDuration.value)
|
||||
if (!next) {
|
||||
scheduleBottomReturn()
|
||||
return
|
||||
}
|
||||
scheduleBottomReturn()
|
||||
isSnapping.value = true
|
||||
next.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
||||
setTimeout(() => { isSnapping.value = false }, switchDuration.value + 120)
|
||||
}
|
||||
|
||||
const canAutoAdvance = () =>
|
||||
isAutoScroll.value
|
||||
&& !isInteracting.value
|
||||
&& !isSnapping.value
|
||||
&& !isDrawerOpen.value
|
||||
&& !showBlessingDrawer.value
|
||||
&& !previewVisible.value
|
||||
@@ -1080,16 +1096,19 @@ const startAutoScroll = () => {
|
||||
}, 4500)
|
||||
} else {
|
||||
freeScrollLastTs = 0
|
||||
// 首次或重启动时,把 offset 同步到当前真实位置
|
||||
if (freeScrollOffset < 0) freeScrollOffset = pageScrollTop()
|
||||
const tickFreeScroll = (now) => {
|
||||
const dt = freeScrollLastTs ? Math.min(34, now - freeScrollLastTs) : 16.7
|
||||
freeScrollLastTs = now
|
||||
if (canAutoAdvance()) {
|
||||
const top = pageScrollTop()
|
||||
const max = pageScrollMax()
|
||||
if (max - top <= 8) {
|
||||
if (max - freeScrollOffset <= 8) {
|
||||
scheduleBottomReturn()
|
||||
} else {
|
||||
window.scrollTo(0, top + FREE_SCROLL_BASE_STEP * (dt / freeInterval.value))
|
||||
// 单向写入,避免 read-then-write 触发 iOS 合成器/主线程同步抖动
|
||||
freeScrollOffset += FREE_SCROLL_BASE_STEP * (dt / freeInterval.value)
|
||||
window.scrollTo(0, freeScrollOffset)
|
||||
}
|
||||
}
|
||||
rafId = requestAnimationFrame(tickFreeScroll)
|
||||
@@ -1397,10 +1416,8 @@ watch(
|
||||
display: none;
|
||||
}
|
||||
:global(html.page-scroll-snap) {
|
||||
scroll-snap-type: y mandatory;
|
||||
}
|
||||
:global(html.page-scroll-snap body) {
|
||||
scroll-snap-type: y mandatory;
|
||||
scroll-snap-type: y proximity;
|
||||
scroll-behavior: auto;
|
||||
}
|
||||
.wrapper .font-serif { font-family: var(--font-serif); }
|
||||
.wrapper .font-sans { font-family: var(--font-sans); }
|
||||
@@ -2088,6 +2105,7 @@ watch(
|
||||
}
|
||||
.page-section {
|
||||
min-height: 100vh;
|
||||
min-height: 100dvh;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
@@ -19,7 +19,8 @@ export default defineConfig({
|
||||
proxy: {
|
||||
// 婚礼纪 Go 后端运行在 :8080,接口统一以 /api 开头,无需重写
|
||||
'/api': {
|
||||
target: 'http://127.0.0.1:15201',
|
||||
// target: 'http://127.0.0.1:15201',
|
||||
target: 'https://hl.yqh.nailaoyun.cn',
|
||||
changeOrigin: true
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user