CSS秀起来真没JS什么事
发现了一个纯CSS实现,具有渐变倒影和3D旋转效果的栅栏动画,他的实现方式是:利用10个<div>
元素创建10个栅条,接着再复制整份<div>
元素,并创建一个渐变遮罩形成渐变效果,以此作为栅栏的倒影。
这听起来有点像用左脚的脚趾去抓你的右耳背部!更不用说这种渐变遮罩的方式根本不适用于非单一颜色的背景。难道没有基于CSS的更好的方法吗?
答案是有的,我们有更好的方式可以实现这种效果!但遗憾的是,这种方式的兼容性不够好,如果我们不想使用canvas
,但又希望能够兼容所有的主浏览器,上述的方法无疑还是最好的。
本文将探讨今天我们创建倒影的所有可行选择,阐述这些“相似”的解决方案,在跨浏览器问题上导致的痛苦,最后就这些问题该怎么做讨论下我的想法。
基础代码
在讨论倒影之前,我们先看看如何创建、定位和着色栅栏条,因为这部分对所有的浏览器都是通用的。
首先我们创建了一个.loader
包装器并包含10个.bar
元素
<div class='loader'>
<div class='bar'></div>
<!-- 继续创建9条 -->
</div>
web前端开发学习Q-q-u-n: 767273102 ,分享开发工具,零基础,进阶视频教程,希望新手少走弯路
除了直接写HTML外,我们也可以用预处理器,比如Haml:
.loader
- 10.times do
.bar
我们从视口的中间位置开始定位这些元素。通常我们都是使用top: 50%
去居中,但如果我们用bottom: 50%
后面会更方便。
div {
position: absolute;
bottom: 50%; left: 50%;
}
我们给这些栅栏条定义宽度和高度,同时给它一个背景:
$bar-w: 1.25em;
$bar-h: 5 * $bar-w;
.bar {
width: $bar-w; height: $bar-h;
background: currentColor;
}
我们希望栅条的底部边缘可以跟视口的横向中轴线相重叠,通过前面我们设置bottom: 50%
已经实现了这种效果。
此时,我们所有栅条是全部堆叠在一起的,他们的左边缘与视口的纵向中轴线重合,底部边缘则与横向中轴线重叠,!
定位栅栏
我们需要将这些栅栏定位为:第一条栅条左边缘和最后一条栅条右边缘与纵向中轴线之间的距离相同,这个距离总是等于条数($n
)乘以条宽($bar-w
)结果值的一半。最初的演示用的是普通的CSS
,现在我们使用Sass
来简化代码量
特别提醒,条宽$bar-w
中间是连接符,不是减号,避免引起歧义,后续使用$bar_w
替代
这意味着,从所有栅条的位置开始,我们需要把第一个栅条向左移动 0.5 * $n * $bar_w
。左边是x
轴的负方向,这意味着前面需要加一个负号,因此第一个栅条的margin-left
值是 -.5 * $n * $bar_w
。
第二个栅条的位置则是在第一个栅条位置上往右移动一个栅条宽度的距离,那么它的margin-left
值应该是 -.5 * $n * $bar_w + $bar_w
;
同理第三个栅条的margin-left
值为 -.5 * $n * $bar_w + 2 * $bar_w
;
那么最后一个栅条的margin-left
就是:-.5 * $n * $bar-w + ($n - 1) * $bar-w
.
如下图:
我们用代码表述为:
$n: 10;
@for $i from 0 to $n {
.bar:nth-child(#{$i + 1}) {
margin-left: ($i - .5 * $n) * $bar-w;
}
}
我们给它们设置下box-shadow
,这样我们可以清楚地看到一个栅条的结束和下一个栅条的开始:
给栅栏上色
栅栏的背景颜色从最左边的深蓝色(#1e3f57
)过度到最右边的浅蓝色(#63a6c1
),这听起来像是Sass
的mix()
函数做的事情。mix()
的第一个参数是淡蓝色,第二个参数是深蓝色,第三个参数(称为相对权重,以%为单位)是最终混合结果中淡蓝色包含的量。
对于第一个栅条,淡蓝色的量应该为 0% - 0%
,因此最终结果仅为深蓝色;
相同地,对于最后一个栅条,淡蓝色的量应该为100% -100%
,背景色的构成就是淡蓝色;
对于剩下的栅条,我们需要让中间值均匀分布。假设我们有$n
个栅条,第一个是0%
,最后一个是100%
,然后我们需要把它们分成$n - 1
个等距间隔。如下图:
一般来说,编号$i
的栅条的相对权重为: $i * 100% / ($n - 1)
,我们添加如下代码:
$c: #63a6c1 #1e3f57; // 1st = light 2nd = dark
@for $i from 0 to $n {
// list of mix() arguments for current bar
$args: append($c, $i * 100% / ($n - 1));
.bar:nth-child(#{$i + 1}) {
background: mix($args...);
}
}
现在,这些栅栏看起来跟最初演示的一样:
-webkit-box-reflect
box-reflect
仍然是一个非标准属性,许多主流浏览器尚未对其进行支持,幸运的是,它可以在webkit
浏览器中很好地运行,只需要加上浏览器的私有属性就可以了。
让我们看一下它是怎么工作的,他的值有三部分:
-webkit-box-reflect:none | <direction> <offset>? <mask-box-image>?
-
<direction>
:倒影的方向,可以是below
,left
,above
,right
中的任意值 -
<offset>
:倒影与原像的距离,取值可以是固定像素值或百分比 -
<mask-box-image>
:设置倒影的遮罩效果,可以是背景图片或渐变图像
在我们的例子中,首先想到的是在.loader
中添加这个:
.loader {
-webkit-box-reflect: below 0 linear-gradient(rgba(#fff, 0), rgba(#fff, .7));
}
web前端开发学习Q-q-u-n: 767273102 ,分享开发工具,零基础,进阶视频教程,希望新手少走弯路
其次我们还需要给.loader
设置尺寸,因为它包含的栅栏都是绝对定位的,此时.loader
的实际尺寸是0px X 0px
。我们将其宽度定义为所有栅栏宽度之和,高度则与栅栏高度一致:
$loader-w: $n * $bar-w;
.loader {
width: $loader-w; height: $bar-h;
box-shadow: 0 0 0 1px red;
}
添加上面的代码后,在webkit浏览器中会看到下面的效果:在线查看
我们已经可以看到加载器的边界和一些倒影,但它们的位置不再正确了。我们需要将加载器左移居中,同时让栅栏底部与它们父元素的底部重合:
.loader { margin-left: -.5 * $loader-w; }
.bar { bottom: 0; }
这样就解决了定位的问题,效果如:
// 这里省略一堆关于Firefox的兼容方案:element()+svg的mask ......
动画
最初在CodePen实现的动画非常简单,只是一个3D旋转栅栏:
@keyframes bar {
0% {
transform: rotate(-.5turn) rotateX(-1turn);
}
75%, 100% { transform: none; }
}
将动画应用在所有的栅栏上:
animation: bar 3s cubic-bezier(.81, .04, .4, .7) infinite;
接着为每个栅条添加不同的延时:
animation-delay: $i*50ms;
由于我们是3D旋转栅栏,因此我们还需要在loader
元素上添加perspective
属性:.
.loader {
perspective: 62.5em;
}
web前端开发学习Q-q-u-n: 767273102 ,分享开发工具,零基础,进阶视频教程,希望新手少走弯路
但这只在webkit
浏览器中使用-webkit-box-reflect
时有效。最终实现的效果:
最后一点思考
算了,这段不用翻译了!
大意讲作者对-webkit-box-reflect
和element()+mask
(svg
的mask
标签)解决跨浏览器实现倒影问题上的看法,不影响我们理解box-reflect
的优秀,有兴趣的读者可以去看看原文,感恩~