Home > Article > Web Front-end > Research thoughts on CSS reflection
Original address: https://css-tricks.com/state-css-reflections
Translator: nzbin
Friendly reminder: Due to the compatibility of the demo, Firefox is recommended. The article is long, complex and somewhat difficult. Due to my limited knowledge and lack of time, the translated content may be inappropriate and obscure. Everyone is welcome to make corrections.
I recently saw this loader on codePen, a 3D rotating vertical bar with gradient reflection made in pure CSS. It is made by creating an element for each vertical bar, then duplicating each element to create a reflection, and finally adding a gradient background to the reflection to create a fade effect. It sounds a bit complicated, and creating a fade effect with a gradient background won't work on a non-solid color background. Is there a better CSS method?
The answer is ‘yes’ and ‘no’. ‘Yes’ because there are ways to do it, ‘No’ because some methods don’t exist yet. Sadly, this code can only be compressed a little with the preprocessor (which mainly implements the compression function through loops). If we don't want to use canvas and want to be compatible with the current versions of major browsers, the method of copying vertical bars to create reflections and creating fade effects through gradient backgrounds is still the best method.
This article explores existing methods of making reflections, gives examples of possible solutions, cross-browser issues, and some of my thoughts.
First we create a .loader element and create 10 .bar elements
inside it<span style="color: #0000ff;"><</span><span style="color: #800000;">div </span><span style="color: #ff0000;">class</span><span style="color: #0000ff;">='loader'</span><span style="color: #0000ff;">></span> <span style="color: #0000ff;"><</span><span style="color: #800000;">div </span><span style="color: #ff0000;">class</span><span style="color: #0000ff;">='bar'</span><span style="color: #0000ff;">></</span><span style="color: #800000;">div</span><span style="color: #0000ff;">></span> <span style="color: #008000;"><!--</span><span style="color: #008000;"> repeat to create 9 more bars </span><span style="color: #008000;">--></span> <span style="color: #0000ff;"></</span><span style="color: #800000;">div</span><span style="color: #0000ff;">></span>
Writing the same thing many times is a pain, so using a preprocessor in this case makes it easy. We use the Haml template here, but some people may choose other templates.
<span style="color: #000000;">.loader - 10.times do .bar</span>
We put all elements in the middle of the view through absolute positioning. Most of the time, we use top: 50% , but now, using bottom: 50% will make the later operations easier.
<span style="color: #800000;">div </span>{<span style="color: #ff0000;"> position</span>:<span style="color: #0000ff;"> absolute</span>;<span style="color: #ff0000;"> bottom</span>:<span style="color: #0000ff;"> 50%</span>;<span style="color: #ff0000;"> left</span>:<span style="color: #0000ff;"> 50%</span>; }
We set width and height for the vertical bar, and in order to see it, set another background
<span style="color: #800000;">$bar-w: 1.25em; $bar-h: 5 * $bar-w; .bar </span>{<span style="color: #ff0000;"> width</span>:<span style="color: #0000ff;"> $bar-w</span>;<span style="color: #ff0000;"> <br> height</span>:<span style="color: #0000ff;"> $bar-h</span>;<span style="color: #ff0000;"> background</span>:<span style="color: #0000ff;"> currentColor</span>; }
我们希望竖条的底部贴合视图的水平中心线。设置 bottom: 50% 已经可以了。
现在所有的竖条都重合在一起,它们的左边贴在垂直中心线上,底部贴在水平中心线上。
See the Pen bar loader 1.1 creating the bars by Zongbin Niu (@nzbin) on CodePen.
我们需要让最左边的竖条和最右边的竖条到垂直中心线的距离相等。这个距离就是竖条数量( $n )的一半乘以竖条的宽度( $bar-w )。原始demo用的是普通的CSS,我们会使用Sass来减少代码量。
See the Pen initial (stacked) vs. final (distributed) by Zongbin Niu (@nzbin) on CodePen.
这意味着,从竖条的起始位置开始,我们需要将第一个竖条向左移动 .5 * $n * $bar-w 。左侧是x轴的负方向,需要在前面加负号。所以第一个竖条的 margin-left 就是 -.5 * $n * $bar-w 。
第二个竖条(以0为基数,索引值是1)就是向右(x轴的正方向)移动 1 个竖条的宽度。所以第二个竖条的 margin-left 就是 -.5 * $n * $bar-w + $bar-w 。
第三个竖条(以0为基数,索引值是2)就是向右(x轴的正方向)移动 2 个竖条的宽度。所以第三个竖条的 margin-left 就是 -.5 * $n * $bar-w + 2 * $bar-w 。
最后一个(以0为基数,索引值是 $n - 1
)就是向右(x轴的正方向)移动 $n - 1 个竖条的宽度。所以最后一个竖条 margin-left 就是 -.5 * $n * $bar-w + ($n - 1) * $bar-w 。
See the Pen bar distribution by Zongbin Niu (@nzbin) on CodePen.
通常情况下,如果我们认为当前竖条的索引值是 $i (以0为基数),那么 $i 竖条的 margin-left 就是 -.5 * $n * $bar-w + $i * $bar-w ,可以简化为 ($i - .5 * $n) * $bar-w 。
这就允许我们使用Sass的循环来定位竖条。
<span style="color: #800000;">$n: 10; @for $i from 0 to $n </span>{<span style="color: #ff0000;"> .bar</span>:<span style="color: #0000ff;">nth-child(#{$i + 1</span>}<span style="color: #800000;">) </span>{<span style="color: #ff0000;"> margin-left</span>:<span style="color: #0000ff;"> ($i - .5 * $n) * $bar-w</span>; }<span style="color: #800000;"> }</span>
为了看清楚竖条的边界,我们给它一个 box-shadow 。
See the Pen bar loader 1.2 positioning the bars by Zongbin Niu (@nzbin) on CodePen.
竖条的背景色是从最左边的深蓝色( #1e3f57 )过渡到最右边的浅蓝色( #63a6c1 )。这听上去很像 the Sass mix()
function 所做的。第一个参数是浅蓝色,第二个参数是深蓝色,第三个参数(称作相对权重)表示将多少(百分比表示)浅蓝色混合进去。
对于第一个竖条,这个数量就是 0% ( 0% 数量的浅蓝色),混合结果就是深蓝色。
对于最后一个竖条,这个数量是 100% ( 100% 数量的浅蓝色,也就是 0% 数量的深蓝色),得到的背景色就是浅蓝色。
对于剩下的竖条,我们需要平均分布的中间值。如果我们有 $n 个竖条,第一个竖条在 0% 的位置,最后一个竖条在 100% 的位置,那么我们需要在两者之间平分成 $n-1 个区间。
See the Pen distribute n points on 100% interval (interactive) by Zongbin Niu (@nzbin) on CodePen.
一般来说,索引值为 $i 的竖条的相对权重是 $i * 100% / ($n - 1) ,这意味着我们要添加如下代码:
<span style="color: #800000;">$c: #63a6c1 #1e3f57; // 1st = light 2nd = dark @for $i from 0 to $n </span>{<span style="color: #ff0000;"> // list of mix() arguments for current bar $args</span>:<span style="color: #0000ff;"> append($c, $i * 100% / ($n - 1))</span>;<span style="color: #ff0000;"> .bar</span>:<span style="color: #0000ff;">nth-child(#{$i + 1</span>}<span style="color: #800000;">) </span>{<span style="color: #ff0000;"> background</span>:<span style="color: #0000ff;"> mix($args...)</span>; }<span style="color: #800000;"> }</span>
现在这些竖条看起来就和原始demo的一样了:
See the Pen bar loader #1.3 shading the bars by Zongbin Niu (@nzbin) on CodePen.
很遗憾,这不是一个标准属性!我不知道为什么这个属性没有标准化。这一属性首次出现在Safari浏览器上时,我还不知道CSS。 但是对于WebKit内核的浏览器,这是一个非常好的实现方法。它做了很多工作。它的使用很简单,即使在不支持该属性的浏览器上,除了不显示反射以外,并没有什么其他影响。
让我们看看它是怎么工作的,它需要三个参数值:
See the Pen how `-webkit-box-reflect` works by Ana Tudor (@thebabydino) on CodePen.
注意:linear-gradient()
可以有更多的颜色断点,也可以用radial-gradient()
替换。
在我们的demo中,我首先想到的就是给 .loader 元素添加这一属性。
<span style="color: #800000;">.loader </span>{<span style="color: #ff0000;"> -webkit-box-reflect</span>:<span style="color: #0000ff;"> below 0 linear-gradient(rgba(#fff), rgba(#fff, .7))</span>; }
但是在WebKit浏览器中测试时,我们并没有看到反射。
See the Pen bar loader 2.1.1 -webkit-box-reflect by Zongbin Niu (@nzbin) on CodePen.
这里发生了什么?我们给所有的元素设置了绝对定位,但是并没有设置 .loader 元素的尺寸。所以这是一个宽高都为0的元素。
让我们给 .loader 元素一个明确的尺寸,高度 height 等于竖条的高度 $bar-h ,宽度等于所有竖条的宽度之和 $n * $bar-w 。为了看清元素的边界,我们暂时给它一个 box-shadow 。
<span style="color: #800000;">$loader-w: $n * $bar-w; .loader </span>{<span style="color: #ff0000;"> width</span>:<span style="color: #0000ff;"> $loader-w</span>;<span style="color: #ff0000;"> height</span>:<span style="color: #0000ff;"> $bar-h</span>;<span style="color: #ff0000;"> box-shadow</span>:<span style="color: #0000ff;"> 0 0 0 1px red</span>; }
我之所以用 box-shadow 而不用 outline 是因为如果子元素溢出父元素,在不同的浏览器上使用 outline
突出物体的效果是不一样的。
outline属性在WebKit浏览器中的对比。Edge(上)vs. Firefox(下)
添加以上代码后的结果可以在WebKit浏览器中看到:
See the Pen bar loader 2.1.2 explicitly sizing the loader by Zongbin Niu (@nzbin) on CodePen.
如果你用的不是WebKit浏览器,看下面的图片,就是这个样子:
现在我们可以看到loader元素的边界和倒影,但是位置不正确。我们希望loader元素在水平中间的位置,所以把它向左移动宽度的一半。我们也希望子元素的底部与父元素的底部贴合,所以设置子元素 bottom: 0 。
<span style="color: #800000;">.loader </span>{<span style="color: #ff0000;"> margin-left</span>:<span style="color: #0000ff;"> -.5 * $loader-w</span>; }<span style="color: #800000;"> .bar </span>{<span style="color: #ff0000;"> bottom</span>:<span style="color: #0000ff;"> 0</span>; }
修正位置之后的样子如下:
See the Pen bar loader 2.1.3 tweaking loader and bar positions by Zongbin Niu (@nzbin) on CodePen.
##用 element() 制作反射
element() 函数(现在仍然有效,必须在火狐浏览器中使用并且添加 -moz- 前缀)给我们提供了一个像真实图片一样可以任意使用的图像值。它需要一个参数值,就是我们希望以 background 还是 border-image 显示的被选元素的 id 。这允许我们做很多事情,比如使用可以控制的图片作为背景。我们也可以在Firefox中制作一个反射元素。
需要着重了解的一点就是 element() 函数不是递归函数,我们不能创建使用元素作为自身背景的图像。这在创建反射的loader元素的伪类上使用是安全的,因此我们不用创建额外的元素。
好吧,让我们看看如何操作。首先,我们给 .loader 元素一个 id 。转到样式表,我们从适用WebKit浏览器的CSS着手。我们在loader元素上添加一个 ::after 伪类
<span style="color: #800000;">.loader::after </span>{<span style="color: #ff0000;"> position</span>:<span style="color: #0000ff;"> absolute</span>;<span style="color: #ff0000;"> top</span>:<span style="color: #0000ff;"> 0</span>;<span style="color: #ff0000;"> right</span>:<span style="color: #0000ff;"> 0</span>;<span style="color: #ff0000;"> bottom</span>:<span style="color: #0000ff;"> 0</span>;<span style="color: #ff0000;"> left</span>:<span style="color: #0000ff;"> 0</span>;<span style="color: #ff0000;"> box-shadow</span>:<span style="color: #0000ff;"> 0 0 0 2px currentColor</span>;<span style="color: #ff0000;"> color</span>:<span style="color: #0000ff;"> crimson</span>;<span style="color: #ff0000;"> content</span>:<span style="color: #0000ff;"> 'REFLECTION'</span>; }
为了在最终效果中看清伪类的边界和方向,我们设置了一些暂时性的样式,我们想让它是颠倒的。
See the Pen bar loader 2.2.1 adding a pseudo by Zongbin Niu (@nzbin) on CodePen.
现在我们需要以底边为基准把 ::after 伪类镜像过来。为了做到这一点,我们使用 scaleY() 属性并且选择合适的 transform-origin 。
以下的可交互demo说明了包含多个缩放因子以及变换中心的定向缩放是如何工作的:
See the Pen how CSS scaling w.r.t. various origins works by Ana Tudor (@thebabydino) on CodePen.
注意:缩放因子的数值和变换中心可以超出demo中规定的限制。
在我们的例子中,我们需要 scaleY(-1) 并且 transform-origin 在 ::after 伪类的底边上。
使用scaleY(-1)
和一个合适的 transform-origin
来镜像元素
我们把这些设置添加到代码中,并且用 element () 函数把 ::after 伪类的背景设置为 #loader
<span style="color: #800000;">.loader::after </span>{<span style="color: #ff0000;"> transform-origin</span>:<span style="color: #0000ff;"> 0 100%</span>;<span style="color: #ff0000;"> transform</span>:<span style="color: #0000ff;"> scaleY(-1)</span>;<span style="color: #ff0000;"> background</span>:<span style="color: #0000ff;"> -moz-element(#loader)</span>; }
注意:由于特别的原因我们使用 .loader 作为选择器并且由于element() 函数参数的需要我们设置它的 id 为 #loader 。
添加以上代码后的效果如下所示(只在Firefox浏览器中有效)
See the Pen bar loader 2.2.2 -moz-element() for reflecting pseudo by Zongbin Niu (@nzbin) on CodePen.
对于在其他浏览器阅读这篇文章的朋友,以下是截图
在Firefox中使用 element() 制作的反射效果
##用 mask 制作渐变
我们使用和WebKit情况下一样的方法给反射添加渐变。在WebKit的情况下,遮罩是 -webkit-box-reflect 属性的一部分。而现在,我们讨论CSS的 mask 属性,它需要引用SVG作为值。
<span style="color: #800000;">mask: url(#fader);</span>
#fader 元素是一个包含长方形的SVG mask 元素。
<span style="color: #0000ff;"><</span><span style="color: #800000;">svg</span><span style="color: #0000ff;">></span> <span style="color: #0000ff;"><</span><span style="color: #800000;">mask </span><span style="color: #ff0000;">id</span><span style="color: #0000ff;">='fader' </span><span style="color: #ff0000;">maskContentUnits</span><span style="color: #0000ff;">='objectBoundingBox'</span><span style="color: #0000ff;">></span> <span style="color: #0000ff;"><</span><span style="color: #800000;">rect </span><span style="color: #ff0000;">width</span><span style="color: #0000ff;">='1' </span><span style="color: #ff0000;">height</span><span style="color: #0000ff;">='1'</span><span style="color: #0000ff;">/></span> <span style="color: #0000ff;"></</span><span style="color: #800000;">mask</span><span style="color: #0000ff;">></span> <span style="color: #0000ff;"></</span><span style="color: #800000;">svg</span><span style="color: #0000ff;">></span>
我们可以用Haml模板压缩一下
<span style="color: #000000;">%svg %mask#fader(maskContentUnits='objectBoundingBox') %rect(width='1' height='1')</span>
但是,如果我们加上以上代码,我们的反射倒影消失了,在Firefox中查看如下demo
See the Pen bar loader 2.2.3 adding a SVG mask by Zongbin Niu (@nzbin) on CodePen.
这是因为,默认情况下,SVG图形会有一个纯黑色的 fill ,完全不透明,但是,我们的 遮罩 默认是有透明度的。因此为了实现反射渐变的效果我们需要给长方形一个 fill (需引入SVG linearGradient )
%rect(width='1' height='1' fill='url(#grad)')
一个SVG linearGradient 由定义的两个点 x1 , y1 , x2 , y2 组成。 x1 和 y1 是渐变线的起始点(0%)坐标,而 x2 和 y2 是这条线的终点(100%)坐标。如果这些数值是空的,默认设为 0% , 0% , 100% , 0% 。这些数值描绘了从指定元素(由于 gradientUnits 的默认值是 objectBoundingBox )的左上( 0% 0% )到右上( 100% 0% )的一条线。这意味着,默认情况下,渐变是从左到右。
但是在我们的例子中,我们希望渐变从上到下,所以我们将 x2 的值从 100% 设置为 0% 并且将 y2 的值从 0% 设置为 100% 。这使得指定元素的渐变向量从左上角( 0% 0% )指向左下角( 0% 100% )。
%linearGradient#grad(x2='0%' y2='100%')
在 linearGradient 元素之内,我们至少需要两个 stop 元素。其中有三个特定的属性, 偏移值 , 颜色断点 , 透明度断点。
我们需要记住我们应用渐变遮罩的伪类已经通过 scaleY(-1) 属性镜像过来了,这意味着渐变遮罩的底部在视觉上是顶端。因此我们的渐变是从顶部(视觉下端)的完全透明到底部(视觉上端)的 .7 。
因为我们的渐变从上到下,所以第一个 stop 是完全透明的。
<span style="color: #000000;">%linearGradient#grad(x2='0%' y2='100%') %stop(offset='0' stop-color='#fff' stop-opacity='0') %stop(offset='1' stop-color='#fff' stop-opacity='.7')</span>
添加线性渐变之后,在Firefox中就是我们想要的结果了
实时效果如下:
See the Pen bar loader 2.2.4 linearGradient it by Zongbin Niu (@nzbin) on CodePen.
在我们的例子中,因为我们的遮罩渐变是垂直的所以看起来很简单。但是如果我们的渐变不是垂直或者水平或者从一个角到另一个角怎么办?如果我们想要一个特定角度的渐变怎么办?
SVG中有一个 gradientTransform 属性,它可以通过指定 x1 , y1 , x2 , y2 来旋转渐变线。有人可能会认为这是制作具有特定角度的CSS渐变的简单方法。但是,并没有想象的那么简单!
想一想从金色到深红色渐变的例子。为了看得清楚一点,我们在两者之间50%的位置设置一个剧烈的过渡。首先我们将这个渐变的CSS角度设置为 0deg 。这意味着渐变会从底部(金色)过渡到顶部(深红色)。创建这个渐变的CSS如下:
<span style="color: #800000;">background-image: linear-gradient(0deg, #e18728 50%, #d14730 0);</span>
如果你还不明白CSS线性渐变的工作原理,你可以看一下Patrick Brosset 做的这个 优秀作品。
我们看到的结果如下:
See the Pen CSS linear-gradient at 0deg with sharp stop at 50% by Ana Tudor (@thebabydino) on CodePen.
为了制作SVG的渐变,我们设置 y1 为 100% y2 为 0% 以及把 x1 和 x2 设置成相同的数值(简单起见设置为0)。这意味着渐变线从底部垂直上升到顶部。我们同时把断点的偏移值设置为50%。
<span style="color: #000000;">linearGradient#g(y1='100%' x2='0%' y2='0%' gradientTransform='rotate(0 .5 .5)') stop(offset='50%' stop-color='#e18728') stop(offset='50%' stop-color='#d14730')</span>
编辑注:我问Ana为什么她现在要使用Jade模板。她说:我起初使用Haml模板是因为我想避免引入我不需要的循环变量,而之后使用Jade模板是因为它允许变量和计算。
这个渐变还没有旋转,因为 gradientTransform 的值是 rotate(0 .5 .5) 。其中后两个数值表示渐变旋转的坐标。其中 0 0 表示左上角, 1 1 表示右下角, .5 .5 表示中心。
实时效果如下:
See the Pen SVG linearGradient bottom to top rotated by 0deg with sharp stop at 50% by Ana Tudor (@thebabydino) on CodePen.
如果我们希望渐变从左到右,在CSS渐变中,我们把角度从 0deg 设置为 90deg
<span style="color: #800000;">background-image: linear-gradient(90deg, #e18728 50%, #d14730 0);</span>
See the Pen CSS linear-gradient at 90deg with sharp stop at 50% by Ana Tudor (@thebabydino) on CodePen.
为了在SVG渐变中得到同样的结果,我们将 gradientTransform 的值设置为 rotate(90 .5 .5) 。
<span style="color: #000000;">linearGradient#g(y1='100%' x2='0%' y2='0%' gradientTransform='rotate(90 .5 .5)') // same stops as before</span>
See the Pen SVG linearGradient bottom to top rotated by 90deg with sharp stop at 50% by Ana Tudor (@thebabydino) on CodePen.
到目前为止,一切正常。用SVG来代替CSS渐变并没有遇到太多问题。让我们尝试一下其他的角度。在下面的可交互demo中,左侧是一个CSS渐变,右边是一个SVG渐变。紫色的线是渐变线,它与金色和深红色的交界线是垂直的。拖拽滑块可以同时改变CSS和SVG的渐变角度。我们会看到一些错误:有些数值不是 90deg 的倍数。
See the Pen CSS vs. SVG gradient, same angle (interactive, responsive) by Ana Tudor (@thebabydino) on CodePen.
如以上demo所示,有些数值不是 90deg 的倍数,我们无法得到相同的结果。只有当我们设置渐变的元素是正方形时结果是相同的。这意味着我们可以给一个更大的正方形元素设置渐变,然后裁剪成实际的形状。然而做这些工作会让 element() 和 mask 来创建渐变倒影的方法更加复杂。
令人遗憾的是,以上提到的两种方法在Edge中都没有用。因此既能在Edge中运行又无需手动复制每个竖条的仅有的方法就是,放下前面的工作重新制作SVG加载器。这中方法具有跨浏览器的优势。
总的来说,我们创建一个带有 viewBox 的SVG元素,以便把 0 0 点放在中间。我们定义一个竖条,它的底边在x轴上,左边在y轴上。然后我们在 #loader 群组中根据需要复制(通过SVG use 元素)多次。我们如之前一样放置这些竖条的位置。
<span style="color: #000000;">- var bar_w = 125, bar_h = 5 * bar_w; - var n = 10; - var vb_w = n * bar_w; - var vb_h = 2 * bar_h; - var vb_x = -.5 * vb_w, vb_y = -.5 * vb_h; svg(viewBox=[vb_x, vb_y, vb_w, vb_h].join(' ')) defs rect#bar(y=-bar_h width=bar_w height=bar_h) g#loader - for(var i = 0; i </span><span style="color: #0000ff;"><</span><span style="color: #800000;"> n</span><span style="color: #ff0000;">; i++) { - var x </span><span style="color: #0000ff;">= (i </span><span style="color: #ff0000;">- .5 * n) * bar_w; use(xlink:href</span><span style="color: #0000ff;">='#bar' </span><span style="color: #ff0000;">x</span><span style="color: #0000ff;">=x) </span><span style="color: #ff0000;">- }</span>
以上代码的效果如下:
See the Pen bar loader 2.3.1 creating and positioning SVG bars by Zongbin Niu (@nzbin) on CodePen.
现在我们已经创建了所有竖条,我们想把svg元素的位置调整的更准确而且我们使用flexbox属性。同时我们也和之前一样给竖条添加渐变色。我们用Sass做这些事情
<span style="color: #800000;">$n: 10; $c: #63a6c1 #1e3f57; $bar-w: 1.25em; $bar-h: 5 * $bar-w; $loader-w: $n * $bar-w; $loader-h: 2 * $bar-h; body </span>{<span style="color: #ff0000;"> display</span>:<span style="color: #0000ff;"> flex</span>;<span style="color: #ff0000;"> justify-content</span>:<span style="color: #0000ff;"> center</span>;<span style="color: #ff0000;"> margin</span>:<span style="color: #0000ff;"> 0</span>;<span style="color: #ff0000;"> height</span>:<span style="color: #0000ff;"> 100vh</span>; }<span style="color: #800000;"> svg </span>{<span style="color: #ff0000;"> align-self</span>:<span style="color: #0000ff;"> center</span>;<span style="color: #ff0000;"> width</span>:<span style="color: #0000ff;"> $loader-w</span>;<span style="color: #ff0000;"> height</span>:<span style="color: #0000ff;"> $loader-h</span>; }<span style="color: #800000;"> @for $i from 0 to $n </span>{<span style="color: #ff0000;"> $args</span>:<span style="color: #0000ff;"> append($c, $i * 100%/($n - 1))</span>;<span style="color: #ff0000;"> [id='loader'] use</span>:<span style="color: #0000ff;">nth-child(#{$i + 1</span>}<span style="color: #800000;">) </span>{<span style="color: #ff0000;"> fill</span>:<span style="color: #0000ff;"> mix($args...)</span>; }<span style="color: #800000;"> }</span>
添加以上代码后的效果如下:
See the Pen bar loader 2.3.2 sizing + positioning the SVG & shading the bars by Zongbin Niu (@nzbin) on CodePen.
我们复制 #loader 群组(再次使用 use 元素)。我们通过 scale(1 -1) 方法镜像克隆体并且给它添加一个遮罩,和我们之前给伪类元素设置的一样。默认情况下,SVG元素相对于SVG画布的 0 0 点缩放,这个点正好位于loader元素的底边上,可以很完美的将loader元素镜像过来,我们不用设置 transform-origin 。
use(xlink:href='#loader' transform='scale(1 -1)')
我们使用 transform 属性代替CSS变换,因为CSS变换在Edge中不支持。
现在我们有了反射倒影,如下所示:
See the Pen bar loader 2.3.3 getting the reflection by Zongbin Niu (@nzbin) on CodePen.
最后一步就是用 mask 给反射添加渐变。它的方法及代码和之前都是一样的,我们不再赘述。所有的代码都在下面的Pen中
See the Pen bar loader 2.3.4 fading the reflection by Zongbin Niu (@nzbin) on CodePen.
原始案例中的CSS动画很简单,就是用3D方式旋转竖条:
<span style="color: #800000;">@keyframes bar </span>{<span style="color: #ff0000;"> 0% { transform</span>:<span style="color: #0000ff;"> rotate(-.5turn) rotateX(-1turn)</span>; }<span style="color: #800000;"> 75%, 100% </span>{<span style="color: #ff0000;"> transform</span>:<span style="color: #0000ff;"> none</span>; }<span style="color: #800000;"> }</span>
所有的竖条都是同样的动画:
<span style="color: #800000;">animation: bar 3s cubic-bezier(.81, .04, .4, .7) infinite;</span>
我们只是给循环的竖条添加了不同的延迟时间。
<span style="color: #800000;">animation-delay: $i*50ms;</span>
因为我们希望旋转的竖条具有3D效果,所有我们给loader元素添加一个 perspective 属性
但是使用 -webkit-box-reflect 方法后和预期的一样只能在WebKit浏览器中执行。
在Chrome浏览器中使用 -webkit-box-reflect 属性后的最终结果
我们同时添加了一张背景图片来看一下它的表现效果。只能在WebKit浏览器中预览的效果如下:
See the Pen bar loader 3.1 animating the bars by Zongbin Niu (@nzbin) on CodePen.
我们也尝试在Firefox中执行动画。但是,如果我们把动画添加到之前在Firefox中运行良好的代码中,好像出现了一些问题。
在Firefox中使用element()和mask方法做的动画雏形
这里有一点问题,下面的demo可以在Firefox中实时检测:
See the Pen bar loader 3.2.1 adding animation by Zongbin Niu (@nzbin) on CodePen.
第一个问题就是反射在伪类的边界处被切断。我们可以通过增加loader元素的尺寸来修复这一问题(伪类元素不受影响):
<span style="color: #800000;">$loader-w: ($n + 1) * $bar-w + $bar-h;</span>
但是我们对于其余的两个问题就束手无策了。当竖条进行3D旋转时,反射无法平滑的渲染更新;以及 perspective 属性导致了竖条的消失。
添加perspective属性的结果 没有添加perspective属性的结果
以下是实时的显示结果:
See the Pen bar loader 3.2.2 tweaks by Zongbin Niu (@nzbin) on CodePen.
全部都用SVG的方案怎么样?很不幸,上面的例子中,我们只用CSS的3D变化制作动画。在Edge中,SVG元素不支持CSS的变换属性,所以我们之前在创建倒影时使用了SVG的 transform 属性。但是 transform 属性是严格的2D模式,我们只能使用JavaScript添加动画。
所以就目前来看,想要制作一个兼容所有浏览器并且不用复制每一个竖条的加载动画是不可能了。我们现在能做的就是创建两个loader元素,每一个都有相同数量的竖条。
<span style="color: #000000;">- 2.times do .loader - 10.times do .bar</span>
竖条的样式和之前一样,我们使用 scale(-1) 来镜像第二个loader元素。
<span style="color: #800000;">.loader:nth-child(2) </span>{<span style="color: #ff0000;"> transform</span>:<span style="color: #0000ff;"> scaleY(-1)</span>; }
我们添加竖条动画后得到如下结果:
See the Pen bar loader 3.3.1 reflection via duplication by Zongbin Niu (@nzbin) on CodePen.
现在我们需要给反射添加渐变。遗憾的是,我们不能在第二个loader元素上使用 mask ,因为它只在跨浏览器的SVG元素上有效。Edge目前还不支持HTML元素的遮罩效果,但是你可以给官方提建议。
我们只能在第二个loader元素上添加渐变背景。这样一来我们就不能使用图片背景了。渐变背景只在纯色背景或者有限的情况下才有效。我们在第二个loader元素的 ::after 上添加渐变背景并且设置的大一点,这样就不会挡住旋转的竖条。
<span style="color: #800000;">$bgc: #eee; $cover-h: $bar-w + $bar-h; $cover-w: $n * $bar-w + $cover-h; html </span>{<span style="color: #ff0000;"> background</span>:<span style="color: #0000ff;"> $bgc</span>; }<span style="color: #800000;"> .loader:nth-child(2)::after </span>{<span style="color: #ff0000;"> margin-left</span>:<span style="color: #0000ff;"> -.5 * $cover-w</span>;<span style="color: #ff0000;"> width</span>:<span style="color: #0000ff;"> $cover-w</span>;<span style="color: #ff0000;"> height</span>:<span style="color: #0000ff;"> $cover-h</span>;<span style="color: #ff0000;"> background</span>:<span style="color: #0000ff;"> linear-gradient($bgc $bar-w, rgba($bgc, .3))</span>;<span style="color: #ff0000;"> content</span>:<span style="color: #0000ff;"> ''</span>; }
The final result is as follows:
See the Pen bar loader 3.3.2 emulate fading with cover by Zongbin Niu (@nzbin) on CodePen.
We need a better cross-browser solution. I believe making an object's reflection does not require copying all the child elements like we did in this example. In order to create a gradient reflection that can be placed on a background image, we cannot replace the SVG solution (which has its own problems).
Which solution is better? -webkit-box-reflect or element() + mask? I don't know either. I personally like to use both.
While using the :reflection
pseudo-element seemed reasonable, I was convinced that I didn't want to use an extra element to create reflection. But now there are things I like more than not having to insert extra elements. Using element() you can freely create multiple reflections in different directions, and transform the reflections in different ways, such as 3D rotation or tilt. That's exactly why I love it. And using SVG for masking means we can apply more complex masks on reflections and get cooler effects.
On the other hand, with greater ability comes greater responsibility. Maybe you don’t have time to get into the intricate details behind powerful features. Sometimes you just want a simple way to get a simple result.