4
头图
欢迎关注我的公众号:前端侦探

再次介绍一些你可能没用过的SVG小技巧。

有时候会遇到一些完全相同的图形,如果能用上 CSS背景平铺,那就再合适不过了。举个例子,有这样一个按钮

image-20240413153411123

相比普通的按钮,多个左右两个小装饰,如果是你,会怎样实现呢?

假设这个小图标是a.svg,想了一下,应该有以下几种方式

1.伪元素

刚好用上::before::after,设置相同的背景就行了,示意如下

button::before,
button::after{
  content:'';
  background: url(a.svg)
}

2. 多重背景

利用 CSS背景可以叠加的特性,设置两个背景就可以了,分别定位,示意如下

button{
  background: url(a.svg) 10px center no-repeat,url(a.svg) right 10px top center no-repeat
}

这两种方式都是不错的方式,但是感觉还是有些浪费,毕竟把两个相同的图案重复写了两遍。

为啥不能直接用背景平铺呢?因为无法直接设置平铺的间隔,就像这样

image-20240414155058634

这时,如果能充分发挥SVG的特性,就可以仅仅使用平铺的方式来实现我们想要的效果了,一起看看吧~

一、SVG的自适应和viewBox

就以上面的小图标为例,从 Figma中可以复制这段svg,如下

<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
    <path d="M15.021 8.028L15 8.027c-.262-.26-.876-.747-2.008-.701.13-.314.027-1.204-.676-1.227.179.574-.803.974-1.361 1.155-.484.156-1.025.344-1.16.622l-.437-.003.002-.43-.276-.001-.004.428-.25-.002c-.028-.022-.058-.047-.091-.07a.643.643 0 0 0-.316-.29.663.663 0 0 0-.21-.22 3.071 3.071 0 0 0-.109-.157l.002-.21.43.004.003-.277-.43-.004.002-.436c.283-.127.478-.667.644-1.152.19-.555.606-1.53 1.18-1.341-.013-.704-.902-.822-1.216-.695.063-1.135-.417-1.754-.67-2.02V.977L8.036.99 8.028.977l-.001.02c-.26.263-.747.877-.701 2.009-.314-.13-1.204-.027-1.228.676.574-.179.974.803 1.155 1.362.157.484.345 1.025.622 1.159l-.003.437-.43-.002v.277l.427.003-.002.25c-.022.028-.046.058-.069.091a.643.643 0 0 0-.29.317.664.664 0 0 0-.222.21 3.07 3.07 0 0 0-.156.108l-.21-.002.004-.43-.276-.002-.004.43-.436-.002c-.127-.283-.667-.478-1.152-.644-.555-.19-1.53-.606-1.341-1.18-.704.013-.822.902-.695 1.216-1.135-.063-1.754.417-2.02.67H.977l.012.012-.012.009.02.001c.263.26.877.747 2.009.702-.13.314-.027 1.203.676 1.227-.179-.574.803-.974 1.362-1.155.484-.157 1.024-.345 1.159-.622l.437.003-.002.43h.277l.003-.427.25.002c.028.022.058.046.091.069a.643.643 0 0 0 .317.29c.058.101.135.172.21.222.04.06.077.114.108.156l-.002.21-.43-.004-.002.276.43.004-.002.437c-.283.127-.478.666-.644 1.15-.19.556-.606 1.532-1.18 1.342.013.705.902.822 1.216.696-.063 1.134.417 1.753.67 2.02v.022l.012-.011c.004.003.006.007.009.012l.001-.021c.26-.263.747-.877.702-2.009.314.13 1.203.027 1.227-.676-.574.179-.974-.803-1.155-1.362-.157-.483-.345-1.024-.622-1.159l.003-.437.43.002v-.277l-.427-.003.002-.25c.022-.028.046-.058.069-.091a.643.643 0 0 0 .29-.317.664.664 0 0 0 .222-.21c.06-.04.114-.077.156-.108l.21.002-.004.43.276.002.004-.43.437.002c.127.283.666.478 1.15.644.556.19 1.532.606 1.342 1.18.705-.013.823-.902.696-1.216 1.134.063 1.753-.417 2.02-.67h.022a1.093 1.093 0 0 1-.011-.012c.003-.004.007-.006.012-.009h-.002z" fill="#000"/>
</svg>

嗯,看着非常乱,没关系,我们不必关注里面的细节。

我们直接放在 html 中来展示这段svg,效果是这样的

image-20240413154811023

因为这段 svg有自带的尺寸,最后展示的就是16 * 16的大小。

如果我们手动改变这个svg的尺寸呢?为了方便观察,我们给svg添加一个边框,如下

svg{
    width: 200px;
  height: 100px;
  outline: 1px dashed;
}

你猜会是什么样的?

下面有 3 个选项

image-20240413160151834

思考一分钟...

🤔

🤔

🤔

🤔

🤔

🤔

🤔

🤔

🤔

思考完成,答案是A,你猜对了吗

image-20240413160340167

为什么会这样呢?有点类似于object-fit:contain的效果。其实这是viewBox造成的,viewBox会按照尺寸等比放大从而铺满整个svg

有关 viewBox的更多介绍,可以参考张鑫旭老师的这篇文章:理解SVG viewport,viewBox,preserveAspectRatio缩放

如果去除viewBoxsvg内部该是什么样就是什么样,也就是仍然是16*16的大小

<!--去除viewBox属性-->
<svg width="16" height="16" fill="none" xmlns="http://www.w3.org/2000/svg">
  ...
</svg>

结果如下

image-20240413161250237

也就是说,在去除viewBox之后,无论svg尺寸是多少,里面的图标都不会变化。

看似好像没什么用?🤔

其实用处可大了,可以让背景以我们想要的方式平铺,那就来看接下来的应用

二、自适应尺寸与背景平铺

现在来简单实现文章开头所示的按钮效果。html很简单,就一个标签

<button>召唤卡牌</button>

然后简单装饰一下,绘制圆角和背景

button{
  border: 0;
  outline: 0;
  padding: 8px 36px;
  font-size: 12px;
  line-height: 16px;
  border-radius: 30px;
  color: #FFEFDB;
  background: #FF2A2A;
}

效果如下

image-20240413162246429

接下来,我们要用上前面的那段svg,先去除viewBox属性,为了能够通过背景尺寸控制svg大小,我们可以将svg的尺寸改成100%

这个技巧在上一篇也有提到过:不一样的SVG!SVG 渐变边框在 CSS 中的应用

示意如下

<svg width="100%" height="100%" fill="none" xmlns="http://www.w3.org/2000/svg">
  ...
</svg>

然后将这段svg转换成css内联格式,推荐用张鑫旭老师的在线转换工具

SVG在线压缩合并工具

可以得到这样一段

--icon: url("data:image/svg+xml,%3Csvg width='16' height='16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M15.021 8.028L15 8.027c-.262-.26-.876-.747-2.008-.701.13-.314.027-1.204-.676-1.227.179.574-.803.974-1.361 1.155-.484.156-1.025.344-1.16.622l-.437-.003.002-.43-.276-.001-.004.428-.25-.002c-.028-.022-.058-.047-.091-.07a.643.643 0 0 0-.316-.29.663.663 0 0 0-.21-.22 3.071 3.071 0 0 0-.109-.157l.002-.21.43.004.003-.277-.43-.004.002-.436c.283-.127.478-.667.644-1.152.19-.555.606-1.53 1.18-1.341-.013-.704-.902-.822-1.216-.695.063-1.135-.417-1.754-.67-2.02V.977L8.036.99 8.028.977l-.001.02c-.26.263-.747.877-.701 2.009-.314-.13-1.204-.027-1.228.676.574-.179.974.803 1.155 1.362.157.484.345 1.025.622 1.159l-.003.437-.43-.002v.277l.427.003-.002.25c-.022.028-.046.058-.069.091a.643.643 0 0 0-.29.317.664.664 0 0 0-.222.21 3.07 3.07 0 0 0-.156.108l-.21-.002.004-.43-.276-.002-.004.43-.436-.002c-.127-.283-.667-.478-1.152-.644-.555-.19-1.53-.606-1.341-1.18-.704.013-.822.902-.695 1.216-1.135-.063-1.754.417-2.02.67H.977l.012.012-.012.009.02.001c.263.26.877.747 2.009.702-.13.314-.027 1.203.676 1.227-.179-.574.803-.974 1.362-1.155.484-.157 1.024-.345 1.159-.622l.437.003-.002.43h.277l.003-.427.25.002c.028.022.058.046.091.069a.643.643 0 0 0 .317.29c.058.101.135.172.21.222.04.06.077.114.108.156l-.002.21-.43-.004-.002.276.43.004-.002.437c-.283.127-.478.666-.644 1.15-.19.556-.606 1.532-1.18 1.342.013.705.902.822 1.216.696-.063 1.134.417 1.753.67 2.02v.022l.012-.011c.004.003.006.007.009.012l.001-.021c.26-.263.747-.877.702-2.009.314.13 1.203.027 1.227-.676-.574.179-.974-.803-1.155-1.362-.157-.483-.345-1.024-.622-1.159l.003-.437.43.002v-.277l-.427-.003.002-.25c.022-.028.046-.058.069-.091a.643.643 0 0 0 .29-.317.664.664 0 0 0 .222-.21c.06-.04.114-.077.156-.108l.21.002-.004.43.276.002.004-.43.437.002c.127.283.666.478 1.15.644.556.19 1.532.606 1.342 1.18.705-.013.823-.902.696-1.216 1.134.063 1.753-.417 2.02-.67h.022a1.093 1.093 0 0 1-.011-.012c.003-.004.007-.006.012-.009h-.002z' fill='%23FFEFDB'/%3E%3C/svg%3E")

接着,将这段svg背景放到按钮中

button{
  background: var(--icon) #FF2A2A;
}

效果如下

image-20240413163243863

很明显,此时这个svg图标背景默认尺寸是充满整个容器的,也就是100% * 100%。如果希望右边也出现平铺一个图标,可以减小背景尺寸,比如

button{
  background: var(--icon) #FF2A2A;
  background-size: calc(100% - 48px)
}

效果如下

image-20240413165910030

然后改变水平位置

button{
  background: var(--icon) #FF2A2A;
  background-size: calc(100% - 48px);
  background-position: 16px;
}

效果如下

image-20240413170002289

左右已经出现图标了,现在只需要垂直居中就可以了,这个也很好实现,设置尺寸为图标本身大小,然后改变背景位置就行了

button{
  background: var(--icon) #FF2A2A;
  background-size: calc(100% - 48px) 16px;
  background-position: 16px center;
}

效果如下

image-20240413170326111

垂直方向也平铺了,所以还需要改变一下平铺方式,仅限水平方向

button{
  background: var(--icon) #FF2A2A;
  background-size: calc(100% - 48px) 16px;
  background-position: 16px center;
  background-repeat: repeat-x;
}

这样就仅仅使用平铺完成了想要的效果!

image-20240413170505723

是不是有些不可思议?下面是一个示意图,紫色圆圈代表图标,紫色边框代表svg尺寸,也就是背景尺寸,设置为100% - 36px后,水平方向的平铺就可以显示两个圆圈了,然后适当移动背景位置,就可以看到左右两边的图标了。

image-20240413165113727

当然这种实现也是完全是自适应的,无论什么尺寸都可以完美适配

image-20240413170613975

完整代码可以查看以下链接:

三、有时候可替代径向渐变

之前写过这样一篇文章:CSS 实现优惠券的技巧 ,讲述了非常多的优惠券绘制技巧,非常巧妙。

里面有提到用径向渐变的方式来绘制内凹圆角,但是渐变一直都是一个非常难学的技巧,语法非常多,一般同学表示接受不了。

这里就采用 SVG来代替径向渐变的方式来实现这样的优惠券效果

image-20240413185127207

首先,我们用设计软件随便画个圆,这里以Figma为例,然后复制出SVG

image-20240413183900321

其实就是这样一段代码

<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="20" cy="20" r="20" fill="#FF336F"/>
</svg>

非常简单吧

然后去除viewBox,并且设置宽高为100%,这是为了让SVG画布尺寸撑满整个容器

<svg width="100%" height="100%" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="20" cy="20" r="20" fill="#FF336F"/>
</svg>

接着转换成CSS内联格式

--icon: url("data:image/svg+xml,%3Csvg width='100%' height='100%' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Ccircle cx='20' cy='20' r='20' fill='%23FF336F'/%3E%3C/svg%3E")

然后用在 CSS 背景中

div{
  background: var(--icon);
}

目前效果是这样的

image-20240413184228126

我们需要将这个圆分割到4个角落上,可以直接用背景平移的方式

div{
  background: var(--icon);
  background-position: -20px -20px;
}

这样就可以了,演示如下

Kapture 2024-04-13 at 18.44.20

不过这样还没完,我们需要实现的反向圆角,所以这里需要用遮罩的方式,减去 4 个角落

关于遮罩,之前在多篇文章中都有提到,有兴趣可以回顾一下

回到这里,我们只需要一个完整的背景,减去刚才的圆角就可以了,具体实现如下

.coupon{
  background: linear-gradient(red,orange);
  -webkit-mask:linear-gradient(red,red), var(--icon);
  -webkit-mask-position: -20px -20px;
  -webkit-mask-composite: xor;
}

效果如下,其实和前面几乎一致

Kapture 2024-04-13 at 18.48.09

是不是没有用到径向渐变?

完整代码可以查看以下链接:

四、SVG还可以更灵活

有时候径向渐变还是有很多局限的,复杂的图形绘制不了或者成本很高。而 SVG就没有这样的限制了,如果能有一定的自适应特性,相信可以更方便的解决问题。

比如这样一个带圆角的自适应聚焦框,可能在大屏可视化比较常见,如下(CSS表示无能为力😭)

image-20240413172058046

除了使用border-image实现以外,还可以采用背景平铺来实现

要实现背景平铺,首先要考虑,哪个是平铺最小单元?

image-20240413173016816

思考一分钟...

🤔

🤔

🤔

🤔

🤔

🤔

🤔

🤔

🤔

思考完成,答案还是A,你猜对了吗?

为什么是A呢?其实要从全局视野来观察,从全局来看,其实是由4个半圆弧组合而成,示意如下

image-20240413174955449

那么,如何用 CSS背景平铺来实现呢?

思路是一致的,首先从设计稿把这段svg复制下来

image-20240413180540843

得到这样一个片段

<svg width="60" height="60" viewBox="0 0 60 60" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M30 2C30 0.895431 29.1046 0 28 0C26.8954 0 26 0.895431 26 2V10C26 18.8366 18.8366 26 10 26H2C0.895431 26 0 26.8954 0 28C0 29.1046 0.895431 30 2 30C0.895431 30 0 30.8954 0 32C0 33.1046 0.895431 34 2 34H10C18.8366 34 26 41.1634 26 50V58C26 59.1046 26.8954 60 28 60C29.1046 60 30 59.1046 30 58C30 59.1046 30.8954 60 32 60C33.1046 60 34 59.1046 34 58V50C34 41.1634 41.1634 34 50 34H58C59.1046 34 60 33.1046 60 32C60 30.8954 59.1046 30 58 30C59.1046 30 60 29.1046 60 28C60 26.8954 59.1046 26 58 26H50C41.1634 26 34 18.8366 34 10V2C34 0.895431 33.1046 0 32 0C30.8954 0 30 0.895431 30 2ZM50 30C38.9543 30 30 38.9543 30 50C30 38.9543 21.0457 30 10 30C21.0457 30 30 21.0457 30 10C30 21.0457 38.9543 30 50 30Z" fill="#FF336F"/>
</svg>

然后去除viewBox,并且设置宽高为100%

<svg width="100%" height="100%" fill="none" xmlns="http://www.w3.org/2000/svg">
  ...
</svg>

接着转换成CSS内联格式

--icon: url("data:image/svg+xml,%3Csvg width='100%' height='100%' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M30 2a2 2 0 1 0-4 0v8c0 8.837-7.163 16-16 16H2a2 2 0 1 0 0 4 2 2 0 1 0 0 4h8c8.837 0 16 7.163 16 16v8a2 2 0 1 0 4 0 2 2 0 1 0 4 0v-8c0-8.837 7.163-16 16-16h8a2 2 0 1 0 0-4 2 2 0 1 0 0-4h-8c-8.837 0-16-7.163-16-16V2a2 2 0 1 0-4 0zm20 28c-11.046 0-20 8.954-20 20 0-11.046-8.954-20-20-20 11.046 0 20-8.954 20-20 0 11.046 8.954 20 20 20z' fill='%23FF336F'/%3E%3C/svg%3E")

最后设置为CSS背景

div{
  background: var(--icon);
}

效果如下

image-20240413181051322

此时无论容器尺寸是多少,这个背景位于左上角。

最后只需要改变一下背景的位置,设置负的偏移量,就可以平铺到 4 个角落了

div{
  background: var(--icon) #eee;
  background-position: -30px -30px;/*图案的一半*/
}

动态示意如下

Kapture 2024-04-13 at 18.14.11

是不是非常简单的实现?

完整代码可以查看以下链接:

五、总结一下

以上就是本文的全部内容了,一个成本非常小的 SVG小技巧,仅仅需要小小的改动,就能让SVG自适应背景平铺,如下

<!--去除viewBox属性,并设置宽高100%-->
<svg width="100%" height="100%" fill="none" xmlns="http://www.w3.org/2000/svg">
  ...
</svg>

你学到了吗?下面总结一下实现要点

  1. SVG默认会根据viewBox填充整个画布,有点类似于object-fit:contain的效果
  2. 去除viewBox后,无论svg尺寸是多少,里面的内容大小都不会变化
  3. 利用这个特性,可以通过设置背景尺寸的方式,让背景以我们想要的方式平铺
  4. 有时候可替代径向渐变,毕竟 SVG 比渐变还是容易很多
  5. 复杂的图形渐变绘制不了或者成本很高,SVG还能更灵活

最后,如果觉得还不错,对你有帮助的话,欢迎点赞、收藏、转发 ❤❤❤


XboxYan
18.1k 声望14.1k 粉丝