CSS 的 box-shadow 属性用于在元素的框架周围添加阴影效果。它可以接受多个由逗号分隔的阴影效果,每个阴影效果由以下几部分组成:
以下是一个 box-shadow 的示例:
.box {
box-shadow: 10px 5px 5px black;
}
在这个示例中,阴影向右移动10px,向下移动5px,模糊距离为5px,颜色为黑色。
其阴影效果如下图所示
你也可以指定多个阴影,如下所示:
.box {
box-shadow: 3px 3px 5px #000, -1px -1px 5px red;
}
在这个示例中,定义了两段阴影,一个向下3px,向右3px的黑色阴影;一个向左1px,向上1px的红色阴影。其显示阴影效果如下:
以上阴影都是向外扩散的外阴影,加上inset属性后,阴影向内扩散。
box-shadow: 3px 3px 5px #000, -1px -1px 5px red inset;
其效果如下:
在 CSS 的 box-shadow 属性中,除了blur和spread,其他几个参数应该都很容易理解。这里重点研究下blur和spread参数。blur 和 spread 参数分别控制阴影的模糊程度和大小。
blur:这个值定义了阴影的模糊程度。它的值是一个长度,表示模糊半径,即从阴影的边缘开始向外或向内延伸的距离。blur 的值越大,阴影的边缘就越模糊,阴影的扩散面积也就越大。如果 blur 的值为 0,那么阴影就是一个固定大小的矩形,没有模糊效果。
spread:这个值定义了阴影的大小。它的值也是一个长度,表示阴影的扩展范围。正值会增加阴影的扩展范围,负值会减小阴影的扩展范围。如果 spread 的值为 0,那么阴影的大小就等于元素的大小。
以下是一个示例:
.box {
box-shadow: 10px 10px 5px 3px rgba(0,0,0,0.75);
}
在这个示例中,阴影的模糊半径是 5px,扩展范围是 3px。这意味着阴影从边缘开始向外延伸 5px,过渡到完全透明,同时阴影的大小比元素的大小大 3px。
从实际展示的效果来看,这俩参数都影响了阴影的大小。但是观察实际的绘制过程,发现blur并不影响阴影矩形的大小,而spread是会实际影响阴影矩形的大小的。为什么这么说,下面可以通过Chrome的devtools中的layers来具体看下实际的绘制过程。
在Chrome中,可以通过"开发者工具"中的"Layers"标签来可视化页面的分层情况,并查看每个图层的clipRect。
具体步骤如下:
在Chrome中任意位置右键点击,选择"检查";
进入到开发者工具后,选择"Layers"标签;
在Layer页面,可以看到每个图层的平移、旋转、复位操作按钮,以及clipRect属性。
在 Google Chrome 开发者工具的 Paint Profiler 中,时间的顺序是由上至下的。也就是说,最早发生的绘制操作在顶部,最后发生的绘制操作在底部。
Paint Profiler 提供了一个详细的视图,显示了浏览器在绘制页面时的每一步操作。这可以帮助你理解页面的渲染过程,以及可能导致性能问题的绘制操作。
在 Paint Profiler 中,每一行代表一个绘制命令,例如填充矩形、绘制文本等。每个命令都有一个颜色条,表示该命令的执行时间。颜色条越长,执行时间就越长。
你可以点击每一行来查看该命令的详细信息,包括命令类型、参数、执行时间等。你还可以使用工具栏上的按钮来放大、缩小、选择和导航视图。
不设偏移,也不设spread,查看blur半径对渲染的影响。
.box-shadow {
position: fixed;
left: 200px;
top: 200px;
width: 100px;
height: 100px;
background: yellow;
box-shadow: 0 0 100px 0 red;
}
一下是对应的paint profiler调用过程:
查看profiler,可以发现主要调用了以下4个方法。
drawPaint(paint) //绘制底部白色画板
translate(150, 150) //blur属性会影响这个偏移量
clipRect(1,1,99,99, "kDifference_Op",false) // 做差值区域裁剪
drawRect(0,0,100,100, paint) //red 绘制红色阴影矩形
drawRect(0,0,100, 100, paint) //yellow //绘制黄色区域
以上几个调用的方法,熟悉canvas的从命名大致能理解是用来干啥的,这里不具体研究每个方法。这些方法是Skia提供的,Skia 是一个开源的 2D 图形处理库,用于Chrome的图形引擎。有兴趣的可以去看Skia的文档,后面会给出具体的链接(估计是需要翻墙的)。
从绘制阴影矩形的参数可知,绘制的阴影矩形面积和实际的元素尺寸是一样的,都是100px,blur属性不影响阴影矩形的面积。为了有所对比,我们改变一下blur,将blur值设为原来的一般50px,再查看下绘制过程中发生了哪些变化。
.box-shadow {
position: fixed;
left: 200px;
top: 200px;
width: 100px;
height: 100px;
background: yellow;
box-shadow: 0 0 50px 0 red;
}
新的绘制调用堆栈如下:
drawPaint(paint)
translate(75, 75)
clipRect(1,1,99,99, "kDifference_Op",false)
drawRect(0, 0, 100, 100, paint)
drawRect(0, 0, 100, 100, paint)
这里可以看到,在第二行中的translate的入参发生了变化,而第四行绘制阴影矩形的尺寸并没有发生变化。所以我这里推论,blur参数并不影响阴影矩形的面积尺寸,但是会改变阴影开始模糊的位置。在layer中,可以看见图形外围的那个绿色框框。
在上面的基础上,设置spread参数,查看spread对那些绘制产生印象。
.box-shadow {
position: fixed;
left: 200px;
top: 200px;
width: 100px;
height: 100px;
background: yellow;
box-shadow: 0 0 50px 10px red;
}
这里面执行了以下序列函数,从中可以看出在第二步和第四步发生了变化,首先可以肯定的是,第二步translate的变化会对阴影模糊的效果产生影响,第四步对阴影矩形的大小和位置做了改变。对于阴影矩阵,可以很容易看出其相对于内容矩阵而言,分别向4个方向扩展了10px,也就是spread的大小。而translate的偏移量也相对的增加了10px。
drawPaint(paint)
translate(85, 85)//这里的偏移量发生了变化
clipRect(1,1,99,99,"kDifference_Op",false)
drawRect(-10,-10,110,110,paint)//这里阴影rect的尺寸和绘制起点都发生了变化
drawRect(0, 0,100,100, paint)
如果再在此基础上加横向和纵向偏移,会改变哪几行代码的执行呢?其实,根据上面的执行效果来看,可以很容易的推断出,首先阴影的尺寸大小应该不会发生变化,实际显示区域的内容也不会发生变化,也就是最后3行,理论上都不会变化。那么,第二行,变化就可能发生在第二行。
.box-shadow {
position: fixed;
left: 200px;
top: 200px;
width: 100px;
height: 100px;
background: yellow;
box-shadow: 10px 10px 50px 10px red;//这里增加了x y向的偏移
}
修改成上面的代码,运行一下,实际的效果如下。可以很明显的看到,整体阴影向右下偏移。
外层绿色框框的区域是整个阴影绘制画板区域,横向纵向偏移可以理解为移动该区域。如何计算外层区域绘制的大小呢?
先忽略x轴和y轴偏移,计算外层矩形的位置。以下图为例,需计算出N点的位置。
.box-shadow {
position: fixed;
left: 200px;
top: 200px;
width: 100px;
height: 100px;
background: yellow;
box-shadow: 0 0 50px 10px red;
}
在上面这段代码中,可以看出,内层显示区域大小ABCD为100X100;由于有spread:10px,所以一外层HGFE的尺寸为110X110;blur半径为50px,所以从外层阴影边缘向外扩展50,此处为模糊区域;从模糊边缘在向外扩展模糊半径的一般即25,即是最外层边框的位置,整体距离内层显示区域距离85。左上角横向85,纵向85,即是外层画布的起点,由此可以确定整个外层边框区域。计算公式大致为:spreadWidth + blurWidth + blurWidth/2。
计算出外层区域后,之后的横向纵向偏移,在保持内部显示区域不懂,只需对整个大的外层进行平移即可。
以上都是针对外阴影的计算与绘制,内阴影则不适用。接下来可以看下内阴影的绘制过程
设置以下内阴影,查看绘制过程如下
.box-shadow {
position: fixed;
left: 200px;
top: 200px;
width: 100px;
height: 100px;
background: yellow;
box-shadow: 0 0 50px 0 red inset;
}
从下面图中右侧顶部的绘制图层可以看出,最外层的绿色大边框画布与元素大小重叠。在整个的绘制过程中,执行了以下几个函数:
drawPaint(paint)
drawRect(0, 0, 100, 100, paint) //黄色矩形框
clipRect(0,0,100,100, "kIntersect_Op", false)
drawDRRect(outer, inner, paint)
这里与外阴影不同的是,外阴影先绘制了阴影rect,后绘制的显示rect,内阴影则相反。模糊半径影响了哪里?毫无疑问,肯定是最后一个函数了。查看最后一个函数的入参可以看见,这里有些变化。
改变blur的大小,会影响outer的位置和大小;改变spread的大小,会影响inner的尺寸和位置。
.box {
width: 100px;
height: 100px;
box-shadow: 0 0 50px 10px inset;
}
如上面的例子,元素大小100,设置spread为10。则inner的大小为90X90,由于内阴影向内扩展,所以inner的rect为{top: 10,left: 10,right: 90,bottom:90}
如果设置blur为50,则outer为元素向外扩展50,对应的rect为{top: -50,left: -50,right: 100+50,bottom: 100+50}
内阴影的x轴y轴偏移:
.box-shadow {
position: fixed;
left: 200px;
top: 200px;
width: 100px;
height: 100px;
background: yellow;
box-shadow: 10px 10px 20px 10px red inset;
}
Skia是一个开源的2D图形库,它提供了可以在各种硬件和软件平台上工作的通用api。它是Google Chrome和ChromeOS、Android、Flutter、Mozilla Firefox和Firefox OS以及许多其他产品的图形引擎。官方地址:https://skia.org/docs/。这里只是简单列举了下渲染过程中出现的几个函数,有兴趣的再继续研究吧。
void SkCanvas::drawRect(const SkRect &rect,const SkPaint &paint )
在Chrome中,clipRect方法通常用于定义一个矩形区域,该区域将被裁剪或剪切,以只显示该区域内的内容。这个方法通常与Canvas API或SVG等图形绘制技术一起使用。
例如,在Canvas API中,clipRect方法可以用于裁剪一个矩形区域,以便在绘制其他图形或文本时只显示该区域内的内容。
clipRect(1,1,99,99,"kDifference_Op",false)
SkRegion::Op 是 Skia 中用于定义区域操作(例如合并、差集等)的枚举。kDifference_Op 是这个枚举的一个值,表示执行差集操作。
差集操作意味着从一个区域中减去另一个区域。如果两个区域有重叠的部分,那么重叠的部分将从结果区域中被移除。
简单地说,如果你有两个区域 A 和 B,并且对它们执行差集操作,那么结果区域将只包含那些在 A 中但不在 B 中的点。
drawDRRect的作用有点类似于canvas中的drawDRRect方法,用于在画布上绘制一个带有圆角的矩形,并在其内部挖出一个小的带有圆角的矩形,形成一个“圆角矩形环”,在box-shadow绘制内阴影时会调用该函数。
以下是 drawDRRect 方法的基本用法:
void SkCanvas::drawDRRect(const SkRRect &outer,const SkRRect &inner,const SkPaint &paint)
通过查看图层渲染的过程,可以很好的帮助我们理解box-shadow的绘制,以及如何设置blur和spread参数的值来达到想要的阴影效果。本文主要是想研究blur和spread在阴影绘制过程中如何影响最终的渲染效果的,只有知道这些,你才能知道如何合理的设置对应的值。