使用CSS渐变绘图
特别声明,本文根据@JON KANTNER的《Drawing Images with CSS Gradients》一文所整理。
这里所说的绘制是指CSS图像,即使用HTML元素和CSS属性绘制的图像。它们看起来像是Adobe Illustrator绘制的svg
,但它们是在浏览器中渲染出来的。我所见过的一些技巧是使用border
、box-shadow
和clip-path
来绘制图像。如果你在Codepen搜索“daily css images”,你会发现有很多优秀的案例。我自己也画了一些,也做过一些极限挑战,就是在一个元素上使用background
和尽量使用其他属性来绘制图像。
让我们来睦看如何创建CSS图像。
方法
了解background
语法和CSS渐变的工作原理是使用一个元素绘制任何东西所需要基础。先来看background
语法:
background: <'background-color'> || <image> || <position> [ / <size> ]? || <repeat> || <attachment> || <origin> || <clip>;
除了background-position
和background-size
之间必须要有一个/
来隔开之外,其他任何一个属性都可以以任何顺序出现。这里需要特别注意的是:background-position
和background-size
在简写的background
属性中必须要有/
,否则会得到意想不到的结果。另外不是所有属性都必须使用上的,比如,我们有可能不会使用background-color
、background-repeat
、background-attachment
、background-origin
或background-clip
。如此下来就剩下了background-image
、background-size
和background-position
。由于background-repeat
的默认值是repeat
,所以必须将其设置为no-repeat
。如果背景中有内容需要重复,我们可以使用repeating-linear-gradient()
和repeating-radial-gradient()
两个渐变属性。在这种情况之下,我们的CSS可以这样写:
.image {
background: <image> <position> / <size>;
background-repeat: no-repeat;
}
如果您从未接触过CSS渐变相关的知识,建议您花点时间先阅读下面几篇文章:
我们甚至可以使用多组背景参数(浏览器支持多背景的运用)!因此,我们只需要使用逗号将每组背景参数分隔开来:
.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;
}
简单的理解,background
中有多组背景时,渲染出来的背景顺序和书写的顺序一致。
绘制
我们将使用Sass(SCSS)来绘制这些图像,这样做主要是可以将变量用于调色板。这将代码变得更短,更易于阅读和更易于修改颜色(将颜色变得更暗或更浅)。我们可以使用CSS自定义属性,而不用Sass,但由于IE还不支持CSS自定义属性,所以还是继续使用Sass。为了解释这是如何工作的,我们将使用线性和径向渐变来绘制CSS图像。
特别声明,文章主要出发点是向大家阐述如何使用CSS的渐变来绘制CSS图像,另外为了加强CSS自定义属性的理解和积累相关经验,接下来的示例,译者改变了原作者的初衷,将在示例中使用CSS自定义属性来替代原文中的Sass变量。
设置一个调色板
我们的调色板将用RGB
或HSL
颜色组成。稍后我将解释为什么要用这两种格式来声明颜色。在本例中,我将使用RGB
颜色格式。
有关于颜色更深入的介绍,可以阅读@Jamie Wong的《Color: From Hexcodes to Eyeballs》一文和《优化Web上的颜色》一文。
--r: rgb(255,0,0); // hsl(0,100%,50%)
--o: rgb(255,128,0); // hsl(32,100%,50%)
我个人喜欢使用简短的代码,使用至少一字母来表示每种颜色(例如,--r
表示red
颜色),这样易于阅读。如果使用较深或较浅的一种颜色,我将在字母前增加字母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
等于元素的font-size
,因此如果要改变图像大小,只需要相应调整font-size
大小。我们将font-size
设置为10px
,width
和height
设置为24em
。将font-size
设置为10px
是最简单的,因为基于它做数学计算是最简单的,因此24em
你就知道它对应的是240px
。然后画布的边框设置1px
的灰色。
em
是CSS单位中的其中一个,也是易于造成计算混乱的单位之一,如果你感兴趣,可以点击这里进行了解。当然,你也可以使用CSS处理器中的函数功能来做相应的转换,比如px2em这样的。
--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;
}
另外前面提到,为了易于计算,将font-size
的值设置为10px
,但要注意的是,Chrome浏览器的最小的font-size
的值为12px
,所以在实际使用的时候,要注意这个细节。
此外,你还可以通过使用calc()
和viewport
单位来启用响应性。也许我们可以使用像calc(10px + 2vmin)
这样的东西,不过为了简单易于理解,这里还是使用10px
吧。
calc()
和viewport
结合在一起可以计算出混合字号,这样可以实现在一个范围内的视窗下有具体的像素值。其有一个典型的计算公式:
有关于这方面更详细的介绍,可以阅读下面的相关文章:
绘制图形
有趣的部分从这里开始。在正中间绘制一个8em x 8em
在小的红色正方形。这个红色正方使用使用linear-gradient()
来绘制,不过起始颜色和终止颜色相同。
.image {
background: linear-gradient(var(--r), var(--r)) 50% 50% / 8em 8em;
...
}
如果想绘制一个梯形,只需要在渐变中设置一个60deg
的角度值,用来指定渐变的方向。与此同时,在颜色面板上添加--T
自定义属性的值设置为transparent
。然后将--r
和--T
的位置都设置为63%
(右上角被截):
--T: transparent;
.image {
background: linear-gradient(60deg, var(--r) 63%, var(--T) 63%) 50% 50% / 8em 8em;
...
}
在两个值上设置相同的停止位置,斜切的一侧会有毛边。如果你仔细观察它,它看起来像下面这样:
为了让效果不会有毛边,将其中一个停止的值稍微调整一点(大约1%
),这样就会使用毛边去除掉,边缘更平滑。在上面的示例中,将--r
的63%
换成62%
。
这将是一个圆边的问题,同时在径向渐变中也存在,稍后会看到。如果用的不是Safari浏览器查看效果,那么即使切换到非透明颜色(比如orange
),一切看起来都很不错。但在Safari中,你会注意到斜切的一边有一些黑虚边。
这是因为Safari中的transparent
关键词始终是黑色透明的,因此我们会看到一些黑色的虚边。我真的希望苹果能解决这个问题,但他们永远不会。现在,让我们在--r
添加一个新的属性--redT
,并将其值设置为红色透明度(rgba(255, 0, 0, 0)
)。再把--T
删除,因为将不会再用到这个自定义属性。
--rT: rgba(255,0,0,0); // hsla(0,100%,50%,0)
然后,我们使用--redT
替换transparent
。这就解决了Safari浏览器中虚边的问题。
.image {
background: linear-gradient(60deg,var(--r) 62%, var(--rT) 63%) 50% 50% / 8em 8em;
...
}
你可能会感到好奇,为什么我们不使用十六进制颜色?那是因为IE和Edge不支持#rgba
和#rrggbbaa
的语法(事实上,HEX 早在2016年末就有了alpha
通道,你不知道吧)。我也希望它能尽可能地得到所有浏览器支持。我们还是将颜色格式保持一致吧。
现在把图形垂直移动到20%
,并且在其下面绘制一个相同尺寸的orange
圆。此外,为其透明版本添加另一个自定义属性--oT
。同样的,为了边缘光滑,起始值和终址值之间也有一个1%
的差值。
--oT: rgba(255,128,0,0); // hsla(32,100%,50%,0)
.image {
background:
linear-gradient(60deg,var(--r) 62%, var(--rT) 63%) 50% 20% / 8em 8em,
radial-gradient(8em 8em at 50% 80%, var(--o) 49%, var(--oT) 50%);
...
}
为了与我们的尺寸保持一致,第二个颜色停止值应该是50%
,而不是100%
。
图形位置
渐变的位置取决于单位采用的是固定的还是百分比。假设我们把这两个渐度都变成正方形,并试着把它们横向放到div
中。
.image {
background:
linear-gradient(var(--r), var(--r)) 24em 20% / 8em 8em,
linear-gradient(var(--o), var(--o)) 100% 80% / 8em 8em;
...
}
红色方块完全移出画布,橙色方块的右边与画布右边相连接。使用固定单位就像在HTML5的canvas
中放置绝对定位的元素或绘制图形。从这个意义上说,原点在左上方。当使用百分比设置背景大小时,div
获得“假填充”是background-size
的一半。同时,背景的原点是居中的(不要与background-origin
混淆,背景原点是指盒模型的左上角)。
现在,我们把渐变换成径向渐变来绘制圆,x
位置设置为24em
和100%
,最后的效果是两个圆的另一半都被移到画布的外面(被切掉一半)。这是因为,我们这样写背景,原点总是在中间:
.image {
background:
radial-gradient(8em 8em at 24em 20%, var(--r) 49%, var(--rT) 50%),
radial-gradient(8em 8em at 100% 80%, var(--o) 49%, var(--oT) 50%);
...
}
如果我们重写背景,让位置和大小都在渐变之后,并且使用100% 100% at center
,这样一来,它们会被认为是线性渐变。红色的移到画布外面,橙色的在画布最右边。“假填充”再次出现在橙色部分。
.image {
background:
radial-gradient(100% 100% at center, var(--r) 49%, var(--rT) 50%) 24em 20% / 8em 8em,
radial-gradient(100% 100% at center, var(--o) 49%, var(--oT) 50%) 100% 80% / 8em 8em;
...
}
没有一种正确的方法来定位形状,但是要将其定位成一个绝对的或固定的HTML元素,最好是使用固定的单位。如果需要一个快速的方法来放置一个形状(使用position/size
)在死中心,50%
是最好的选择,因为形状的原点将是它的中心。如果它应该触及可侧,那就用100%
。
图形尺寸
CSS背景下的大小调整和我们预期的一样,但是它们仍然受到固定或百分比的单位类型的影响。同样拿方块来举例,把它们的宽度改变10em
,红色的方块向右展开,橙色的方块向两边展开。
.image {
background:
linear-gradient(var(--r), var(--r)) 12em 20% / 10em 8em,
linear-gradient(var(--o), var(--o)) 50% 80% / 10em 8em;
...
}
如果在y
位置使用em
单位,形状会向上或向上收缩改变高度。如果我们用百分比单位,会在两个方向展开。
刚才,我们讨论了用径向渐变画圆的两种方法。第一种方法是在(
和at
指定width
和height
,然后在at
后指定位置:
.image {
background:
radial-gradient(8em 8em at 50% 50%, var(--r) 49%, var(--rT) 50%);
...
}
第二种方法是在(
和at
之间使用100% 100%
,然后给出位置和尺寸:
.image {
background:
radial-gradient(100% 100% at 50% 50%, var(--r) 49%, var(--rT) 50%) 50% 50% / 8em 8em;
...
}
这些方法都可以使用径向渐变画圆,但会产生不同的输出,那是因为:
- 第一种方法占用整个
div
,因为没有真正的background-position
和background-size
- 第二种方法设置了一个边框,有实际的位置和大小。因此,它就像一个线性渐变
假设我们用--o
替换--rT
。你会看到橙色覆盖白色。如果使用第二种方法,你将很容易地注意到橙色所显示的边框。
另外,使用circle
或ellipse
替换100% 100%
的目的是让圆占据整个包围盒。它甚至能让我们完全控制它的尺寸。那样的话,如果你把50% 50%
换成别的东西,它就会保持不变。如果使用这两个关键字中的一个,在居中时圆的边缘只有大约71%
的距离,在调整位置时变得更加扭曲。例如,当我们将circle
和ellipse
的x
坐标改为0
时,会发生以下情况:
从长远来看,你可以将语法重新想象为radial-gradient(width height at x y)
或radial-gradient(100% 100% at 限定框x位置 限定框y位置) x y / width height
。如果你只是绘制一个圆或椭圆,你可以使用第一种简化的代码。如果画一个圆的一部分或者一个环的一部分,那么第二种简化的方法是很好的选择。在接下来的示例中,我们将会有很多这样的应用。
案例
准备好了吗?我们将一步一步地介绍三个例子。前两个是静态的,一个有很多半圆,另一个有圆形也有矩形。最后一个例子将更小,但重点是动画。
绘制雨伞
这个雨伞将是我们要绘制的第一个静态图像:
我们的调色板配置有red
(--r
和--rT
),white
(--w
和--wT
),orange
(--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);
绘图区域的大小为30em x 29em
。
.parasol {
// 背景相关的样式放在这里
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%, var(--do) 49%, var(--doT) 50%) 14em 0 / 1em 1em,
radial-gradient(200% 200% at 0% 100%, var(--o) 49%, var(--oT) 50%) 15em 0 / 1em 1em,
// 2
linear-gradient(90deg, var(--do) 50%, var(--o) 50%) 14em 1em / 2em 25em,
// 3
radial-gradient(100% 200% at 50% 0, var(--oT) 0.95em, var(--o) 1em, var(--o) 1.95em, var(--do) 2em, var(--do) 2.95em, var(--doT) 3em) 14em 26em / 6em 3em,
// 4
radial-gradient(200% 200% at 100% 100%, var(--o) 49%, var(--oT) 50%) 18em 25em / 1em 1em,
radial-gradient(200% 200% at 0% 100%, var(--do) 49%, var(--doT) 50%) 19em 25em / 1em 1em;
...
}
上面一坨代码,估计一下子无法理解(对于初学CSS的同学而言)。把上面的代码拆分一下:
// 1
radial-gradient(200% 200% at 100% 100%, var(--do) 49%, var(--doT) 50%) 14em 0 / 1em 1em,
radial-gradient(200% 200% at 0% 100%, var(--o) 49%, var(--oT) 50%) 15em 0 / 1em 1em,
绘制伞把顶部,即一个1em x 1em
的半圆。为了让它们占据整个容器,把整个圆圈放大了两倍(设置 200% 200%
),它们分别位于右下角和左下角。我们也可以使用关键字来设置圆的位置,比如bottom right
和bottom left
,但使用百分比,要简短些。注意,两个颜色停止之间有一个1%
的位置差距,以确保绘制的图形效果平滑。
// 2
linear-gradient(90deg, var(--do) 50%, var(--o) 50%) 14em 1em / 2em 25em,
接着绘制最长的那部分,这部分是一个从深橙色到橙色的长矩形。这两个颜色的停止位置不需要有一个很小的差值,因为这部分没有曲线,也没有斜切角。
// 3
radial-gradient(100% 200% at 50% 0, var(--oT) 0.95em, var(--o) 1em, var(--o) 1.95em, var(--do) 2em, var(--do) 2.95em, var(--doT) 3em) 14em 26em / 6em 3em,
第三部分相对而言要复杂一些,因为我们要保持一个2em
直径的弧形。画这个圆弧,将background-size
设置为6em x 3em
,两者之间有一个2em
的间距。然后在中心处使用径向渐变,每一个停止的位置都发生在1em
处,为了让圆弧平滑,设置了一个差值为.05em
。
// 4
radial-gradient(200% 200% at 100% 100%, var(--o) 49%, var(--oT) 50%) 18em 25em / 1em 1em,
radial-gradient(200% 200% at 0% 100%, var(--do) 49%, var(--doT) 50%) 19em 25em / 1em 1em;
最后一部分和第一部分一样,只是它们的位置调整了。另外颜色也互换了一下。
把这四个部分结合起来,就绘制出雨伞的雨把。
雨伞的伞把绘制出来了,接下来把下面的代码添加到最顶部,绘制伞布:
// 雨伞伞布
radial-gradient(100% 200% at 50% 100%, var(--r) 50%, var(--rT) 50.25%) 50% 1.5em / 9em 12em,
radial-gradient(100% 200% at 50% 100%, var(--w) 50%, var(--wT) 50.25%) 50% 1.5em / 21em 12em,
radial-gradient(100% 200% at 50% 100%, var(--r) 50%, var(--rT) 50.25%) 50% 1.5em / 30em 12em,
为了画出这部分的半圆,我们使用了100% 200%
的径向渐变,使每个直径与背景宽度相匹配,但高度是背景的两倍,并且在底部居中。通过从下到上的排序,使得最大的在下面,最小的在上面。这样就得到了我们想要的曲线。
随着渐变叠加越来越多,代码也就逐渐变得多起来,过一段时间就很难区分出哪个背景或一组背景对应图像的哪个部分。因此,为了更容易地确定它们,我们把它们分成几个小组,每个小组都添加上注释。这样易于后期能更读懂代码。
.parasol {
background:
// 雨伞伞布
radial-gradient(100% 200% at 50% 100%, var(--r) 50%, var(--rT) 50.25%) 50% 1.5em / 9em 12em,
radial-gradient(100% 200% at 50% 100%, var(--w) 50%, var(--wT) 50.25%) 50% 1.5em / 21em 12em,
radial-gradient(100% 200% at 50% 100%, var(--r) 50%, var(--rT) 50.25%) 50% 1.5em / 30em 12em,
// 雨伞伞把
// 1
radial-gradient(200% 200% at 100% 100%, var(--do) 49%, var(--doT) 50%) 14em 0 / 1em 1em,
radial-gradient(200% 200% at 0% 100%, var(--o) 49%, var(--oT) 50%) 15em 0 / 1em 1em,
// 2
linear-gradient(90deg, var(--do) 50%, var(--o) 50%) 14em 1em / 2em 25em,
// 3
radial-gradient(100% 200% at 50% 0, var(--oT) 0.95em, var(--o) 1em, var(--o) 1.95em, var(--do) 2em, var(--do) 2.95em, var(--doT) 3em) 14em 26em / 6em 3em,
// 4
radial-gradient(200% 200% at 100% 100%, var(--o) 49%, var(--oT) 50%) 18em 25em / 1em 1em,
radial-gradient(200% 200% at 0% 100%, var(--do) 49%, var(--doT) 50%) 19em 25em / 1em 1em;
background-repeat: no-repeat;
font-size: 10px;
outline: 1px solid #aaa;
width: 30em;
height: 29em;
}
然后在雨伞的伞布和伞把之间添加另一部分背景,用来绘制伞布边缘的弧形效果。为了确定每个线段的宽度,我们必须得到红白相交点之间的距离。它们加起来必须是30em
。
从白色和最窄的红色半圆开始,从白色的宽度21em
中减去红色的9em
宽度,再除以2
,得到两个白色部分的宽度(也就是上图中b
的宽度),结果是6em
(b = (21 - 9) / 2 = 6em
)。中间的红色线段是9em
(21 - (6 + 6) = 9em
)。现在剩下的是最外面的红色线段(图中的a
点),从最大的红色半圆的宽度30em
减去现在得到的和(也就是中间白色的宽度21em
),再除以2
。这样a
点的值为(30 - 21) / 2 = 4.5em
。
.parasol {
background:
...
// 雨伞伞布边缘的弧形
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:
// 雨伞伞布
...
// 雨伞伞布边缘的弧形
radial-gradient(105% 200% at 50% 100%, var(--rT) 49%, var(--r) 50%) 0 13.5em / 4.5em 3em,
radial-gradient(105% 200% at 50% 100%, var(--wT) 49%, var(--w) 50%) 4.5em 13.5em / 6em 3em,
radial-gradient(105% 200% at 50% 100%, var(--rT) 49%, var(--r) 50%) 50% 13.5em / 9em 3em,
radial-gradient(105% 200% at 50% 100%, var(--wT) 49%, var(--w) 50%) 19.5em 13.5em / 6em 3em,
radial-gradient(105% 200% at 50% 100%, var(--rT) 49%, var(--r) 50%) 25.5em 13.5em / 4.5em 3em,
// 雨伞伞把
...
}
最后,将不再需要那个灰色的辅助边框线,这时可以把outline: 1px solid #aaa
样式删除。最终的雨伞效果
就完成了。如下所示:
绘制带圆角的矩形
接下来这个示例是使用同样的手法来绘制一个旧的iPhone模型,在这个模型中有比新模型更多的细节。这个模型有两个特点:两个带圆形的矩形和Home键。
接下来的示例和
Devices.css
中绘制的iPhone模型有所不同,接下来的示例是使用CSS渐变在一个HTML元素上完成的。
和前面的示例一样,同样先创建一个调色板。这个调色板包括用于Home键按钮边缘的黑色(--bk
和--bkT
),用于相机和话筒的灰色(--g
和--gT
),模型外边框的浅灰色(--lg
和--lgT
),镜头的蓝色(--bl
和blT
)和屏幕的深紫色(--p
和--pT
)。
:root {
--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);
}
设置一个20em x 40em
画布和使用10px
的font-size
:
.iphone {
// 背景样式放置在这
background-repeat: no-repeat;
font-size: 10px;
outline: 1px solid #aaa;
width: 20em;
height: 40em;
}
在开始绘制第一个圆角矩形之前,我们需要考虑圆角的半径,这里设置为2em
。另外,我们还需要考虑给锁开关和音量按钮留一些空间,这里设置为0.25em
。出于这个原因,矩形的大小将是19.75em x 40em
。考虑到2em
的圆角,我们需要两个线性渐变相交。那么矩形的宽度是15.75em
(19.75 - 2 x 2 = 15.75em
)和高度是36em
(40 - 2 x 2 = 36em
)。第一个位置是2.25em 0
,第二个位置是0.25em 2em
:
.iphone {
background:
// body
linear-gradient() 2.25em 0 / 15.75em 40em,
linear-gradient() 0.25em 2em / 19.75em 36em;
...
}
模型浅灰色的边框厚度是.5em
,第一个线性渐变从浅灰(--lg
)到深灰(--dg
),它们的结束位置都在0.5em
位置,接着从深灰(--dg
)到浅灰(--lg
),而它们的结束位置都在39.5em
(40 - 0.5 = 39.5em
)。第二个线性渐变设置了一个90deg
的角度,让渐变是一个水平渐变,这个渐变同样的从浅灰(--lg
)到深灰(--dg
),两者结束位置是0.5em
,接着从深灰(--dg
)到浅灰(--lg
),它们的结束位置是19.25em
(19.75 - 0.5 = 19.25em
)。
.iphone {
background:
// body
linear-gradient(var(--lg) 0.5em, var(--dg) 0.5em, var(--dg) 39.5em, var(--lg) 39.5em) 2.25em 0 / 15.75em 40em,
linear-gradient(90deg, var(--lg) 0.5em, var(--dg) 0.5em, var(--dg) 19.25em, var(--lg) 19.25em) 0.25em 2em / 19.75em 36em;
}
分解一下,易于理解:
linear-gradient(var(--lg) 0.5em, var(--dg) 0.5em, var(--dg) 39.5em, var(--lg) 39.5em) 2.25em 0 / 15.75em 40em
linear-gradient(90deg, var(--lg) 0.5em, var(--dg) 0.5em, var(--dg) 19.25em, var(--lg) 19.25em) 0.25em 2em / 19.75em 36em
动态组合一下:
每个方形的角落将放置圆形的边缘。要创建这些形状,我们就需要使用径向渐变,它的大小是它们的边框的两倍,并且位于每个角落。把这四个圆形的代码放在模型主体之上:
.iphone {
background:
// 四个圆角
radial-gradient(200% 200% at 100% 100%, var(--dg) 1.45em, var(--lg) 1.5em, var(--lg) 50%, var(--lgT) 51%) 0.25em 0 / 2em 2em,
radial-gradient(200% 200% at 0% 100%, var(--dg) 1.45em, var(--lg) 1.5em, var(--lg) 50%, var(--lgT) 51%) 18em 0 / 2em 2em,
radial-gradient(200% 200% at 100% 0%, var(--dg) 1.45em, var(--lg) 1.5em, var(--lg) 50%, var(--lgT) 51%) 0.25em 38em / 2em 2em,
radial-gradient(200% 200% at 0% 0%, var(--dg) 1.45em, var(--lg) 1.5em, var(--lg) 50%, var(--lgT) 51%) 18em 38em / 2em 2em,
...
}
要得到0.5em
厚的浅灰色末端,考虑一下渐变从哪里开始,然后算一下。因为浅灰色在最后,我们从2em
中减去.5em
。对于平滑度,从1.5em
中去掉一点小距离0.05em
,然后在后面的停止位置添加1%
,变成51%
。
现在,如果我们将font-size
修改为40px
或更大来放大图像,我们会注意到圆角和平角之间的接缝(用橙色圈起来的位置):
因为它们看起来很小,我们可以很容易地把它们补上,只要把字体大小改回10px
就可以了。
.iphone {
background:
// body
linear-gradient(var(--lg) 0.5em, var(--dg) 0.55em, var(--dg) 39.5em, var(--lg) 39.55em) 2.25em 0 / 15.75em 40em,
linear-gradient(90deg, var(--lg) 0.5em, var(--dg) 0.55em, var(--dg) 19.175em, var(--lg) 19.25em) 0.25em 2em / 19.75em 36em;
...
}
然后在一个线性渐变中,将添加锁开关和音量按钮来填充左边的0.25em
空间。如果按钮和主体之间有一个1px
的空间,我们可以在背景宽度(0.3em
)上增加0.05em
,这样它就不会突出到深灰色上。
.iphone {
background:
// 锁开关和音量按钮
linear-gradient(var(--lgT) 5em, var(--lg) 5em, var(--lg) 7.5em, var(--lgT) 7.5em, var(--lgT) 9.5em, var(--lg) 9.5em, var(--lg) 11em, var(--lgT) 11em, var(--lgT) 13em, var(--lg) 13em, var(--lg) 14.5em, var(--lgT) 14.5em) 0 0 / 0.3em 100%,
...
}
看起来我们可以使用三个浅灰到浅灰的渐变。只是在透明和不透明之间发生了些变化。
接下来,添加Home键以及它里面正方形的平边。Home键大小是1.5em x 1.5em
,其绘制和模型主体有相同的过程:两个相交线性渐变和圆角来组成。只不过要将它们放置水平方向正中间,这个时候calc()
就非常有用了。50% + 0.125em
为表达式,如果我们只将模型主体居中,那么每边将有0.125em
空间。因此,我们向右移0.125em
。相同的x
定位将适用于两个背景上。
.iphone {
background:
// Home键
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, var(--bkT) 1.25em, var(--bk) 1.3em, var(--bk) 49%, var(--bkT) 50%),
...
}
类似于我们模型主体的线性渐变,停止将以浅灰色开始和结束,但中间是透明的。注意,我们在每个灰度到透明的转换之间留下了0.05em
的间距,就像模型主体的圆形一样,为了确保圆角和内部的平角能融合起来。
.iphone {
background:
// Home键
linear-gradient(var(--lg) 0.15em, var(--lgT) 0.2em, var(--lgT) 1.35em, var(--lg) 1.35em) calc(50% + 0.125em) 36.5em / 0.5em 1.5em,
linear-gradient(90deg, var(--lg) 0.15em, var(--lgT) 0.2em, var(--lgT) 1.3em, var(--lg) 1.35em) calc(50% + 0.125em) 37em / 1.5em 0.5em,
radial-gradient(3em 3em at calc(50% + 0.125em) 37.25em, var(--bkT) 1.25em, var(--bk) 1.3em, var(--bk) 49%, var(--bkT) 50%),
...
}
顺便说一下,和前面一样,我们可以通过将font-size
增加到至少20px
来看看我们在做什么。这有点像图像编辑软件中的缩放工具。
现在要把灰色方块的角精确到它们应该在的位置,首先要关注的是x
的位置。同样从calc(50% + 0.125em)
开始,然后加上或减去每一块的宽度,或者应该说是正方形的圆角半径。这些背景将超过最后三个。
.iphone {
background:
// Home键
radial-gradient(200% 200% at 100% 100%, var(--lgT) 0.3em, var(--lg) 0.35em, var(--lg) 0.48em, var(--lgT) 0.5em) calc(50% + 0.125em - 0.5em) 36.5em / 0.5em 0.5em,
radial-gradient(200% 200% at 0% 100%, var(--lgT) 0.3em, var(--lg) 0.35em, var(--lg) 0.48em, var(--lgT) 0.5em) calc(50% + 0.125em + 0.5em) 36.5em / 0.5em 0.5em,
radial-gradient(200% 200% at 100% 0%, var(--lgT) 0.3em, var(--lg) 0.35em, var(--lg) 0.48em, var(--lgT) 0.5em) calc(50% + 0.125em - 0.5em) 37.5em / 0.5em 0.5em,
radial-gradient(200% 200% at 0% 0%, var(--lgT) 0.3em, var(--lg) 0.35em, var(--lg) 0.48em, var(--lgT) 0.5em) calc(50% + 0.125em + 0.5em) 37.5em / 0.5em 0.5em,
...
}
然后制作屏幕,屏幕是一个17.25em x 30em
的矩形。就像Home键的一部分一样,使用calc(50% + 0.125em)
让矩形水平居中。而且这个矩形从顶部5em
位置处开始。
.iphone {
background:
// 屏幕
linear-gradient(var(--p), var(--p)) calc(50% + 0.125em) 5em / 17.25em 30em,
...
}
最后,将添加摄像头和扬声器。摄像头是一个简单的1em x 1em
从蓝色到灰色的径向渐变。不过,纯灰色的扬声器会更复杂一些。这将是一个5em x 1em
矩形和有带两个0.5em
半径的圆弧。要画出来,先画一个矩形,宽度是4em
,水平居中使用calc(50% + 0.125em)
。然后使用0.5em x 1em
的径向渐变画圆弧,这两个圆弧的位置分别是100% 50%
和50% 0%
。把圆弧放置到矩形左右两边,其最佳方式还是要使用calc()
表达式。左边的将从主体中心减去矩形一半宽度和半圆宽度(50% + 0.125em - 2em - 0.25em
)。右边的遵循同样的模式,但不是做减法,是做加法(50% + 0.125em + 2em + 0.25em
)。
.iphone {
background:
// 摄像头
radial-gradient(1em 1em at 6.25em 2.5em, var(--bl) 0.2em, var(--g) 0.21em, var(--g) 49%, var(--gT) 50%),
// 扬声器
radial-gradient(200% 100% at 100% 50%, var(--g) 49%, var(--gT) 50%) calc(50% + 0.125em - 2em - 0.25em) 2em / 0.5em 1em,
radial-gradient(200% 100% at 0% 50%, var(--g) 49%, var(--gT) 50%) calc(50% + 0.125em + 2em + 0.25em) 2em / 0.5em 1em,
linear-gradient(var(--g), var(--g)) calc(50% + 0.125em) 2em / 4em 1em,
...
}
最终的效果如下:
雷达图
你可能认为可以使用background-position
让这些背景图像动起来(有动画效果),但是你只能做这么多。例如,一个独立的背景是不可能产生动画效果的。事实上,background-position
动画通常不如transform
的动画效果好,所以我不推荐它。
如果想使图像的任何部分以我们希望的方式动画,我们可以借助伪元素:before
或:after
来完成。如果我们需要更多的选择,那么我们可以使用多个div
。接下来我们要做一个雷达扫描的动画效果,如下图所示:
我们先画静态部分,灰色的框架和转盘。同样的,先创建一个调色板和写一些基本代码:
:root {
--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;
}
雷达图完全是个圆的,所以我们可以使用border-radius: 50%
来搞定圆形。然后,可以使用一个repeating-radial-gradient
来绘制这些圆环,它们之间的距离约为1/3
。
.radar {
background:
/* rings */
repeating-radial-gradient(var(--dgn), var(--dgn) 2.96em, var(--gn) 3em, var(--gn) 3.26em, var(--dgn) 3.3em);
background-repeat: no-repeat;
border-radius: 50%;
...
}
与前面的示例不同,这个示例没有使用calc()
来设置水平居中,因为稍后使用伪元素时,IE将会渲染的很慢。接下来要在中间画四条相交的0.4em
的直线,要知道在10em
时,这条线的一半应该是div
的一半。然后,两边同时减去0.2
(0.4 / 2 = 0.2em
)。换句话说,绿色的左边应该是9.8em
,右边应该是10.2em
。45
度的对角线必须用10em
乘以2
的平方根,计算出它的中心(10 × √2 ≈ 14.14
)。它是10em
直角三角开最长边的长度。因此,每条对角线的边长大约为13.94em
和14.34em
。
.radar {
background:
// lines
linear-gradient(var(--gnT) 9.8em, var(--gn) 9.8em, var(--gn) 10.2em, var(--gnT) 10.2em),
linear-gradient(45deg,var(--gnT) 13.94em, var(--gn) 13.98em, var(--gn) 14.3em, var(--gnT) 14.34em),
linear-gradient(90deg,var(--gnT) 9.8em, var(--gn) 9.8em, var(--gn) 10.2em, var(--gnT) 10.2em),
linear-gradient(-45deg,var(--gnT) 13.94em, var(--gn) 13.98em, var(--gn) 14.3em, var(--gnT) 14.34em),
...
}
为了防止对角线的像素化,我们在green
和transparent
之间留下0.04em
的间隙。然后,为了模拟照明效果,添加一个透明到黑色的径向渐变。
.radar {
background:
// lighting
radial-gradient(100% 100%, var(--bkT), var(--bk) 9.9em,var(--bkT) 10em),
...
}
这就完成了雷达的静态部分。现在我们把灰色的框架和手柄放在:before
,主要是用来添加动画。有一个原因,在这里没有包含坐标系统。因为手感器应该适合整个div
,我们不希望它与框架重叠。
这个伪元素要填满整个div
的空间,这里使用绝对定位来做。
.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(var(--gyT) 9.20em, var(--gy) 9.25em, var(--gy) 10em, var(--gyT) 10.05em),
// hand
linear-gradient(45deg, var(--gnT) 6em, var(--gn)) 0 0 / 50% 50%;
...
}
}
@keyframes scan {
from {
transform: rotate(0);
}
to {
transform: rotate(1turn);
}
}
最终效果如下:
总结
简单地说,这篇文章介绍了CSS绘制图像的方法。这里主要用到了:
- 使用CSS自定义属性为颜色设置调色板
- 禁用
background-repeat
为repeat
,并且使用em
为单位,更好的基于font-size
做缩放 - 要想得到想要的结果,需要进地大量的思考和实验
- 从下到上绘制每个图形,这主要是考虑背景是按这个顺序呈现的,每个形状的背景语法遵循
background-positon / size
(是否包含位置和大小)
从上面这几个示例中可以看出,基于一个HTML元素,使用CSS渐变可以绘制很多图像。不过要想得到想的结果,需要进行大量的思考和实验。从文章中我们学会了如何对生个背景进行排序,如何绘制圆的部分,圆角矩形,以及如何为圆弧和斜切角边缘平滑。要了解更多,请自由的剖析和研究我在Codepen上收集的其他例子。
如需转载,烦请注明出处:https://www.fedev.cn/css/drawing-images-with-css-gradients.htmlNeo Cloudfoam Pure