Files
gxwebsoft 2b69686795 feat(app): 添加多模板关于我们页面及相关路由和404页面
- 新增404页面,优化未找到页面体验,避免被搜索引擎索引
- 增加文件代理接口,隐藏真实文件服务器地址,支持文件请求代理
- 实现/article、/case、/product及/page动态路由兼容列表与详情展示
- 添加动态CMS页面兼容入口处理旧式路径,统一路由与SEO设置
- 新增模板1、模板7、模板2、模板3关于我们页面,实现多模板支持
- 模板增强支持CMS单页内容加载及SEO信息动态设置
- 配置环境变量及Git忽略文件规则辅助开发和构建环境管理
2026-09-08 12:13:44 +08:00

101 lines
3.3 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 滚动渐显插件(客户端)
*
* 扫描页面中所有 [data-reveal] 元素,进入视口时为其添加 .is-visible
* 触发 animations.css 中定义的过渡。支持通过 data-reveal-delay(毫秒)做 stagger。
*
* 设计要点:
* - 初始隐藏状态完全由 CSS `.js [data-reveal]` 控制,<html class="js"> 由
* nuxt.config 的 head 内联脚本在首屏绘制前注入,因此不会造成 SSR 闪烁,
* 也无 JS 时内容正常显示。
* - 兼容客户端路由切换:在 app:mounted 与 page:finish 时重新扫描。
* - 【关键】使用 MutationObserver 持续监听 DOM 变更,兜住「挂载后才出现」的元素:
* 例如组件在客户端异步取数后渲染、SSR 水合不一致导致 Vue 重建子树、
* 或列表分页 / 条件区块延迟出现。这些元素若漏扫,会因 CSS 的 opacity:0
* 永久不可见,表现为「整块区域样式丢失 / 空白」——这正是本插件必须防住的故障。
* - 尊重 prefers-reduced-motion:此时直接跳过观察器,内容由 CSS 强制可见。
*/
export default defineNuxtPlugin((nuxtApp) => {
if (typeof window === 'undefined') return
const reduceMotion = window.matchMedia(
'(prefers-reduced-motion: reduce)'
).matches
if (reduceMotion) return
const observer = new IntersectionObserver(
(entries) => {
for (const entry of entries) {
if (!entry.isIntersecting) continue
const el = entry.target as HTMLElement
const delay = Number(el.dataset.revealDelay || 0)
const reveal = () => el.classList.add('is-visible')
if (delay > 0) {
window.setTimeout(reveal, delay)
} else {
reveal()
}
observer.unobserve(el)
}
},
{ threshold: 0, rootMargin: '0px 0px -10% 0px' }
)
const scan = () => {
const els = document.querySelectorAll<HTMLElement>(
'[data-reveal]:not(.is-visible)'
)
els.forEach((el) => observer.observe(el))
}
// 合并短时间内的多次 DOM 变更,避免频繁全量查询
let rafId = 0
const scheduleScan = () => {
if (rafId) return
rafId = requestAnimationFrame(() => {
rafId = 0
scan()
})
}
// 持续监听 DOM 新增节点:任何后续插入的 [data-reveal] 都会被补扫,
// 从根本上避免「元素永久停留在 opacity:0」的问题。
const mutationObserver = new MutationObserver((mutations) => {
for (const m of mutations) {
if (m.addedNodes.length > 0) {
scheduleScan()
return
}
}
})
const startMutationWatch = () => {
mutationObserver.observe(document.body, {
childList: true,
subtree: true
})
}
// 首屏挂载后扫描一次,并开始监听后续 DOM 变更
nuxtApp.hook('app:mounted', () => {
scheduleScan()
startMutationWatch()
// 兜底:水合完成后 Vue 可能仍在替换不一致的子树,稍后再补扫一次
window.setTimeout(scheduleScan, 300)
})
// 客户端路由切换后重新扫描(新页面可能有新的 [data-reveal]
nuxtApp.hook('page:finish', () => {
scheduleScan()
})
// 页面卸载时释放监听
if (import.meta.hot) {
import.meta.hot.dispose(() => {
mutationObserver.disconnect()
observer.disconnect()
})
}
})