共计 7828 个字符,预计需要花费 20 分钟才能阅读完成。
前不久,在网上看到这么一张十分乏味的图:
想必很多同学都看到这张图,是一个开发小哥被一个日间 / 黑夜模式切换按钮成果逼疯的视频。
其最终成果大抵如下:
原残缺代码在这里:Night && Day Toggle ☀️/🌙 [Completed It!]
原成果用了大量 HTML 标签,大量 SVG 元素以及 350 行的 CSS 实现的上述成果。
而本文,咱们将尝试优化一下代码,尝试仅仅应用一个标签,实现上述成果。
当然,首先,咱们须要一个标签:
<div></div>
接下来,在单个标签内,咱们一步一步来实现这个成果。
拟态暗影
先把整个按钮的形态确定下来,咱们须要这样一个整体的拟物形态:
能够看到,这个造型十分的平面。这里的外围是 — 利用暗影,构建拟态成果。
怎么操作呢?其原理就在于,应用两组暗影,应用两个相同的方向,应用两组比照显著的色彩值,来实现凹凸成果。
咱们须要应用盒子的内暗影实现。
看个例子:
<div> 浮雕暗影 </div> | |
<div> 浮雕暗影 </div> |
div { | |
width: 120px; | |
height: 120px; | |
background: #e9ecef; | |
color: #333; | |
box-shadow: | |
7px 7px 12px rgba(0, 0, 0, .4), | |
-7px -7px 12px rgba(255, 255, 255, .9); | |
} | |
div:nth-child(2) { | |
box-shadow: | |
inset -7px -7px 12px rgba(255, 255, 255, .9), | |
inset 7px 7px 12px rgba(0, 0, 0, .4); | |
} |
这样,就能够失去拟态格调的按钮,如下图所示,左凸右凹:
借鉴这个形式,咱们很快就能失去整个按钮的形状代码:
body { | |
width: 100%; | |
height: 100%; | |
background: #d9deea; | |
} | |
div { | |
width: 220px; | |
height: 90px; | |
border-radius: 90px; | |
box-shadow: | |
0 -3px 4px #999, | |
inset 0 3px 5px #333, | |
0 4px 4px #ffe, | |
inset 0 -3px 5px #ddd; | |
} |
这样,整个外框就实现了:
日间模式的实现
好,接下来,咱们来实现日间模式,其整个成果如下:
仔细观察上述图形,除了外框外,次要还有几大部分:
- 一个圆形太阳
- 太阳的光晕
- 云朵成果
发现了吗?它们都是 圆形!而在 CSS 中,可能利用单个属性构建多个圆形的形式有十分多种:
box-shadow
filter: drop-shadow()
- background 突变
并且,下面咱们只应用了 div 自身,还有两个伪元素没有应用。咱们须要充沛把这两个伪元素利用起来。这里,咱们这样分工一下:
- 伪元素
::before
: 用于实现太阳自身 - 伪元素
::after
:用于实现太阳的光晕及云朵成果
咱们一步一步来。
利用伪元素 ::before
: 实现太阳自身
这个还是十分好了解的,间接上代码:
div::before { | |
content: ""; | |
position: absolute; | |
width: 75px; | |
height: 75px; | |
border-radius: 50%; | |
background: #e9cb50; | |
inset: 7.5px; | |
box-shadow: | |
0 0 5px #333, | |
inset 2px 2px 3px #f8f4e4, | |
inset -2px -2px 3px #665613; | |
} |
外围就是利用伪元素,再生成一个圆,再增加相应的暗影即可,成果如下:
利用伪元素 ::after
: 实现太阳的光晕及云朵成果
留神!这里是本文最为要害的中央。如何利用剩下一个伪元素实现太阳的光晕及云朵成果?
这里就须要利用到 box-shadow
能够复制本身的技巧。在十分多篇的文章中也有重复提到过。
譬如,当咱们想实现一朵云朵,像是这样:
应用 box-shadow 即可轻松实现:
<div></div>
div{ | |
width:100px; | |
height:100px; | |
margin:50px auto; | |
background:#999; | |
border-radius:50%; | |
box-shadow: | |
120px 0px 0 -10px #999, | |
95px 20px 0 0px #999, | |
30px 30px 0 -10px #999, | |
90px -20px 0 0px #999, | |
40px -40px 0 0px #999; | |
} |
通过动图,感受一下是什么意思:
嘿,这个云朵不是和咱们成果中的云朵十分相似吗?只须要调整一些地位,利用 overflow: hidden
裁剪掉多余局部即可。
光圈其实也是同理,这里,利用 ::after
伪元素,生成一个圆,利用多重 box-shadow
,生成光晕和云朵!
代码如下:
&::after { | |
content: ""; | |
position: absolute; | |
width: 70px; | |
height: 70px; | |
inset: 10px; | |
border-radius: 50%; | |
box-shadow: | |
10px 60px 0 10px #fff, | |
65px 60px 0 5px #fff, | |
95px 70px 0 10px #fff, | |
135px 45px 0 5px #fff, | |
170px 35px 0 10px #fff, | |
195px -5px 0 10px #fff, | |
-10px 0 0 50px rgba(255, 255, 255, .2), | |
15px 0 0 50px rgba(255, 255, 255, .15), | |
40px 0 0 50px rgba(255, 255, 255, .21), | |
10px 40px 0 10px #abc1d9, | |
70px 35px 0 10px #abc1d9, | |
95px 40px 0 10px #abc1d9, | |
135px 20px 0 10px #abc1d9, | |
155px 15px 0 10px #abc1d9, | |
190px -20px 0 10px #abc1d9; | |
} |
其外围,或者说费时间的中央在于调整每个 box-shadow
的地位和色彩,这样,咱们就失去了残缺的日间效果图:
夜间模式的实现
实现完日间成果,接下来,咱们就须要实现夜间成果。其效果图如下:
为了实现最终的点击切换,咱们能够把夜间成果下,按钮的款式,写在一个新的 class 内,这样,前面只须要在点击的过程中,去切换这个 class 即可。
<div class="active"></div>
div.active{...}
如上所示,咱们接下来的工作就是寻找日间、夜间的差别点,将代码填入上述的 div.active
即可。
首先,太阳变成了月亮,地位进行了挪动,色彩进行了变动,并且月亮上多出了一些陨石坑,当然,其本质还是圆形。
这些批改都非常简单,还是在原来的 ::before
根底上批改即可:
div.active{ | |
&::before { | |
translate: 130px; | |
background: | |
radial-gradient(circle at 50% 20px, #939aa5, #939aa5 6.5px, transparent 7px, transparent), | |
radial-gradient(circle at 35% 45px, #939aa5, #939aa5 11.5px, transparent 12px, transparent), | |
radial-gradient(circle at 72% 50px, #939aa5, #939aa5 8.5px, transparent 9px, transparent), | |
radial-gradient(#cbcdda, #cbcdda); | |
} | |
} |
这里是十分好批改的,利用 radila-gradient()
,也就是多重突变,咱们能够轻松的在一个元素内实现背景加上陨石坑的代码:
持续,夜间模式下,月亮也有光圈,代码是能够复用的,并且夜间模式没有了云朵,取而代之是星星。
星星看起来有点简单,咱们待会解决,这里仅仅须要把云朵局部的暗影色彩,设置为 transparent
即可。
div.active { | |
&::after {transform: translate(130px); | |
box-shadow: | |
10px 60px 0 10px transparent, | |
65px 60px 0 5px transparent, | |
95px 70px 0 10px transparent, | |
135px 45px 0 5px transparent, | |
170px 35px 0 10px transparent, | |
195px -5px 0 10px transparent, | |
10px 0 0 50px rgba(255, 255, 255, .2), | |
-15px 0 0 50px rgba(255, 255, 255, .15), | |
-40px 0 0 50px rgba(255, 255, 255, .21), | |
10px 40px 0 10px transparent, | |
70px 35px 0 10px transparent, | |
95px 40px 0 10px transparent, | |
135px 20px 0 10px transparent, | |
155px 15px 0 10px transparent, | |
190px -20px 0 10px transparent; | |
} | |
} |
成果如下:
为什么这里不是去掉云朵的代码,而是把云朵局部的暗影色彩,设置为 transparent
呢?这样做的起因是可能在切换过程中,失去更好的动画成果。
好!到这里,只剩下夜间模式下的星星和背景了,背景是十分好解决的,次要是星星,看原成果的动图,每一颗星星是带有棱角的,而这种不规则图案,的确是 CSS 最辣手的问题。
到这里,无奈退而求其次,思考应用小圆点模仿星星成果。(没想到成果其实也很不错!)
那这个问题又变成了和月亮与陨石坑相似的问题了,都是圆形,那就十分好解决。
最终,思考在 div
自身的背景之上,设置一个大背景 background-size: 200% 100%
,这样,一半是日间的背景,一半是夜间的背景,在切换的过程中,只须要扭转 background-position
即可。
这样一来,代码如下:
div { | |
background: | |
radial-gradient(circle at 18% 20px, #fff, #fff 6px, transparent 7px, transparent), | |
radial-gradient(circle at 35% 45px, #fff, #fff 1px, transparent 2px, transparent), | |
radial-gradient(circle at 10% 70px, #fff, #fff 2.5px, transparent 3.5px, transparent), | |
radial-gradient(circle at 25% 15px, #fff, #fff 3px, transparent 4px, transparent), | |
radial-gradient(circle at 15% 50px, #fff, #fff 1.5px, transparent 2.5px, transparent), | |
radial-gradient(circle at 30% 75px, #fff, #fff 5px, transparent 6px, transparent), | |
radial-gradient(circle at 5% 30px, #fff, #fff 0.5px, transparent 1.5px, transparent), | |
radial-gradient(circle at 25% 60px, #fff, #fff 0.5px, transparent 1.5px, transparent), | |
radial-gradient(circle at 7% 35px, #fff, #fff 0.5px, transparent 1.5px, transparent), | |
linear-gradient(90deg, #2b303e, #2b303e 50%, #5a81b4 50%, #5a81b4); | |
background-repeat: no-repeat; | |
background-size: 200% 100%; | |
background-position: 100% 0; | |
} | |
div.active {background-position: 0 0;} |
这样,夜间成果也就完满实现了:
增加过渡成果以及切换成果
最初,只须要加上一些过渡成果以及点击切换时,元素款式类名变动的 JavaScript 代码即可。
残缺的整个成果,代码如下:
<div id="g-btn"></div>
body {background: #d9deea;} | |
div { | |
position: relative; | |
width: 220px; | |
height: 90px; | |
background: | |
radial-gradient(circle at 18% 20px, #fff, #fff 6px, transparent 7px, transparent), | |
radial-gradient(circle at 35% 45px, #fff, #fff 1px, transparent 2px, transparent), | |
radial-gradient(circle at 10% 70px, #fff, #fff 2.5px, transparent 3.5px, transparent), | |
radial-gradient(circle at 25% 15px, #fff, #fff 3px, transparent 4px, transparent), | |
radial-gradient(circle at 15% 50px, #fff, #fff 1.5px, transparent 2.5px, transparent), | |
radial-gradient(circle at 30% 75px, #fff, #fff 5px, transparent 6px, transparent), | |
radial-gradient(circle at 5% 30px, #fff, #fff 0.5px, transparent 1.5px, transparent), | |
radial-gradient(circle at 25% 60px, #fff, #fff 0.5px, transparent 1.5px, transparent), | |
radial-gradient(circle at 7% 35px, #fff, #fff 0.5px, transparent 1.5px, transparent), | |
linear-gradient(90deg, #2b303e, #2b303e 50%, #5a81b4 50%, #5a81b4); | |
background-repeat: no-repeat; | |
background-size: 200% 100%; | |
background-position: 100% 0; | |
border-radius: 90px; | |
box-shadow: | |
0 -3px 4px #999, | |
inset 0 3px 5px #333, | |
0 4px 4px #ffe, | |
inset 0 -3px 5px #ddd; | |
cursor: pointer; | |
overflow: hidden; | |
transition: .5s all; | |
&::before, | |
&::after { | |
content: ""; | |
position: absolute; | |
transition: .5s all; | |
} | |
&::before { | |
width: 75px; | |
height: 75px; | |
border-radius: 50%; | |
background: #e9cb50; | |
inset: 7.5px; | |
box-shadow: | |
0 0 5px #333, | |
inset 2px 2px 3px #f8f4e4, | |
inset -2px -2px 3px #665613; | |
z-index: 1; | |
} | |
&::after { | |
width: 70px; | |
height: 70px; | |
inset: 10px; | |
border-radius: 50%; | |
box-shadow: | |
10px 60px 0 10px #fff, | |
65px 60px 0 5px #fff, | |
95px 70px 0 10px #fff, | |
135px 45px 0 5px #fff, | |
170px 35px 0 10px #fff, | |
195px -5px 0 10px #fff, | |
-10px 0 0 50px rgba(255, 255, 255, .2), | |
15px 0 0 50px rgba(255, 255, 255, .15), | |
40px 0 0 50px rgba(255, 255, 255, .21), | |
10px 40px 0 10px #abc1d9, | |
70px 35px 0 10px #abc1d9, | |
95px 40px 0 10px #abc1d9, | |
135px 20px 0 10px #abc1d9, | |
155px 15px 0 10px #abc1d9, | |
190px -20px 0 10px #abc1d9; | |
} | |
} | |
div:hover::before {filter: contrast(90%) brightness(110%); | |
scale: 1.05; | |
} | |
div.active { | |
background-position: 0 0; | |
&::before { | |
translate: 130px; | |
background: | |
radial-gradient(circle at 50% 20px, #939aa5, #939aa5 6.5px, transparent 7px, transparent), | |
radial-gradient(circle at 35% 45px, #939aa5, #939aa5 11.5px, transparent 12px, transparent), | |
radial-gradient(circle at 72% 50px, #939aa5, #939aa5 8.5px, transparent 9px, transparent), | |
radial-gradient(#cbcdda, #cbcdda); | |
} | |
&::after {transform: translate(130px); | |
box-shadow: | |
10px 60px 0 10px transparent, | |
65px 60px 0 5px transparent, | |
95px 70px 0 10px transparent, | |
135px 45px 0 5px transparent, | |
170px 35px 0 10px transparent, | |
195px -5px 0 10px transparent, | |
10px 0 0 50px rgba(255, 255, 255, .2), | |
-15px 0 0 50px rgba(255, 255, 255, .15), | |
-40px 0 0 50px rgba(255, 255, 255, .21), | |
10px 40px 0 10px transparent, | |
70px 35px 0 10px transparent, | |
95px 40px 0 10px transparent, | |
135px 20px 0 10px transparent, | |
155px 15px 0 10px transparent, | |
190px -20px 0 10px transparent; | |
} | |
} |
const btn = document.querySelector('#g-btn'); | |
btn.addEventListener('click', (e) => {btn.setAttribute('class', btn.getAttribute("class") === "active" ? "":"active"); | |
}); |
来看看最终成果:
是不是基本上还原了原成果?这里咱们仅仅应用了一个标签,外围配合了 box-shadow
以及背景突变实现了整个按钮成果。
残缺的代码,你能够戳这里 CodePen Demo — Single Div BTN Toggle Effect
总结一下
好了,本文到此结束,心愿对你有帮忙 :)
更多精彩 CSS 技术文章汇总在我的 Github — iCSS,继续更新,欢送点个 star 订阅珍藏。
如果还有什么疑难或者倡议,能够多多交换,原创文章,文笔无限,满腹经纶,文中若有不正之处,万望告知。