我们先看俩组网页动画效果:
一、原理
第一个:重排布局效果,类似app
的重排布局效果
第二个:切换主题特效,很多第三方组件库都用到了这个特效切换主题
效果核心用到了浏览器的一个新特性startViewTransition
,基本原理如下:
- 触发条件:当调用
document.startViewTransition()
时,浏览器会识别出即将发生的DOM
变化,并准备一个视图过渡。 - 捕获快照:浏览器会在当前渲染帧结束时自动捕捉页面的一个快照(旧视图)。这包括页面上的所有可见元素及其样式和布局信息。
- 执行回调函数:
startViewTransition
接受一个回调函数作为参数,在这个回调中你可以更新DOM
,比如改变类名、添加或移除元素、修改属性等。这些改动代表了新视图的状态。 - 生成新快照:一旦回调函数完成并且新的
DOM
状态被提交给浏览器,浏览器会再次捕捉页面的新快照(新视图)。 - 应用过渡效果:浏览器使用这两个快照来计算从旧视图到新视图之间的差异,并根据这些差异自动应用合适的动画效果。默认情况下,浏览器会选择一种适当的动画方式来确保过渡看起来自然流畅。
- 完成过渡:当过渡完成后,旧视图被完全替换为新视图,且浏览器恢复正常的渲染流程。此时,
Promise
返回,表示过渡已经结束。 - 伪元素控制:在整个过程中,可以利用
::view-transition-old(root)
和::view-transition-new(root)
这两个特殊的CSS
伪元素来定义旧视图和新视图的具体样式,包括它们如何混合、层叠顺序等。 - 性能优化:由于视图过渡是在浏览器层面处理的,因此它们通常比手动编写的
JavaScript
动画更高效,并且能够更好地与硬件加速相结合,从而提供更流畅的用户体验。 - 兼容性注意:需要注意的是,
View Transition API
是一个相对较新的特性,不是所有的浏览器都支持它。因此,在实际项目中使用时,请务必检查目标用户的浏览器支持情况。
二、案例
- 特效一:核心是
view-transition-name: var(--index)
,给一个不同的值,单独控制其过渡动画
.container {
width: 100vw;
height: 100vh;
display: flex;
align-items: center;
flex-direction: column;
background-color: var(--bg-color);
header {
width: 100vh;
height: 100px;
margin-bottom: 100px;
display: flex;
justify-content: flex-end;
align-items: center;
color: var(--color);
}
section {
width: 500px;
--count: 3;
display: flex;
flex-wrap: wrap;
.item {
width: 150px;
height: 50px;
border-radius: 3px;
box-shadow: 0 0 10px 0px rgba(0, 0, 0, 0.3);
margin: 10px calc((100% - var(--count) * 150px) / var(--count) / 2);
background-color: #6d3fe3;
display: flex;
justify-content: center;
align-items: center;
font-size: 25px;
font-weight: bold;
color: var(--color);
cursor: pointer;
user-select: none;
view-transition-name: var(--index);
}
}
}
<div class="container">
<section>
<div onclick="handle_delete(event, 1)" class="item" style="--index: a1">1</div>
<div onclick="handle_delete(event, 2)" class="item" style="--index: a2">2</div>
<div onclick="handle_delete(event, 3)" class="item" style="--index: a3">3</div>
<div onclick="handle_delete(event, 4)" class="item" style="--index: a4">4</div>
<div onclick="handle_delete(event, 5)" class="item" style="--index: a5">5</div>
<div onclick="handle_delete(event, 6)" class="item" style="--index: a6">6</div>
<div onclick="handle_delete(event, 7)" class="item" style="--index: a7">7</div>
<div onclick="handle_delete(event, 8)" class="item" style="--index: a8">8</div>
<div onclick="handle_delete(event, 9)" class="item" style="--index: a9">9</div>
<div onclick="handle_delete(event, 10)" class="item" style="--index: a10">10</div>
<div onclick="handle_delete(event, 11)" class="item" style="--index: a11">11</div>
</section>
</div>
核心逻辑:
<script>
// 刪除元素
function handle_delete(event, index) {
document.startViewTransition(() => {
event.target.remove()
})
}
</script>
- 特效二: 利用
clip-path
动态分割,利用::view-transition-old
改变其层级z-index
即可,
:root {
--color: #333;
background-color: #fff;
--x: 50%;
--y: 50%;
}
/** Animated Theme Toggle */
::view-transition-old(root),
::view-transition-new(root) {
animation: none;
mix-blend-mode: normal;
}
html[theme=dark] {
--color: #fff;
--bg-color: darkslategray;
}
html[theme=dark]::view-transition-old(root) {
z-index: 1;
}
html[theme=dark]::view-transition-new(root) {
z-index: 999;
}
::view-transition-old(root) {
z-index: 999;
}
::view-transition-new(root) {
z-index: 1;
}
<div class="container">
<header>
<button onclick="handle_change(event)">切换主题</button>
</header>
</div>
核心逻辑:
// 切换系统主题
function handle_change(event) {
document.documentElement.style.setProperty('--x', event.pageX + 'px')
document.documentElement.style.setProperty('--y', event.pageY + 'px')
let result = document.documentElement.getAttribute('theme') === 'dark' ? 'light' : 'dark'
let transition = document.startViewTransition(() => {
document.documentElement.setAttribute('theme', result)
})
// 返回一个transition对象
transition.ready.then(() => {
let clipPath = [
'circle(0% at var(--x) var(--y))',
'circle(100% at var(--x) var(--y))'
]
document.documentElement.animate(
{
clipPath: result === 'dark' ? clipPath : [...clipPath].reverse()
},
{
duration: 300,
pseudoElement: result === 'dark'
? "::view-transition-new(root)"
: "::view-transition-old(root)",
easing: 'ease-in' // 使用缓动函数
}
)
})
}
pseudoElement
是用来标记效果触发的载体为伪元素