具体效果
element-plus的效果
如图所示,点击主题切换按钮后出现一个圆形的扩散动画并具有这些特点
首先先分析动画效果, 通过点击右上角的切换按钮
如果要切换到深色(dark)模式, 程序会在点击位置渲染一个圆, 圆初始很小, 随着时间流动逐渐放大, 直到占满整个屏幕, 并且被圆覆盖的部分会显示夜间模式的效果, 从而完成一个用户友好的主题切换效果.
如果要切换到亮色(light)模式, 程序会渲染一个占满屏幕的圆, 圆初始很大, 随着时间流动逐渐缩小, 直到缩小到点击的位置, 并且没有被圆覆盖的部分会显示亮色模式的效果, 从而完成一个用户友好的主题切换效果.
扩散的圆形里面和外面分别是不同主题色的页面,里圆可以仅遮盖一部分文字, 仅让文字的一部分变成新主题的颜色.
实现分析
要想达到效果,最容易想到的就是有两张一模一样的页面重叠在一起
- 其中用旧主题的截图定位覆盖在最上层
- 在切换主题对截图进行裁剪动画,新主题随着上层完成动画后完全出现,并且截图层隐藏
裁剪的效果使用css的clip-path属性
/* 放大缩小动画 */
@keyframes scaleGrowAnimation {
0% {
clip-path: circle(0 at 200px 50px);
}
100% {
clip-path: circle(200% at 200px 50px);
}
}
而这个对旧主题截图的操作正好有一个Web APi实现了,他就是 View Transitions API,并且他直接支持不同 DOM 状态之间的动画过渡。同时还可以在单个步骤中更新 DOM 内容。
View Transitions API属性说明,具体的属性说明和使用案例这里都有详细的说明,这里就不赘述了
直接上最后实现的代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>切换主题</title>
<style>
/* 默认主题样式 */
html {
margin: 0;
padding: 0;
background-color: white;
}
html.dark {
background-color: #1b1b1b;
}
header {
text-align: left;
padding: 10px;
}
img {
width: auto;
}
/* Alternative custom animation style */
::view-transition-old(root),
::view-transition-new(root) {
height: auto;
width: 100vw;
animation: none;
mix-blend-mode: normal;
}
html.dark::view-transition-old(root) {
z-index: 2147483646;
}
html.dark::view-transition-new(root) {
z-index: 1;
}
html::view-transition-old(root) {
z-index: 1;
}
html::view-transition-new(root) {
z-index: 2147483646;
}
button {
background-color: blue;
color: white;
}
button.dark {
background-color: white;
color: #000000;
}
.dark #light {
display: none;
}
.dark #dark {
display: block;
}
#dark {
display: none;
}
#light {
display: block;
}
</style>
</head>
<body>
<header>
<button id="themeButton">切换主题</button>
</header>
<div class="box">
<img src="./light.png" id="light" />
<img src="./dark.png" id="dark" />
</div>
<script>
// 获取按钮元素
const themeButton = document.getElementById("themeButton");
let isDark = false;
themeButton.textContent = isDark ? "白天" : "晚上";
function changeTheme() {
// 切换页面主题
isDark = !isDark;
document.documentElement.classList.toggle("dark");
themeButton.textContent = isDark ? "白天" : "晚上";
}
function updateView(event) {
//在不支持的浏览器里不做动画
if (!document.startViewTransition) {
changeTheme();
return;
}
// 开始一次视图过渡:
const transition = document.startViewTransition(() => changeTheme());
transition.ready.then(() => {
const x = event.clientX;
const y = event.clientY;
//计算按钮到最远点的距离用作裁剪圆形的半径
const endRadius = Math.hypot(
Math.max(x, innerWidth - x),
Math.max(y, innerHeight - y)
);
const clipPath = [
`circle(0px at ${x}px ${y}px)`,
`circle(${endRadius}px at ${x}px ${y}px)`,
];
//开始动画
document.documentElement.animate(
{
clipPath: isDark ? [...clipPath].reverse() : clipPath,
},
{
duration: 400,
easing: "ease-in",
pseudoElement: isDark
? "::view-transition-old(root)"
: "::view-transition-new(root)",
}
);
});
}
// 添加点击事件监听器
themeButton.addEventListener("click", updateView);
</script>
</body>
</html>
以上就实现和elementplus切换主题效果一样了,可以直接拷贝运行看看,有不懂的看注释