这篇文章主要介绍名片页的路由过渡是如何去做的
介绍
在19年,我就写了一个较为炫酷的个人名片页。当时的我热衷于使用各种过渡效果,当然,也尝试了很多新鲜的 css 特性,例如为了实现多种主题色使用了 css 变量(好像还是我首次使用flex布局呢)
但当时的我显然还尚未深谙前端布局之道🤯,许多页面元素在当时的浏览器渲染是正常的,现在却有些崩坏了,很多细节处理不完善,遂准备将这个名片页进行重制
不过还是有挺多小巧思在里面的。比如为了防止滚动导致卡片被切开,给容器加了一个伪元素实现的阴影。实现很简单,效果却非常不错。
现在重制也基本上完成了(还剩几个页面没写完,不过无伤大雅),可以先看看效果 im.daidr.me
不难看出整体的页面风格和以前非常相似,不过确实很符合我对「炫酷」的想象
技术栈
Vue3 + WindiCSS + SCSS + Nuxt
这个名片页其实在去年12月就开始写了,刚开始没做SSR,最近尝试迁移到了Nuxt,路由动效之类兼容也很折磨,不过这不是这篇文章的重点,就不多说啦~
文章页是前几个星期才刚加上的,目前是把旧WordPress博客当CMS用,但是有些太重了——给文章页加上了Redis swr缓存,才能勉强保证流畅访问。最近在食用基于 Crossbell 链的 xLog,感觉非常不错,希望研究明白之后能把文章页接入 xLog😗。
分析
路由结构
首先是路由结构(关系到之后页面如何进行变换)。
目前,这个名片页有5套页面 /me
(别名 /
),/friends
,/projects
,/blog/:slug
,/404
。他们的结构是这样的:
因为 /me 和 /friends 页面的容器大小一致,翻牌的效果不太合适,所以互相切换时使用另外一套过渡效果。
(现在看来这种实现并不合适,维护和自定义都会比较困难。Nuxt有自带的路由过渡配置选项,不需要依赖子路由来实现。)
容器定位
能够将一个容器固定在页面正中心的方法其实蛮少的,我使用的是 fixed 绝对定位。先给元素设置 top: 50%; left: 50%;
,但这时候的元素并不在页面正中心(而是其左上角在页面中心)
所以需要接着设置 transform: translate(-50%, -50%);
,将元素向左/向上偏移。
翻牌过渡
来讲讲图片中的翻牌过渡是如何实现的。(仅考虑翻牌元素本身)
下面的代码各位 Vuer 一定不陌生,这能让 vue-router 在切换页面时应用过渡效果
而实际上,Transition 组件可不仅仅能传递 name,还能够通过 JavaScript 具体控制过渡的每个环节。将过渡动效的相关逻辑封装到 RouterTransition
中:
分析
使用 JavaScript 去控制过渡,我们就需要知道过渡前后元素的尺寸以及位置,拿到元素倒是好办,但是这里有一个问题:需要应用过渡的元素并不一定是页面根元素。
比方说 /projects
页面,只有顶部的菜单栏应用了过渡。所以需要有一个手段去标识这些元素。我使用的方法是为需要过渡的元素加上类名 transition-page-wrapper
写一个工具函数,传入页面根元素,返回需要过渡的元素
过渡开始前
之后,路由切换前的页面元素会被称为
fromEl
,路由切换后的页面元素会被称为toEl
首先,我们来搞定 before-leave
事件。在这个函数中,我们需要将 fromEl
的位置、尺寸信息记录下来,为了保证过渡顺滑,我还准备额外记录 border-radius
属性。
其中,xywh
的值可以使用 getBoundingClientRect
方法取到。而 b
(border-radius) t
(transform) 是 css 样式属性,元素的 style 属性只能拿到其内联样式,为了拿到浏览器计算之后元素的所有准确 css 样式,需要使用 getComputedStyle
方法。封装成 writeCfgObj
工具函数方便之后使用:
transition 组件的 before-leave
事件有一个参数,该参数会传递将在过渡中消失的元素(即 fromEl
)
有了 fromEl
的位置/尺寸,接下来就是 toEl
的位置尺寸了,可以通过 before-enter
事件拿到
需要注意和 before-leave
不同的是:此时的 toEl
实际上还没有被插入到 dom 树中 (都插入进去了还过渡什么),此时元素的位置和尺寸都没法直接获取,我们需要一些额外的步骤。
实际上就是将
toEl
克隆一份插入到 dom 中,获取完位置立刻删掉。因为 opacity 被我们设置成了 0,此时元素不可见,用户其实不太会感知到。
TransitionGroup
的实现其实差不多
过渡进行中!
拿到了 toEl
和 fromEl
的这些属性,过渡就可以开始啦!过渡主要会使用到 tranform
元素
不过先别急😜,在开始过渡之前,我们需要算出 toEl
和 fromEl
的位置和尺寸差值,这样我们才方便使用 translate
和 scale
对元素应用变换。
这里需要注意的是:我们对元素应用变换使用了 transform 属性,而元素本身可能就有位移。过渡的过程中,我们会对其进行覆盖,所以计算时千万别忘了把元素本身的位移考虑进去。
看到上面的代码可能会有些懵,matrix3d
是什么?什么时候冒出来的?
还记得之前取元素 transform
属性时使用的 getComputedStyle
么?浏览器会返回计算后的样式。我们拿到的,并不是形似 translate(-50%, -50%)
的字符串,而是一个 matrix3d
函数所代表的变换矩阵。为了拿到元素的位移,我们只需要第13个参数 a4
和第14个参数 b4
就够了
scaleX/Y
– 通过 toEl
和 fromEl
的尺寸算出应该缩放的比例
deltaX/Y
– 通过 toEl
和 fromEl
的位置和位移算出应该移动的距离,由于需要进行缩放,还需要使用缩放比例对这个差值进行修正
接下来,就可以正式来处理 toEl
的离开了,需要使用到 transition 组件的 leave
事件
调用 leave 事件传递进来的
done()
回调函数之后,fromEl
就会被 transition 组件删除,不需要我们自己删除。
现在,fromEl
已经完成过渡并且被清除了,最后一件事,就是要将 toEl
显示出来,正好和 fromEl
相反。fromEl
旋转 180°,toEl
就旋转 -180°。
这个 onEnter
函数看起来和之前的 onLeave
差不多,但仔细一看又差很多🤯。这是因为两者的原理是不一样的。
onLeave
事件用于处理 fromEl
,fromEl
在过渡完成后就要被删掉的,谁管它会不会残留什么乱七八糟的内联样式呢。所以,我们选择先给 fromEl
一个 transition 属性,然后给他赋予位移,使其慢慢过渡到新元素的位置。
onEnter
事件用于处理 toEl
,这里的 toEl
在过渡完成后是要留在页面上的,我们不能因为过渡,就往上面写一堆内联样式,写了至少也要在过渡完成后删掉。
所以,这里的逻辑是:先禁用 transition,然后通过内联的 transform
将 toEl
放置到 fromEl
的位置上。这时候,开启 transition,然后删除之前设置的 transform 属性,toEl
就会过渡回来啦!而且过渡完成后,transform
属性不会残留在元素上,棒!
过渡完成后
我们给 toEl
设置了 transition
属性,所以需要 after-enter
事件来「擦擦屁股」
现在,整个翻牌过渡就完成啦
你可能会发现在某些情况下,会出现另外一种过渡(加载超过100ms时,先转变到loading)
这个动画通过路由守卫实现,原理也差不多,只是将 toEl/fromEl 替换成 Loading 元素。不过在 Workbox 和 Nuxt Prefetch 双重加持下,这个动画已经没有什么意义了。