56

使用CSS渐变绘图

 5 years ago
source link: https://www.w3cplus.com/css/drawing-images-with-css-gradients.html?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

特别声明,本文根据 @JON KANTNER 的《 Drawing Images with CSS Gradients 》一文所整理。

这里所说的绘制是指 CSS图像 ,即使用HTML元素和CSS属性绘制的图像。它们看起来像是Adobe Illustrator绘制的 svg ,但它们是在浏览器中渲染出来的。我所见过的一些技巧是使用 borderbox-shadowclip-path 来绘制图像。如果你在 Codepen搜索“daily css images” ,你会发现有很多优秀的案例。我自己也画了一些,也做过 一些极限挑战 ,就是在一个元素上使用 background 和尽量使用其他属性来绘制图像。

让我们来睦看如何创建CSS图像。

方法

了解 background 语法和CSS渐变的工作原理是使用一个元素绘制任何东西所需要基础。先来看 background 语法:

background: <'background-color'> || <image> || <position> [ / <size> ]? || <repeat> || <attachment> || <origin> || <clip>;

除了 background-positionbackground-size 之间必须要有一个 / 来隔开之外,其他任何一个属性都可以以任何顺序出现。这里需要特别注意的是: background-positionbackground-size 在简写的 background 属性中必须要有 / ,否则会得到意想不到的结果。另外不是所有属性都必须使用上的,比如,我们有可能不会使用 background-colorbackground-repeatbackground-attachmentbackground-originbackground-clip 。如此下来就剩下了 background-imagebackground-sizebackground-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;
}

vI7jemf.png!web

简单的理解, background 中有多组背景时,渲染出来的背景顺序和书写的顺序一致。

绘制

我们将使用Sass(SCSS)来绘制这些图像,这样做主要是可以将变量用于调色板。这将代码变得更短,更易于阅读和更易于修改颜色(将颜色变得更暗或更浅)。我们可以使用CSS自定义属性,而不用Sass,但由于IE还不支持CSS自定义属性,所以还是继续使用Sass。为了解释这是如何工作的,我们将使用线性和径向渐变来绘制CSS图像。

特别声明,文章主要出发点是向大家阐述如何使用CSS的渐变来绘制CSS图像,另外为了加强CSS自定义属性的理解和积累相关经验,接下来的示例,译者改变了原作者的初衷,将在示例中使用CSS自定义属性来替代原文中的Sass变量。

设置一个调色板

我们的调色板将用 RGBHSL 颜色组成。稍后我将解释为什么要用这两种格式来声明颜色。在本例中,我将使用 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 设置为 10pxwidthheight 设置为 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 结合在一起可以计算出混合字号,这样可以实现在一个范围内的视窗下有具体的像素值。其有一个典型的计算公式:

advanced-calc-800-opt.pn

有关于这方面更详细的介绍,可以阅读下面的相关文章:

绘制图形

有趣的部分从这里开始。在正中间绘制一个 8em x 8em 在小的红色正方形。这个红色正方使用使用 linear-gradient() 来绘制,不过起始颜色和终止颜色相同。

.image {
    background: linear-gradient(var(--r), var(--r)) 50% 50% / 8em 8em;

    ...
}

vMNNjyV.png!web

如果想绘制一个梯形,只需要在渐变中设置一个 60deg 的角度值,用来指定渐变的方向。与此同时,在颜色面板上添加 --T 自定义属性的值设置为 transparent 。然后将 --r--T 的位置都设置为 63% (右上角被截):

--T: transparent;

.image {
    background: linear-gradient(60deg, var(--r) 63%, var(--T) 63%) 50% 50% / 8em 8em;

    ...
}

RB3I7vV.png!web

在两个值上设置相同的停止位置,斜切的一侧会有毛边。如果你仔细观察它,它看起来像下面这样:

maiEvym.png!web

为了让效果不会有毛边,将其中一个停止的值稍微调整一点(大约 1% ),这样就会使用毛边去除掉,边缘更平滑。在上面的示例中,将 --r63% 换成 62%

这将是一个圆边的问题,同时在径向渐变中也存在,稍后会看到。如果用的不是Safari浏览器查看效果,那么即使切换到非透明颜色(比如 orange ),一切看起来都很不错。但在Safari中,你会注意到斜切的一边有一些黑虚边。

trapezoid_smooth_safari.png

这是因为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;
    ...
}

6zMJfqj.png!web

你可能会感到好奇,为什么我们不使用十六进制颜色?那是因为 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%);

    ...
}

yEbIBvR.png!web

为了与我们的尺寸保持一致,第二个颜色停止值应该是 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;

    ...
}

F3A7bqi.png!web

红色方块完全移出画布,橙色方块的右边与画布右边相连接。使用固定单位就像在HTML5的 canvas 中放置绝对定位的元素或绘制图形。从这个意义上说,原点在左上方。当使用百分比设置背景大小时, div 获得“假填充”是 background-size 的一半。同时,背景的原点是居中的(不要与 background-origin 混淆,背景原点是指盒模型的左上角)。

26vymu3.png!web

现在,我们把渐变换成径向渐变来绘制圆, x 位置设置为 24em100% ,最后的效果是两个圆的另一半都被移到画布的外面(被切掉一半)。这是因为,我们这样写背景,原点总是在中间:

.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%);

    ...
}

qye6V3V.png!web

如果我们重写背景,让位置和大小都在渐变之后,并且使用 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;

    ...
}

rMj2uqz.png!web

没有一种正确的方法来定位形状,但是要将其定位成一个绝对的或固定的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;

    ...
}

iiaIbau.png!web

如果在 y 位置使用 em 单位,形状会向上或向上收缩改变高度。如果我们用百分比单位,会在两个方向展开。

刚才,我们讨论了用径向渐变画圆的两种方法。第一种方法是在 (at 指定 widthheight ,然后在 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-positionbackground-size
  • 第二种方法设置了一个边框,有实际的位置和大小。因此,它就像一个线性渐变

假设我们用 --o 替换 --rT 。你会看到橙色覆盖白色。如果使用第二种方法,你将很容易地注意到橙色所显示的边框。

Zf6jeu6.png!web

另外,使用 circleellipse 替换 100% 100% 的目的是让圆占据整个包围盒。它甚至能让我们完全控制它的尺寸。那样的话,如果你把 50% 50% 换成别的东西,它就会保持不变。如果使用这两个关键字中的一个,在居中时圆的边缘只有大约 71% 的距离,在调整位置时变得更加扭曲。例如,当我们将 circleellipsex 坐标改为 0 时,会发生以下情况:

n2MFFzE.png!web

从长远来看,你可以将语法重新想象为 radial-gradient(width height at x y)radial-gradient(100% 100% at 限定框x位置 限定框y位置) x y / width height 。如果你只是绘制一个圆或椭圆,你可以使用第一种简化的代码。如果画一个圆的一部分或者一个环的一部分,那么第二种简化的方法是很好的选择。在接下来的示例中,我们将会有很多这样的应用。

案例

准备好了吗?我们将一步一步地介绍三个例子。前两个是静态的,一个有很多半圆,另一个有圆形也有矩形。最后一个例子将更小,但重点是动画。

绘制雨伞

这个雨伞将是我们要绘制的第一个静态图像:

qIZn2yB.png!web

我们的调色板配置有 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;
    ...
}

RNfQJzy.png!web

上面一坨代码,估计一下子无法理解(对于初学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 rightbottom left ,但使用百分比,要简短些。注意,两个颜色停止之间有一个 1% 的位置差距,以确保绘制的图形效果平滑。

Zr6f6b6.png!web

// 2
linear-gradient(90deg, var(--do) 50%, var(--o) 50%) 14em 1em / 2em 25em,

接着绘制最长的那部分,这部分是一个从深橙色到橙色的长矩形。这两个颜色的停止位置不需要有一个很小的差值,因为这部分没有曲线,也没有斜切角。

jiyiInV.png!web

// 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

QfaMb2Q.png!web

// 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;

最后一部分和第一部分一样,只是它们的位置调整了。另外颜色也互换了一下。

jUbuEjR.png!web

把这四个部分结合起来,就绘制出雨伞的雨把。

IRfmYjn.gif

雨伞的伞把绘制出来了,接下来把下面的代码添加到最顶部,绘制伞布:

// 雨伞伞布
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,

Fzme6nn.png!web

为了画出这部分的半圆,我们使用了 100% 200% 的径向渐变,使每个直径与背景宽度相匹配,但高度是背景的两倍,并且在底部居中。通过从下到上的排序,使得最大的在下面,最小的在上面。这样就得到了我们想要的曲线。

viyqyuy.gif

随着渐变叠加越来越多,代码也就逐渐变得多起来,过一段时间就很难区分出哪个背景或一组背景对应图像的哪个部分。因此,为了更容易地确定它们,我们把它们分成几个小组,每个小组都添加上注释。这样易于后期能更读懂代码。

.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

IbeURbb.png!web

从白色和最窄的红色半圆开始,从白色的宽度 21em 中减去红色的 9em 宽度,再除以 2 ,得到两个白色部分的宽度(也就是上图中 b 的宽度),结果是 6emb = (21 - 9) / 2 = 6em )。中间的红色线段是 9em21 - (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,

        // 雨伞伞把
        ...
}

eyUniqn.png!web

最后,将不再需要那个灰色的辅助边框线,这时可以把 outline: 1px solid #aaa 样式删除。最终的雨伞效果 就完成了。如下所示:

绘制带圆角的矩形

接下来这个示例是使用同样的手法来绘制一个旧的iPhone模型,在这个模型中有比新模型更多的细节。这个模型有两个特点:两个带圆形的矩形和Home键。

接下来的示例和 Devices.css 中绘制的iPhone模型 有所不同,接下来的示例是使用CSS渐变在一个HTML元素上完成的。

rAFFv2F.png!web

和前面的示例一样,同样先创建一个调色板。这个调色板包括用于Home键按钮边缘的黑色( --bk--bkT ),用于相机和话筒的灰色( --g--gT ),模型外边框的浅灰色( --lg--lgT ),镜头的蓝色( --blblT )和屏幕的深紫色( --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 画布和使用 10pxfont-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.75em19.75 - 2 x 2 = 15.75em )和高度是 36em40 - 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.5em40 - 0.5 = 39.5em )。第二个线性渐变设置了一个 90deg 的角度,让渐变是一个水平渐变,这个渐变同样的从浅灰( --lg )到深灰( --dg ),两者结束位置是 0.5em ,接着从深灰( --dg )到浅灰( --lg ),它们的结束位置是 19.25em19.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;
}

iphone_step1.png

分解一下,易于理解:

linear-gradient(var(--lg) 0.5em, var(--dg) 0.5em, var(--dg) 39.5em, var(--lg) 39.5em) 2.25em 0 / 15.75em 40em

UvMjeiZ.png!web

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

ErmMjaj.png!web

动态组合一下:

jeIb2qm.gif

每个方形的角落将放置圆形的边缘。要创建这些形状,我们就需要使用径向渐变,它的大小是它们的边框的两倍,并且位于每个角落。把这四个圆形的代码放在模型主体之上:

.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,

    ...
}

3yMfqau.png!web

要得到 0.5em 厚的浅灰色末端,考虑一下渐变从哪里开始,然后算一下。因为浅灰色在最后,我们从 2em 中减去 .5em 。对于平滑度,从 1.5em 中去掉一点小距离 0.05em ,然后在后面的停止位置添加 1% ,变成 51%

现在,如果我们将 font-size 修改为 40px 或更大来放大图像,我们会注意到圆角和平角之间的接缝(用橙色圈起来的位置):

BVRr2ia.png!web

因为它们看起来很小,我们可以很容易地把它们补上,只要把字体大小改回 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%,
        ...
}

biMZnyq.png!web

看起来我们可以使用三个浅灰到浅灰的渐变。只是在透明和不透明之间发生了些变化。

接下来,添加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%),
        ...
}

RfmQzuq.png!web

顺便说一下,和前面一样,我们可以通过将 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,

        ...
}

AVn2ima.png!web

然后制作屏幕,屏幕是一个 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,

    ...
}

yQzAZnY.png!web

最后,将添加摄像头和扬声器。摄像头是一个简单的 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,

        ...
}

F7Nfumu.png!web

最终的效果如下:

雷达图

你可能认为可以使用 background-position 让这些背景图像动起来(有动画效果),但是你只能做这么多。例如,一个独立的背景是不可能产生动画效果的。事实上, background-position 动画通常不如 transform 的动画效果好,所以我不推荐它。

如果想使图像的任何部分以我们希望的方式动画,我们可以借助伪元素 :before:after 来完成。如果我们需要更多的选择,那么我们可以使用多个 div 。接下来我们要做一个雷达扫描的动画效果,如下图所示:

VBRVrqq.gif

我们先画静态部分,灰色的框架和转盘。同样的,先创建一个调色板和写一些基本代码:

: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%;
        ...
}

6Z36Rnn.png!web

与前面的示例不同,这个示例没有使用 calc() 来设置水平居中,因为稍后使用伪元素时,IE将会渲染的很慢。接下来要在中间画四条相交的 0.4em 的直线,要知道在 10em 时,这条线的一半应该是 div 的一半。然后,两边同时减去 0.20.4 / 2 = 0.2em )。换句话说,绿色的左边应该是 9.8em ,右边应该是 10.2em45 度的对角线必须用 10em 乘以 2 的平方根,计算出它的中心( 10 × √2 ≈ 14.14 )。它是 10em 直角三角开最长边的长度。因此,每条对角线的边长大约为 13.94em14.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),

    ...
}

Av67z2U.png!web

为了防止对角线的像素化,我们在 greentransparent 之间留下 0.04em 的间隙。然后,为了模拟照明效果,添加一个透明到黑色的径向渐变。

.radar {
    background:
        // lighting
        radial-gradient(100% 100%, var(--bkT), var(--bk) 9.9em,var(--bkT) 10em),

    ...
}

2mIji2U.png!web

这就完成了雷达的静态部分。现在我们把灰色的框架和手柄放在 :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-repeatrepeat ,并且使用 em 为单位,更好的基于 font-size 做缩放
  • 要想得到想要的结果,需要进地大量的思考和实验
  • 从下到上绘制每个图形,这主要是考虑背景是按这个顺序呈现的,每个形状的背景语法遵循 background-positon / size (是否包含位置和大小)

从上面这几个示例中可以看出,基于一个HTML元素,使用CSS渐变可以绘制很多图像。不过要想得到想的结果,需要进行大量的思考和实验。从文章中我们学会了如何对生个背景进行排序,如何绘制圆的部分,圆角矩形,以及如何为圆弧和斜切角边缘平滑。要了解更多,请自由的剖析和研究 我在Codepen上收集的其他例子

FbqYviq.jpg!web

大漠

常用昵称“大漠”,W3CPlus创始人,目前就职于手淘。对HTML5、CSS3和Sass等前端脚本语言有非常深入的认识和丰富的实践经验,尤其专注对CSS3的研究,是国内最早研究和使用CSS3技术的一批人。CSS3、Sass和Drupal中国布道者。2014年出版《 图解CSS3:核心技术与案例实战 》。

如需转载,烦请注明出处: https://www.w3cplus.com/css/drawing-images-with-css-gradients.html


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK