从展示页到网站入口:首屏页(Shouping)的架构演进与开门动画转场实践

27 天前(已编辑)
/ , , ,
6
1
摘要
Gemini摘要:本文系统梳理了 Sin的UIAnimation小项目,首屏从展示页向全站门户演进的技术全貌。文章深入剖析了如何依靠 1:1 响应式 6 栅格与 Nuxt UI 原生 Carousel 构建自适应手势看板,并重点解密了一套无需 clip-path 损耗性能的高清全屏“开门”动画转场方案:利用盒模型实现 GPU 级视口裁剪,击穿 Web Component Shadow DOM 无法被深克隆的时钟黑洞,并结合 MutationObserver 页面卸载感知与 requestAnimationFrame 帧同步,彻底根除路由过渡中的白屏与频闪顽疾。

从展示页到网站入口:首屏页(Shouping)的架构演进与开门动画转场实践

很早就想试试网格布局了,刚好看见了imsyy多年前的项目布局,很喜欢,拿来改成了更加符合现代的技术栈的页面重新审视入口:从独立展示页到门户 最初,shouping 只是一个挂在 /shouping 路由下的独立实验页面,网站原有的内容流主页依然在 /。它的诉求清晰而纯粹:访客进入网站时,首先感知站点的调性与主人的气质,随后再决定是探索主站,还是直达特定工具。 随着视觉打磨与交互逻辑的逐渐成熟,它的角色发生了质的转变——它不再只是一个“附属展示页”,而应当是整个站点的“正门”。我们逐步将完整的站内生态、动态数据看板与沉浸式转场融入其中,最终将它与原首页平滑对调,确立为网站的正式入口(页面灵感来源于(imsyy) 多年前的新手项目)。 布局骨架演进:1:1 对称 左右对称的视觉平衡 页面最外层采用沉浸式全屏纯暗色背景(bg-[#282a30]【NuxtUI默认】),主体内容通过居中容器(max-w-7xl mx-auto)水平垂直居中。 很多页面习惯采用 5:7 或 4:8 的不对称划分,但在以卡片为核心的个人主页中,左右采用 严格的 1:1(grid-cols-1 lg:grid-cols-2)对称平分 才是视觉均衡的最佳解法(原本是想5:7分的,但是效果不好): 左侧(个人名片区):包含了悬停微旋转的圆形头像、个性化大字标语、深色毛玻璃一言卡片(古典双引号包裹)、以及社交平台矩阵; 右侧(信息展板与导航区):横向宽度与左侧卡片严格保持一致,达成几何上的严谨呼应。 右侧 6 栅格体系(grid-cols-6) 右侧需要同时容纳两个信息看板(诗词、时钟天气)与六个快捷导航。我们采用了一个极其工整的 6 栅格体系: 上半部分:2 个信息卡片各占 3 列(sm:col-span-3,3+3=63 + 3 = 6),等宽平分一行; 下半部分:快捷导航卡片各占 2 列(sm:col-span-2,2×3=62 \times 3 = 6),一行整齐排布 3 个,两行共 6 个。

拒绝臃肿依赖:挖掘 Nuxt UI Carousel 的手势滑动潜力 当快捷导航项逐渐丰富(分为站内功能集与站外社交/主页),卡片开始超出单屏舒适容量。直觉上很多开发者会立刻 pnpm install swiper 原项目之前就是使用的swiper,根据我的技术栈重新换了个技术,选择优先挖掘当前技术栈(Nuxt UI)的内置能力,如果使用Reka UI作为项目的UI库我可能会使用Swiper/Embla Carousel。 为什么选择 <UCarousel> 而非外部 Swiper? 零额外依赖与包体积优化:避免为了一组简单的导航引入几十 KB 的 Swiper 运行时与额外的 CSS; 底层基于一流手势引擎:Nuxt UI 的 <UCarousel> 底层直接基于著名的 **embla-carousel-vue**,具备开箱即用的触摸滑动(Touch Swipe)、鼠标按住拖拽(Mouse Drag)、阻尼物理惯性与键盘无障碍切换; 双向数据驱动指示器:我们重构了 navPages 分页结构,并将外部经典的胶囊指示条与轮播事件双向绑定——用户手指或鼠标拖拽滑屏时,指示条通过 @select 实时变宽高亮;点击指示条圆点时,调用 carouselRef.value?.emblaApi?.scrollTo(index) 平滑滚到对应页。 真实世界的动态看板:时钟、天气与水合时区

时钟跳动与 SSR 水合防御: 诗词卡片提供点击循环切换,而时钟卡片则由 dayjs 格式化日期,并结合 @number-flow/vue 呈现机械质感的数字平滑翻滚。 关键边界防御:由于 Nitro 服务端渲染(SSR)通常处于 UTC 时间,而客户端浏览器处于本地时区,时间与星期极易引发水合不一致(Hydration Mismatch)报错。时钟区域严格使用 <ClientOnly> 包裹并提供优雅的骨架屏 fallback,将时间计算收敛在客户端(项目中之前踩过这个坑)。 天气接口与本地缓存解耦: 城市与气温不能靠浏览器预设。我们将其从 UI 中完全抽离至 app/Api/weather.ts; 页面挂载后请求用户地理定位,获得经纬度后向 Open-Meteo 查询实时气温与天气状况; 30分钟缓存机制:天气数据连同坐标与时间戳写入 localStorage,半小时内重进页面优先读取缓存,避免因频繁切换路由向天气服务器重复请求;用户拒绝定位或弱网时,优雅降级为状态提示(没用使用VueQuery是因为觉得在当前接口很少的情况下,完全没有必要引入新的依赖包去增加自己的心智负担和打包后的体积)。 多语言路径安全(useLocalePath): 站内所有链接使用 @nuxtjs/i18n 的 localePath 统一包装,确保用户不论在中文还是 /en/ 英文环境下点击跳转,多语言上下文都不会被粗暴冲刷。 @click 的边界 在功能趋于完善时,我们经历了两次具有典型工程教育意义的细节排错与探索:

@click.capture 与 @click 的实战真相 在拦截 <NuxtLink> 跳转以注入自定义转场动画时,代码中使用了 @click.capture。 原理剖析:.capture 是 DOM 事件流的“捕获阶段”,意在事件刚进入顶层时先发制人,截胡 <NuxtLink> 的默认路由处理; 实测解惑:实测改成普通的 @click 表现完全一致。这是因为浏览器的“跳转默认行为”必须等待**整个事件流(捕获 →\to 目标 →\to 冒泡)完全结束**之后才会执行。只要在冒泡阶段调用了 event.preventDefault(),依然能成功截停跳转。在内部没有子元素滥用 stopPropagation 的纯净结构下,使用语义更自然的 @click 即可。 “开门”转场:DOM 快照、卸载感知与无闪帧同步 Visit 按钮、头像以及所有站内导航,绝不仅仅是一次简单的 router.push,而是:首屏沿中间分割线向两侧缓缓撕裂滑开,露出门后准备就绪的主站。 这套复杂的动画被统一收敛在 useEnter.ts 中,背后经历了三层核心攻坚:

  1. 视口裁剪与 NumberFlow 快照修复 我们通过 fixed inset-0 z-[9999] 创建全屏遮罩,利用 sourcePage.cloneNode(true) 完整复制当前视口画面,放入两扇各占 50% 宽度的面板容器中,配合 overflow: hidden 精确裁切出左半边和右半边; 时钟消失踩坑:@number-flow/vue 底层是一个封装了 Web Component 原生自定义元素(<number-flow>)的高级组件,shadow DOM,使用cloneNode无法穿透clone,两者不在一个DOM树下,最后是维护了一个H5的自定义属性,让DOM 树在clone的时候读取这个属性渲染页面的(这里应该是最大的坑点了,毕竟是好几年的库了,有坑很正常)。
  2. 为什么不能直接在 navigateTo 后面执行动画?(isConnected 与 MutationObserver) await navigateTo(to) 仅仅代表路由切换指令发出了,此时 Vue 还需要异步加载新页面的 Chunk 并执行 setup。 如果直接执行动画,在开发服务器上, 动画首次开始执行的一瞬间,看到的会是旧页面白白停滞了几十毫秒,或者露出一瞬间的白屏,而生产服务器上没有这个问题,但是这也说明了代码不健全的问题; 我们使用 sourcePage.isConnected(浏览器原生 DOM 存在属性)配合 MutationObserver,递归监听 #__nuxt 根节点({ childList: true, subtree: true })。 只有当检测到&#32;!sourcePage.isConnected(旧页面彻底从 DOM 树中被拔除、新页面正式就位)的瞬间,才放Promise(resolve())通过&#9;
  3. 消灭白屏闪烁:requestAnimationFrame 即使旧 DOM 拔除了,在浏览器的事件循环机制中,DOM 变化(JS执行)并不等于像素已经画到屏幕上(还需要计算样式、布局与绘制 Paint)。 我们在开门前加入了: 强制让出主线程,等待浏览器把新页面的第一帧稳稳绘制到显卡上,再将门板向左右两侧优雅拉开。至此,白屏、频闪被彻底消除,丝滑度拉满。同时配合 prefers-reduced-motion: reduce,在用户开启减弱动画时自动跳过开门直接秒切,兼顾无障碍体验(requeAnimation在梅花生长动画中也有到了,而动画减弱判断则是在GSAP官方那学到的)。 路由平移与体验闭环 在所有动效、容错与状态机都经过严苛检验后,我们完成了最终的“移花接木”: 根路由对调:原本的 /shouping 正式进驻网站根路径 /,原主站流式页面后移至 /home; 闭环互通:首屏的 Visit 按钮、头像与“主站首页”卡片,通过双开门动画平滑进入 /home;而进入主站后,左上角头像点击可重返 / 门面; 环境验证:Nuxt 预渲染、多语言路径自动转换、SSR 本地构建与全量 TypeScript 类型检查均以 Exit status 0 零警告无缝收官。 昨天晚上加今天一下午完成了这个shouping页并且迁移,今天JOJO飙马野郎更新第二集,看看能不能改个黄金分割的动画😶‍🌫️

使用社交账号登录

  • Loading...
  • Loading...
  • Loading...
  • Loading...
  • Loading...