用了那么久的 SVG,你还没有入门吗?

前言

欢迎关注同名公众号《熊的猫》,后续文章会同步更新!

其实在大部分的项目中都有 直接间接 使用到 SVGCanvas,但是在大多数时候我们只是选择 简单了解 或 直接跳过,这有问题吗?没有问题,毕竟砖还是要搬的

用了那么久的 SVG,你还没有入门吗?

话说回来,前端其实还是需要对 SVGCanvas 进行学习的,因为其实你一直都在用,多了解不会让你有什么损失,最基本的可以避免:

  • 不至于当 UI 扔给你一个不符合设计稿颜色的 SVG 时摸不着头脑
  • 不至于无法通过 CSS 为 SVG 图自定义其色值时无从下手
  • 甚至不知道为什么要在项目中将展示 SVG 的内容封装成组件

再往上些就可以基于 SVG 进行自定义编码,例如完成各种基于 SVG 的动画需求、在可视化大屏项目中的应用等等。

相关文章其实早就想写(在草稿箱里挺长时间了),但由于各种原因没有真正输出,本篇文章以 SVG 为主要内容正式开启探索之旅。

文中如有不正确之处,可在评论区斧正!!!

SVG 简介

是什么?

SVG 的全称是 Scalable Vector Graphics(即 可缩放矢量图形),它基于 XML 标记语言,用于描述二维的 矢量图形

同时 SVG 是一个基于文本的开放网络标准,其 图像内容相关行为 会被定义于 XML 文本文件之中,并能够和 CSSDOMJavaScript 等网络标准进行衔接。

以上是 官方解释,那么再给一个 非官方解释

人话就是说 SVG 是一种可缩放矢量图形,它可以用 类 HTML(即 XML) 来表示,也就是 可编码,因此你可以在 DOM 中直接使用,可以通过 CSS 控制样式,可以通过 JavaScript 动态修改。

具有什么优势?

其实通过上述的简介,你就不难看出 SVG 至少有以下两个优点(说多了记不住没意义):

  • 可缩放矢量图形
  • 可编码性

可缩放矢量图形

点阵图/位图

通常我们经见的 如 JPEG(后缀名为 .jpg.jpeg) 和 PNG 属于 点阵图/位图

也就是 由很多的像素组成的图形,平时看到的大多图片数属于点阵图,但由于像素非常高,每个点非常小,人的眼睛看不清那一个个的点,所以没有感知,但其实都是用点组成的,通常将它们一直放大,就看到区别了,如下:

  • 原始尺寸图片

    用了那么久的 SVG,你还没有入门吗?

  • 通过画图软件放大

    用了那么久的 SVG,你还没有入门吗?

从以上来看 点阵图/位图 缺点之一就是 不适合进行无限放大,因为会导致原本人眼不可见的像素被放大,导致视觉上看起来图片 模糊/失真

但是也正是因为 点阵图/位图 由不同的像素块组成的,因此可以给不同的像素块填充不同颜色,使得整个图形色彩可以非常丰富。

矢量图形

矢量图形 是用 数学向量、根据几何特性 来绘制图形,矢量可以是 一个点 或 一条线,它是通过 多个对象的组合生成 的,对其中的每一个对象的记录方式,都是以数学 函数 来实现的。

并且每个对象都是一个 互相独立的实体,意味着多次移动和改变它的属性时不会影响图例中的其它对象,仍可以在维持它原有清晰度和弯曲度,也就是说它们可以按 最高分辨率 显示到 输出设备 上。

因此,矢量图形的优点如下:

  • 文件占用内在空间较小
    • 矢量图实际上保存的是 点、线、矩形 等元素的信息,我们能够看到图像是因为在我们打开矢量图时,由计算机根据这些信息计算出来的,而 点阵图/位图 则是保存各个点的图像信息
  • 放大后不失真(不会变模糊)
    • 因为它和分辨率无关,无论我们移动或改变它的属性多少次,它都可以在维持它原有清晰度和弯曲度

用了那么久的 SVG,你还没有入门吗?

可编码性

可编码性应该不难理解,完全可以通过下面这个图来解释(一图胜千言):

用了那么久的 SVG,你还没有入门吗?

SVG 的可编码性使得它相对于 点阵图/位图 具备更强的灵活性,只要按照其对应的规则去修改 点、线 等相关信息,我们就可以改变图形,而不像 点阵图/位图 意义比如只是想要不同颜色的同一张图片用于切换时,还得单独准备两张图片等等。

SVG 的使用场景

icon 图标

在项目中某些图标也会使用 SVG 的格式,最常见的例如在 iconfont 上可自行选择是否以 SVG 的方式使用:

用了那么久的 SVG,你还没有入门吗?

而在项目中使用 SVG 文件时,通常需要对应的 loader 处理,例如 vite 中的 vite-svg-loaderwebpakc 中的 svg-sprite-loader 等,还会封装成组件的形式去使用,实际上就是通过 <svg>、<symbol>、<use> 根据 name 作为标识实现引用,这里就不再展开。

平面图绘制

在项目中需要进行平面图绘制,如需要绘制 线、多边形、图片 等等,比如需要实现前端引导页功能,其中的遮罩层及高亮矩形部分就是基于 SVG 实现的,详情可见往期文章 不使用第三方库怎么实现【前端引导页】功能?

用了那么久的 SVG,你还没有入门吗?

动画绘制

通常会存在需要与用户产生共鸣或互动的场景,特别是需要满足创意性排版的需求等,最常见比如公司年度回顾、一些重要内容的营销宣传等,也可基于 SVG 的方式来实现。

当然你也可以基于 greensock (一个业界知名的工具套件)实现快速使用基于 SVG 的动画形式,如下:

用了那么久的 SVG,你还没有入门吗?

SVG 基本形状元素

SVG 图像是使用各种元素创建的,这些元素分别应用于矢量图像的结构、绘制与布局,因此和 SVG 元素相关的内容是需要去了解的,但内容不算少, 按 A-Z 的顺序大概包括以 A、C、D、E、F、G、H、I、L、M、P、R、S、T、U、V 等作为前缀的元素,按类别还可以分类为:

在本文中我们将焦点关注在 基本形状元素 上。

<circle> 圆形元素

<circle> 是用来创建圆,它是基于一个圆心和一个半径,对应属性为:

  • cx 表示圆心的 X 坐标
  • cy 表示圆心的 Y 坐标
  • r 表示半径
<svg>
    <circle cx="50" cy="50" r="50"></circle>
</svg>

用了那么久的 SVG,你还没有入门吗?

从上图可以看得出来 <svg> 默认的宽高为 300 * 150

那么如果想要其他 html 元素一样添加 边框色背景色,该怎么办呢?

SVG 元素可不是使用 border-colorbackground-color 属性实现的,而是通过 strokefill 来实现的:

 <svg>
    <circle cx="50" cy="50" r="50" fill="pink" stroke="green"></circle>
 </svg>

用了那么久的 SVG,你还没有入门吗?

<rect> 矩形元素

<rect> 元素用来创建矩形,基于一个角位置以及它的宽和高,还可以用来创建圆角矩形,其对应属性有:

  • x 表示 x 轴坐标
  • y 表示 y 轴坐标
  • rx 用于定义水平轴向的圆角半径尺寸
  • ry 用于定义纵轴向的圆角半径尺寸

常规矩形:

<svg>
   <rect x="25" y="25" width="100" height="100" fill="pink" stroke="green"></rect>
</svg>

用了那么久的 SVG,你还没有入门吗?

圆角矩形:

<svg>
   <rect x="25" y="25"  width="100" height="100" rx="10" fill="pink" stroke="green"></rect>
</svg>

用了那么久的 SVG,你还没有入门吗?

矩形 变 圆形:正方形时将 rx 或 ry 设置为宽高的一半即可,长方形时可变椭圆

<svg>
   <rect x="25" y="25"  width="100" height="100" rx="50" fill="pink" stroke="green"></rect>
</svg>

<ellipse> 椭圆元素

<ellipse> 元素用来创建一个椭圆,基于一个 中心坐标、x 半径、y 半径:

  • cx 定义一个中心点的 x 轴坐标
  • cy 定义一个中心点的 y 轴坐标
  • rx 用于定义水平轴向的圆角半径尺寸
  • ry 用于定义纵轴向的圆角半径尺寸
<svg>
   <ellipse cx="150" cy="75" rx="100" ry="50" fill="pink" stroke="green"></ellipse>
 </svg>

用了那么久的 SVG,你还没有入门吗?

椭圆 变 圆形:只需要将 rx 和 ry 设置为相同值即可

<svg>
   <ellipse cx="150" cy="75" rx="50" ry="50" fill="pink" stroke="green"></ellipse>
</svg>

<line> 线条元素

<line> 用来创建一条连接两个点的线,其属性有:

  • x1 表示第一个点的 x 坐标
  • x2 表示第二个点的 x 坐标
  • y1 表示第一个点的 y 坐标
  • y2 表示第二个点的 y 坐标

值得注意的是:需要为 stroke 赋对应的色值,否则是无法观察到效果

  <svg>
    <line x1="50" y1="20" x2="150" y2="100" stroke="red"></line>
  </svg>

用了那么久的 SVG,你还没有入门吗?

<polyline> 折线元素

<polyline> 是用来创建一系列直线,也就是连接多个点,它通常创建的是一个开放的形状,即最后一点不与第一点相连,如果需要闭合形式可以使用 <polygon> 元素。

其专有属性为:

  • points 用于表示多个点的坐标:x1,y1 x2,y2 x3,y3 … xn,yn
<svg>
    <polyline
    points="0,40 40,40 40,80 80,80 80,120 120,120 120,160"
    fill="white" 
    stroke="red" 
    stroke-width="4"
    ></polyline>
</svg>

用了那么久的 SVG,你还没有入门吗?

<polygon> 多边形元素

<polygon> 元素定义了一个由一组首尾相连的直线线段构成的闭合多边形形状,最后一点连接到第一点,其属性和 <polygon> 一致,不同在于表现上。

例如同一个例子在 <polygon> 的表现如下:

<svg>
    <polygon
      points="0,40 40,40 40,80 80,80 80,120 120,120 120,160"
      fill="white" 
      stroke="red" 
      stroke-width="4"
    ></polygon>
</svg>

用了那么久的 SVG,你还没有入门吗?

<path> 路径元素

<path> 元素是用来定义形状的通用元素,所有的基本形状都可以用 path 元素来创建。

其专有属性包括:

  • d 用于定义一个要绘制的路径,路径定义是一个 路径命令(下文中会介绍 组成的列表,其中的每一个命令由命令字母和用于表示命令参数的数字组成

      <svg>
        <path
          fill="#f40"
          stroke="#000"
          d="M 10,30
       A 20,20 0,0,1 50,30
       A 20,20 0,0,1 90,30
       Q 90,60 50,90
       Q 10,60 10,30 z"
        />
      </svg>
    

用了那么久的 SVG,你还没有入门吗?

路径命令

路径命令是对要绘制的路径的说明,每一个命令由代表命令的字母和代表参数的 数字 组成,SVG 定义了六种路径命令类型,一共 20 条命令。

  • 移动到:Mm
  • 画线至:LlHhVv
  • 三次方贝塞尔曲线:CcSs
  • 二次方贝塞尔曲线:QqTt
  • 椭圆曲线:Aa
  • 封闭路径:Zz

不难发现命令是区分大小写的(即大小写敏感),大写 的命令指定 绝对坐标小写 命令指定 相对坐标(相对当前位置的)。

命令中的数字可以使用 负数 形式,只不过代表的意思不同:

  • 角度 而言 负数 表示是 逆时针
  • 绝对坐标 中,-x 和 -y 均表示为负坐标
  • 相对坐标 中,-x 值为 向左 移动,负的 -y 值为 向上 移动

MoveTo 命令

顾名思义,就是指从 当前点 移动到 下一点,即从 当前位置(Po {xo, yo}) 移动到 新位置(Pn {xn, yn}),并且 Pn 与 Po 之间不会绘制连接线:

  • M 绝对坐标,将当前位置移动到坐标 x,y
  • m 相对坐标,将当前位置沿 x 轴移动 dx,沿 y 轴移动 dy,即
    Pn = { xo + dx, yo + dy }
<svg>
    <path
      fill="none"
      stroke="#f40"
      stroke-width="10"
      d="M 10,10 h 30
      m  0,10 h 30
      m  0,10 h 30
      M 40,20 h 30
      m  0,10 h 30
      m  0,10 h 30
      m  0,10 h 30
      m  -30,10 h 30
      m  -60,10 h 30
      m  -60,10 h 30
      m  -60,10 h 30
      m  -60,10 h 30"
    />
</svg>

用了那么久的 SVG,你还没有入门吗?

Lineto 命令

Lineto 指令将绘制一条直线段,从 当前位置 (Po { xo, yo }) 移到 **指定位置(Pn { xn, yn })指定位置(Pn) 将变成下一个命令中的 当前位置(Po)

  • L 在 当前位置 和 指定位置 x,y 之间绘制 一条线段
    • Po = Pn = { x, y }
  • I 在 当前位置 和 指定位置 x,y 之间绘制 一条线段,指定位置 为 当前位置 沿 x 轴偏移 dx、沿 y 轴偏移 dy 处
    • Po = Pn = { xo + dx, yo + dy }
  • H 在 当前位置 与 指定位置 之间绘制一条 水平线段,指定位置 由 x 参数 和 当前位置的 y 坐标指定
    • Po = Pn = { x, yo }
  • h 在 当前位置 与 指定位置 之间绘制一条 水平线段,指定位置 由 当前位置 沿 x 轴偏移 dx 的 x 坐标 和 当前位置 的 y 坐标指定
    • Po = Pn = { xo + dx, yo }
  • V 在 当前位置 与 指定位置 之间绘制一条 垂直线段,指定位置 由 y 参数和 当前位置 的 x 坐标指定
    • Po = Pn = { xo, y }
  • v 在 当前位置 与 指定位置 之间绘制一条 垂直线段,指定位置 由 当前位置 沿 y 轴偏移 dy 的 y 坐标 和 当前位置 的 x 坐标指定
    • Po = Pn = { x, yo + dy }
<svg>
    <path
      fill="none"
      stroke="#f40"
      stroke-width="10"
      d="M 10,10
      L 100,100
      V 10
      H 30"
    />
</svg>

用了那么久的 SVG,你还没有入门吗?

三次贝塞尔曲线

三次 贝塞尔曲线 是使用 四个点 定义的平滑曲线,绘制完成后 终点(Pn) 将成为下一个命令中的 当前位置(Po)

  • 起始点(当前位置) — 第一个点

    (Po = {xo, yo})

  • 终点 — 第二个点

    (Pn = {xn, yn})

  • 起始控制点 — 第三个点

    (*Pcs* = {xcs, ycs})(控制在起点附近的曲线的曲率)

  • 终点控制点 — 第四个点

    (Pce = {xce, yce})(控制在终点附近的曲线的曲率)

其对应的命令如下:

  • C
    • 当前位置终点 x,y 之间绘制一条三次贝塞尔曲线,起始控制点 通过 x1,y1 指定,终点控制点 通过 x2,y2 指定
    • 参数形式为 (x1,y1, x2,y2, x,y),各点表示 Po = Pn = {x, y}; Pcs = {x1, y1};Pce = {x2, y2}
  • c
    • 当前位置终点(当前位置沿 x 轴偏移 dx、沿 y 轴偏移 dy 处)之间绘制一条三次贝塞尔曲线,起始控制点 为当前位置沿 x 轴偏移 dx1、沿 y 轴偏移 dy1 处;终点控制点 为当前位置沿 x 轴偏移 dx2、沿 y 轴偏移 dy2 处
    • 参数形式为 (dx1,dy1, dx2,dy2, dx,dy),各点表示 Po = Pn = {xo + dx, yo + dy} ;Pcs = {xo + dx1, yo + dy1} ;Pce = {xo + dx2, yo + dy2}
  • S
    • 当前位置终点 x,y 之间绘制一条平滑的三次贝塞尔曲线,终点控制点 通过 x2,y2 指定,起始控制点 是上一条曲线命令的终点控制点在当前位置上的 反射点;若上一条命令不是曲线命令,则其与曲线的 起始点(当前位置) 相同
    • 参数形式为 (x2,y2, x,y)
  • s
    • 当前位置终点(当前位置沿 x 轴偏移 dx、沿 y 轴偏移 dy 处)之间绘制一条平滑的三次贝塞尔曲线,终点控制点 为当前位置沿 x 轴偏移 dx2、沿 y 轴偏移 dy2 处;起始控制点 是上一条曲线命令的终点控制点在当前位置上的 反射点;若上一条命令不是曲线命令,则其与曲线的 起始点(当前位置) 相同
    • 参数形式为 (dx2,dy2, dx,dy)
<svg>
    <path
      fill="none"
      stroke="red"
      d="M 10,90
       C 30,90 25,10 50,10
       S 70,90 90,90"
    />

    <path
      fill="none"
      stroke="red"
      d="M 110,90
       c 20,0 15,-80 40,-80
       s 20,80 40,80"
    />
</svg>

用了那么久的 SVG,你还没有入门吗?

二次贝塞尔曲线

二次 贝塞尔曲线是使用 三个点 定义的平滑曲线,绘制完成后** 终点(Pn)** 将成为下一个命令中的 当前位置(Po)

  • 起始点(当前位置)
    Po = {xo, yo} — 第一个点

  • 终点
    Pn = {xn, yn} — 第二个点

  • 控制点 — 第三个点

    Pc = {xc, yc}(控制曲率)

其对应命令如下:

  • Q
    • 当前位置终点 x,y 之间绘制一条二次贝塞尔曲线,控制点 通过 x1,y1 指定
    • 参数形式为 (x1,y1, x,y),各点表示 Po = Pn = {x, y} ;Pc = {x1, y1}
  • q
    • 当前位置终点(当前位置沿 x 轴偏移 dx、沿 y 轴偏移 dy 处)之间绘制一条二次贝塞尔曲线,控制点 为 当前位置(曲线的起始点)沿 x 轴偏移 dx1、沿 y 轴偏移 dy1 处
    • 参数形式为 (dx1,dy1, dx,dy),各点表示 Po = Pn = {xo + dx, yo + dy} ;Pc = {xo + dx1, yo + dy1}
  • T
    • 当前位置终点 x,y 之间绘制一条平滑的二次贝塞尔曲线,控制点 是上一条曲线命令的控制点在当前位置上的 反射点;若上一条命令不是曲线命令,则其与曲线的 起始点(当前位置) 相同
    • 参数形式为 (x, y),各点表示 Po = Pn = {x, y}
  • t
    • 当前位置终点(当前位置沿 x 轴偏移 dx、沿 y 轴偏移 dy 处)之间绘制一条平滑的二次贝塞尔曲线,控制点 是上一条曲线命令的控制点在当前位置上的 反射点;若上一条命令不是曲线命令,则其与曲线的 起始点(当前位置) 相同
    • 参数形式为 (dx, dy),各点表示 Po = Pn = {xo + dx, yo + dy}
<svg>
    <path
      fill="none"
      stroke="red"
      d="M 10,50
       Q 25,25 40,50
       t 30,0 30,0 30,0 30,0 30,0"
    />
</svg>

用了那么久的 SVG,你还没有入门吗?

椭圆曲线

椭圆曲线 实际上就是用来定义为椭圆的一部分的曲线,当需要绘制 高度规则的曲线 时使用 椭圆曲线 相比于 贝塞尔曲线 会更容易:

  • A
    • 当前位置坐标 x,y 之间绘制一条椭圆曲线,用于绘制圆弧的椭圆中心根据命令的其他参数确定,参数形式 (rx ry angle large-arc-flag sweep-flag x y)
    • 坐标 x,y 将成为下一个命令中的当前位置
  • a
    • 当前位置指定位置 之间绘制一条椭圆曲线,指定位置 为当前位置沿 x 轴偏移 dx、沿 y 轴偏移 dy 处,用于绘制圆弧的椭圆中心根据命令的其他参数确定,参数形式 (rx ry angle large-arc-flag sweep-flag dx dy)
    • 坐标 dx,dy 将成为下一个命令中的当前位置

A 和 a 共同参数表示如下:

  • rx 和 ry 是椭圆的两个半径
  • angle 表示椭圆相对于 x 轴的旋转角度
  • large-arc-flag 和 sweep-flag 允许选择必须绘制的弧线,因为其他参数可以绘制 4 条可能的弧线
    • large-arc-flag 允许选择 一个 大弧线(1)或 一个 小弧线(0)
    • sweep-flag 允许选择一条 顺时针 旋转的 弧线(1)或一条 逆时针 旋转的 弧线(0)
  <svg stroke-width="4">
    <path
      fill="none"
      stroke="red"
      d="M 150,50
       A 150 50 10 1 0 14,10"
    />
    <path
      fill="none"
      stroke="lime"
      d="M 150,50
       A 150 50 10 1 1 14,10"
    />
    <path
      fill="none"
      stroke="purple"
      d="M 150,50
       A 150 50 10 0 1 14,10"
    />
    <path
      fill="none"
      stroke="pink"
      d="M 150,50
       A 150 50 10 0 0 14,10"
    />
 </svg>

用了那么久的 SVG,你还没有入门吗?

ClosePath

ClosePath 命令将从 当前位置 绘制一条 直线 到 路径中的 第一个点,其对应的命令为 Z 或 z,没有什么参数,并且大小写不敏感。

<svg stroke-width="3">
    <path
      fill="pink"
      stroke="red"
      d="M 150,50
       l -10,50 80,0
       z"
    />
</svg>

用了那么久的 SVG,你还没有入门吗?

用了那么久的 SVG,你还没有入门吗?

基础案例实践

由于文章篇幅有限,第一个案例会说得细一些,后续的案例就不再一一详细分析了。

环形进度条

这个应该是比较常见的一个需求了,但是如果不让使用组件库你打算怎么实现?

要是没有了解 SVG 之前,恐怕连这么个看似简单的效果都不好实现,但是现在我们可以借助 SVG 来实现,或者我们先来看看组件库是怎么实现的:

用了那么久的 SVG,你还没有入门吗?

以上是 ElementUi 中的实现,没错就是 SVG ,现在你知道其实你项目里一直都有在用 SVG 了吧!

简单分析

  • 整体可以看成是 两个圆形 重叠,因此可以通过 <circle> 元素实现(也可以选择 <path>元素,如上述给出的例图)
  • <circle> 元素默认是会绘制整个圆,但是进度条肯定不是一开始就是 100% 的,因此可以使用 stroke-dashoffset 属性 来将完整的圆变成部分的,但是直接使用 stroke-dashoffset 属性是不会生效的,因为它要配合 stroke-dasharray 属性 一起来使用
    • stroke-dashoffset:指定 dash 模式 到 路径开始 的距离
    • stroke-dasharray:控制用来描边的 点划线 的图案范式,其实你完全可以结合 border: 1px solid dash 来理解,就是用于将一段连续的内容变成非连续的,设置了它就开启了 dash 模式

静态实现

<svg stroke-width="5">
    <!-- 背景圆形 -->
    <circle cx="150" cy="75" r="50" stroke="#ebeef5" fill="none"></circle>

    <!-- 进度条 -->
    <circle
      class="process-circle"
      cx="150"
      cy="75"
      r="50"
      stroke="#20a0ff"
      transform="rotate(-90 150 75)"
      fill="none"
      stroke-dasharray="314"
      stroke-dashoffset="314"
    ></circle>
</svg>

用了那么久的 SVG,你还没有入门吗?

添加动画

通过以上的方式就实现了一个静态的环形进度条了,接下来就需要它动起来,由于 stroke-dasharray(在这它的值其实就是圆的周长stroke-dashoffset 的属性值是需要动态生成的,因此我们得添加一些 JavaScript 代码:

function setProcessCircle(percent = 0) {
    const processCircle = document.querySelector('.process-circle')

    // 获取圆的周长
    const circumference = processCircle.getTotalLength()

    // 把周长赋值给 strokeDasharray
    processCircle.style.strokeDasharray = circumference

    // 动态计算 offset 赋值给 strokeDashoffset
    // 为了支持 percent = 0 | '0%',所以使用 parseInt 转换
    processCircle.style.strokeDashoffset =
      circumference * (1 - parseInt(percent) / 100)
}

效果图中的 html + css 部分就不贴出来了,容易占篇幅。

用了那么久的 SVG,你还没有入门吗?

loading 动画

例如 ElementUI 中 区域加载 的 loading 形式如下:

用了那么久的 SVG,你还没有入门吗?

简单分析

  • 观察整体不难发现只需要一个圆形,即使用 <circle> 元素即可
  • 其蓝色部分一直在 缩短 或 增长,这点可以通过 stroke-dasharraystroke-dashoffset 属性来实现,同时添加 transition 过渡动画即可
  • 最后是每个 缩短 或 增长 的位置不是相同的,那么可以直接用过动画 animationroate 来实现停的旋转即可

具体实现

// html
<svg stroke-width="3">
    <circle
      cx="50"
      cy="50"
      r="20"
      fill="none"
      class="circle-loading"
    ></circle>
</svg>

// style
<style>
  .circle-loading {
    stroke-dasharray: 90, 150;
    stroke-dashoffset: 0;
    stroke: #409eff;
    stroke-linecap: round;
    animation: loading-dash 1.5s ease-in-out infinite, 
    laoding-color 1.5s ease-in-out infinite;
  }

  @keyframes laoding-color {
    0% {
      stroke: #de3e35;
    }
    25% {
      stroke: #ffa500;
    }
    50% {
      stroke: #ffff00;
    }
    75% {
      stroke: #008000;
    }
    100% {
      stroke: #0000ff;
    }
  }

  @keyframes loading-dash {
    0% {
      stroke-dasharray: 1, 200;
      stroke-dashoffset: 0;
    }

    50% {
      stroke-dasharray: 90, 150;
      stroke-dashoffset: -40px;
    }

    to {
      stroke-dasharray: 90, 150;
      stroke-dashoffset: -120px;
    }
  }
</style>

用了那么久的 SVG,你还没有入门吗?

按钮动效

用了那么久的 SVG,你还没有入门吗?

简单分析

  • 外层包裹的矩形可以通过 <rect> 元素实现
  • 矩形的线条残缺不完整,可以通过 stroke-dasharraystroke-dashoffset 属性来实现
  • 当鼠标放置上去时,产生的动画可通过 animationstroke-dasharraystroke-dashoffset 等属性结合即可

具体实现

// html
 <svg class="svg-wrap">
    <text class="text" x="60" y="75">CLICK HERE</text>
    <rect class="rect" x="40" y="35" />
  </svg>

// style
<style>
  .svg-wrap {
    stroke-width: 3px;
    background-color: black;
  }

  .rect {
    height: 60px;
    width: 220px;
    stroke: #fff;
    fill: transparent;
    stroke-dasharray: 220 60;
    stroke-dashoffset: -260;
    stroke-width: 4px;
  }

  .text {
    fill:  #1287f4;
    font-size: 30px;
  }

  .svg-wrap:hover .rect {
    animation: line-move 0.5s linear forwards;
  }

  .svg-wrap:hover .text{
    fill:  #fff;
    transition: all 1s ease-in-out;
  }

  @keyframes line-move {
    0% {
      stroke-dasharray: 220 60;
      stroke-dashoffset: -260;
      stroke-width: 4px;
      stroke: #e32727;
    }
    to {
      stroke-dasharray: 560;
      stroke-dashoffset: 0;
      stroke-width: 2px;
      stroke: #1234f4;
    }
  }
</style>

复杂案例实战

以上的基础案例由于涉及的计算不多,因此我们可以人工进行计算宽、高、偏移等等数据,但是针对复杂的内容,很多参数就不是那么容易计算出来了,因此,我们需要一些工具来快速得到一些必要参数,然后我们在基于这些必要参数取做修改,让其产生更丰富的效果即可。

工具推荐

和 SVG 相关的工具就现在来说已经遍地开花了(就好像现在的 ChatGPT、文心一言),通过搜索引擎查找都能找出来很多,这里也不谈这些工具的优劣了,下面简单列举一些:

  • figma:支持浏览器在线,支持客户端下载
  • sketch:支持浏览器在线,支持客户端下载(Mac)
  • Adobe Illustrator:支持浏览器在线,支持客户端下载
  • Pixso:号称 Sketch 的新选择,几乎还原了Sketch 的专业功能

下面的示例都是基于 figma 来演示的,不再后续赘述。

文字动效

用了那么久的 SVG,你还没有入门吗?

还是同样的第一个案例这里尽量讲详细点,后续案例就不再述说过于详细的内容了。

使用 figma 创建文字路径如下

由于文字内容的路径很难直接计算处理,因此我们可以通过 figma 来帮助我们生成相应的文字路径,大致操作如下:

用了那么久的 SVG,你还没有入门吗?

处理 SVG 代码内容

得到的 SVG 内容大致如下,代码内容比较多这里用图片代替,其中各个内容的对应关系如下所示:

用了那么久的 SVG,你还没有入门吗?

其中的 <mask> 需要去掉,否则文字的颜色值将无法填充。

stroke-dasharray & stroke-dashoffset 属性

由于我们需要让文字具备断续连接的能力,这里还是得使用前面提到 stroke-dasharraystroke-dashoffset 属性来实现,它们的功能前面已经描述过了,也用了几次了,如果你不记得了可以回过头去看看。

但是这里的 stroke-dasharraystroke-dashoffset 的值到底该设置成多少呢?

首先一定是设置成每个文字的长度,那么问题就变成了怎么获取文字的长度了,不用担心,还记得在实现 环形进度条 时用到的 getTotalLength() 方法吗?

这里我们仍然可以使用它来获取每个文字的长度了,代码如下:

 const paths = document.querySelectorAll('path')
  for (const [ptah,index] of paths) {
    console.log(`path 的路径长度 = ${ptah.getTotalLength()}`)
  }
  
  输出结果:
   path 的路径长度 = 179.47874450683594  S
   path 的路径长度 = 150.7733917236328   V
   path 的路径长度 = 274.966064453125    G
   path 的路径长度 = 442.99505615234375  文
   path 的路径长度 = 514.3809204101562

接下来为每个 path 设置样式:

#svg-text > path:nth-of-type(1) {
    stroke-dasharray: 179.479;
    stroke-dashoffset: 179.479;
}
#svg-text > path:nth-of-type(2) {
    stroke-dasharray: 150.773;
    stroke-dashoffset: 150.773;
}
#svg-text > path:nth-of-type(3) {
    stroke-dasharray: 274.966;
    stroke-dashoffset: 274.966;
}
#svg-text > path:nth-of-type(4) {
    stroke-dasharray: 442.995;
    stroke-dashoffset: 442.995;
}
#svg-text > path:nth-of-type(5) {
    stroke-dasharray: 514.381;
    stroke-dashoffset: 514.381;
}

设置了这些值之后,文字内容就会不可见了,而这也是 动画的初始状态

animation 动画

前面种种操作已经完成了动画的初始状态,接下来我们要添加上 animation 动画,核心改变的就是 stroke-dashoffset 的值,就是把它设置成 0,文字内容就会被从隐藏变成显示,再添加过渡时间就完成了,重复内容就不贴出来了:

#svg-text > path:nth-of-type(1) {
    /* ... */
    animation: text-line 2s ease forwards;
}
#svg-text > path:nth-of-type(2) {
    /* ... */
    animation: text-line 2s ease forwards 300ms;
}
#svg-text > path:nth-of-type(3) {
     /* ... */
    animation: text-line 2s ease forwards 600ms;
}
#svg-text > path:nth-of-type(4) {
    /* ... */
    animation: text-line 2s ease forwards 900ms;
}
#svg-text > path:nth-of-type(5) {
    /* ... */
    animation: text-line 2s ease forwards 1200ms;
}

#svg-text{
    stroke: #d205f7;
    fill: none;
    animation: fill-color 2.5s ease-in forwards;
}

@keyframes text-line {
    to {
      stroke-dashoffset: 0;
    }
}

@keyframes fill-color{
    to{
      stroke: transparent;
      fill: #d205f7;
    }
}

重复的事交给 JavaScript

上面才 5 个文字就需要写那么多的样式,属实不能忍,于是我们可以将重复的部分交给 JavaScript 代码去处理:

<style>
  #svg-text {
    stroke: #d205f7;
    stroke-width: 2px;
    fill: none;
    animation: fill-color 2.5s ease-in forwards;
  }

  @keyframes text-line {
    to {
      stroke-dashoffset: 0;
    }
  }

  @keyframes fill-color {
    to {
      stroke: transparent;
      fill: #d205f7;
    }
  }
</style>

<script>
    function init() {
        const paths = Array.from(document.querySelectorAll('path'))
        paths.forEach((path, index) => {
          const len = path.getTotalLength()
          path.style.strokeDasharray = len
          path.style.strokeDashoffset = len
          path.style.animation = `text-line 2.5s ease-in forwards ${
            index * 300
          }ms`
        })
    }

    init()
</script>

运动轨迹

用了那么久的 SVG,你还没有入门吗?

简单分析

完成以上效果需要包括以下几个内容:

  • 获取 目标物SVG 代码
  • 绘制 运动路径
  • 让目标物 沿着路径运动

获取目标物的 SVG 代码

这个可以直接从 iconfont 上选择对应的内容,然后复制它的 SVG 代码。

用了那么久的 SVG,你还没有入门吗?

绘制运动路径

运动路径的绘制我们仍然可以通过 SVG 工具 figma 来获取,值得注意的是默认的路径是用还是填充的,真正在应用时可以通过 stroke="transparent" 将路径颜色变成透明色,整体感官更好,大致如下:

用了那么久的 SVG,你还没有入门吗?

让目标物沿着路径运动

针对这种按特定运动轨迹进行的动画,也不需要你去一点点计算了,我们可以借助 GreenSock 中的 motionpath 插件来实现,在其对应的文档中有详细的介绍,核心就是引入以下两个内容:

用了那么久的 SVG,你还没有入门吗?

<div id="app">
  <!-- 鸟 -->
  <svg
    id="bird"
    t="1679665908640"
    viewBox="0 0 1024 1024"
    version="1.1"
    xmlns="http://www.w3.org/2000/svg"
    p-id="14202"
    width="100"
    height="100"
  >
    省略内容
  </svg>

  <!-- 路径 -->
  <svg
    width="719"
    height="419"
    viewBox="0 0 719 419"
    fill="none"
    xmlns="http://www.w3.org/2000/svg"
  >
    <path
      id="path"
      d="M1 268C146.006 483.259 245.695 450.805 336 268C518.233 -96.9549 599.902 -63.7284 718 222"
      stroke="transparent"
    />
  </svg>
</div>

<script src="https://unpkg.co/gsap@3/dist/gsap.min.js"></script>
<script src="https://unpkg.com/gsap@3/dist/MotionPathPlugin.min.js"></script>
<script>
  // 注册插件
  gsap.registerPlugin(MotionPathPlugin);
  
  // 开启动画
  gsap.to("#bird", {
    duration: 5,
    repeat: 12,
    repeatDelay: 1,
    yoyo: true,
    ease: "power1.inOut",
    motionPath: {
      path: "#path",
      align: "#path",
      alignOrigin: [0.5, 0.5],
    },
  });
</script>

最后

欢迎关注同名公众号《熊的猫》,后续文章会同步更新!

以上就是本文有关 SVG 基础部分的介绍和使用了,不知道你是入门了,还是被门绊了一下,还是那句话:希望本文对你所有帮助!!!

用了那么久的 SVG,你还没有入门吗?

本文正在参加「金石计划」

原文链接:https://juejin.cn/post/7214855127625760805 作者:熊的猫

(1)
上一篇 2023年3月27日 上午10:42
下一篇 2023年3月27日 上午10:53

相关推荐

发表评论

登录后才能评论