想必写过 CSS 的同学都用过 box-shadow,它可以给元素设置阴影,增加立体效果。
比如说这样:
但它能做的可不只是阴影,还可以用来做出很多有趣的效果:
比如画蒙娜丽莎:
画星空:
这些效果都是 box-shadow 实现的!
是不是不敢相信?
今天我们就一起研究下 box-shadow 的高阶用法,来实现这些效果吧。
先过一下基础:
box-shadow 基础
box-shadow 可以设置 5 个值:x偏移量 y偏移量 阴影模糊半径 阴影扩散半径 阴影颜色
box-shadow: 2px 2px 2px 1px rgba(0, 0, 0, 0.2);
比如这个案例:
阴影中心点 x 轴偏移了 300px,y 轴偏移了 300px:
那阴影扩散半径是啥意思?
看这张图就明白了:
还有阴影模糊半径:
再来看下这几个值:
box-shadow: 300px 300px 30px 100px blue;
x 轴位移、y 轴位移都是指中心点的位移。阴影的半径就是元素的 width/2 + 扩散半径 + 模糊半径。
而且 box-shadow 可以设置多个,通过逗号分隔,也就是多重阴影。
这样就可以用来做一些有意思的事情了:
比如把 width、height 设置为 0,然后设置多个阴影:
width、height 为 0,模糊半径为 0,扩散半径为 5px,那整个阴影就是一个 10px * 10px 的方块。
这样设置不同位置的 8 个阴影块就是上面的效果。
如果这样的块多了,是不是就类似像素块那样能展示图像了呢?
没错,蒙娜丽莎就是这么画出来的:
box-shadow 画蒙娜丽莎
整体思路上面已经分析出来了,就是通过 box-shadow 多重阴影设置每个像素块的颜色和位置:
这里 width、height 为 0,模糊半径为 0,扩散半径为 2px,那总体宽高就是 4px。
而每个块的中心点相距 5px,所以会留下一个间距。
我们试一下:
确实,通过这样一个个阴影块就能把蒙娜丽莎画出来。
然后我们把间距去掉,也就是把扩散半径设置大一点:
现在就连在一起了:
但这样看起来像素感太强了,我们给它加点模糊半径,比如设置个 4px:
这样好多了:
至此,神秘的蒙娜丽莎的微笑就完成了,只用到了 box-shadow 和一个 div!
这里是画了蒙娜丽莎,其实各种图片都能画,只要拿到像素数据就行,这个可以通过 canvas 的 getImageData 来拿到。
这里是一个个排列的阴影块,那如果随机打算这些阴影块,是不是可以做一些粒子效果呢?
比如星空。
我们来试一下:
box-shadow 画星空
星空大概是这样的:
画完蒙娜丽莎,我们知道了可以通过 box-shadow 多重阴影画出任意多个方块。不过那时是顺序排列的,现在我们希望把位置打乱,增加一些随机效果。
怎么随机呢?
css 里确实设置不了随机的东西,但是可以通过预处理器来做到,比如 sass。
我们通过 sass 来一个写循环生成随机 box-shadow 的函数:
@function multiple-box-shadow($n) {$value: '#{random(2000)}px #{random(2000)}px #FFF';@for $i from 2 through $n {$value: '#{$value} , #{random(2000)}px #{random(2000)}px #FFF';}@return unquote($value);
}
这段代码是通过 @function 声明 sass 的函数,作用是传入 n,生成随机位置的白色 n 个阴影块。
声明了一个 $value 的变量作为初始值,然后循环生成 box-shadow 的值加到 $value 里,最后返回 $value,但要用 unquote 把引号去掉。
循环使用 @for $i from xx through yy 的语法,每次循环调用 random 函数生成 2000 内的随机整数。
这样就完成了 n 个随机位置的 box-shadow 的生成逻辑。
然后我们用一下它:
在 html 里放个 div:
<div id='stars'></div>
给它设置宽高和 box-shadow:
#stars {width: 1px;height: 1px;box-shadow: multiple-box-shadow(700);
}
这里就没有设置扩散半径和模糊半径了,所以阴影块大小就是元素的宽高。
效果是这样的:
看下现在的 css:
确实有随机生成的 700 个 box-shadow 值。
这就是预处理器的作用。
当然,这种逻辑也可以用 JS 来写,运行时生成随机 box-shadow,但是渲染速度上会比 sass 编译期间生成的方案慢很多。
然后我们让它动起来,加上 animation:
#stars {animation: animStar 50s linear infinite;
}
@keyframes animStar{from{transform: translateY(0px)}to { transform: translateY(-2000px)}
}
前面随机生成的 700 个星星的位置就是 0 到 2000px 的,所以这里是从 0 运动到 -2000px。
先把时间改短点,改成 3s 看下效果:
你会发现有段时间下面全是黑的,没有星星,这是为什么呢?
这个很容易想明白:当 translateY 快到 -2000px 的时候,剩下的部分星星不到一屏,其余的位置自然就没有星星了。
怎么解决这个问题呢?
其实这种还是比较经典的 CSS 问题,比如轮播图的无缝滚动也是同种原因。
解决方式就是在后面再接一个一模一样的,然后位移到了 -2000px 的时候,马上定位到 0 重新开始。这样就无缝了。
我们通过伪元素来设置这个:
$shadows-small:multiple-box-shadow(700);
#stars {width: 1px;height: 1px;box-shadow: $shadows-small;animation: animStar 3s linear infinite;&:after {content: " ";position: absolute;top: 2000px;width: 1px;height: 1px;box-shadow: $shadows-small;}
}
注意,这里要保证两次的 box-shadow 是一样的,所以通过一个变量来保存生成的值,两处都引用这个变量。
这样就无缝了:
但现在还是有点假,我们多加两种不同大小不同运动速度的星星:
当然,个数也不一样,越大的越少,分别生成 200 和 100个,动画时长分别设置 100s 和 150s:
<div id='stars2'></div>
<div id='stars3'></div>
$shadows-medium: multiple-box-shadow(200);
$shadows-big:multiple-box-shadow(100);
#stars2 {width: 2px;height: 2px;box-shadow: $shadows-medium;animation: animStar 100s linear infinite;&:after {content: " ";position: absolute;top: 2000px;width: 2px;height: 2px;box-shadow: $shadows-medium;}
}
#stars3 {width: 3px;height: 3px;box-shadow: $shadows-big;animation: animStar 150s linear infinite;&:after {content: " ";position: absolute;top: 2000px;width: 3px;height: 3px;box-shadow: $shadows-big;}
}
看下效果:
星空的感觉是不是就出来了!
不过现在的代码还有点不优雅,star 的样式重复写了 3 次,既然用了 sass,那可以把它抽成一个 mixin 来复用:
@mixin stars($size, $duration, $boxShadow) {width: $size;height: $size;background: transparent;box-shadow: $boxShadow;animation: animStar $duration linear infinite;&:after {content: " ";position: absolute;top: 2000px;width: $size;height: $size;background: transparent;box-shadow: $shadows-small;}
}
三处样式只要 include 这个 mixin,传入参数即可:
#stars {@include stars(1px, 50s, $shadows-small);
}
#stars2 {@include stars(2px, 100s, $shadows-medium);
}
#stars3 {@include stars(3px, 150s, $shadows-big);
}
代码优雅了很多!
全部 scss 代码如下:
@function multiple-box-shadow($n) {$value: '#{random(2000)}px #{random(2000)}px #FFF';@for $i from 2 through $n {$value: '#{$value} , #{random(2000)}px #{random(2000)}px #FFF';}@return unquote($value);
}
$shadows-small:multiple-box-shadow(700);
$shadows-medium: multiple-box-shadow(200);
$shadows-big:multiple-box-shadow(100);
html {height: 100%;background: #000;overflow: hidden;
}
@mixin stars($size, $duration, $boxShadow) {width: $size;height: $size;background: transparent;box-shadow: $boxShadow;animation: animStar $duration linear infinite;&:after {content: " ";position: absolute;top: 2000px;width: $size;height: $size;background: transparent;box-shadow: $shadows-small;}
}
#stars {@include stars(1px, 50s, $shadows-small);
}
#stars2 {@include stars(2px, 100s, $shadows-medium);
}
#stars3 {@include stars(3px, 150s, $shadows-big);
}@keyframes animStar{from{transform: translateY(0px)}to { transform: translateY(-2000px)}
}
总结
box-shadow 我们一般用来做阴影,但其实也可以用来做一些有趣的效果。
阴影块的大小是由元素宽高、扩散半径、模糊半径这些决定的。
通过多重阴影顺序排列阴影块可以达到像素块的效果,画出蒙娜丽莎或者其他任意的图片。
也可以通过 sass 预处理器随机生成不同位置的阴影块来做出粒子效果,比如星空。
除了可以随机生成样式外,还可以通过 sass 的 mixin 来抽离相似的代码,多处复用,让 css 代码更优雅。这就是预处理器的意义。
box-shadow 的高阶玩法,你学会了么?
最后
整理了一套《前端大厂面试宝典》,包含了HTML、CSS、JavaScript、HTTP、TCP协议、浏览器、VUE、React、数据结构和算法,一共201道面试题,并对每个问题作出了回答和解析。
有需要的小伙伴,可以点击文末卡片领取这份文档,无偿分享
部分文档展示:
文章篇幅有限,后面的内容就不一一展示了
有需要的小伙伴,可以点下方卡片免费领取