Mirror源码精读:Hash路由、虚拟滚动与页面过渡动画的实现技巧
【免费下载链接】MirrorA blogging tool powered by GitHub API. Write your blog on GitHub issue.项目地址: https://gitcode.com/gh_mirrors/mirror9/Mirror
一句话认识 Mirror:这是一个基于 GitHub API 的开源博客工具,你的每一篇博文就是 GitHub 上的一个 Issue,真正实现"用 GitHub Issues 写博客"。
Mirror 源码精读来了!作为一款以 GitHub Issues 为内容后端的轻量级博客工具,Mirror 的前端代码规模不大,却浓缩了前端开发中最经典的三大技巧:Hash 路由、虚拟滚动、页面过渡动画。本文带你逐行拆解这三个模块的实现思路,无论你是刚入门前端路由原理的新手,还是想给项目加上丝滑动画的进阶开发者,都能从中获得可以直接抄作业的灵感。全部源码都在src/目录下,配合阅读效果更佳。
Mirror 是什么:用 GitHub Issues 写博客的开源工具 🚀
Mirror 的思路非常巧妙:内容不存数据库,而是直接用 GitHub 仓库的 Issues 当文章存储。写博客就是在 GitHub 上提 Issue,发布、分类、评论全部复用 GitHub 的能力,前端只需要通过 GitHub GraphQL API 把数据拉下来渲染即可。
这种架构让整个前端保持极简——没有 Vue、React,而是用原生 JavaScript 加极简构建工具 Falco 打包。入口文件 src/index.js 里,路由、滚动、动画三大模块被组织得井井有条:
| 模块 | 路径 | 职责 |
|---|---|---|
| 路由 | src/router/ | 解析 URL、分发页面 |
| 滚动 | src/scroller/ | 自定义滚动与进度条 |
| 动画 | src/switch/ | 页面切换过渡动画 |
下面逐一精读。
一、Hash 路由:零依赖的轻量路由方案 🔗
SPA(单页应用)离不开路由。Mirror 没有引入任何路由库,而是自己手写了一个不足 60 行的 Hash 路由,全部代码在src/router/index.js和src/router/params.js两个文件里。
核心入口:监听 hashchange 事件
Hash 路由的原理是:URL 中#后面的部分变化时,浏览器会触发hashchange事件,且不会刷新页面。Mirror 正是抓住这一点:
window.addEventListener('hashchange', (e) => { this.resolve(e) })hash()函数负责从window.location.hash中取出路由字符串,比如#/posts/123解析出/posts/123。这就是 Hash 路由的完整雏形——用 URL 变化驱动视图变化,实现"前进后退都能用"的体验。
路由匹配与动态参数提取
路由定义在src/index.js中,只有四条:
/首页/posts/:id文章详情/after/:cursor向后翻页/before/:cursor向前翻页
src/router/params.js里的routeMatch函数把/posts/:id这种模板转成正则表达式^/posts/([\w-]+)$,再与当前 URL 匹配;computeParams则负责把:id对应的实际值提取出来。一个正则搞定动态参数解析,思路值得借鉴。
巧用 Base64 游标实现翻页 🔄
GitHub GraphQL API 的分页靠的是cursor(游标),它是一串很长的 Base64 字符串,直接放进 URL 既不美观也容易超长。Mirror 的处理很聪明:
if (params.cursor) { params.cursor = window.atob(params.cursor) }进入路由时用window.atob()解码还原。前后翻页都指向同一个数据源,配合内存缓存,来回切换几乎零延迟。
二、虚拟滚动:用 transform 打造丝滑滚动体验 🎢
长列表页面的滚动性能,是博客站点的门面。Mirror 的滚动模块在src/scroller/index.js,基于virtual-scroll库扩展,核心思路是:把原生滚动交给滚轮事件,把页面位移交给 CSS transform。
为什么不直接用原生滚动
原生滚动在部分浏览器上会触发大量布局重绘,尤其在长列表场景下容易卡顿。Mirror 的做法是:监听滚轮事件拿到deltaY,累加到scrollY上,然后用translateY()位移整个内容层:
this.scrollY += e.deltaY this.scrollY = Math.max((this.child.scrollHeight - window.innerHeight) * -1, this.scrollY) this.scrollY = Math.min(0, this.scrollY)两行Math.max/Math.min把滚动范围死死限制在 0 到内容底部之间,边界控制干净利落。
requestAnimationFrame 驱动 GPU 加速滚动 ⚡
位移动画没有用 setInterval,而是用requestAnimationFrame(配合raf库做了 polyfill)。每帧执行一次:
const style = `translateY(${scrollY}px) translateZ(0)` child.style.transform = style注意这里的translateZ(0)——它把元素提升到独立的 GPU 合成层,位移不再触发重排重绘,只做合成,滚动自然丝滑。同时兼容了webkitTransform、mozTransform等旧前缀,细节到位。
移动端自动降级为原生滚动 📱
src/helper/mobile.js用一行代码检测触摸设备:
export default () => !!('ontouchstart' in window)在移动端,start()直接返回,改用overflow-y: scroll原生滚动(见src/style/transition.less里的.page.scroll),既省性能又保留 iOS 的惯性滚动。"桌面端自定义、移动端原生"的双轨策略,是兼容性设计的典范。
阅读进度条:滚动事件的高级玩法 📊
顶部那条细窄的阅读进度条(#bar)就是这么来的:scroller.onScroll回调把当前滚动位置除以总高度,换算成百分比宽度,实时更新到进度条上。滚动事件不仅能翻页,还能做出进度反馈,这个思路可以迁移到任何长文阅读场景。
三、页面过渡动画:600ms 的丝滑切换 🎬
从列表页点进文章,页面不是生硬切换,而是有一组优雅的滑入滑出动画。实现集中在src/switch/index.js和src/style/transition.less。
双页面叠层:两个固定定位的页面
.page使用position: fixed铺满全屏,列表页(.home)和文章页(.single)叠在一起,通过z-index控制谁在上层。切换时新页面滑入、旧页面滑出,形成"推开"的视觉效果。
动画与 Promise 的时序配合 ⏱️
看switchToPost()的写法:
$('.home').addClass('page-moveto') // 旧页向左滑出 $('.single').addClass('page-movefrom') // 新页从右侧滑入 await sleep(600) // 等动画播完 $('.single').addClass('page-current') // 再切换层级关键细节是:动画执行期间两层都可见,动画结束后才切换z-index。这中间的等待用src/switch/sleep.js的 Promise 封装实现,配合async/await,时序逻辑读起来像流水账一样清晰。CSS 侧用@keyframes定义了movefrom(从 -200px 滑入)和moveto(滑出到 -100%),并加了both填充模式让动画首尾状态连贯。
细节打磨:缓存、标题与空态 🧩
动画之外,Mirror 还有几个值得学习的细节:
- 数据缓存:已加载的文章、列表、评论都存在
mirror.issue/mirror.issues/mirror.comments内存对象里,二次进入直接渲染,配合src/observer/的观察者模式自动触发模板更新,堪称手写版响应式。 - 动态标题:进入文章时先设
document.title = 'loading',加载完再替换为"文章标题 - 站点名",反馈明确。 - 路由兜底:
router.notFound把未知路由重定向回首页,避免用户迷路。
总结:小项目的大学问 💡
Mirror 的源码体量不大,但把Hash 路由、虚拟滚动、页面过渡动画三大前端经典课题都给出了教科书式的答案:
- 路由:监听
hashchange+ 正则匹配 + Base64 游标,60 行搞定 SPA 路由。 - 滚动:滚轮事件 +
requestAnimationFrame+translateZ(0)GPU 合成,性能拉满;移动端优雅降级原生滚动。 - 动画:双页面叠层 + Promise 时序控制 + CSS keyframes,切换行云流水。
如果你想亲自跑起来研究,克隆仓库https://gitcode.com/gh_mirrors/mirror9/Mirror后执行npm install && npm start即可本地预览。对着本文的三个模块逐个打断点调试,相信你对前端路由与动画的理解会上一个台阶。如果你还想看 Mirror 的 GraphQL API 封装或观察者模式源码解析,欢迎留言告诉我!
【免费下载链接】MirrorA blogging tool powered by GitHub API. Write your blog on GitHub issue.项目地址: https://gitcode.com/gh_mirrors/mirror9/Mirror
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考