更新页面效果
This commit is contained in:
BIN
vite-tailwindcss/dist (2).zip
Normal file
BIN
vite-tailwindcss/dist (2).zip
Normal file
Binary file not shown.
Binary file not shown.
@@ -2,7 +2,7 @@
|
|||||||
<html lang="zh-CN">
|
<html lang="zh-CN">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
<link rel="icon" type="image/svg+xml" href="https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/liqi/20260731/1774761336736_compressed_compressed.webp" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<meta name="description" content="我们将在2026年9月4日举行婚礼,诚挚邀请你前来" />
|
<meta name="description" content="我们将在2026年9月4日举行婚礼,诚挚邀请你前来" />
|
||||||
<meta property="og:type" content="website" />
|
<meta property="og:type" content="website" />
|
||||||
@@ -10,6 +10,7 @@
|
|||||||
<meta property="og:description" content="我们将在2026年9月4日举行婚礼,诚挚邀请你前来" />
|
<meta property="og:description" content="我们将在2026年9月4日举行婚礼,诚挚邀请你前来" />
|
||||||
<meta name="twitter:title" content="李琦&李逸烁的婚礼请柬" />
|
<meta name="twitter:title" content="李琦&李逸烁的婚礼请柬" />
|
||||||
<meta name="twitter:description" content="我们将在2026年9月4日举行婚礼,诚挚邀请你前来" />
|
<meta name="twitter:description" content="我们将在2026年9月4日举行婚礼,诚挚邀请你前来" />
|
||||||
|
<meta property="og:image" content="https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/liqi/20260731/1774761336736_compressed_compressed.webp">
|
||||||
<title>李琦&李逸烁的婚礼请柬</title>
|
<title>李琦&李逸烁的婚礼请柬</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
30
vite-tailwindcss/package-lock.json
generated
30
vite-tailwindcss/package-lock.json
generated
@@ -14,6 +14,7 @@
|
|||||||
"@tailwindcss/vite": "^4.1.4",
|
"@tailwindcss/vite": "^4.1.4",
|
||||||
"@vueuse/head": "^2.0.0",
|
"@vueuse/head": "^2.0.0",
|
||||||
"ant-design-vue": "^4.1.1",
|
"ant-design-vue": "^4.1.1",
|
||||||
|
"aos": "^2.3.4",
|
||||||
"axios": "^1.6.2",
|
"axios": "^1.6.2",
|
||||||
"lucide-vue-next": "^0.507.0",
|
"lucide-vue-next": "^0.507.0",
|
||||||
"pinia": "^2.1.7",
|
"pinia": "^2.1.7",
|
||||||
@@ -1756,6 +1757,17 @@
|
|||||||
"vue": ">=3.2.0"
|
"vue": ">=3.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/aos": {
|
||||||
|
"version": "2.3.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/aos/-/aos-2.3.4.tgz",
|
||||||
|
"integrity": "sha512-zh/ahtR2yME4I51z8IttIt4lC1Nw0ktsFtmeDzID1m9naJnWXhCoARaCgNOGXb5CLy3zm+wqmRAEgMYB5E2HUw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"classlist-polyfill": "^1.0.3",
|
||||||
|
"lodash.debounce": "^4.0.6",
|
||||||
|
"lodash.throttle": "^4.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/array-tree-filter": {
|
"node_modules/array-tree-filter": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/array-tree-filter/-/array-tree-filter-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/array-tree-filter/-/array-tree-filter-2.1.0.tgz",
|
||||||
@@ -1815,6 +1827,12 @@
|
|||||||
"url": "https://paulmillr.com/funding/"
|
"url": "https://paulmillr.com/funding/"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/classlist-polyfill": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/classlist-polyfill/-/classlist-polyfill-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-GzIjNdcEtH4ieA2S8NmrSxv7DfEV5fmixQeyTmqmRmRJPGpRBaSnA2a0VrCjyT8iW8JjEdMbKzDotAJf+ajgaQ==",
|
||||||
|
"license": "Unlicense"
|
||||||
|
},
|
||||||
"node_modules/combined-stream": {
|
"node_modules/combined-stream": {
|
||||||
"version": "1.0.8",
|
"version": "1.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||||
@@ -2538,6 +2556,18 @@
|
|||||||
"integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==",
|
"integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/lodash.debounce": {
|
||||||
|
"version": "4.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
|
||||||
|
"integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/lodash.throttle": {
|
||||||
|
"version": "4.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz",
|
||||||
|
"integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/loose-envify": {
|
"node_modules/loose-envify": {
|
||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
||||||
|
|||||||
@@ -9,18 +9,19 @@
|
|||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tailwindcss": "^4.1.4",
|
|
||||||
"vue": "^3.5.13",
|
|
||||||
"@ant-design-vue/pro-layout": "^3.2.5",
|
"@ant-design-vue/pro-layout": "^3.2.5",
|
||||||
"@ant-design/icons-vue": "^7.0.1",
|
"@ant-design/icons-vue": "^7.0.1",
|
||||||
"@fortawesome/fontawesome-free": "^6.7.2",
|
"@fortawesome/fontawesome-free": "^6.7.2",
|
||||||
"@tailwindcss/vite": "^4.1.4",
|
"@tailwindcss/vite": "^4.1.4",
|
||||||
"@vueuse/head": "^2.0.0",
|
"@vueuse/head": "^2.0.0",
|
||||||
"ant-design-vue": "^4.1.1",
|
"ant-design-vue": "^4.1.1",
|
||||||
|
"aos": "^2.3.4",
|
||||||
"axios": "^1.6.2",
|
"axios": "^1.6.2",
|
||||||
"lucide-vue-next": "^0.507.0",
|
"lucide-vue-next": "^0.507.0",
|
||||||
"pinia": "^2.1.7",
|
"pinia": "^2.1.7",
|
||||||
"swiper": "^11.0.5",
|
"swiper": "^11.0.5",
|
||||||
|
"tailwindcss": "^4.1.4",
|
||||||
|
"vue": "^3.5.13",
|
||||||
"vue-router": "^4.2.5"
|
"vue-router": "^4.2.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
27
vite-tailwindcss/pnpm-lock.yaml
generated
27
vite-tailwindcss/pnpm-lock.yaml
generated
@@ -26,6 +26,9 @@ importers:
|
|||||||
ant-design-vue:
|
ant-design-vue:
|
||||||
specifier: ^4.1.1
|
specifier: ^4.1.1
|
||||||
version: 4.2.6(vue@3.5.40)
|
version: 4.2.6(vue@3.5.40)
|
||||||
|
aos:
|
||||||
|
specifier: ^2.3.4
|
||||||
|
version: 2.3.4
|
||||||
axios:
|
axios:
|
||||||
specifier: ^1.6.2
|
specifier: ^1.6.2
|
||||||
version: 1.18.1(debug@4.4.3)
|
version: 1.18.1(debug@4.4.3)
|
||||||
@@ -678,6 +681,9 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
vue: '>=3.2.0'
|
vue: '>=3.2.0'
|
||||||
|
|
||||||
|
aos@2.3.4:
|
||||||
|
resolution: {integrity: sha512-zh/ahtR2yME4I51z8IttIt4lC1Nw0ktsFtmeDzID1m9naJnWXhCoARaCgNOGXb5CLy3zm+wqmRAEgMYB5E2HUw==}
|
||||||
|
|
||||||
array-tree-filter@2.1.0:
|
array-tree-filter@2.1.0:
|
||||||
resolution: {integrity: sha512-4ROwICNlNw/Hqa9v+rk5h22KjmzB1JGTMVKP2AKJBOCgb0yL0ASf0+YvCcLNNwquOHNX48jkeZIJ3a+oOQqKcw==}
|
resolution: {integrity: sha512-4ROwICNlNw/Hqa9v+rk5h22KjmzB1JGTMVKP2AKJBOCgb0yL0ASf0+YvCcLNNwquOHNX48jkeZIJ3a+oOQqKcw==}
|
||||||
|
|
||||||
@@ -698,6 +704,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==}
|
resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==}
|
||||||
engines: {node: '>= 20.19.0'}
|
engines: {node: '>= 20.19.0'}
|
||||||
|
|
||||||
|
classlist-polyfill@1.2.0:
|
||||||
|
resolution: {integrity: sha512-GzIjNdcEtH4ieA2S8NmrSxv7DfEV5fmixQeyTmqmRmRJPGpRBaSnA2a0VrCjyT8iW8JjEdMbKzDotAJf+ajgaQ==}
|
||||||
|
|
||||||
combined-stream@1.0.8:
|
combined-stream@1.0.8:
|
||||||
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
|
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
|
||||||
engines: {node: '>= 0.8'}
|
engines: {node: '>= 0.8'}
|
||||||
@@ -936,6 +945,12 @@ packages:
|
|||||||
lodash-es@4.18.1:
|
lodash-es@4.18.1:
|
||||||
resolution: {integrity: sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==}
|
resolution: {integrity: sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==}
|
||||||
|
|
||||||
|
lodash.debounce@4.0.8:
|
||||||
|
resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==}
|
||||||
|
|
||||||
|
lodash.throttle@4.1.1:
|
||||||
|
resolution: {integrity: sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==}
|
||||||
|
|
||||||
lodash@4.18.1:
|
lodash@4.18.1:
|
||||||
resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==}
|
resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==}
|
||||||
|
|
||||||
@@ -1619,6 +1634,12 @@ snapshots:
|
|||||||
vue-types: 3.0.2(vue@3.5.40)
|
vue-types: 3.0.2(vue@3.5.40)
|
||||||
warning: 4.0.3
|
warning: 4.0.3
|
||||||
|
|
||||||
|
aos@2.3.4:
|
||||||
|
dependencies:
|
||||||
|
classlist-polyfill: 1.2.0
|
||||||
|
lodash.debounce: 4.0.8
|
||||||
|
lodash.throttle: 4.1.1
|
||||||
|
|
||||||
array-tree-filter@2.1.0: {}
|
array-tree-filter@2.1.0: {}
|
||||||
|
|
||||||
async-validator@4.2.5: {}
|
async-validator@4.2.5: {}
|
||||||
@@ -1644,6 +1665,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
readdirp: 5.0.0
|
readdirp: 5.0.0
|
||||||
|
|
||||||
|
classlist-polyfill@1.2.0: {}
|
||||||
|
|
||||||
combined-stream@1.0.8:
|
combined-stream@1.0.8:
|
||||||
dependencies:
|
dependencies:
|
||||||
delayed-stream: 1.0.0
|
delayed-stream: 1.0.0
|
||||||
@@ -1856,6 +1879,10 @@ snapshots:
|
|||||||
|
|
||||||
lodash-es@4.18.1: {}
|
lodash-es@4.18.1: {}
|
||||||
|
|
||||||
|
lodash.debounce@4.0.8: {}
|
||||||
|
|
||||||
|
lodash.throttle@4.1.1: {}
|
||||||
|
|
||||||
lodash@4.18.1: {}
|
lodash@4.18.1: {}
|
||||||
|
|
||||||
loose-envify@1.4.0:
|
loose-envify@1.4.0:
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
allowBuilds:
|
allowBuilds:
|
||||||
'@parcel/watcher': set this to true or false
|
'@parcel/watcher': true
|
||||||
core-js: set this to true or false
|
core-js: true
|
||||||
esbuild: set this to true or false
|
esbuild: true
|
||||||
vue-demi: set this to true or false
|
vue-demi: true
|
||||||
|
|||||||
@@ -167,11 +167,11 @@ onUnmounted(() => {
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.canvas-effects {
|
.canvas-effects {
|
||||||
position: absolute;
|
position: fixed;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100vh;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -37,8 +37,13 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 九宫格 -->
|
<!-- 九宫格 -->
|
||||||
<div v-else-if="page.type === 'nine-grid'" class="grid grid-cols-3 gap-1.5 px-4 mt-6">
|
<div v-else-if="page.type === 'nine-grid'" class="nine-grid grid grid-cols-3 gap-1.5 px-4 mt-6">
|
||||||
<div v-for="(img, imgIdx) in page.images" :key="imgIdx" v-reveal="{ variant: 'up', delay: imgIdx * 24 }"
|
<div v-for="(img, imgIdx) in page.images" :key="imgIdx"
|
||||||
|
data-aos="nine-grid-zoom"
|
||||||
|
:data-aos-delay="imgIdx * 200"
|
||||||
|
data-aos-duration="500"
|
||||||
|
data-aos-easing="ease-out-cubic"
|
||||||
|
data-aos-once="true"
|
||||||
class="nine-grid-reveal aspect-square relative" :class="frameClass">
|
class="nine-grid-reveal aspect-square relative" :class="frameClass">
|
||||||
<img :src="img" loading="lazy" decoding="async" class="nine-grid-img" />
|
<img :src="img" loading="lazy" decoding="async" class="nine-grid-img" />
|
||||||
</div>
|
</div>
|
||||||
@@ -144,15 +149,19 @@ function polaroidTransform(i) {
|
|||||||
.gallery-subtitle { font-family: var(--font-display); }
|
.gallery-subtitle { font-family: var(--font-display); }
|
||||||
.gallery-desc { font-family: var(--font-kai); }
|
.gallery-desc { font-family: var(--font-kai); }
|
||||||
|
|
||||||
.nine-grid-reveal.reveal {
|
.nine-grid-reveal {
|
||||||
transform: translate3d(0, 12px, 0);
|
opacity: 1;
|
||||||
transition:
|
transform: translate3d(0, 0, 0);
|
||||||
opacity 420ms ease-out,
|
}
|
||||||
transform 420ms cubic-bezier(0.22, 1, 0.36, 1);
|
[data-aos='nine-grid-zoom'] {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translate3d(0, 10px, 0) scale(0.94);
|
||||||
|
transition-property: opacity, transform;
|
||||||
will-change: opacity, transform;
|
will-change: opacity, transform;
|
||||||
}
|
}
|
||||||
.nine-grid-reveal.reveal--in {
|
[data-aos='nine-grid-zoom'].aos-animate {
|
||||||
transform: translate3d(0, 0, 0);
|
opacity: 1;
|
||||||
|
transform: translate3d(0, 0, 0) scale(1);
|
||||||
}
|
}
|
||||||
.nine-grid-img {
|
.nine-grid-img {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import Antd from 'ant-design-vue'
|
|||||||
import './style.css'
|
import './style.css'
|
||||||
import './styles/animations.css'
|
import './styles/animations.css'
|
||||||
import './styles/frames.css'
|
import './styles/frames.css'
|
||||||
|
import 'aos/dist/aos.css'
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
import '@fortawesome/fontawesome-free/css/all.css' // 引入所有样式
|
import '@fortawesome/fontawesome-free/css/all.css' // 引入所有样式
|
||||||
// 注册路由
|
// 注册路由
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="wrapper relative w-full h-[100vh] bg-[#Fdfbf7] overflow-hidden text-[#2c2c2c]">
|
<div class="wrapper relative w-full min-h-[100vh] bg-[#Fdfbf7] overflow-x-hidden text-[#2c2c2c]">
|
||||||
|
|
||||||
<!-- 入场揭幕帘幕(请柬封面) -->
|
<!-- 入场揭幕帘幕(请柬封面) -->
|
||||||
<div v-show="showIntro" class="curtain-root" :class="[{ 'is-opening': introOpening }, introExitClass]" @click="openInvitation">
|
<div v-show="showIntro" class="curtain-root" :class="[{ 'is-opening': introOpening }, introExitClass]" @click="openInvitation">
|
||||||
@@ -33,10 +33,10 @@
|
|||||||
<CanvasEffects :petal-enabled="data.petalEnabled" :bubble-enabled="data.bubbleEnabled" />
|
<CanvasEffects :petal-enabled="data.petalEnabled" :bubble-enabled="data.bubbleEnabled" />
|
||||||
|
|
||||||
<!-- 顶部滚动进度条 -->
|
<!-- 顶部滚动进度条 -->
|
||||||
<div class="absolute top-0 left-0 h-[2px] bg-[#a88c6b] z-[55] transition-[width] duration-150" :style="{ width: progress * 100 + '%' }"></div>
|
<div class="fixed top-0 left-0 h-[2px] bg-[#a88c6b] z-[55] transition-[width] duration-150" :style="{ width: progress * 100 + '%' }"></div>
|
||||||
|
|
||||||
<!-- 右上角控制台 -->
|
<!-- 右上角控制台 -->
|
||||||
<div class="absolute top-6 right-5 z-50 flex flex-col items-end gap-4">
|
<div class="fixed top-6 right-5 z-50 flex flex-col items-end gap-4">
|
||||||
<!-- 音乐控制 -->
|
<!-- 音乐控制 -->
|
||||||
<div class="music-wrap">
|
<div class="music-wrap">
|
||||||
<div class="flex flex-col gap-3 items-end">
|
<div class="flex flex-col gap-3 items-end">
|
||||||
@@ -70,9 +70,8 @@
|
|||||||
|
|
||||||
<!-- 主滚动区域(注意:不要加 scroll-smooth,它会劫持 JS 滚动赋值导致卡顿) -->
|
<!-- 主滚动区域(注意:不要加 scroll-smooth,它会劫持 JS 滚动赋值导致卡顿) -->
|
||||||
<div ref="scrollContainerRef"
|
<div ref="scrollContainerRef"
|
||||||
class="w-full h-full overflow-y-auto no-scrollbar relative"
|
class="w-full min-h-[100vh] no-scrollbar relative"
|
||||||
:class="data.scrollMode === 'snap' ? 'snap-y snap-mandatory' : ''"
|
@touchstart="handleUserInteraction" @touchmove="handleUserInteraction"
|
||||||
@scroll="handleScroll" @touchstart="handleUserInteraction" @touchmove="handleUserInteraction"
|
|
||||||
@wheel="handleUserInteraction" @mousedown="handleUserInteraction">
|
@wheel="handleUserInteraction" @mousedown="handleUserInteraction">
|
||||||
|
|
||||||
<!-- 1. 封面页 -->
|
<!-- 1. 封面页 -->
|
||||||
@@ -251,8 +250,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- RSVP 抽屉 -->
|
<!-- RSVP 抽屉 -->
|
||||||
<div v-if="isDrawerOpen" class="absolute inset-0 z-[60] bg-black/40 backdrop-blur-sm transition-opacity duration-500" @click="closeDrawer"></div>
|
<div v-if="isDrawerOpen" class="fixed inset-0 z-[60] bg-black/40 backdrop-blur-sm transition-opacity duration-500" @click="closeDrawer"></div>
|
||||||
<div class="absolute bottom-0 left-0 w-full bg-white rounded-t-[2.5rem] shadow-2xl transition-transform duration-500 z-[70] p-10 flex flex-col" :style="{ transform: isDrawerOpen ? 'translateY(0)' : 'translateY(100%)' }">
|
<div class="fixed bottom-0 left-0 w-full bg-white rounded-t-[2.5rem] shadow-2xl transition-transform duration-500 z-[70] p-10 flex flex-col" :style="{ transform: isDrawerOpen ? 'translateY(0)' : 'translateY(100%)' }">
|
||||||
<div @click="closeDrawer" class="absolute top-6 right-6 p-2 text-[#a88c6b] cursor-pointer hover:text-[#2c2c2c] transition-colors text-xl">✕</div>
|
<div @click="closeDrawer" class="absolute top-6 right-6 p-2 text-[#a88c6b] cursor-pointer hover:text-[#2c2c2c] transition-colors text-xl">✕</div>
|
||||||
<span class="text-xl text-center text-[#a88c6b] mb-1 tracking-[0.3em] font-light mt-2">出席回执</span>
|
<span class="text-xl text-center text-[#a88c6b] mb-1 tracking-[0.3em] font-light mt-2">出席回执</span>
|
||||||
<span class="text-center text-[10px] text-[#8A8680] mb-10 tracking-[0.4em]">敬 请 回 复</span>
|
<span class="text-center text-[10px] text-[#8A8680] mb-10 tracking-[0.4em]">敬 请 回 复</span>
|
||||||
@@ -283,7 +282,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="showMapOptions" class="absolute inset-0 z-[80] bg-black/35 backdrop-blur-sm flex items-end justify-center px-4 pb-5" @click.self="showMapOptions = false">
|
<div v-if="showMapOptions" class="fixed inset-0 z-[80] bg-black/35 backdrop-blur-sm flex items-end justify-center px-4 pb-5" @click.self="showMapOptions = false">
|
||||||
<div class="map-sheet">
|
<div class="map-sheet">
|
||||||
<div class="map-sheet-title">选择导航方式</div>
|
<div class="map-sheet-title">选择导航方式</div>
|
||||||
<button type="button" @click="openMap('amap')"><i class="fa-solid fa-location-arrow"></i>高德地图</button>
|
<button type="button" @click="openMap('amap')"><i class="fa-solid fa-location-arrow"></i>高德地图</button>
|
||||||
@@ -297,11 +296,12 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, reactive, computed, onMounted, onUnmounted } from 'vue'
|
import { ref, reactive, computed, onMounted, onUnmounted, nextTick, watch } from 'vue'
|
||||||
import { getConfig, submitRsvp } from '@/api/wedding'
|
import { getConfig, submitRsvp } from '@/api/wedding'
|
||||||
import { useAudio } from '@/composables/useAudio'
|
import { useAudio } from '@/composables/useAudio'
|
||||||
import CanvasEffects from '@/components/CanvasEffects.vue'
|
import CanvasEffects from '@/components/CanvasEffects.vue'
|
||||||
import PhotoGallery from '@/components/PhotoGallery.vue'
|
import PhotoGallery from '@/components/PhotoGallery.vue'
|
||||||
|
import AOS from 'aos'
|
||||||
|
|
||||||
const DEFAULTS = {
|
const DEFAULTS = {
|
||||||
scrollMode: 'snap', petalEnabled: true, bubbleEnabled: true, introEnabled: true,
|
scrollMode: 'snap', petalEnabled: true, bubbleEnabled: true, introEnabled: true,
|
||||||
@@ -326,12 +326,17 @@ const rsvpOptions = [ { value: '1', label: '1 人出席' }, { value: '2', label:
|
|||||||
let rafId = null, snapTimer = null, pauseTimer = null, introTimer = null, freeTimer = null, animId = null, scrollStartTimer = null, bottomReturnTimer = null
|
let rafId = null, snapTimer = null, pauseTimer = null, introTimer = null, freeTimer = null, animId = null, scrollStartTimer = null, bottomReturnTimer = null
|
||||||
let freeScrollLastTs = 0
|
let freeScrollLastTs = 0
|
||||||
const BOTTOM_RETURN_DELAY = 30000
|
const BOTTOM_RETURN_DELAY = 30000
|
||||||
|
const FREE_SCROLL_BASE_STEP = 1.2
|
||||||
|
|
||||||
// 后台可配置的滚动参数(带默认值兜底,防止脏数据)
|
// 后台可配置的滚动参数(带默认值兜底,防止脏数据)
|
||||||
const freeInterval = computed(() => {
|
const freeInterval = computed(() => {
|
||||||
const v = Number(data.value.freeScrollInterval)
|
const v = Number(data.value.freeScrollInterval)
|
||||||
return v >= 10 && v <= 1000 ? v : 50
|
return v >= 10 && v <= 1000 ? v : 50
|
||||||
})
|
})
|
||||||
|
const freeScrollSignature = computed(() => `${data.value.scrollMode}:${freeInterval.value}`)
|
||||||
|
const restartAutoScroll = () => {
|
||||||
|
if (snapTimer || rafId) startAutoScroll()
|
||||||
|
}
|
||||||
const switchDuration = computed(() => {
|
const switchDuration = computed(() => {
|
||||||
const v = Number(data.value.pageSwitchDuration)
|
const v = Number(data.value.pageSwitchDuration)
|
||||||
return v >= 0 && v <= 5000 ? v : 800
|
return v >= 0 && v <= 5000 ? v : 800
|
||||||
@@ -389,12 +394,12 @@ const formalPageHeightStyle = computed(() => sectionHeightStyle(data.value.forma
|
|||||||
const pageHeightStyle = (page) => sectionHeightStyle(page.height)
|
const pageHeightStyle = (page) => sectionHeightStyle(page.height)
|
||||||
|
|
||||||
const closeDrawer = () => { isDrawerOpen.value = false }
|
const closeDrawer = () => { isDrawerOpen.value = false }
|
||||||
|
const pageScrollTop = () => window.scrollY || document.documentElement.scrollTop || document.body.scrollTop || 0
|
||||||
|
const pageScrollMax = () => Math.max(0, document.documentElement.scrollHeight - window.innerHeight)
|
||||||
const handleScroll = () => {
|
const handleScroll = () => {
|
||||||
const el = scrollContainerRef.value
|
const max = pageScrollMax()
|
||||||
if (!el) return
|
progress.value = max > 0 ? pageScrollTop() / max : 0
|
||||||
const max = el.scrollHeight - el.clientHeight
|
if (isAtBottom()) scheduleBottomReturn()
|
||||||
progress.value = max > 0 ? el.scrollTop / max : 0
|
|
||||||
if (isAtBottom(el)) scheduleBottomReturn()
|
|
||||||
else clearBottomReturn()
|
else clearBottomReturn()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -403,13 +408,13 @@ const handleUserInteraction = () => {
|
|||||||
// 用户接管时立即取消程序化的翻页动画,避免与人争抢滚动
|
// 用户接管时立即取消程序化的翻页动画,避免与人争抢滚动
|
||||||
cancelAnimationFrame(animId)
|
cancelAnimationFrame(animId)
|
||||||
clearTimeout(pauseTimer)
|
clearTimeout(pauseTimer)
|
||||||
if (isAtBottom(scrollContainerRef.value)) scheduleBottomReturn(true)
|
if (isAtBottom()) scheduleBottomReturn(true)
|
||||||
pauseTimer = setTimeout(() => (isInteracting.value = false), 3000)
|
pauseTimer = setTimeout(() => (isInteracting.value = false), 3000)
|
||||||
}
|
}
|
||||||
|
|
||||||
const sectionEls = () => Array.from(scrollContainerRef.value?.querySelectorAll('.page-section') || [])
|
const sectionEls = () => Array.from(scrollContainerRef.value?.querySelectorAll('.page-section') || [])
|
||||||
|
|
||||||
const isAtBottom = (el) => !!el && el.scrollHeight - el.clientHeight - el.scrollTop <= 8
|
const isAtBottom = () => pageScrollMax() - pageScrollTop() <= 8
|
||||||
|
|
||||||
const clearBottomReturn = () => {
|
const clearBottomReturn = () => {
|
||||||
clearTimeout(bottomReturnTimer)
|
clearTimeout(bottomReturnTimer)
|
||||||
@@ -417,45 +422,41 @@ const clearBottomReturn = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const scheduleBottomReturn = (reset = false) => {
|
const scheduleBottomReturn = (reset = false) => {
|
||||||
const el = scrollContainerRef.value
|
if (!isAtBottom() || !isAutoScroll.value) return
|
||||||
if (!el || !isAtBottom(el) || !isAutoScroll.value) return
|
|
||||||
if (bottomReturnTimer && !reset) return
|
if (bottomReturnTimer && !reset) return
|
||||||
clearBottomReturn()
|
clearBottomReturn()
|
||||||
bottomReturnTimer = setTimeout(() => {
|
bottomReturnTimer = setTimeout(() => {
|
||||||
bottomReturnTimer = null
|
bottomReturnTimer = null
|
||||||
const current = scrollContainerRef.value
|
if (!isAtBottom() || !isAutoScroll.value || isDrawerOpen.value) return
|
||||||
if (!current || !isAtBottom(current) || !isAutoScroll.value || isDrawerOpen.value) return
|
|
||||||
if (isInteracting.value) {
|
if (isInteracting.value) {
|
||||||
scheduleBottomReturn(true)
|
scheduleBottomReturn(true)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
smoothScrollTo(current, 0, switchDuration.value)
|
smoothScrollTo(0, switchDuration.value)
|
||||||
}, BOTTOM_RETURN_DELAY)
|
}, BOTTOM_RETURN_DELAY)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 带时长的平滑滚动(easeInOutQuad),duration 由后台 pageSwitchDuration 控制
|
// 带时长的平滑滚动(easeInOutQuad),duration 由后台 pageSwitchDuration 控制
|
||||||
const smoothScrollTo = (el, target, duration) => {
|
const smoothScrollTo = (target, duration) => {
|
||||||
cancelAnimationFrame(animId)
|
cancelAnimationFrame(animId)
|
||||||
const start = el.scrollTop, dist = target - start
|
const start = pageScrollTop(), dist = target - start
|
||||||
if (!dist) return
|
if (!dist) return
|
||||||
if (duration <= 0) { el.scrollTop = target; return }
|
if (duration <= 0) { window.scrollTo(0, target); return }
|
||||||
const t0 = performance.now()
|
const t0 = performance.now()
|
||||||
const tick = (now) => {
|
const tick = (now) => {
|
||||||
const p = Math.min(1, (now - t0) / duration)
|
const p = Math.min(1, (now - t0) / duration)
|
||||||
const e = p < 0.5 ? 2 * p * p : 1 - Math.pow(-2 * p + 2, 2) / 2
|
const e = p < 0.5 ? 2 * p * p : 1 - Math.pow(-2 * p + 2, 2) / 2
|
||||||
el.scrollTop = start + dist * e
|
window.scrollTo(0, start + dist * e)
|
||||||
if (p < 1) animId = requestAnimationFrame(tick)
|
if (p < 1) animId = requestAnimationFrame(tick)
|
||||||
}
|
}
|
||||||
animId = requestAnimationFrame(tick)
|
animId = requestAnimationFrame(tick)
|
||||||
}
|
}
|
||||||
|
|
||||||
const goNextSection = () => {
|
const goNextSection = () => {
|
||||||
const el = scrollContainerRef.value
|
const top = pageScrollTop()
|
||||||
if (!el) return
|
|
||||||
const top = el.scrollTop
|
|
||||||
const next = sectionEls().find((e) => e.offsetTop > top + 12)
|
const next = sectionEls().find((e) => e.offsetTop > top + 12)
|
||||||
if (next) {
|
if (next) {
|
||||||
smoothScrollTo(el, next.offsetTop, switchDuration.value)
|
smoothScrollTo(next.offsetTop, switchDuration.value)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
scheduleBottomReturn()
|
scheduleBottomReturn()
|
||||||
@@ -466,24 +467,23 @@ const startAutoScroll = () => {
|
|||||||
clearInterval(snapTimer)
|
clearInterval(snapTimer)
|
||||||
clearInterval(freeTimer)
|
clearInterval(freeTimer)
|
||||||
cancelAnimationFrame(rafId)
|
cancelAnimationFrame(rafId)
|
||||||
|
rafId = null
|
||||||
if (data.value.scrollMode === 'snap') {
|
if (data.value.scrollMode === 'snap') {
|
||||||
if (isAutoScroll.value && !isInteracting.value && !isDrawerOpen.value) goNextSection()
|
if (isAutoScroll.value && !isInteracting.value && !isDrawerOpen.value) goNextSection()
|
||||||
snapTimer = setInterval(() => {
|
snapTimer = setInterval(() => {
|
||||||
if (isAutoScroll.value && !isInteracting.value && !isDrawerOpen.value) goNextSection()
|
if (isAutoScroll.value && !isInteracting.value && !isDrawerOpen.value) goNextSection()
|
||||||
}, 4500)
|
}, 4500)
|
||||||
} else {
|
} else {
|
||||||
// 自由滚动:按浏览器帧率匀速推进,速度仍由后台间隔控制(间隔越小越快)
|
|
||||||
freeScrollLastTs = 0
|
freeScrollLastTs = 0
|
||||||
const tickFreeScroll = (now) => {
|
const tickFreeScroll = (now) => {
|
||||||
const dt = freeScrollLastTs ? Math.min(34, now - freeScrollLastTs) : 16.7
|
const dt = freeScrollLastTs ? Math.min(34, now - freeScrollLastTs) : 16.7
|
||||||
freeScrollLastTs = now
|
freeScrollLastTs = now
|
||||||
const el = scrollContainerRef.value
|
if (isAutoScroll.value && !isInteracting.value && !isDrawerOpen.value) {
|
||||||
if (el && isAutoScroll.value && !isInteracting.value && !isDrawerOpen.value) {
|
if (isAtBottom()) {
|
||||||
if (isAtBottom(el)) {
|
|
||||||
scheduleBottomReturn()
|
scheduleBottomReturn()
|
||||||
} else {
|
} else {
|
||||||
el.scrollTop += dt * (1.2 / freeInterval.value)
|
window.scrollTo(0, pageScrollTop() + FREE_SCROLL_BASE_STEP * (dt / freeInterval.value))
|
||||||
if (isAtBottom(el)) scheduleBottomReturn()
|
if (isAtBottom()) scheduleBottomReturn()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rafId = requestAnimationFrame(tickFreeScroll)
|
rafId = requestAnimationFrame(tickFreeScroll)
|
||||||
@@ -492,6 +492,17 @@ const startAutoScroll = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const syncPageScrollMode = () => {
|
||||||
|
document.documentElement.classList.toggle('page-scroll-snap', data.value.scrollMode === 'snap')
|
||||||
|
}
|
||||||
|
|
||||||
|
const refreshAos = () => {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
AOS.refreshHard()
|
||||||
|
setTimeout(() => AOS.refresh(), 250)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const scheduleAutoScrollStart = () => {
|
const scheduleAutoScrollStart = () => {
|
||||||
clearTimeout(scrollStartTimer)
|
clearTimeout(scrollStartTimer)
|
||||||
scrollStartTimer = setTimeout(() => startAutoScroll(), firstScreenDuration.value)
|
scrollStartTimer = setTimeout(() => startAutoScroll(), firstScreenDuration.value)
|
||||||
@@ -506,8 +517,7 @@ const completeIntro = () => {
|
|||||||
|
|
||||||
const toggleAutoScroll = () => {
|
const toggleAutoScroll = () => {
|
||||||
isAutoScroll.value = !isAutoScroll.value
|
isAutoScroll.value = !isAutoScroll.value
|
||||||
const el = scrollContainerRef.value
|
if (isAutoScroll.value && isAtBottom()) scheduleBottomReturn(true)
|
||||||
if (isAutoScroll.value && isAtBottom(el)) scheduleBottomReturn(true)
|
|
||||||
if (!isAutoScroll.value) clearBottomReturn()
|
if (!isAutoScroll.value) clearBottomReturn()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -572,6 +582,9 @@ const handleSubmitRsvp = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
if ('scrollRestoration' in history) history.scrollRestoration = 'manual'
|
||||||
|
window.scrollTo(0, 0)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await getConfig()
|
const res = await getConfig()
|
||||||
if (res.data && res.data !== '{}') {
|
if (res.data && res.data !== '{}') {
|
||||||
@@ -592,6 +605,8 @@ onMounted(async () => {
|
|||||||
}
|
}
|
||||||
} catch (e) { /* 使用默认配置 */ }
|
} catch (e) { /* 使用默认配置 */ }
|
||||||
|
|
||||||
|
syncPageScrollMode()
|
||||||
|
|
||||||
if (data.value.introEnabled) showIntro.value = true
|
if (data.value.introEnabled) showIntro.value = true
|
||||||
|
|
||||||
audio.setTracks(data.value.musicList, data.value.activeMusicUrl)
|
audio.setTracks(data.value.musicList, data.value.activeMusicUrl)
|
||||||
@@ -606,10 +621,25 @@ onMounted(async () => {
|
|||||||
scheduleAutoScrollStart()
|
scheduleAutoScrollStart()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await nextTick()
|
||||||
|
AOS.init({
|
||||||
|
offset: 40,
|
||||||
|
duration: 500,
|
||||||
|
easing: 'ease-out-cubic',
|
||||||
|
once: true,
|
||||||
|
mirror: false,
|
||||||
|
throttleDelay: 120,
|
||||||
|
debounceDelay: 80,
|
||||||
|
disableMutationObserver: true,
|
||||||
|
})
|
||||||
|
refreshAos()
|
||||||
|
window.addEventListener('scroll', handleScroll, { passive: true })
|
||||||
handleScroll()
|
handleScroll()
|
||||||
})
|
})
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
|
document.documentElement.classList.remove('page-scroll-snap')
|
||||||
|
window.removeEventListener('scroll', handleScroll)
|
||||||
cancelAnimationFrame(rafId)
|
cancelAnimationFrame(rafId)
|
||||||
cancelAnimationFrame(animId)
|
cancelAnimationFrame(animId)
|
||||||
clearInterval(snapTimer)
|
clearInterval(snapTimer)
|
||||||
@@ -619,6 +649,9 @@ onUnmounted(() => {
|
|||||||
clearTimeout(scrollStartTimer)
|
clearTimeout(scrollStartTimer)
|
||||||
clearBottomReturn()
|
clearBottomReturn()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
watch(() => data.value.scrollMode, syncPageScrollMode)
|
||||||
|
watch(freeScrollSignature, restartAutoScroll)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@@ -627,6 +660,12 @@ onUnmounted(() => {
|
|||||||
font-variant-numeric: lining-nums;
|
font-variant-numeric: lining-nums;
|
||||||
text-rendering: geometricPrecision;
|
text-rendering: geometricPrecision;
|
||||||
}
|
}
|
||||||
|
:global(html.page-scroll-snap) {
|
||||||
|
scroll-snap-type: y mandatory;
|
||||||
|
}
|
||||||
|
:global(html.page-scroll-snap body) {
|
||||||
|
scroll-snap-type: y mandatory;
|
||||||
|
}
|
||||||
.wrapper .font-serif { font-family: var(--font-serif); }
|
.wrapper .font-serif { font-family: var(--font-serif); }
|
||||||
.wrapper .font-sans { font-family: var(--font-sans); }
|
.wrapper .font-sans { font-family: var(--font-sans); }
|
||||||
.control-btn {
|
.control-btn {
|
||||||
|
|||||||
Reference in New Issue
Block a user