我所说的“CSS 图像”是指仅使用 HTML 元素和 CSS 创建的图像。它们看起来像是用 Adobe Illustrator 绘制的 SVG,但它们是在浏览器中直接制作的。我看到的一些技术包括调整边框半径、阴影以及有时使用 clip-path
。如果您在 CodePen 上 搜索“daily css images”,可以找到许多很棒的示例。我自己也画了一些,包括这个 无限手套,但我只用一个元素,仅使用背景和最少的其他属性。
让我们看看如何自己创建这种 CSS 图像。
方法
理解 background
简写语法以及 CSS 渐变 的工作原理,实际上就是您用一个元素绘制任何内容所需的全部知识。作为回顾,参数如下
background: <'background-color'> || <image> || <position> [ / <size> ]? || <repeat> || <attachment> || <origin> || <clip>;
它们可以以任何顺序出现,但 position
和 size
之间必须有一个 /
。我们也必须按照这种顺序保持这两个参数,否则我们将得到意外的结果。并非所有这些参数都需要包含,并且出于此目的,我们不会使用 color
、repeat
、attachment
、origin
或 clip
参数。这将使我们剩下 image
、size
和 position
。但是,由于背景默认情况下会重复,因此我们必须在 background
中的所有内容下方放置 background-repeat: no-repeat;
(如果某些背景应该重复,我们可以使用 repeating-linear-gradient()
和 repeating-radial-gradient()
)。在这种情况下,骨架 CSS 将是这个
.image {
background: <image> <position> / <size>;
background-repeat: no-repeat;
}
我们甚至可以使用多组背景参数!因此,我们可以像这样用逗号将它们堆叠和分隔开
.image {
background:
<image> <position> / <size>,
<image> <position> / <size>,
<image> <position> / <size>;
background-repeat: no-repeat;
}
上面的结构是我们绘制图像的基础——每种形状一行。请记住,渲染顺序与绝对定位或固定定位元素的顺序相反。第一个将显示在顶部,而不是在底部。换句话说,下面的圆圈(径向渐变)将从底部到顶部渲染(蓝色在底部,红色在顶部)。
.circles {
background:
radial-gradient(7em 7em at 35% 35%, red 50%, transparent 50%),
radial-gradient(7em 7em at 50% 50%, gold 50%, transparent 50%),
radial-gradient(7em 7em at 65% 65%, blue 50%, transparent 50%);
background-repeat: no-repeat;
width: 240px;
height: 240px;
}

绘制
我们将使用 Sass (SCSS) 来绘制这些图像,以便我们可以使用变量来创建调色板。这样可以使代码更短,更易于阅读和更改深色或浅色变体。我们可以使用 普通 CSS 中的变量 并忘记 Sass,但由于 Internet Explorer 不支持,让我们坚持使用 Sass。为了解释它的工作原理,我们将使用线性渐变和径向渐变来尝试使用形状。
设置调色板
我们的调色板将由 RGB 或 HSL 颜色组成。我稍后会解释为什么要将颜色保持在这些格式中。在此示例中,我们将使用 RGB。
$r: rgb(255,0,0); // hsl(0,100%,50%)
$o: rgb(255,128,0); // hsl(32,100%,50%)
我喜欢做的是使用最少的字母来表示每种颜色(例如,$r
代表红色)。如果使用一种颜色的深色或浅色色调,我在基本字母或字母前添加一个 d
代表深色,或者添加一个 l
代表浅色。我将使用 $dr
代表深红色,$lr
代表浅红色。如果需要超过两种其他色调,则我在末尾添加一个数字来表示色调级别。例如,$dr1
代表深红色,$dr2
代表更深的红色,$lr1
代表浅红色,$lr2
代表更浅的红色。这样的调色板将如下所示(深色在前,正常色在中间,浅色在后)
$dr1: rgb(224,0,0);
$dr2: rgb(192,0,0);
$r: rgb(255,0,0);
$lr1: rgb(255,48,48);
$lr2: rgb(255,92,92);
设置比例和画布
我们将使用 em
单位作为图像尺寸,以便图像可以轻松地按比例调整大小。由于 1em 等于元素的字体大小,如果更改图像,图像的每个单位也会相应调整。让我们设置 10px 的字体大小,并将宽度和高度都设置为 24em。10px 的单位最容易想到,因为如果您在脑子里计算,您会立即得到 240 × 240px。然后,为了查看画布边缘的位置,我们将使用 1px 的灰色轮廓。
$r: rgb(255,0,0); // hsl(0,100%,50%)
$o: rgb(255,128,0); // hsl(32,100%,50%)
.image {
background-repeat: no-repeat;
font-size: 10px;
outline: 1px solid #aaa;
width: 24em;
height: 24em;
}
但是请注意,使用较小的字体大小;浏览器有一个最小的字体大小设置(出于可访问性原因)。如果您设置 4px 的字体大小,而最小值为 6px,它将被强制设置为 6px。
此外,您可以通过使用 calc()
和视窗单位来启用响应能力。也许我们可以使用类似 calc(10px + 2vmin)
的东西,如果需要,但现在让我们坚持使用 10px。
绘制形状
有趣的部分从这里开始。要绘制一个位于中心的 8 × 8em 的正方形,我们使用一个具有两个相同颜色停止点的 linear-gradient()
。
.image {
background:
linear-gradient($r, $r) 50% 50% / 8em 8em;
...
}

要将其塑造成更像梯形的东西,请将角度设置为 60deg。同时,让我们在调色板中添加 $T
代表 transparent
,然后将 $r
和 $T
的两个停止点都放在 63%(在右下角被切掉之前)。
$T: transparent;
.image {
background:
linear-gradient(60deg,$r 63%, $T 63%) 50% 50% / 8em 8em;
...
}

将两个停止点都设置为相同的值会使倾斜边与其他边一样清晰。但如果您仔细观察,您会发现它似乎是像素化的。

要更正此问题,我们稍微调整一下其中一个停止点(大约 1% 或左右),以便边缘足够平滑。因此,让我们将 $r
的 63% 更改为 62%。
使用径向渐变处理圆形边缘时,这也是一个问题,我们将在后面看到。如果您在 Safari 以外的浏览器中查看,即使过渡到非透明颜色(例如橙色),一切看起来都很棒。在 Safari 中过渡到 transparent
的问题是,您会在倾斜边看到一些黑色线条。

这是因为 Safari 中的 transparent
关键字始终是黑色透明度,因此我们会看到一些黑色。我真的希望苹果能解决这个问题,但他们永远不会。暂时,让我们在 $r
下添加一个新的 $redT
变量来表示红色透明度。放弃我们用于 transparent
的 $T
,因为我们不再使用它了。
$rT: rgba(255,0,0,0); // hsla(0,100%,50%,0)
然后让我们用 $redT
替换 transparent
。这样就解决了我们的 Safari 问题!
.image {
background:
linear-gradient(60deg,$r 62%, $rT 63%) 50% 50% / 8em 8em;
...
}

如果您一直在想为什么我们没有使用十六进制颜色,因为 Internet Explorer 和 Edge 不支持 #rgba 和 #rrggbbaa 符号(是的,十六进制颜色从 2016 年末开始就有了 alpha 通道,如果您不知道的话),而且我们希望它尽可能跨浏览器工作。我们还希望保持我们对颜色格式的选择的一致性。
现在让我们将形状垂直移动到 20%,并绘制一个相同尺寸的橙色圆圈。另外,为它的透明版本添加另一个变量。为了使边缘平滑,在实心橙色和透明橙色之间插入 1% 的间隙。
$oT: rgba(255,128,0,0); // hsla(32,100%,50%,0)
.image {
background:
linear-gradient(60deg,$r 62%, $rT 63%) 50% 20% / 8em 8em,
radial-gradient(8em 8em at 50% 80%, $o 49%, $oT 50%);
...
}

为了保持我们尺寸的一致性,第二个颜色停止点应该在 50% 而不是 100%。
定位形状
渐变的定位方式取决于单位是固定的还是百分比。假设我们将两个渐变都变成正方形,并尝试将它们全部横向放置在 div 上。
.image {
background:
linear-gradient($r, $r) 24em 20% / 8em 8em,
linear-gradient($o, $o) 100% 80% / 8em 8em;
...
}

红色正方形最终完全超出画布(带轮廓),橙色正方形的右侧接触另一侧。使用固定单位就像放置绝对定位元素或在 HTML5 画布中绘制形状一样。从这个意义上说,原点确实位于左上角。当使用百分比和设置背景大小时,div 会获得背景大小一半的“假填充”。同时,背景的原点是居中的(不要与 background-origin
混淆,background-origin
与盒子角相关)。

现在,如果我们将这些渐变变成径向渐变作为圆圈,并应用相同的 x 坐标 24em 和 100%,两者都将最终在另一侧被切成两半。这是因为如果我们像这样编写背景,原点始终位于中心
.image {
background:
radial-gradient(8em 8em at 24em 20%, $r 49%, $rT 50%),
radial-gradient(8em 8em at 100% 80%, $o 49%, $oT 50%);
...
}

如果我们重新编写背景,使位置和大小在渐变之后,并使用 100% 100% at center
,它们将像线性渐变一样定位。红色最终会在外面,橙色会接触到右边缘。“假填充”再次出现在橙色上。
.image {
background:
radial-gradient(100% 100% at center, $r 49%, $rT 50%) 24em 20% / 8em 8em,
radial-gradient(100% 100% at center, $o 49%, $oT 50%) 100% 80% / 8em 8em;
...
}

没有一种单一正确的方式来定位形状,但是要像绝对定位或固定定位的 HTML 元素一样放置它,请使用固定单位。如果需要快速定位形状(使用 position / size
参数)在正中央,50% 是最佳选择,因为形状的原点将是它的中心。如果它应该接触最右侧,请使用 100%。
调整形状大小
CSS 背景中的大小调整按预期工作,但仍受用于定位的单位类型影响——固定或百分比。如果我们再次使用我们的正方形并将它们的宽度更改为 10em,红色正方形会向右扩展,橙色正方形会向两侧扩展。
.image {
background:
linear-gradient($r, $r) 12em 20% / 10em 8em,
linear-gradient($o, $o) 50% 80% / 10em 8em;
...
}

如果我们对 y 轴位置使用 em
单位,则在更改高度后,形状将向下增长或向上收缩。如果我们使用百分比,则它将在两个垂直方向上扩展。
刚才,我们研究了使用径向渐变绘制圆圈的两种方法。第一种方法是在 (
和 at
之间指定宽度和高度,然后指定位置。
.image {
background:
radial-gradient(8em 8em at 50% 50%, $r 49%, $rT 50%);
...
}
第二种方法是在中心使用 100% 100%
,然后给出位置和大小。
.image {
background:
radial-gradient(100% 100% at 50% 50%, $r 49%, $rT 50%) 50% 50% / 8em 8em;
...
}
这两种方法都绘制圆圈,但会产生不同的输出,因为
- 第一种方法占用了整个 div,因为没有真正的背景位置或大小。
- 对第二种方法给出一个真实的位置和大小会为它设置一个边界框。因此,它的行为就像线性渐变形状一样。
假设我们将 $rT
替换为 $o
。您会看到橙色会覆盖原来是白色或它下面的形状(如果我们添加了任何形状)的第一种方法。使用第二种方法,您会很容易注意到橙色揭示的边界框。

此外,使用 100% 100%
而不是使用 circle
或 ellipse
的目的是让圆圈占据整个边界框。它甚至可以让我们完全控制它的尺寸。这样一来,即使您将 50% 50%
位置更改为其他位置,它也会保持不变。如果使用这两个关键字中的一个,圆圈的边缘在居中时只停留在大约 71% 的位置,并且如果调整其位置,它会变得更加扭曲。例如,以下是在将 circle
和 ellipse
的 x 位置更改为 0
时会发生的情况。

从长远来看,您可以将语法重新想象为 radial-gradient(width height at x y)
或 radial-gradient(100% 100% at x-in-bounding-box y-in-bounding-box) x y / width height
。如果您只是绘制一个圆形或椭圆形,可以使用第一种方法简化代码。如果绘制圆形的一部分或环的一部分,则第二种方法会派上用场。在接下来创建的示例中,我们将有许多这种方法的应用。
示例
准备好现在真实地绘制一些东西了吗?我们将逐步介绍三个示例。前两个将是静态的——一个有很多半圆,另一个处理一些圆角矩形。最后一个示例将更小,但重点是动画。
静态图像
这把遮阳伞将是我们第一个静态图像

我们将使用包含红色 ($r
和 $rT
)、白色 ($w
和 $wT
)、橙色 ($o
和 $oT
) 以及深橙色 ($do
和 $doT
) 的调色板。
$r: rgb(255,40,40);
$rT: rgba(255,40,40,0);
$w: rgb(240,240,240);
$wT: rgba(240,240,240,0);
$o: rgb(255,180,70);
$oT: rgba(255,180,70,0);
$do: rgb(232,144,0);
$doT: rgba(232,144,0,0);
让我们设置 30 × 29em 的绘图区域。
.parasol {
// background to go here
background-repeat: no-repeat;
font-size: 10px;
outline: 1px solid #aaa;
width: 30em;
height: 29em;
}
在 background-repeat
上方,我们将开始绘制遮阳伞的各个部分。首先,添加构成杆子的渐变(由于它们彼此不重叠,因此从下到上的顺序无关紧要)。
.parasol {
background:
// 1
radial-gradient(200% 200% at 100% 100%, $do 49%, $doT 50%) 14em 0 / 1em 1em,
radial-gradient(200% 200% at 0% 100%, $o 49%, $oT 50%) 15em 0 / 1em 1em,
// 2
linear-gradient(90deg, $do 50%, $o 50%) 14em 1em / 2em 25em,
// 3
radial-gradient(100% 200% at 50% 0, $oT 0.95em, $o 1em, $o 1.95em, $do 2em, $do 2.95em, $doT 3em) 14em 26em / 6em 3em,
// 4
radial-gradient(200% 200% at 100% 100%, $o 49%, $oT 50%) 18em 25em / 1em 1em,
radial-gradient(200% 200% at 0% 100%, $do 49%, $doT 50%) 19em 25em / 1em 1em;
...
}

- 为了绘制杆子顶部的每一侧,我们使用了 1 × 1em 的四分之一圆。为了让它们占据它们的边界框,我们使用了大小是它们两倍的圆 (
200% 200%
),位于右下角和左下角。我们也可以使用像right bottom
或left bottom
这样的关键字对,但使用百分比等效项更短。请注意,停止点之间有 1% 的间隙,以确保平滑过渡。 - 对于长部分,我们使用了一个长方形,它有一个突兀的深橙色到橙色的渐变。由于我们没有处理曲线或斜线,因此不需要小数分隔的小间隙。
- 这部分杆子比其他部分更难绘制,因为我们必须保持 2em 的直径。为了绘制这个圆弧,我们使用了一个 6 × 3em 的盒子,这样在两个也是 2em 的端点之间有一个 2em 的空间。然后,我们在中心顶部使用一个径向渐变,其中每个停止点都发生 1em(并通过 0.05em 平滑地扩展)。
- 最后两个与第一个相同,只是它们位于圆弧的右侧末端,以便它们无缝地匹配。此外,颜色互换位置。
然后,在之前的渐变上方,从下到上添加以下内容,以绘制雨伞的顶部,不包括尖端。
.parasol {
background:
radial-gradient(100% 200% at 50% 100%, $r 50%, $rT 50.25%) 50% 1.5em / 9em 12em,
radial-gradient(100% 200% at 50% 100%, $w 50%, $wT 50.25%) 50% 1.5em / 21em 12em,
radial-gradient(100% 200% at 50% 100%, $r 50%, $rT 50.25%) 50% 1.5em / 30em 12em,
...
}

为了绘制构成这部分的半圆,我们使用了 100% 200%
的渐变大小,这使得每个直径适合其背景宽度,但高度是其两倍,并在底部居中。通过从下到上排序它们,使得最大的在底部,最小的在顶部,我们得到了我们想要的曲线。
随着我们的渐变堆栈越来越高,过一段时间后,确定哪个背景或背景组对应于图像的哪个部分将变得很困难。因此,为了便于识别,我们可以将它们分成由描述每个组用途的注释引导的组。现在,我们已将堆栈分为遮阳伞顶部和杆子的组。
.parasol {
background:
/* top */
radial-gradient(100% 200% at 50% 100%, $r 50%, $rT 50.25%) 50% 1.5em / 9em 12em,
radial-gradient(100% 200% at 50% 100%, $w 50%, $wT 50.25%) 50% 1.5em / 21em 12em,
radial-gradient(100% 200% at 50% 100%, $r 50%, $rT 50.25%) 50% 1.5em / 30em 12em,
/* pole */
radial-gradient(200% 200% at 100% 100%, $do 49%, $doT 50%) 14em 0 / 1em 1em,
radial-gradient(200% 200% at 0% 100%, $o 49%, $oT 50%) 15em 0 / 1em 1em,
linear-gradient(90deg, $do 50%, $o 50%) 14em 1em / 2em 25em,
radial-gradient(100% 200% at 50% 0, $oT 0.95em, $o 1em, $o 1.95em, $do 2em, $do 2.95em, $doT 3em) 14em 26em / 6em 3em,
radial-gradient(200% 200% at 100% 100%, $o 49%, $oT 50%) 18em 25em / 1em 1em,
radial-gradient(200% 200% at 0% 100%, $do 49%, $doT 50%) 19em 25em / 1em 1em;
...
}
然后,在顶部和杆子之间,我们将添加下一块背景,以呈现尖端。为了确定每个片段的宽度,我们必须获得红色和白色相遇的每个点的距离。它们都必须加起来为 30em。

从白色和最窄的红色半圆开始,我们将红色的宽度 9em 从白色的宽度 21em 中减去,并将结果除以 2,得到两个白色片段的宽度(图中的点“b”)。因此,结果将是 6em(b = (21 – 9) / 2 = 6)。然后中间的红色片段将是 9em (21 – (6 + 6) = 9)。现在我们剩下的是外侧的红色片段(图中的点“a”)。从我们现在拥有的总和中减去较大红色半圆的宽度,并将结果除以 2。这将使点 a 的值成为:(30em – (6 + 6 + 9)) / 2 = 4.5em。
.parasol {
background:
...
/* pointy ends */
radial-gradient() 0 13.5em / 4.5em 3em,
radial-gradient() 4.5em 13.5em / 6em 3em,
radial-gradient() 50% 13.5em / 9em 3em,
radial-gradient() 19.5em 13.5em / 6em 3em,
radial-gradient() 25.5em 13.5em / 4.5em 3em,
...
}
为了绘制与我们绘制顶部部分类似的半圆,我们从每个形状的颜色透明对应部分开始,以便它们类似于弧形桥。我们还将为每个渐变宽度(不是背景框宽度)额外添加 5%,以便由相邻背景形成的每个点不会过于尖锐和细薄。
.parasol {
background:
...
/* pointy ends */
radial-gradient(105% 200% at 50% 100%, $rT 49%, $r 50%) 0 13.5em / 4.5em 3em,
radial-gradient(105% 200% at 50% 100%, $wT 49%, $w 50%) 4.5em 13.5em / 6em 3em,
radial-gradient(105% 200% at 50% 100%, $rT 49%, $r 50%) 50% 13.5em / 9em 3em,
radial-gradient(105% 200% at 50% 100%, $wT 49%, $w 50%) 19.5em 13.5em / 6em 3em,
radial-gradient(105% 200% at 50% 100%, $rT 49%, $r 50%) 25.5em 13.5em / 4.5em 3em,
...
}

最后,您将不再需要 1px solid #aaa
的轮廓。我们的遮阳伞完成了!
请参阅 CodePen 上 Jon Kantner (@jkantner) 的 遮阳伞。
带圆角矩形的图形
下一个示例将是一个旧款 iPhone 型号,其中包含比新款型号更多的细节。这个模型的特点是两个圆角矩形,它们是主页按钮的外侧和中间。

调色板将包含黑色 ($bk
和 $bkT
) 用于主页按钮边缘、深灰色 ($dg
和 $dgT
) 用于机身、灰色 ($g
和 $gT
) 用于摄像头和扬声器、浅灰色 ($lg
和 $lgT
) 用于外框、蓝色 ($bl
和 $blT
) 用于摄像头镜头,以及非常深的紫色 ($p
和 $pT
) 用于屏幕。
$bk: rgb(10,10,10);
$bkT: rgba(10,10,10,0);
$dg: rgb(50,50,50);
$dgT: rgba(50,50,50,0);
$g: rgb(70,70,70);
$gT: rgba(70,70,70,0);
$lg: rgb(120,120,120);
$lgT: rgba(120,120,120,0);
$bl: rgb(20,20,120);
$blT: rgba(20,20,120,0);
$p: rgb(25,20,25);
$pT: rgba(25,20,25,0);
让我们在 20 × 40em 处设置画布,并使用与遮阳伞相同的字体大小,10px
.iphone {
// background goes here
background-repeat: no-repeat;
font-size: 10px;
outline: 1px solid #aaa;
width: 20em;
height: 40em;
}
在我们开始绘制第一个圆角矩形之前,我们需要考虑圆角半径,它将是 2em。此外,我们希望在左侧留出一些空间用于锁定开关和音量按钮,它们将是 0.25em。因此,矩形将是 19.75 × 40em。考虑到 2em 的圆角半径,我们将需要两个相互交叉的线性渐变。一个必须有 15.75em 的宽度 (19.75em – 2 × 2),另一个必须有 36em 的高度 (40em – 2 × 2)。将第一个从左侧放置 2.25em,然后将第二个从左侧放置 0.25em,从顶部放置 2em。
.iphone {
background:
/* body */
linear-gradient() 2.25em 0 / 15.75em 40em,
linear-gradient() 0.25em 2em / 19.75em 36em;
...
}
由于浅灰色边框将是 0.5em 厚,让我们让每个渐变立即停止,从浅灰色 ($lg
) 转换为深灰色 ($dg
),反之亦然,在末端之前 0.5em 和 0.5em(对于第一个渐变为 40em – 0.5 = 39.5em,对于第二个渐变为 19.75em – 0.5 = 19.25em)。为第二个设置 90deg 的角度,使其变为水平。
.iphone {
background:
/* body */
linear-gradient($lg 0.5em, $dg 0.5em, $dg 39.5em, $lg 39.5em) 2.25em 0 / 15.75em 40em,
linear-gradient(90deg, $lg 0.5em, $dg 0.5em, $dg 19.25em, $lg 19.25em) 0.25em 2em / 19.75em 36em;
...
}

在每个方形角中,如图中橙色框所示,我们将放置圆角。为了创建这些形状,我们使用大小是它们的边界框两倍的径向渐变,并且位于每个角中。将它们插入机身背景之上。
.iphone {
background:
/* corners */
radial-gradient(200% 200% at 100% 100%, $dg 1.45em, $lg 1.5em, $lg 50%, $lgT 51%) 0.25em 0 / 2em 2em,
radial-gradient(200% 200% at 0% 100%, $dg 1.45em, $lg 1.5em, $lg 50%, $lgT 51%) 18em 0 / 2em 2em,
radial-gradient(200% 200% at 100% 0%, $dg 1.45em, $lg 1.5em, $lg 50%, $lgT 51%) 0.25em 38em / 2em 2em,
radial-gradient(200% 200% at 0% 0%, $dg 1.45em, $lg 1.5em, $lg 50%, $lgT 51%) 18em 38em / 2em 2em,
...
}

为了获得 0.5em 厚的浅灰色末端,考虑渐变的起点,然后进行数学计算。因为浅灰色位于末端,所以我们从 2em 中减去 0.5em 以正确放置第一个停止点。为了平滑过渡,我们从第一个 1.5em 中减去一点,并在第二个 50% 中添加 1%,以便圆角与平角融合。
现在,如果我们通过将字体大小更改为 40px 或更大来放大图像,我们会注意到圆角和平角之间的接缝(橙色圈起来的部分)。

由于它们看起来很小,我们可以通过返回机身背景并稍微调整渐变停止点来轻松修补它们,只要在将字体大小更改回 10px 时一切看起来仍然正确即可。
.iphone {
background:
/* body */
linear-gradient($lg 0.5em, $dg 0.55em, $dg 39.5em, $lg 39.55em) 2.25em 0 / 15.75em 40em,
linear-gradient(90deg, $lg 0.5em, $dg 0.55em, $dg 19.175em, $lg 19.25em) 0.25em 2em / 19.75em 36em;
...
}
然后在一个线性渐变中,我们将添加锁定开关和音量按钮,以填充左侧的 0.25em 空间。如果按钮和机身之间将有 1px 的空间,我们可以在背景宽度中添加 0.05em 的微小出血(使其变为 0.3em),这样它就不会伸出深灰色部分。
.iphone {
background:
/* volume buttons */
linear-gradient($lgT 5em, $lg 5em, $lg 7.5em, $lgT 7.5em, $lgT 9.5em, $lg 9.5em, $lg 11em, $lgT 11em, $lgT 13em, $lg 13em, $lg 14.5em, $lgT 14.5em) 0 0 / 0.3em 100%,
...
}

看起来我们可以使用三个浅灰色到浅灰色的渐变,但由于它可以实现,因此只需要一个。它只是很多从透明浅灰色到不透明浅灰色的突然过渡向下运行。
接下来,让我们添加 Home 按钮的边缘,以及它内部正方形的平边。现在 Home 按钮内的正方形将是 1.5 × 1.5em,并遵循与主体基本相同的步骤:两个相交的线性渐变和径向渐变来填充角。为了将它们水平放置在中心,calc()
会派上用场。表达式将是 50% + 0.125em;如果我们只将手机主体居中,则两侧将有 0.125em 的间距。因此,我们将它向右移动 0.125em。相同的 x 位置将应用于上面的两个背景。
.iphone {
background:
/* home button */
linear-gradient() calc(50% + 0.125em) 36.5em / 0.5em 1.5em,
linear-gradient() calc(50% + 0.125em) 37em / 1.5em 0.5em,
radial-gradient(3em 3em at calc(50% + 0.125em) 37.25em, $bkT 1.25em, $bk 1.3em, $bk 49%, $bkT 50%),
...
}
与我们对手机主体线性渐变部分进行阴影处理的方式类似,停靠点将以浅灰色开头和结尾,但中间为透明。注意,我们在每个灰色到透明过渡(反之亦然)之间留下了 0.05em 的间隙。就像主体角一样,这样做是为了确保圆角和内部平坦端之间的混合。
.iphone {
background:
/* home button */
linear-gradient($lg 0.15em, $lgT 0.2em, $lgT 1.35em, $lg 1.35em) calc(50% + 0.125em) 36.5em / 0.5em 1.5em,
linear-gradient(90deg, $lg 0.15em, $lgT 0.2em, $lgT 1.3em, $lg 1.35em) calc(50% + 0.125em) 37em / 1.5em 0.5em,
radial-gradient(3em 3em at calc(50% + 0.125em) 37.25em, $bkT 1.25em, $bk 1.3em, $bk 49%, $bkT 50%),
...
}

顺便说一句,由于轮廓将像您之前看到的那样很小,因此我们可以通过将字体大小增加到至少 20px 来更好地查看我们的操作。这就像在图像编辑软件中使用缩放工具一样。
现在,为了将灰色正方形的角精确地放到它们应该在的位置,让我们首先关注 x 位置。我们从 calc(50% + 0.125em)
开始,然后我们添加或减去每个部分的宽度,或者我应该说正方形的边框半径。这些背景将位于最后三个背景之上。
.iphone {
background:
/* home button */
radial-gradient(200% 200% at 100% 100%, $lgT 0.3em, $lg 0.35em, $lg 0.48em, $lgT 0.5em) calc(50% + 0.125em - 0.5em) 36.5em / 0.5em 0.5em,
radial-gradient(200% 200% at 0% 100%, $lgT 0.3em, $lg 0.35em, $lg 0.48em, $lgT 0.5em) calc(50% + 0.125em + 0.5em) 36.5em / 0.5em 0.5em,
radial-gradient(200% 200% at 100% 0%, $lgT 0.3em, $lg 0.35em, $lg 0.48em, $lgT 0.5em) calc(50% + 0.125em - 0.5em) 37.5em / 0.5em 0.5em,
radial-gradient(200% 200% at 0% 0%, $lgT 0.3em, $lg 0.35em, $lg 0.48em, $lgT 0.5em) calc(50% + 0.125em + 0.5em) 37.5em / 0.5em 0.5em,
...
}

然后,屏幕将是一个 17.25 × 30em 的矩形。就像 Home 按钮的某些部分一样,我们将使用 calc(50% + 0.125em)
将其水平居中。从顶部开始,它将是 5em。
.iphone {
background:
/* screen */
linear-gradient($p, $p) calc(50% + 0.125em) 5em / 17.25em 30em,
...
}

最后,我们将添加摄像头和扬声器。摄像头是一个简单的 1 × 1 蓝色到灰色的径向渐变,没有花哨的计算。纯灰色的扬声器将稍微复杂一些。它将是一个 5 × 1em 的矩形,并有一个 0.5em 的边框半径。为了绘制它,我们首先使用前 4em 的宽度绘制一个矩形,并使用 calc(50% + 0.125em)
将其居中。然后,我们使用 0.5 × 1em 的半圆,其中渐变位置分别是 100% 50%
和 0% 50%
。将这些半圆放置在矩形的左侧和右侧的最佳方法是使用一些新的 calc()
表达式。对于左侧,我们将从主体中心减去一半矩形宽度和一半半圆宽度(50% + 0.125em - 2em - 0.25em
)。右侧将遵循相同的模式,但使用加法,因此是 50% + 0.125em + 2em + 0.25em
。
.iphone {
background:
/* camera */
radial-gradient(1em 1em at 6.25em 2.5em, $bl 0.2em, $g 0.21em, $g 49%, $gT 50%),
/* speaker */
radial-gradient(200% 100% at 100% 50%, $g 49%, $gT 50%) calc(50% + 0.125em - 2em - 0.25em) 2em / 0.5em 1em,
radial-gradient(200% 100% at 0% 50%, $g 49%, $gT 50%) calc(50% + 0.125em + 2em + 0.25em) 2em / 0.5em 1em,
linear-gradient($g, $g) calc(50% + 0.125em) 2em / 4em 1em,
...
}

删除 div 周围的灰色轮廓,iPhone 就完成了!
查看 CodePen 上 Jon Kantner (@jkantner) 创建的 Pen iPhone。
动画图像
您可能在想,您可以使用 background-position
来为这类图像制作动画,但您只能做这么多。例如,无法单独为单个背景的旋转制作动画。事实上,background-position
动画的性能通常不如 transform
动画,因此我不推荐使用它。
为了以我们希望的任何方式为图像的任何部分制作动画,我们可以让 :before
或 :after
伪元素负责该部分。如果我们需要更多选择,那么我们可以恢复到使用多个子 div,但不需要为每个小细节使用一个 div。对于我们的动画图像示例,我们将创建这个动画雷达

我们首先绘制静态部分,即除灰色框架和旋转指针以外的所有部分。在此之前,让我们提供调色板(注意:我们不需要 $dgnT
对应 $dgn
)和基础代码。
$gn: rgb(0,192,0);
$gnT: rgba(0,192,0,0);
$dgn: rgb(0,48,0);
$gy: rgb(128,128,128);
$gyT: rgba(128,128,128,0);
$bk: rgb(0,0,0);
$bkT: rgba(0,0,0,0);
.radar {
background-repeat: no-repeat;
font-size: 10px;
outline: 1px solid #aaa;
width: 20em;
height: 20em;
}
由于此图像将完全圆形,因此我们可以安全地应用 50% 的边框半径。然后,我们可以使用重复的径向渐变来绘制圆环——彼此之间的距离约为 1/3。
.radar {
background:
/* rings */
repeating-radial-gradient($dgn, $dgn 2.96em, $gn 3em, $gn 3.26em, $dgn 3.3em);
background-repeat: no-repeat;
border-radius: 50%;
...
}

还要注意开头处的额外 $dgn
。为了让重复的渐变按预期开始、结束和循环,我们需要在 0 处指定起始颜色(或者没有 0)。
与之前的示例不同,我们没有使用 calc()
来居中线条,因为当我们稍后使用伪元素时,Internet Explorer 将以笨拙的方式渲染整个内容。为了绘制四条相交于中心的 0.4em 线条,请知道线条的一半应该是 div 一半,即 10em。因此,我们在每侧减去和加上 0.4 的一半(0.4 / 2 = 0.2)。换句话说,绿色的左侧应该是 9.8em,右侧应该是 10.2em。但是,对于 45deg 对角线,我们必须将 10em 乘以 2 的平方根才能得到它们的中心(10 × √2 ≈ 14.14)。这是 10em 直角三角形最长边的长度。因此,每条对角线的边将大约位于 13.94 和 14.34em 处。
.radar {
background:
/* lines */
linear-gradient($gnT 9.8em, $gn 9.8em, $gn 10.2em, $gnT 10.2em),
linear-gradient(45deg,$gnT 13.94em, $gn 13.98em, $gn 14.3em, $gnT 14.34em),
linear-gradient(90deg,$gnT 9.8em, $gn 9.8em, $gn 10.2em, $gnT 10.2em),
linear-gradient(-45deg,$gnT 13.94em, $gn 13.98em, $gn 14.3em, $gnT 14.34em),
...
}

为了防止对角线出现像素化,我们在绿色和透明绿色之间留下了 0.04em 的微小间隙。然后,为了创建一些光照效果,添加这个透明到黑色的径向渐变
.radar {
background:
/* lighting */
radial-gradient(100% 100%, $bkT, $bk 9.9em,$bkT 10em),
...
}

这样就完成了雷达的静态部分。现在,我们在 :before
下的另一个背景堆栈中绘制灰色框架和指针,并添加动画。我们没有在此处包含框架是有原因的。由于指针容器应适合整个 div,因此我们不希望它与框架重叠。
此伪元素将填充空间,为了确保它保持在其中,让我们绝对定位它。我们将使用相同的边框半径,以便它在 Safari 中动画时保持圆形。
.radar {
...
position: relative;
&:before {
background-repeat: no-repeat;
border-radius: 50%;
content: "";
position: absolute;
width: 100%;
height: 100%;
}
}
然后,为了绘制指针,我们将其大小设置为容器的一半,并将其保持在左上角。最后,在它的上面,我们绘制框架。
.radar {
...
&:before {
animation: scan 5s linear infinite;
background:
/* frame */
radial-gradient($gyT 9.20em, $gy 9.25em, $gy 10em, $gyT 10.05em),
/* hand */
linear-gradient(45deg, $gnT 6em, $gn) 0 0 / 50% 50%;
...
}
}
@keyframes scan {
from {
transform: rotate(0);
}
to {
transform: rotate(1turn);
}
}
现在我们的小工具就完成了!
查看 CodePen 上 Jon Kantner (@jkantner) 创建的 Pen 雷达。
优点(以及一个缺点)
这种绘制 CSS 图像的方法有几个优点。首先,与栅格化图像文件相比,HTML 将非常轻量级。其次,对于那些没有使用实验性属性和 API(可能不支持)就无法很好绘制的图像,它非常有用。
这并不是说这种方法比使用嵌套有子元素的父元素来创建形状更好。它确实有一个缺点。您必须放弃使用浏览器开发者工具突出显示单个形状的能力。您需要注释和取消注释背景以识别它是哪个。只要您对每组背景进行分组和标记,就可以更快地找到特定的背景。
结论
简而言之,我们在本文中介绍的 CSS 图像绘制方法使我们能够
- 设置一个由颜色的变量组成的调色板。
- 禁用背景重复,使用
font-size
设置比例,并使用em
单位为目标元素设置画布宽度和高度。 - 使用临时的
outline
来显示我们操作时的边缘。 - 从下到上绘制每个形状,因为背景是按此顺序渲染的。每个形状的
background
语法遵循图像位置/大小
(带或不带位置和大小)。
还有一些超乎寻常的思考,以及为了获得预期结果而进行的实验。我们创建的三个示例足以演示该概念。我们已经了解了如何对每个背景进行排序,绘制圆形的部分,绘制圆角矩形,以及稍微调整渐变停靠点以获得平滑的边缘。要了解更多信息,请随时分解和研究我在 这个 CodePen 收藏集 中制作的其他示例!
哇,太棒了
这是一篇写得非常好的文章,示例也制作得很好,但我看不出为什么要使用嵌套元素方法而不是这种方法,因为嵌套元素方法显然更容易调试。但是,这篇文章真正突出了 CSS 渐变的灵活性!
完全同意
这将背景提升到了一个相当疯狂的水平。我可能不会在圆圈中绘制超过一个箭头。
但我只是想感谢您提到并处理了 Internet Explorer 支持问题。当我看一篇关于某个主题的文章时,发现一个主要浏览器不支持它,真是令人沮丧。这篇文章我可能真的会用到!
IE 11 支持这些演示。
在 Firefox 61/Chrome 67 中更改缩放/DPI 时,iPhone 和雨伞会出现故障——当我看到白色线条(背景)或颜色元素的 1 像素边框时。
很棒的解释和示例。供您参考,这是 2011 年使用渐变绘制图像技术的起点:http://lea.verou.me/css3patterns
迫不及待地想看到 conic-gradient() 被广泛支持,它会让很多事情变得容易很多!
是的,想象一下你可以用它实现什么!可能还需要几年才能在所有浏览器(除了 IE)中获得支持。