主题过渡动画
刀刀
7/8/2025
0 字
0 分钟
- 来源:远方os;视频地址:前往学习
Element UI 切换主题时有一个很炫酷的过渡动画,这主要是通过 view transition
实现的。
view transition
view transition 是一个 CSS 规范,用于在页面切换时添加过渡动画。它包含两个部分:
view transition
规则:用于定义过渡动画的属性,如transition-property
、transition-duration
等。view transition
事件:用于监听过渡动画的开始和结束,从而执行相应的操作。
它的工作原理如下:
调用
document.startViewTransition()
方法时,会截取当前页面的屏幕截图接着调用回调函数,在回调函数中修改 DOM 结构
API 捕获页面的新状态并实时展示
API 构造一个具有以下结构的伪元素树
js::view-transition └── ::view-transition-group(root) └── ::view-transition-image-pair(root) ├── ::view-transition-old(root) └── ::view-transition-new(root)
代码实现
基础实现
先写一个能够切换主题色的代码:
html
<style>
:root {
--background-color: #fff;
background-color: var(--background-color);
}
:root.dark {
--background-color: #000;
}
</style>
<body>
<button id="btn">切换主题</button>
<script>
const btn = document.getElementById('btn')
btn.addEventListener('click', () => {
document.documentElement.classList.toggle('dark')
})
</script>
</body>
过渡实现
接下来,我们使用 view transition
实现过渡动画,步骤为:
document.startViewTransition()
方法开始过渡动画,接收一个函数作为参数,该函数用于修改 DOM 结构(即上方代码修改className
部分。得到一个参数,命名为transition
- 在
transition.ready
动画捕获时,添加过渡动画的样式。由于这是一个异步方法,因此需要在.then
回调中执行 - 调用
document.documentElement.animate
方法设置动画,参数一是一个对象,属性名是需要实现的动画的,值是一个数组,第一项是起始样式,第二项是结束样式;参数二是一个对象,包含duration
过渡时间、pseudoElement
过渡元素等属性
而动画过渡本案例采用的是 clip-path
属性,它用于裁剪元素,使元素的部分内容不可见。这里我们设置一个圆形,从 0% 到 100% 的变化,从而实现一个圆形的放大效果。
html
<style>
:root {
--background-color: #fff;
background-color: var(--background-color);
}
:root.dark {
--background-color: #000;
}
::view-transition-new(root),
::view-transition-old(root) {
animation: none !important;
}
</style>
<body>
<button id="btn">切换主题</button>
<script>
const btn = document.getElementById('btn')
btn.addEventListener('click', () => {
const transition = document.startViewTransition(() => {
document.documentElement.classList.toggle('dark')
})
transition.ready.then(() => {
document.documentElement.animate({
clipPath: [`circle(0% at 50% 50%)`, `circle(100% at 50% 50%)`],
}, {
duration: 500,
pseudoElement: '::view-transition-new'
})
})
})
</script>
</body>
现在运行代码,发现实现了从中心圆形扩散的过渡动画了。后面去调整一下细节。
注意
如果没有动画效果,可能是原来的默认动画样式覆盖了设置的动画样式,需要在 ::view-transition-new
和 ::view-transition-old
中添加 animation: none !important;
。
优化细节
还有几点细节需要调整一下:
- 修改中心点为鼠标点击的位置
clip-path
的circle
半径要计算修改,不然如果中心点在四个角时,会因为半径不够,导致后续没有动画
js
const btn = document.getElementById('btn')
btn.addEventListener('click', (e) => {
const transition = document.startViewTransition(() => {
document.documentElement.classList.toggle('dark')
})
const x = e.clientX
const y = e.clientY
const targetRadius = Math.hypot(Math.max(x, window.innerWidth - x), Math.max(y, window.innerHeight - y))
transition.ready.then(() => {
document.documentElement.animate({
clipPath: [`circle(0% at ${x}px ${y}px)`, `circle(${targetRadius}px at ${x}px ${y}px)`],
}, {
duration: 500,
pseudoElement: '::view-transition-new'
})
})
})