前不久,在网上看到这么一张非常有趣的图:
想必很多同学都看到这张图,是一个开发小哥被一个日间/黑夜模式切换按钮效果逼疯的视频。
其最终效果大致如下:
原完整代码在这里:Night && Day Toggle ☀️/🌙 [Completed It!]
原效果用了大量 HTML 标签,大量 SVG 元素以及 350 行的 CSS 完成的上述效果。
而本文,我们将尝试优化一下代码,尝试仅仅使用一个标签,完成上述效果。
当然,首先,我们需要一个标签:
1
2
3HTML
4
5复制代码
6
7`<div></div>`
接下来,在单个标签内,我们一步一步来实现这个效果。
拟态阴影
先把整个按钮的形状确定下来,我们需要这样一个整体的拟物形状:
可以看到,这个造型非常的立体。这里的核心是 — 利用阴影,构建拟态效果。
怎么操作呢?其原理就在于,使用两组阴影,使用两个相反的方向,使用两组对比明显的颜色值,来实现凹凸效果。
我们需要使用盒子的内阴影实现。
看个例子:
1
2
3HTML
4
5复制代码
6
7`<div>浮雕阴影</div>
8<div>浮雕阴影</div>`
1
2
3CSS
4
5复制代码
6
7`div {
8 width: 120px;
9 height: 120px;
10 background: #e9ecef;
11 color: #333;
12 box-shadow:
13 7px 7px 12px rgba(0, 0, 0, .4),
14 -7px -7px 12px rgba(255, 255, 255, .9);
15}
16
17div:nth-child(2) {
18 box-shadow:
19 inset -7px -7px 12px rgba(255, 255, 255, .9),
20 inset 7px 7px 12px rgba(0, 0, 0, .4);
21}`
这样,就可以得到拟态风格的按钮,如下图所示,左凸右凹:
借鉴这个方式,我们很快就能得到整个按钮的外形代码:
1
2
3CSS
4
5复制代码
6
7`body {
8 width: 100%;
9 height: 100%;
10 background: #d9deea;
11}
12div {
13 width: 220px;
14 height: 90px;
15 border-radius: 90px;
16 box-shadow:
17 0 -3px 4px #999,
18 inset 0 3px 5px #333,
19 0 4px 4px #ffe,
20 inset 0 -3px 5px #ddd;
21}`
这样,整个外框就实现了:
日间模式的实现
好,接下来,我们来实现日间模式,其整个效果如下:
仔细观察上述图形,除了外框外,主要还有几大部分:
- 一个圆形太阳
- 太阳的光晕
- 云朵效果
发现了吗?它们都是圆形!而在 CSS 中,能够利用单个属性构建多个圆形的方式有非常多种:
box-shadow
filter: drop-shadow()
- background 渐变
并且,上面我们只使用了 div 本身,还有两个伪元素没有使用。我们需要充分把这两个伪元素利用起来。这里,我们这样分工一下:
- 伪元素
::before
: 用于实现太阳本身 - 伪元素
::after
:用于实现太阳的光晕及云朵效果
我们一步一步来。
利用伪元素 ::before
: 实现太阳本身
这个还是非常好理解的,直接上代码:
1
2
3CSS
4
5复制代码
6
7`div::before {
8 content: "";
9 position: absolute;
10 width: 75px;
11 height: 75px;
12 border-radius: 50%;
13 background: #e9cb50;
14 inset: 7.5px;
15 box-shadow:
16 0 0 5px #333,
17 inset 2px 2px 3px #f8f4e4,
18 inset -2px -2px 3px #665613;
19}`
核心就是利用伪元素,再生成一个圆,再添加相应的阴影即可,效果如下:
利用伪元素 ::after
: 实现太阳的光晕及云朵效果
注意!这里是本文最为关键的地方。如何利用剩下一个伪元素实现太阳的光晕及云朵效果?
这里就需要利用到 box-shadow
可以复制自身的技巧。在非常多篇的文章中也有反复提到过。
譬如,当我们想实现一朵云朵,像是这样:
使用 box-shadow 即可轻松实现:
1
2
3HTML
4
5复制代码
6
7`<div></div>`
1
2
3CSS
4
5复制代码
6
7`div{
8 width:100px;
9 height:100px;
10 margin:50px auto;
11 background:#999;
12 border-radius:50%;
13 box-shadow:
14 120px 0px 0 -10px #999,
15 95px 20px 0 0px #999,
16 30px 30px 0 -10px #999,
17 90px -20px 0 0px #999,
18 40px -40px 0 0px #999;
19}`
通过动图,感受一下是什么意思:
嘿,这个云朵不是和我们效果中的云朵非常类似吗?只需要调整一些位置,利用 overflow: hidden
裁剪掉多余部分即可。
光圈其实也是同理,这里,利用 ::after
伪元素,生成一个圆,利用多重 box-shadow
,生成光晕和云朵!
代码如下:
1
2
3CSS
4
5复制代码
6
7`&::after {
8 content: "";
9 position: absolute;
10 width: 70px;
11 height: 70px;
12 inset: 10px;
13 border-radius: 50%;
14 box-shadow:
15 10px 60px 0 10px #fff,
16 65px 60px 0 5px #fff,
17 95px 70px 0 10px #fff,
18 135px 45px 0 5px #fff,
19 170px 35px 0 10px #fff,
20 195px -5px 0 10px #fff,
21 -10px 0 0 50px rgba(255, 255, 255, .2),
22 15px 0 0 50px rgba(255, 255, 255, .15),
23 40px 0 0 50px rgba(255, 255, 255, .21),
24 10px 40px 0 10px #abc1d9,
25 70px 35px 0 10px #abc1d9,
26 95px 40px 0 10px #abc1d9,
27 135px 20px 0 10px #abc1d9,
28 155px 15px 0 10px #abc1d9,
29 190px -20px 0 10px #abc1d9;
30}`
其核心,或者说费时间的地方在于调整每个 box-shadow
的位置和颜色,这样,我们就得到了完整的日间效果图:
夜间模式的实现
实现完日间效果,接下来,我们就需要实现夜间效果。其效果图如下:
为了实现最终的点击切换,我们可以把夜间效果下,按钮的样式,写在一个新的 class 内,这样,后面只需要在点击的过程中,去切换这个 class 即可。
1
2
3HTML
4
5复制代码
6
7`<div class="active"></div>`
1
2
3CSS
4
5复制代码
6
7`div.active{
8 ...
9}`
如上所示,我们接下来的工作就是寻找日间、夜间的差异点,将代码填入上述的 div.active
即可。
首先,太阳变成了月亮,位置进行了移动,颜色进行了变化,并且月亮上多出了一些陨石坑,当然,其本质还是圆形。
这些修改都非常简单,还是在原来的 ::before
基础上修改即可:
1
2
3CSS
4
5复制代码
6
7`div.active{
8 &::before {
9 translate: 130px;
10 background:
11 radial-gradient(circle at 50% 20px, #939aa5, #939aa5 6.5px, transparent 7px, transparent),
12 radial-gradient(circle at 35% 45px, #939aa5, #939aa5 11.5px, transparent 12px, transparent),
13 radial-gradient(circle at 72% 50px, #939aa5, #939aa5 8.5px, transparent 9px, transparent),
14 radial-gradient(#cbcdda, #cbcdda);
15 }
16}`
这里是非常好修改的,利用 radila-gradient()
,也就是多重渐变,我们可以轻松的在一个元素内完成背景加上陨石坑的代码:
继续,夜间模式下,月亮也有光圈,代码是可以复用的,并且夜间模式没有了云朵,取而代之是星星。
星星看起来有点复杂,我们待会处理,这里仅仅需要把云朵部分的阴影颜色,设置为 transparent
即可。
1
2
3CSS
4
5复制代码
6
7`div.active {
8 &::after {
9 transform: translate(130px);
10 box-shadow:
11 10px 60px 0 10px transparent,
12 65px 60px 0 5px transparent,
13 95px 70px 0 10px transparent,
14 135px 45px 0 5px transparent,
15 170px 35px 0 10px transparent,
16 195px -5px 0 10px transparent,
17 10px 0 0 50px rgba(255, 255, 255, .2),
18 -15px 0 0 50px rgba(255, 255, 255, .15),
19 -40px 0 0 50px rgba(255, 255, 255, .21),
20 10px 40px 0 10px transparent,
21 70px 35px 0 10px transparent,
22 95px 40px 0 10px transparent,
23 135px 20px 0 10px transparent,
24 155px 15px 0 10px transparent,
25 190px -20px 0 10px transparent;
26 }
27}`
效果如下:
为什么这里不是去掉云朵的代码,而是把云朵部分的阴影颜色,设置为 transparent
呢?这样做的原因是能够在切换过程中,得到更好的动画效果。
好!到这里,只剩下夜间模式下的星星和背景了,背景是非常好解决的,主要是星星,看原效果的动图,每一颗星星是带有棱角的,而这种不规则图案,确实是 CSS 最棘手的问题。
到这里,无奈退而求其次,考虑使用小圆点模拟星星效果。(没想到效果其实也很不错!)
那这个问题又变成了和月亮与陨石坑类似的问题了,都是圆形,那就非常好解决。
最终,考虑在 div
本身的背景之上,设置一个大背景 background-size: 200% 100%
,这样,一半是日间的背景,一半是夜间的背景,在切换的过程中,只需要改变 background-position
即可。
这样一来,代码如下:
1
2
3CSS
4
5复制代码
6
7`div {
8 background:
9 radial-gradient(circle at 18% 20px, #fff, #fff 6px, transparent 7px, transparent),
10 radial-gradient(circle at 35% 45px, #fff, #fff 1px, transparent 2px, transparent),
11 radial-gradient(circle at 10% 70px, #fff, #fff 2.5px, transparent 3.5px, transparent),
12 radial-gradient(circle at 25% 15px, #fff, #fff 3px, transparent 4px, transparent),
13 radial-gradient(circle at 15% 50px, #fff, #fff 1.5px, transparent 2.5px, transparent),
14 radial-gradient(circle at 30% 75px, #fff, #fff 5px, transparent 6px, transparent),
15 radial-gradient(circle at 5% 30px, #fff, #fff 0.5px, transparent 1.5px, transparent),
16 radial-gradient(circle at 25% 60px, #fff, #fff 0.5px, transparent 1.5px, transparent),
17 radial-gradient(circle at 7% 35px, #fff, #fff 0.5px, transparent 1.5px, transparent),
18 linear-gradient(90deg, #2b303e, #2b303e 50%, #5a81b4 50%, #5a81b4);
19 background-repeat: no-repeat;
20 background-size: 200% 100%;
21 background-position: 100% 0;
22}
23div.active {
24 background-position: 0 0;
25}`
这样,夜间效果也就完美实现了:
添加过渡效果以及切换效果
最后,只需要加上一些过渡效果以及点击切换时,元素样式类名变化的 JavaScript 代码即可。
完整的整个效果,代码如下:
1
2
3HTML
4
5复制代码
6
7`<div id="g-btn"></div>`
1
2
3CSS
4
5复制代码
6
7`body {
8 background: #d9deea;
9}
10div {
11 position: relative;
12 width: 220px;
13 height: 90px;
14 background:
15 radial-gradient(circle at 18% 20px, #fff, #fff 6px, transparent 7px, transparent),
16 radial-gradient(circle at 35% 45px, #fff, #fff 1px, transparent 2px, transparent),
17 radial-gradient(circle at 10% 70px, #fff, #fff 2.5px, transparent 3.5px, transparent),
18 radial-gradient(circle at 25% 15px, #fff, #fff 3px, transparent 4px, transparent),
19 radial-gradient(circle at 15% 50px, #fff, #fff 1.5px, transparent 2.5px, transparent),
20 radial-gradient(circle at 30% 75px, #fff, #fff 5px, transparent 6px, transparent),
21 radial-gradient(circle at 5% 30px, #fff, #fff 0.5px, transparent 1.5px, transparent),
22 radial-gradient(circle at 25% 60px, #fff, #fff 0.5px, transparent 1.5px, transparent),
23 radial-gradient(circle at 7% 35px, #fff, #fff 0.5px, transparent 1.5px, transparent),
24 linear-gradient(90deg, #2b303e, #2b303e 50%, #5a81b4 50%, #5a81b4);
25 background-repeat: no-repeat;
26 background-size: 200% 100%;
27 background-position: 100% 0;
28 border-radius: 90px;
29 box-shadow:
30 0 -3px 4px #999,
31 inset 0 3px 5px #333,
32 0 4px 4px #ffe,
33 inset 0 -3px 5px #ddd;
34 cursor: pointer;
35 overflow: hidden;
36 transition: .5s all;
37 &::before,
38 &::after {
39 content: "";
40 position: absolute;
41 transition: .5s all;
42 }
43 &::before {
44 width: 75px;
45 height: 75px;
46 border-radius: 50%;
47 background: #e9cb50;
48 inset: 7.5px;
49 box-shadow:
50 0 0 5px #333,
51 inset 2px 2px 3px #f8f4e4,
52 inset -2px -2px 3px #665613;
53 z-index: 1;
54 }
55 &::after {
56 width: 70px;
57 height: 70px;
58 inset: 10px;
59 border-radius: 50%;
60 box-shadow:
61 10px 60px 0 10px #fff,
62 65px 60px 0 5px #fff,
63 95px 70px 0 10px #fff,
64 135px 45px 0 5px #fff,
65 170px 35px 0 10px #fff,
66 195px -5px 0 10px #fff,
67 -10px 0 0 50px rgba(255, 255, 255, .2),
68 15px 0 0 50px rgba(255, 255, 255, .15),
69 40px 0 0 50px rgba(255, 255, 255, .21),
70 10px 40px 0 10px #abc1d9,
71 70px 35px 0 10px #abc1d9,
72 95px 40px 0 10px #abc1d9,
73 135px 20px 0 10px #abc1d9,
74 155px 15px 0 10px #abc1d9,
75 190px -20px 0 10px #abc1d9;
76 }
77}
78div:hover::before {
79 filter: contrast(90%) brightness(110%);
80 scale: 1.05;
81}
82div.active {
83 background-position: 0 0;
84
85
86
87 &::before {
88 translate: 130px;
89 background:
90 radial-gradient(circle at 50% 20px, #939aa5, #939aa5 6.5px, transparent 7px, transparent),
91 radial-gradient(circle at 35% 45px, #939aa5, #939aa5 11.5px, transparent 12px, transparent),
92 radial-gradient(circle at 72% 50px, #939aa5, #939aa5 8.5px, transparent 9px, transparent),
93 radial-gradient(#cbcdda, #cbcdda);
94 }
95 &::after {
96 transform: translate(130px);
97 box-shadow:
98 10px 60px 0 10px transparent,
99 65px 60px 0 5px transparent,
100 95px 70px 0 10px transparent,
101 135px 45px 0 5px transparent,
102 170px 35px 0 10px transparent,
103 195px -5px 0 10px transparent,
104 10px 0 0 50px rgba(255, 255, 255, .2),
105 -15px 0 0 50px rgba(255, 255, 255, .15),
106 -40px 0 0 50px rgba(255, 255, 255, .21),
107 10px 40px 0 10px transparent,
108 70px 35px 0 10px transparent,
109 95px 40px 0 10px transparent,
110 135px 20px 0 10px transparent,
111 155px 15px 0 10px transparent,
112 190px -20px 0 10px transparent;
113 }
114}`
1
2
3javascript
4
5复制代码
6
7`const btn = document.querySelector('#g-btn');
8btn.addEventListener('click', (e) => {
9 btn.setAttribute('class', btn.getAttribute("class") === "active" ? "" : "active");
10});`
来看看最终效果:
是不是基本上还原了原效果?这里我们仅仅使用了一个标签,核心配合了 box-shadow
以及背景渐变完成了整个按钮效果。
完整的代码,你可以戳这里 CodePen Demo — Single Div BTN Toggle Effect