波浪可能是 CSS 中最难制作的形状之一。 我们总是尝试用类似的属性来近似它 border-radius
和很多神奇的数字,直到我们得到一些感觉有点接近的东西。 那是在我们进入波浪形图案之前,这更难。
“SVG它!” 你可能会说,你可能是对的,这是一个更好的方法。 但是我们会看到 CSS 可以产生很好的影响,并且它的代码不必全是疯狂的。 你猜怎么着? 我有 在线生成器 让它变得更琐碎!
如果您使用生成器,您会看到它吐出的 CSS 只有两个渐变和一个 CSS 掩码属性——只有这两个东西,我们可以制作任何类型的波形或图案。 更不用说我们可以轻松控制波浪的大小和曲率。
一些值可能看起来像“魔术数字” 但实际上它们背后有逻辑,我们将剖析代码并发现创建波浪背后的所有秘密。
本文为后续 上一个 我在那里建造了各种不同的锯齿形,范围,扇形,是的,波浪形边界边界。 我强烈建议检查该文章,因为它使用了我们将在此处介绍的相同技术,但更详细。
波浪背后的数学
严格来说,波浪形背后没有一个神奇的公式。 任何具有上下曲线的形状都可以称为波浪,因此我们不会将自己局限于复杂的数学。 相反,我们将使用几何基础来重现波浪。
让我们从一个使用两个圆形的简单示例开始:
我们有两个彼此相邻的半径相同的圆。 你看到那条红线了吗? 它覆盖了第一个圆圈的上半部分和第二个圆圈的下半部分。 现在想象你走这条线并重复它。
我们已经看到了浪潮。 现在让我们填充底部(或顶部)以获得以下内容:
多田! 我们有一个波浪形状,我们可以使用一个变量来控制圆半径。 这是我们能做的最简单的波浪之一,也是我展示过的波浪 this 以前的文章
让我们通过第一个插图并稍微移动圆圈来增加一点复杂性:
我们仍然有两个半径相同的圆,但它们不再水平对齐。 在这种情况下,红线不再覆盖每个圆圈的一半区域,而是覆盖更小的区域。 该区域由红色虚线限定。 这条线穿过两个圆圈相交的点。
现在走这条线并重复它,你会得到另一个波浪,一个更平滑的波浪。
我想你应该已经明白了。 通过控制圆圈的位置和大小,我们可以创建任何我们想要的波浪。 我们甚至可以为它们创建变量,我称之为 P
和 S
。
您可能已经注意到,在在线生成器中,我们使用两个输入来控制波形。 它们映射到上述变量。 S
是“波浪的大小”和 P
是“波浪的曲率”。
我在定义 P
as P = m*S
哪里 m
是您在更新波浪曲率时调整的变量。 这允许我们始终具有相同的曲率,即使我们更新 S。
m
可以是之间的任何值 0
和 2
. 0
将给我们第一个特殊情况,其中两个圆圈都水平对齐。 2
是一种最大值。 我们可以做得更大,但经过几次测试,我发现上面的任何东西 2
产生不良的扁平形状。
让我们不要忘记我们的圆的半径! 这也可以使用定义 S
和 P
喜欢这个:
R = sqrt(P² + S²)/2
什么时候 P
等于 0
, 我们将有 R = S/2
.
我们有一切开始将所有这些转换为 CSS 中的渐变!
创建渐变
我们的波浪使用圆形,当谈论圆形时,我们谈论径向渐变。 由于两个圆圈定义了我们的波浪,我们将在逻辑上使用两个径向渐变。
我们将从特定情况开始 P
等于 0
. 这是第一个渐变的插图:
这个梯度在填充整个底部区域时产生了第一个曲率——可以说是波浪的“水”。
.wave {
--size: 50px;
mask: radial-gradient(var(--size) at 50% 0%, #0000 99%, red 101%)
50% var(--size)/calc(4 * var(--size)) 100% repeat-x;
}
--size
变量定义径向渐变的半径和大小。 如果我们将其与 S
变量,那么它等于 S/2
.
现在让我们添加第二个渐变:
第二个渐变只不过是一个圆圈来完成我们的波浪:
radial-gradient(var(--size) at 50% var(--size), blue 99%, #0000 101%)
calc(50% - 2*var(--size)) 0/calc(4 * var(--size)) 100%
如果您检查 上一篇文章 你会看到我只是在重复我已经在那里做过的事情。
我关注了这两篇文章,但渐变配置不一样。
那是因为我们可以使用不同的梯度配置达到相同的结果。 如果您比较两种配置,您会注意到对齐方式略有不同,但技巧是相同的。 如果您不熟悉渐变,这可能会令人困惑,但不要担心。 通过一些练习,你会习惯它们,你会自己发现不同的语法会导致相同的结果。
这是我们第一波的完整代码:
.wave {
--size: 50px;
mask:
radial-gradient(var(--size) at 50% var(--size),#000 99%, #0000 101%)
calc(50% - 2*var(--size)) 0/calc(4 * var(--size)) 100%,
radial-gradient(var(--size) at 50% 0px, #0000 99%, #000 101%)
50% var(--size)/calc(4 * var(--size)) 100% repeat-x;
}
现在让我们使用这段代码并将其调整到我们引入一个变量的位置,该变量使其完全可重用于创建我们想要的任何波形。 正如我们在上一节中看到的,主要技巧是移动圆圈,使它们不再对齐,所以让我们更新每个圆圈的位置。 我们将第一个向上移动,第二个向下移动。
我们的代码将如下所示:
.wave {
--size: 50px;
--p: 25px;
mask:
radial-gradient(var(--size) at 50% calc(var(--size) + var(--p)), #000 99%, #0000 101%)
calc(50% - 2*var(--size)) 0/calc(4 * var(--size)) 100%,
radial-gradient(var(--size) at 50% calc(-1*var(--p)), #0000 99%, #000 101%)
50% var(--size) / calc(4 * var(--size)) 100% repeat-x;
}
我介绍了一个新的 --p
用于定义每个圆的中心位置的变量。 第一个渐变是使用 50% calc(-1*var(--p))
,所以它的中心在第二个使用时向上移动 calc(var(--size) + var(--p))
将其向下移动。
一个演示值一千字:
圆圈既不对齐也不相互接触。 我们在不改变半径的情况下将它们分开很远,所以我们失去了我们的波浪。 但是我们可以通过使用我们之前用于计算新半径的相同数学来解决问题。 请记住 R = sqrt(P² + S²)/2
. 在我们的案例中, --size
等于 S/2
; 同样的 --p
这也等于 P/2
因为我们正在移动两个圆圈。 因此,它们的中心点之间的距离是值的两倍 --p
为了这:
R = sqrt(var(--size) * var(--size) + var(--p) * var(--p))
这给了我们一个结果 55.9px
.
我们的浪潮回来了! 让我们将该等式插入到我们的 CSS 中:
.wave {
--size: 50px;
--p: 25px;
--R: sqrt(var(--p) * var(--p) + var(--size)*var(--size));
mask:
radial-gradient(var(--R) at 50% calc(var(--size) + var(--p)), #000 99%, #0000 101%)
calc(50% - 2*var(--size)) 0 / calc(4 * var(--size)) 100%,
radial-gradient(var(--R) at 50% calc(-1*var(--p)), #0000 99%, #000 101%)
50% var(--size)/calc(4 * var(--size)) 100% repeat-x;
}
这是有效的 CSS 代码。 sqrt()
是规范的一部分,但在我写这篇文章的时候,还没有浏览器支持它。 这意味着我们需要一些 JavaScript 或 Sass 来计算该值,直到我们变得更广泛 sqrt()
支持。
这真是太酷了:只需要两个渐变就可以得到一个很酷的波浪,你可以使用 mask
财产。 不再需要反复试验——您只需要更新两个变量就可以了!
逆转浪潮
如果我们希望海浪向另一个方向发展,我们填充的是“天空”而不是“水”。 信不信由你,我们所要做的就是更新两个值:
.wave {
--size: 50px;
--p: 25px;
--R: sqrt(var(--p) * var(--p) + var(--size) * var(--size));
mask:
radial-gradient(var(--R) at 50% calc(100% - (var(--size) + var(--p))), #000 99%, #0000 101%)
calc(50% - 2 * var(--size)) 0/calc(4 * var(--size)) 100%,
radial-gradient(var(--R) at 50% calc(100% + var(--p)), #0000 99%, #000 101%)
50% calc(100% - var(--size)) / calc(4 * var(--size)) 100% repeat-x;
}
我所做的只是添加一个等于 100%
,在上面突出显示。 结果如下:
我们可以考虑使用关键字值的更友好的语法,以使其更容易:
.wave {
--size: 50px;
--p: 25px;
--R: sqrt(var(--p)*var(--p) + var(--size) * var(--size));
mask:
radial-gradient(var(--R) at left 50% bottom calc(var(--size) + var(--p)), #000 99%, #0000 101%)
calc(50% - 2 * var(--size)) 0/calc(4 * var(--size)) 100%,
radial-gradient(var(--R) at left 50% bottom calc(-1 * var(--p)), #0000 99%, #000 101%)
left 50% bottom var(--size) / calc(4 * var(--size)) 100% repeat-x;
}
我们正在使用 left
和 bottom
关键字来指定边和偏移量。 默认情况下,浏览器默认为 left
和 top
— 这就是我们使用的原因 100%
将元素移动到底部。 实际上,我们正在将其从 top
by 100%
, 所以真的和说的一样 bottom
. 比数学更容易阅读!
有了这个更新的语法,我们所要做的就是交换 bottom
top
- 或反之亦然 - 改变波浪的方向。
如果您想同时获得顶波和底波,我们将所有渐变组合在一个声明中:
.wave {
--size: 50px;
--p: 25px;
--R: sqrt(var(--p)*var(--p) + var(--size)*var(--size));
mask:
/* Gradient 1 */
radial-gradient(var(--R) at left 50% bottom calc(var(--size) + var(--p)), #000 99%, #0000 101%)
left calc(50% - 2*var(--size)) bottom 0 / calc(4 * var(--size)) 51% repeat-x,
/* Gradient 2 */
radial-gradient(var(--R) at left 50% bottom calc(-1 * var(--p)), #0000 99%, #000 101%)
left 50% bottom var(--size) / calc(4 * var(--size)) calc(51% - var(--size)) repeat-x,
/* Gradient 3 */
radial-gradient(var(--R) at left 50% top calc(var(--size) + var(--p)), #000 99%, #0000 101%)
left calc(50% - 2 * var(--size)) top 0 / calc(4 * var(--size)) 51% repeat-x,
/* Gradient 4 */
radial-gradient(var(--R) at left 50% top calc(-1 * var(--p)), #0000 99%, #000 101%)
left 50% top var(--size) / calc(4 * var(--size)) calc(51% - var(--size)) repeat-x;
}
如果您检查代码,您会看到除了组合所有渐变之外,我还降低了它们的高度 100%
至 51%
这样它们都覆盖了元素的一半。 是的, 51%
. 我们需要一点额外的百分比来避免间隙的小重叠。
左右两边呢?
这是你的功课! 采用我们对顶部和底部所做的操作,并尝试更新值以获得右侧和左侧值。 别担心,这很简单,您唯一需要做的就是交换值。
如果遇到问题,可以随时使用 在线生成器 检查代码并可视化结果。
波浪线
早些时候,我们使用红线制作了第一个波浪,然后填充了元素的底部。 那条波浪线呢? 这也是一波! 更好的是,如果我们可以通过变量控制其厚度,以便我们可以重复使用它。 我们开始做吧!
我们不会从头开始,而是采用以前的代码并对其进行更新。 首先要做的是更新渐变的色标。 两种渐变都从透明颜色开始到不透明颜色,反之亦然。 要模拟一条线或边框,我们需要从透明开始,到不透明,然后再回到透明:
#0000 calc(99% - var(--b)), #000 calc(101% - var(--b)) 99%, #0000 101%
我想你已经猜到了 --b
变量是我们用来控制线条粗细的。 让我们将其应用于我们的渐变:
是的,结果远非波浪线。 但仔细观察,我们可以看到一个渐变正确地创建了底部曲率。 所以,我们真正需要做的就是纠正第二个梯度。 与其保持一个完整的圆圈,不如让我们像另一个渐变一样制作部分圆圈。
仍然很远,但我们有我们需要的两个曲率! 如果您检查代码,您会看到我们有两个相同的渐变。 唯一的区别是它们的定位:
.wave {
--size: 50px;
--b: 10px;
--p: 25px;
--R: sqrt(var(--p)*var(--p) + var(--size)*var(--size));
--_g: #0000 calc(99% - var(--b)), #000 calc(101% - var(--b)) 99%, #0000 101%;
mask:
radial-gradient(var(--R) at left 50% bottom calc(-1*var(--p)), var(--_g))
calc(50% - 2*var(--size)) 0/calc(4*var(--size)) 100%,
radial-gradient(var(--R) at left 50% top calc(-1*var(--p)), var(--_g))
50% var(--size)/calc(4*var(--size)) 100%;
}
现在我们需要调整最终形状的大小和位置。 我们不再需要渐变是全高的,所以我们可以替换 100%
有了这个:
/* Size plus thickness */
calc(var(--size) + var(--b))
这个值背后没有数学逻辑。 它只需要对曲率足够大。 稍后我们将看到它对模式的影响。 同时,让我们也更新位置以垂直居中渐变:
.wave {
--size: 50px;
--b: 10px;
--p: 25px;
--R: sqrt(var(--p)*var(--p) + var(--size)*var(--size));
--_g: #0000 calc(99% - var(--b)), #000 calc(101% - var(--b)) 99%, #0000 101%;
mask:
radial-gradient(var(--R) at left 50% bottom calc(-1*var(--p)), var(--_g))
calc(50% - 2*var(--size)) 50%/calc(4 * var(--size)) calc(var(--size) + var(--b)) no-repeat,
radial-gradient(var(--R) at left 50% top calc(-1 * var(--p)), var(--_g)) 50%
50%/calc(4 * var(--size)) calc(var(--size) + var(--b)) no-repeat;
}
仍然不完全存在:
一个渐变需要向下移动一点,而另一个需要向上移动一点。 两者都需要移动一半的高度。
我们就快到了! 我们需要对半径进行一个小修复以实现完美的重叠。 两条线都需要偏移一半边界(--b
) 厚度:
我们得到了它! 一条完美的波浪线,我们可以通过控制几个变量轻松调整:
.wave {
--size: 50px;
--b: 10px;
--p: 25px;
--R: calc(sqrt(var(--p) * var(--p) + var(--size) * var(--size)) + var(--b) / 2);
--_g: #0000 calc(99% - var(--b)), #000 calc(101% - var(--b)) 99%, #0000 101%;
mask:
radial-gradient(var(--R) at left 50% bottom calc(-1 * var(--p)), var(--_g))
calc(50% - 2*var(--size)) calc(50% - var(--size)/2 - var(--b)/2) / calc(4 * var(--size)) calc(var(--size) + var(--b)) repeat-x,
radial-gradient(var(--R) at left 50% top calc(-1*var(--p)),var(--_g))
50% calc(50% + var(--size)/2 + var(--b)/2) / calc(4 * var(--size)) calc(var(--size) + var(--b)) repeat-x;
}
我知道逻辑需要一点才能掌握。 这很好,正如我所说,在 CSS 中创建波浪形并不容易,更不用说它背后的复杂数学了。 这就是为什么 在线生成器 是救命稻草——即使您不完全理解其背后的逻辑,您也可以轻松获得最终代码。
波浪图案
我们可以用刚刚创建的波浪线制作图案!
哦不,模式的代码会更难看懂!
一点也不! 我们已经有了代码。 我们需要做的就是删除 repeat-x
从我们已经拥有的和tada。 🎉
一个漂亮的波浪图案。 还记得我说过我们会重新讨论的方程式吗?
/* Size plus thickness */
calc(var(--size) + var(--b))
好吧,这就是控制图案中线条之间距离的原因。 我们可以用它做一个变量,但不需要更复杂。 我什至没有在生成器中使用变量。 也许我稍后会改变它。
这是朝不同方向发展的相同模式:
我正在为您提供该演示中的代码,但我希望您对其进行剖析并了解我为实现这一目标所做的更改。
简化代码
在之前的所有演示中,我们总是定义 --size
和 --p
独立。 但是你还记得我之前提到的在线生成器评估 P
等于 m*S
,其中 m
控制波的曲率? 通过定义一个固定的乘数,我们可以处理一个特定的波,代码可以变得更容易。 这是我们在大多数情况下所需要的:一个特定的波浪形状和一个控制其大小的变量。
让我们更新我们的代码并介绍 m
变量:
.wave {
--size: 50px;
--R: calc(var(--size) * sqrt(var(--m) * var(--m) + 1));
mask:
radial-gradient(var(--R) at 50% calc(var(--size) * (1 + var(--m))), #000 99%, #0000 101%)
calc(50% - 2*var(--size)) 0/calc(4 * var(--size)) 100%,
radial-gradient(var(--R) at 50% calc(-1 * var(--size) * var(--m)), #0000 99%, #000 101%)
50% var(--size) / calc(4 * var(--size)) 100% repeat-x;
}
如您所见,我们不再需要 --p
多变的。 我把它换成了 var(--m)*var(--size)
,并相应地优化了一些数学。 现在,如果我们想使用特定的波浪形状,我们可以省略 --m
变量并将其替换为固定值。 我们试试看 .8
例如。
--size: 50px;
--R: calc(var(--size) * 1.28);
mask:
radial-gradient(var(--R) at 50% calc(1.8 * var(--size)), #000 99%, #0000 101%)
calc(50% - 2*var(--size)) 0/calc(4 * var(--size)) 100%,
radial-gradient(var(--R) at 50% calc(-.8 * var(--size)), #0000 99%, #000 101%)
50% var(--size) / calc(4 * var(--size)) 100% repeat-x;
看看代码现在变得更容易了吗? 只有一个变量来控制你的波浪,再加上你不再需要依赖 sqrt()
没有浏览器支持!
您可以将相同的逻辑应用于我们看到的所有演示,即使是波浪线和图案也是如此。 我从详细的数学解释开始并给出了通用代码,但您可能会发现自己在实际用例中需要更简单的代码。 这就是我一直在做的事情。 我很少使用通用代码,但我总是考虑简化版本,尤其是在大多数情况下,我使用一些不需要存储为变量的已知值。 (扰流警报: 最后我会分享几个例子!)
这种方法的局限性
在数学上,我们编写的代码应该给我们完美的波浪形状和图案,但在现实中,我们会面临一些奇怪的结果。 所以,是的,这种方法有其局限性。 例如,在线生成器能够产生较差的结果,尤其是波浪线。 部分问题是由于特定的值组合导致结果被打乱,例如与尺寸相比使用较大的边框厚度值:
对于其他情况,与某些舍入相关的问题会导致波之间的错位和间隙:
话虽如此,我仍然认为我们介绍的方法仍然是一个好方法,因为它在大多数情况下会产生平滑的波浪,并且我们可以通过使用不同的值来轻松避免糟糕的结果,直到我们得到完美为止。
结束了
我希望在阅读完这篇文章之后,您将不再为构建波浪形或图案而反复尝试。 此外 到在线生成器,你拥有创造任何你想要的波浪背后的所有数学秘密!
文章到此结束,但现在您有了一个强大的工具来创建使用波浪形状的精美设计。 这是让您入门的灵感……
那你呢? 使用我的在线生成器(如果您已经熟记所有数学知识,也可以手动编写代码)并向我展示您的创作! 快来评论区好好收藏吧。