最近在网上刷到一个短视频——怎么用一个按钮气死前端, 设计师与程序员的矛盾就是的这么产生的。
这个夜间模式切换开关效果看着很是炫酷,但确实有难为我们前端人员的嫌疑。
我相信所有前端程序员的心里是
万马奔腾·······
不管效果看起来多么复杂,聪明勇敢的前端人总是能想出办法来实现,最近就看到codepen上的大神就把这个切换效果做出来了。纯纯硬核代码完全实现,源代码已在文末整理好。我们来分析一下大神是怎么搞定这个表态的效果的!
动效分析
基于开关的动画效果可以将相关动画步骤进行模块拆分,大概可以拆分为下列模块:
首先我们可以将代码拆分为几大模块, 来一一实现。
- 首先背景是根据开关整个页面背景换为暗色。
- 开关的内部从太阳变成月亮,圆形由黄变灰。
- 按钮内部背景色,由蓝变黑。
- 按钮内部有三个透明度不一的大圆跟随切换
- 开关状态为白天的时候,会有两朵云彩其中并有个飞机飞过。
- 开关状态为黑夜的时候,会有星星闪烁,还有宇航员熊飞过。
主要的动效就是这些,具体的还有很多细节,比如开关边缘的阴影效果,状态切换时候的贝塞尔曲线效果,等等,我们这篇文章主要解析核心代码,细节的代码也会在全代码文件里,有兴趣的可以去研究!
需要的知识点
在开始写代码之前我们知道这几个知识点。首先,是以元素以aria- 开头的属性。aria 英文全称:Accessible Rich Internet Application,翻译成中文就是:可访问的富互联网应用程序。其实它是一组属性,定义了使残疾人更容易访问 web 内容和 web 应用程序(尤其是使用JavaScript开发的应用程序)的方法。
这类属性的作用是为了增强网页在残障辅助阅读设备上的识别读取。aria 是一种比较新的辅助访问技术,用来弥补 HTML 和 JS 本身对可访问性方面的不足。支持 aria 并不是必须或者强制的,但是支持 aria 会让你的应用变得更友好,更健壮。
我们会用到,aria-pressedARIA属性,指示按钮的状态打开关闭混合。他可以表示:
- aria-pressed="false":可以表示按钮为关闭。
- aria-pressed="true":可以表示按钮为开启
- aria-pressed="mixed":可以表示按钮为混合状态,
其次是CSS函数 var()此函数用于引用先定义的变量值。语法:var(--variable-name, default-value),--variable-name 表示变量名称,以两个连字符(--)开头,是任何自定义名称。default-value 是可选的,表示在指定变量未定义或无效时要使用的默认值。 举个栗子假设定义 --dark ,当其值为1的时候,最终的代码则是 scale: 1,如果 --dark 没有定义则最终的代码是 scale: 0:
.element {
scale: var(--dark, 0);
}
这些变量让我们更轻松地维护更新。所以我们可以得到:
BUTTON.setAttribute("aria-pressed", IS_PRESSED ? false : true);
这样我们也就得到--dark 的值,这样也就可以控制整体页面的明暗,颜色等属性。
[aria-pressed=true] {
--dark: 1;
}
我们写这个按钮会用到很多这个属性,来控制元素的隐藏显示和一些动画效果
硬核代码实现
按钮背景切换
我们开始研究代码的实现首先实现开关内部背景颜色的切换,核心代码如下:
--sky: hsl(204, 53%, 47%);
--night: hsl(229, 25%, 16%);
background: hsl(
calc(204 + (var(--dark, 0) * 25))
calc((53 - (var(--dark, 0) * 28)) * 1%)
calc((47 - (var(--dark, 0) * 31)) * 1%)
);
我们通过--dark 的变化更改 background ,也就是 --sky 和 --night 的值,
(tips:hsl 格式的色值,后面两个参数的值是需要百分比格式的值)
整个页面的背景切换是在按钮点击的时候
就会给body设置 data-dark-mode 属性。
document.body.setAttribute("data-dark-mode", IS_PRESSED ? false : true);
通过 data-dark-mode 得到不同的值。
--bg: hsl(219, 30%, 88%);
--color: hsl(219 30% 20%);
}
[data-dark-mode=true] {
--bg: hsl(219, 30%, 12%);
--color: hsl(219 30% 98%);
}
按钮内部的背景色切换
然后就是按钮内部的太阳转变为月亮,也就是黄色变成灰色。
整个过程我们发现,切换后的灰色
然后是开关内部的圆由黄色(太阳的效果)切换为灰色(月亮的效果)
观察后发现,切换动画中,灰色圆由右至左的覆盖黄色圆, 灰色圆层级高于黄色。
那么我们不难发现,效果的实现方式可以用定位和父子元素,即灰色圆是黄色的子元素。
动画过程:
1)整个圆从左往右平移当 --dark
无定义时最终为0当---dark 为1时,需要平移当前总宽度减去自身圆的宽度的距离,即「3 / 8 * var(--width)」。
translate: calc(
var(--dark, 0) * (var(--width) - (3 / 8 * var(--width)))
) 0;
2)圆内的动画月亮的背景元素向左平移X轴的100%,之后修改X轴为0%。
translate: calc((100 - (var(--dark, 0) * 100)) * 1%) 0%;
再加上过渡动画
transition: translate var(--speed) ease-in-out;
外部的父元素增加了 overflow: hidden,所以默认平移X轴100%是不可见的,大家看下面没有增加 overflow: hidden 代码的效果就更清晰实现的原理了。
背景透明圆的切换动画
这个很简单了radial-gradient 径向渐变绘制三层背景色,掌握好色值渐变开始和结束以及大小。将中心定位于太阳的中心。和太阳做一样的平移动画就可以了。
background:
radial-gradient(hsl(0 0% 100% / 0.25) 40%, transparent 40.5%),
radial-gradient(hsl(0 0% 100% / 0.25) 56%, transparent 56.5%)
hsl(0 0% 100% / 0.25);
星空云朵切换动画
因为动效比较复杂,我们可以用svg来实现,源代码就放在最后的源代码里了,云朵分成两个颜色,所有要有两个SVG的代码,所以
<svg aria-hidden=true class="toggle__backdrop" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 290 228">
<g class="clouds">
<path fill="#D9D9D9" d="M335..." />
</g>
</svg>
<svg aria-hidden=true class="toggle__backdrop" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 290 228">
<g class="clouds">
<path fill="#fff" d="M328..." />
</g>
</svg>
白天状态的云朵,并不是左右平移,而是上下,只需改变Y轴的值。
transition: translate var(--speed) var(--easing);
translate: 0 calc(
var(--dark, 0) * (100% - (3 / 8 * var(--width)))
);
星星闪烁的效果其实就是通svg元素设置缩放动画,可以给不同的星星设置不同的执行时间和延迟执行时间。
animation: twinkle 4s -2s infinite;
@keyframes twinkle {
0%, 40%, 60%, 100% {
transform: scale(1);
}
50% {
transform: scale(0);
}
}
宇航员 & 小飞机
这两个元素都有动画效果,宇航员一边飘过一边旋转,小飞机也缓缓上升。
// 平移动画
translate: calc(var(--dark, 0) * 400%) calc(var(--dark, 0) * -350%);
transition: translate calc(var(--speed) + (var(--dark, 0) * (var(--bear-speed) - var(--speed)))) calc(var(--bear-speed) * (0.4 * var(--dark, 0))) linear;
// 自身旋转动画
rotate: calc(var(--dark, 0) * 360deg);
transition: rotate calc(var(--speed) + (var(--dark, 0) * (var(--bear-speed) - var(--speed)))) calc(var(--bear-speed) * 0.4) linear, scale var(--speed) ease-in-out;
最后
到了这的我们基本就已经搞定了整个按钮的核心功能,实现了交互效果,但有一些细节的阴影,过渡,本塞尔曲线等没有进行具体的介绍。