希腊神话讲述了宙斯创造云仙女涅斐勒的故事。 与其他希腊神话一样,这个故事变得相当离奇和色情。 这是一个非常简短、礼貌的版本。
据说,涅斐勒是由宙斯以他美丽妻子的形象创造的。 一个凡人遇见了涅斐勒,爱上了她,并且他们一起睡了一觉™。 最后,在一次奇怪的转变中,云生下了半人半马的肯陶洛斯婴儿。
奇怪吧?就我个人而言,我无法理解它。 谢天谢地,在浏览器中创建云彩的过程要简单得多,而且风险也小得多。

最近,我发现开发者 袁川 实现了代码生成的、照片般真实的云彩。 对我来说,在浏览器中实现这种效果一直是神话般的东西。
只需看一眼 这个笔 中的代码,我们就可以想象,通过使用 CSS 的 box-shadow
以及包含两个 SVG 滤镜作为补充的 <filter>
元素,可以实现令人信服的单个云彩。
我们想要的逼真效果是通过 feTurbulence
和 feDisplacementMap
的巧妙混合实现的。 这些 SVG 滤镜功能强大、复杂且提供了非常令人兴奋的功能(包括 一个获得奥斯卡奖的算法)! 但是,在幕后,它们的复杂性可能会有点令人生畏。
虽然 SVG 滤镜的物理学超出了本文的范围,但 MDN 和 w3.org 上提供了大量可用的文档。 一个关于 feTurbulence
和 feDisplacement
的非常有见地的 页面是免费提供的(并作为 这本精彩的书 的一个章节)。
对于本文,我们将专注于学习如何使用这些 SVG 滤镜来获得惊人的效果。 我们不需要深入研究算法背后的工作原理,就像艺术家不需要了解颜料的分子结构就能渲染出令人惊叹的风景一样。

相反,让我们密切关注少量对在浏览器中绘制令人信服的云彩至关重要的 SVG 属性。 它们的应用将使我们能够将这些强大的滤镜屈服于我们的意志,并学习如何在自己的项目中精确地自定义它们。
让我们从一些基础知识开始
CSS 的 box-shadow
属性有五个值值得密切关注
box-shadow: <offsetX> <offsetY> <blurRadius> <spreadRadius> <color>;
让我们提高这些值(可能高于 任何理智的开发者都会使用的值),以便这个阴影本身成为舞台上的一个角色。

#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;
}
你制作过或见过影子玩偶吗?

就像手改变形状来改变阴影一样,我们 HTML 中的“源形状”可以移动和变形以移动和改变在浏览器中渲染的阴影的形状。 box-shadow
复制原始大小和 border-radius
的“变形”功能。 SVG 滤镜应用于元素及其阴影。
<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>
及其阴影都通过滤镜独立变形。
我们将添加必要的 CSS 规则,使用其 ID 将 HTML 元素(`#cloud-circle`)链接到 SVG 滤镜。
#cloud-circle {
filter: url(#filter);
box-shadow: 200px 200px 50px 0px #fff;
}
瞧!
好的,所以不可否认,添加 SVG 滤镜相当不起眼。

别担心! 我们才刚刚触及表面,还有很多好的东西需要查看。
尝试使用 feDisplacementMap scale 属性
对这个属性进行一些非科学的实验可以产生戏剧性的效果。 目前,让我们保持 feTurbulence
中的所有值不变,只需调整 DisplacementMap
的 scale
属性。
随着 scale
的增加(以 30 的增量),我们的源 <div>
变得扭曲,并投射出一个阴影来反映云彩在天空出现时的随机形式。
<feDisplacementMap in="SourceGraphic" scale="180"/>

好的,我们取得了一些进展! 让我们稍微更改一下颜色,以产生更令人信服的云彩并“增强”效果。
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 值
以下图像集显示了 blur 值对 box-shadow
的影响。 在这里,blur 以 10 像素的增量增加。

为了使我们的云彩具有一定的积云效果,我们可以稍微加宽我们的源 <div>
。
#cloud-circle {
width: 500px;
height: 275px;
background: #000;
border-radius: 50%;
filter: url(#filter);
box-shadow: 200px 200px 60px 0px #fff;
}

等等! 我们加宽了源元素,现在它挡住了我们称为云彩的白色阴影。 让我们将阴影“重新投射”到更远的距离,这样我们的云彩就不会再被源图像遮挡。(可以将其想象成将你的手从墙上移开,这样就不会挡住影子玩偶的视野。)
这可以通过一些 CSS 定位很好地实现。 <body>
是我们云彩的父元素,默认情况下是静态定位的。 让我们通过一些绝对定位将我们的源 <div>
往上和往外移开。 最初,这也会重新定位我们的阴影,因此我们还需要增加阴影与元素的距离,并稍微更多地推动元素。
#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 */
}
是的! 我们已经得到了一个非常有说服力的云彩。
查看 Beau Haus 在 CodePen 上的笔
(@beauhaus)
在 CodePen 上。
渲染到浏览器中的内容是云彩的相当不错的描述——但是,我不确定……这朵云真的能体现云仙女涅斐勒吗? 我相信我们可以做得更好!
通过图层传达深度
这是我们想要的效果

从这张照片中云彩的深度、纹理和丰富性来看,有一点很清楚:宙斯上了艺术学校。 至少,他一定读过 设计通用原则,该书阐述了一个强大但又看似普通的概念
[...] 照明偏差在深度和自然度的解释中起着重要作用,并且可以由设计师以各种方式进行操控……利用明暗区域之间的对比度来改变深度的外观。
这段话为我们提供了一个线索,说明我们如何才能极大地改进我们自己生成的代码云。 通过将不同形式、大小和颜色的图层叠加在一起,我们可以以相当高的保真度渲染我们的云彩,使其与参考图像中的云彩相符。 这只需要根据需要调用我们的 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
并实现其多功能性。 我们将选择可用的更平滑的类型:fractalNoise
,并将 numOctaves
调高到 6。
<feTurbulence type="fractalNoise" baseFrequency="n" numOctaves="6"/>
这一切意味着什么? 现在,让我们特别关注 baseFrequency
属性。 以下是在我们增加 n
的值时得到的结果

诸如**湍流**、**噪声**、**频率**和**倍频程**之类的词语可能显得奇怪,甚至令人困惑。但不要害怕!实际上,将此滤镜的效果比作声波是完全准确的类似于声波。我们可以将低频(baseFrequency=0.001
)等同于低沉、模糊的噪音,并将升高的频率(baseFrequency=0.1
)等同于更高、更清晰的音调。
我们可以看到,对于类似积云的效果,baseFrequency
的最佳范围可能舒适地位于约 0.005 和 0.01 之间。
使用 numOctaves 添加细节
递增 numOctaves
允许我们以极其细致的细节渲染图像。这需要大量的计算,因此请注意:高值会显著影响性能。除非您的浏览器戴着头盔和护膝,否则请尽量不要增加此值。

好消息是,我们不需要将此值调得太高就能产生细节和精致感。如上图所示,我们可以将 numOctaves
值设置为 4 或 5 即可满足需求。
结果如下
查看 CodePen
由 Beau Haus (@beauhaus) 创建
在 CodePen 上。
使用 seed 属性创建无限变化
关于 seed
属性有很多话要说,因为它提供了一丝幕后魔法的线索。但是,就我们的目的而言,seed 的实用性可以概括为四个字:“不同的值,不同的形状”。
Perlin 噪声函数(前面提到过)使用此值作为其随机数生成器的起点。选择不包含此属性将使 seed
默认设置为零。但是,如果包含此属性,无论我们为 seed
提供什么值,都不需要担心性能下降。

上面的 GIF 展示了 seed
可以提供的一些功能。请记住,每朵云都是分层的复合云。(虽然我调整了每一层的属性,但我保持了它们各自的 seed
值相同。)

在这里,仔细观察参考图像,我将 3 个具有不同不透明度的云 <div>
分层到单个基础 div 上。通过反复试验和输入任意 seed
值,我最终得到了一个类似于照片中云朵形状的形状。
查看 CodePen
Nephele 参考图像研究由 BEAU.HAUS (@beauhaus) 创建
在 CodePen 上。
天高任鸟飞
当然,认为我们绘制到浏览器上的 <div>
可以胜过宙斯的涅斐勒,这未免有些自负。
然而,我们能够从 CSS 和 SVG 滤镜中提取的奥秘越多,我们就越能够创造出视觉上令人惊叹的东西,并高度忠实于雷神最初的创造。然后,我们可以继续进行更多实验!


在本文中,我们只是触及了力量和复杂性海洋的表面。SVG 滤镜通常看起来令人难以理解且难以接近。
但是,就像在一个 Div 项目项目或戴安娜·史密斯的绘画技巧中找到的示例一样,一种充满趣味和实验性的方法将始终会带来惊人的结果!
成就解锁!涅斐勒云生成器

我相信你们很多人对制作云所需的全部技术细节感到兴奋,但可能更喜欢一些更简单的方法来在项目中使用云。我开发了一个小工具来帮助生成云并试验形状和变化。
有任何问题、建议或意见?在推特上联系我或在此帖中发表评论。
非常感谢Amelia Bellamy-Royds对本文提供的宝贵建议。
你能否为滤镜的属性添加动画?
可以。
<feTurbulence type="fractalNoise" baseFrequency=".2" numOctaves="10">
<animate attributeName="baseFrequency" dur="30s" values=".01;.02" repeatCount="indefinite" />
</feTurbulence>
<feDisplacementMap in="SourceGraphic" scale="240" />
是的!绝对可以,Carlos!
这里有一个使用 GSAP 为单个属性添加动画的示例。
不过,请注意,这会占用大量的处理器资源。
这是另一个示例
@Greg 太棒了!可惜我的 MacBook 风扇用这个的时候声音像飞机一样 :/
很棒。我的处理器使用这个技巧下降到 -15%,但这太棒了
@Carlos 和 @Rowilsonh,
这个问题一直让我感到沮丧和启发。它确实将浏览器的渲染引擎和 CPU/GPU(?) 推到了其能力的极限。它迫使我考虑在 8 个左右的 CSS 规则和 SVG 属性列表中,哪些是罪魁祸首。
(我相信在这种组合中,border-radius 是其中之一,但可能不是最严重的罪魁祸首)。
能够以不会卡顿整个互联网的方式,让云从 n>100vw 移动到 n<0vw 的动画一直是我的一种“白鲸”般的追求。
我将投入一些时间来解决这个问题。
干杯
我发现使用
box-shadow
有点笨拙,因为您必须为多个层定义多个滤镜。此变体仅使用一个滤镜和一个 SVG 元素。(实现逼真的“羽毛状”外观的技巧是在位移映射之前应用高斯模糊)。这是一项杰出的工作。
我将用一下午时间来研究这里到底发生了什么。:)
谢谢,@ccprog!
酷!!!