IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

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

    熊的猫发表于 2023-03-30 12:01:11
    love 0

    前言

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

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

    29062949.jpg

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

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

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

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

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

    SVG 简介

    是什么?

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

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

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

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

    具有什么优势?

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

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

    可缩放矢量图形

    点阵图/位图

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

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

    • 原始尺寸图片

      image.png

    • 通过画图软件放大

      image.png

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

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

    矢量图形

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

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

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

    • 文件占用内在空间较小

      • 矢量图实际上保存的是 点、线、矩形 等元素的信息,我们能够看到图像是因为在我们打开矢量图时,由计算机根据这些信息计算出来的,而 点阵图/位图 则是保存各个点的图像信息
    • 放大后不失真(不会变模糊)

      • 因为它和分辨率无关,无论我们移动或改变它的属性多少次,它都可以在维持它原有清晰度和弯曲度

    image.png

    可编码性

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

    8.gif

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

    SVG 的使用场景

    icon 图标

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

    image.png

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

    平面图绘制

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

    8.gif

    动画绘制

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

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

    8.gif

    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>

    image.png

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

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

    SVG 元素可不是使用 border-color 和 background-color 属性实现的,而是通过 stroke 和 fill 来实现的:

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

    image.png

    <rect> 矩形元素

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

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

    常规矩形:

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

    image.png

    圆角矩形:

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

    image.png

    矩形 变 圆形:正方形时将 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>

    image.png

    椭圆 变 圆形:只需要将 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>

    image.png

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

    image.png

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

    image.png

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

      image.png

    路径命令

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

    • 移动到:M、m
    • 画线至:L、l、H、h、V、v
    • 三次方贝塞尔曲线:C、c、S、s
    • 二次方贝塞尔曲线:Q、q、T、t
    • 椭圆曲线:A、a
    • 封闭路径:Z、z

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

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

    • 对 角度 而言 负数 表示是 逆时针
    • 在 绝对坐标 中,-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>

    image.png

    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>

    image.png

    三次贝塞尔曲线

    三次 贝塞尔曲线 是使用 四个点 定义的平滑曲线,绘制完成后 终点(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>

    image.png

    二次贝塞尔曲线

    二次 贝塞尔曲线是使用 三个点 定义的平滑曲线,绘制完成后 终点(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>

    image.png

    椭圆曲线

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

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

    image.png

    ClosePath

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

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

    image.png

    image.png

    基础案例实践

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

    环形进度条

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

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

    image.png

    以上是 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>

    image.png

    添加动画

    通过以上的方式就实现了一个静态的环形进度条了,接下来就需要它动起来,由于 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 部分就不贴出来了,容易占篇幅。

    8.gif

    loading 动画

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

    7.gif

    简单分析

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

    具体实现

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

    8.gif

    按钮动效

    8.gif

    简单分析

    • 外层包裹的矩形可以通过 <rect> 元素实现
    • 矩形的线条残缺不完整,可以通过 stroke-dasharray 和 stroke-dashoffset 属性来实现
    • 当鼠标放置上去时,产生的动画可通过 animation、stroke-dasharray 、stroke-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 来演示的,不再后续赘述。

    文字动效

    8.gif

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

    使用 figma 创建文字路径如下

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

    8.gif

    处理 SVG 代码内容

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

    image.png

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

    stroke-dasharray & stroke-dashoffset 属性

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

    但是这里的 stroke-dasharray 和 stroke-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>

    运动轨迹

    6.gif

    简单分析

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

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

      获取目标物的 SVG 代码

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

    image.png

    绘制运动路径

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

    image.png

    让目标物沿着路径运动

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

    image.png

    <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 基础部分的介绍和使用了,不知道你是入门了,还是被门绊了一下,还是那句话:希望本文对你所有帮助!!!

    06F7DAAF.jpg



沪ICP备19023445号-2号
友情链接