fix: ios页面抽搐

This commit is contained in:
2026-08-02 20:54:25 +08:00
parent 405ef6bd52
commit 371a7a4582
2 changed files with 36 additions and 17 deletions

View File

@@ -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',统一升级为 dvhiOS 上 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;

View File

@@ -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
}
}