译文出自:闪电矿工翻译组
原文地址:Drawing Realistic Clouds with SVG and CSS
原文作者:Beau Jackson
仓库原文链接:Drawing Realistic Clouds with SVG and CSS
译者:sichenguo
【译】来用 SVG 和 CSS 画朵云彩吧
希腊神话中有这样一个故事是讲述宙斯创造出来一个云女神涅斐勒,并且类似大多数的希腊神话一样的,这个故事非常的奇异且限制级。下面一个简短克制的版本。
我们能够知道的是:涅斐勒 是由宙斯以他自己美丽的妻子的形象创造的。一个凡人遇见涅斐勒,陷入爱河,并且他们一起有了一个孩子,确切的说是一个半人半马的婴儿。
很怪诞对吧,值得庆幸的是,在浏览器中创建云的过程要简单得多,而且风险要小得多。
(Demo)
最近,我发现开发者 Yuan Chuan 已经实现了用代码生成逼真的云。对我来说,浏览器中的云这个概念一直如同希腊神话中的那边神秘。
让我们来看一下这个’画笔‘吧(点这里),可以见到的是作者通过使用 box-shadow
作为包含两个滤镜的 <filter>
元素的补充实现了这个令人惊叹的‘云图’!
想要绘制出兼顾写实和精致的云图,需要搭配使用feTurbulence
和feDisplacementMap
这两个滤镜。这两个滤镜不仅可以具有强大且复杂的功能,并且还可以提供一些令人兴奋的特性(其中包含奥斯卡获奖算法))!当然,这些功能在浏览器‘引擎盖’下有着令人生畏的复杂性。
虽然这些 SVG 滤镜的物理特性超出了本文的范围,但 MDN 和 w3.org 上提供了大量文档供学习参考。另外还有 feTurbulence
和 feDisplacement
介绍。
对于本文,我们将专注于学习使用这些 SVG 滤镜来实现令人惊奇的效果。滤镜背后的实现算法并不在我们的研究范围内,就像艺术家虽然可以绘制出美丽的景观但却不用懂得油漆的分子结构。
而我们需要做的只是密切关注一小部分 SVG 属性,这些属性使得我们可以在浏览器中绘制逼真的云图。通过学习这些属性可以让我们在项目中按照自己的意愿更好的制作出特定的滤镜效果。
一些必要的前置基础知识
CSS 规则 box-shadow
的五个值得关注的属性:
box-shadow: <offsetX> <offsetY> <blurRadius> <spreadRadius> <color>;
让我们来增大这些值(可能会高于正常的开发者会做的),这样在视图的右下方就会有阴影出现。!(Demo)
#cloud-square {
background: turquoise;
box-shadow: 200px 200px 50px 0px #000;
width: 180px;
height: 180px;
}
#cloud-circle {
background: coral;
border-radius: 50%;
box-shadow: 200px 200px 50px 0px #000;
width: 180px;
height: 180px;
}
你肯定表演或者见过过皮影戏吧?
Credit: Double-M
类似于通过改变手的形状来改变阴影的方式,我们的 HTML 中的“源形状”可以通过移动或者变形来同步影响其在浏览器中呈现的阴影的形状。box-shadow
复制原始图形的圆角效果。SVG 滤镜对于元素都以及元素的 shadow
的都会生效。
<svg width="0" height="0">
<filter id="filter">
<feTurbulence type="fractalNoise" baseFrequency=".01" numOctaves="10" />
<feDisplacementMap in="SourceGraphic" scale="10" />
</filter>
</svg>
到目前为止,上面的 SVG
标签不会被渲染出来的,因为我们没有定义任何视觉样式(更不用说零宽度、高度)。它的唯一目的是保持我们喂养的过滤器 SourceGraphic(也就是我们的<div>
)。我们的源<div>
和它的阴影都被滤波器独立地扭曲。
我们通过 ID
选择器(#cloud-circle
) 将下面的 CSS
规则添加到目标元素上:
#cloud-circle {filter: url(#filter);
box-shadow: 200px 200px 50px 0px #fff;
}
看这里!
好吧,添加上 SVG 滤镜依旧没能给我们带来什么惊喜。
(Demo)
别担心!这才仅仅只是开始,更有趣的还在后面。
测试 feDisplacementMap scale 属性
使用这一属性进行的一些实验可以产生显著的效果。暂时,让我们保持它的 feTurbulence
不变,只调整 DisplacementMap
的 scale
属性。
随着 scale
的增加(每次增加 30),我们的源 <div>
开始扭曲变形且它的投下阴影更接近天空中云随机的形状。
<feDisplacementMap in="SourceGraphic" scale="180" />
The scale attribute incremented by values of 30. (Demo)
好的,通过改变 scale
, 好像终于接近了正确云形状的的数值范围!让我们稍微改变下颜色,以便看起来更像一朵 云彩 ☁️。
body {background: linear-gradient(165deg, #527785 0%, #7fb4c7 100%);
}
#cloud-circle {
width: 180px;
height: 180px;
background: #000;
border-radius: 50%;
filter: url(#filter);
box-shadow: 200px 200px 50px 0px #fff;
}
现在我们的图形越来越接近真实的云效果了!
调整 box-shadow 的值
下面的一组图像显示了 blur 对 box-shadow 效果的影响。下面的图形中 blur 的数组依次递增。
The cloud becomes “softer” as the blur value increases.
为了使我们的云带有一些积云效果,可以稍微扩大 <div>
的尺寸。
#cloud-circle {
width: 500px;
height: 275px;
background: #000;
border-radius: 50%;
filter: url(#filter);
box-shadow: 200px 200px 60px 0px #fff;
}
好的,但是现在的源 div
元素正在成为障碍。????
等等!我们已经扩大了源元素的尺寸,但是它现在已经开始影响我们的“云(白色阴影)”了。很简单,只需要相距更远的地方投影遍可以解决这个遮挡问题。
而通过通过一些 CSS 定位很好地实现源元素的隐层。<body>
元素是云 div
元素的父元素,默认情况下是静态定位的。将苏设置为就绝对定位就可以将不需要展示的 #cloud-circle
隐藏。
#cloud-circle {
width: 500px;
height: 275px;
background: #000;
border-radius: 50%;
filter: url(#filter);
box-shadow: 400px 400px 60px 0px #fff; /* Increase shadow offset */
position: absolute; /* Take the parent out of the document flow */
top: -320px; /* Move a little down */
left: -320px; /* Move a little right */
}
很棒,这样看起来我们的云图已经初步成型了。
codepen
平摊在屏幕之上的云彩,嗯,这应该是一个对我们绘制出的云图一个很好的描述,总是感觉还缺少点什么,我们应该还可以做的会更好的!
用层来表达深度
这就是我们想要的:
Credit: 图源:pcdazero
从这张照片中云的深度,质感和丰富程度来看,有一件事是清楚的:宙斯是上过艺术学校。至少,他肯定是阅读过通用组件设计原则的,这个原则阐述了一个通用性的概念:
[…]照明偏差在深度和自然的解释中起着重要作用,并且可以由设计师以各种方式操纵 …… 使用明暗区域之间的对比度使得外观具有景深。
这段话为我们提供了一个对云图进行优化更加真实的提示。通过将不同形状,大小和颜色的图层堆叠在一起,我们可以在参考图像制造对比度中以来渲染云图。需要的只是使用 filter
制造多个图层。
<svg width="0" height="0">
<!-- Back Layer -->
<filter id="filter-back">
<feTurbulence
type="fractalNoise"
baseFrequency="0.012"
numOctaves="4"
/>
<feDisplacementMap in="SourceGraphic" scale="170" />
</filter>
<!-- Middle Layer -->
<filter id="filter-mid">
<feTurbulence
type="fractalNoise"
baseFrequency="0.012"
numOctaves="2"
/>
<feDisplacementMap in="SourceGraphic" scale="150" />
</filter>
<!-- Front Layer -->
<filter id="filter-front">
<feTurbulence
type="fractalNoise"
baseFrequency="0.012"
numOctaves="2"
/>
<feDisplacementMap in="SourceGraphic" scale="100" />
</filter>
</svg>
再加上图层可以提供更多的角度去探索 feTurbulence
以及 实现更多的功能。我们选择使用更加平滑的 type
:fractalNoise
, 然后将 numOctaves
设置为 6。
<feTurbulence type="fractalNoise" baseFrequency="n" numOctaves="6" />
现在,让我们将注意力集中在 baseFrequency
属性上。以下是我们在逐渐增加 baseFrequency
值的时得到的结果:
值取两端的值时,图形都会趋向于圆形。区别就是越小,更模糊。值越大,图形就更加显得生硬。
像 turbulence
,噪音,频率和 octave
这样的词可能看起来很奇怪甚至令人困惑。但不要害怕!将滤镜的效果类比为声波实际上非常准确。我们可以将低频率(baseFrequency
=0.001)等同于低噪声和低频率,更高的 (baseFrequency
=0.1) 值则与更清晰的音调相对应。
我们可以看到,我们对积云效果的对应的 baseFrequency
值大概位于 0.005~0.01 范围之间。
使用 numOctaves 添加细节
增大 numOctaves
的值可以以极其细致的细节渲染图像。但是这需要大量计算,因此需要注意的是:更细致的渲染也可能带来的还有性能问题,所以如非必要,请将此值尽量控制在一定范围内。
The higher the value we put into numOctaves the more granular detail give to our cloud.
好消息就是,在此例中,我们的云图需要的细节并不需要使用过高的 numOctaves
值,如上图所示,numOctaves
为 4 或者 5 就可以得到不错的效果。
效果图
codepen
改变 seed 属性值可以产生多样的云图
至于 seed
属性还可以再深入研究一下。但是,就我们的目的而言,seed
的作用可以对形状造成影响。
Perlin Noise 函数(上文提到
)会使用此值作为其随机数生成器的初始值。如果不设置该属性将默认 seed
为零。跟 baseFrequency 的区别就是,无论我们给出什么价值 seed
,都不需要担心性能损失。
不同的 seed 值产生不同的形状
上面的 GIF 展示了不同 seed
值时的效果。需要注意的是,每朵云都是分层的复合云。(虽然我调整了每个图层的属性,但我保持各自的 seed
值分布均匀。)
Credit: Brockenhexe
在这里,仔细观察参考图像,我将 3 个‘云’层(不透明度不同)叠加到一个 div
上。通过反复试验和调整 seed
参数值,最终得到的下面这个类似于照片中云的形状的效果图。
codepen
天空才是真正的极限
当然,如果我们认为我们绘制的这个云彩要比宙斯创造的女神还要漂亮,肯定就是狂妄自大了。
但是,随着我们对 CSS
和 SVG
滤镜的更多的了解,我们就越有能力创造出在视觉上令人惊叹的图形,比如下面这些:
Reflecting Mist
Animated Reflecting mist
Alto-Cirrus Clouds
在这篇文章中我们沉浸在 SVG
强大功能和复杂性的海洋中,虽然 SVG
滤镜通常看起来比较复杂且难以理解。
然而就好像 A Single Div project 这个项目和 Diana Smith’s painting techniques 这个例子一样,有趣的和有创意的方法总是可以得到令人惊艳的效果。
我相信很多开发者都有能力制作出一个云彩出来,但是一个可以轻松制作出云彩的工具会更受欢迎。因此我开发了一个小工具做这件事。