数据可视化-SVG动画

2,484 阅读10分钟

一、渐变色和滤镜

1.1 线性渐变

  • SVG除了可以简单的填充和描边,还支持在填充和描边上应用渐变色。渐变有两种类型:线性渐变径向渐变

    • 编写渐变时,必须给渐变内容指定一个 id 属性,use引用需用到。
    • 建议渐变内容定义在<defs>标签内部,渐变通常是可复用的。
  • 线性渐变,是沿着直线改变颜色。线性渐变的使用步骤:

    • 第1步:在 SVG 文件的 defs 元素内部,创建一个<linearGradient>节点,并添加 id 属性。

    • 第2步:在<linearGradient>内编写几个<stop>结点。

      • <stop> 结点指定位置 offset属性和 颜色stop-color属性,用来指定渐变在特定的位置上应用什么颜色

      • offset 和 stop-color 这两个属性值,也可以通过 CSS 来指定。

      • 也可通过 stop-opacity 来设置某个位置的半透明度。

    • 第3步:在一个元素的 fill 属性或 stroke 属性中通过ID来引用 <linearGradient> 节点

      • 比如:属性fill属性设置为url( #Gradient2 )即可。
    • 第4步(可选):控制渐变方向,通过 ( x1, y1 ) 和 ( x2, y2 ) 两个点控制。

      • (0, 0) (0, 1)从上到下;(0, 0)(1, 0)从左到右。

      • 当然也可以通过 gradientTransform 属性 设置渐变形变。比如: gradientTransform=“rotate(90)” 从上到下。

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
      <style>
        body {
          margin: 0;
          padding: 0;
          background-image: url(../images/grid.png);
        }
        svg {
          background-color: rgba(255, 0, 0, 0.1);
        }
      </style>
    
    </head>
    <body>
    
      <svg width="300" height="300" xmlns="http://www.w3.org/2000/svg" >
        <!-- 定义可以复用的元素: 样式, 渐变, 图形, 滤镜... -->
        <defs>
          <!-- 默认的渐变色 -->
          <linearGradient id="gradient1">
            <stop offset="0%" stop-color="red"></stop>
            <stop offset="50%" stop-color="green"></stop>
            <stop offset="100%" stop-color="blue"></stop>
          </linearGradient>
    
          <!-- 改变渐变方向 -->
          <linearGradient id="gradient2" x1="0" y1="0" x2="1" y2="1">
            <stop offset="0%" stop-color="red"></stop>
            <stop offset="50%" stop-color="green"></stop>
            <stop offset="100%" stop-color="blue"></stop>
          </linearGradient>
    
          <!-- 通过形变改变渐变方向 -->
          <linearGradient id="gradient3" gradientTransform="rotate(90)">
            <stop offset="0%" stop-color="red"></stop>
            <stop offset="50%" stop-color="green"></stop>
            <stop offset="100%" stop-color="blue"></stop>
          </linearGradient>
        </defs>
    
        <rect x="0" y="0" width="100" height="50" fill="url(#gradient1)"></rect>
        <rect x="0" y="100" width="100" height="50" fill="url(#gradient2)"></rect>
        <rect x="0" y="200" width="100" height="50" fill="url(#gradient3)"></rect>
    
      </svg>
    
    </body>
    </html>
    
    image.png

1.2 毛玻璃效果(高斯模糊)

1.2.1 css实现

  • backdrop-filter:可以给一个元素后面区域添加模糊效果

    • 适用于元素背后的所有元素。为了看到效果,必须使元素或其背景至少部分透明
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
      <style>
        body {
          margin: 0;
          bottom: 0;
        }
        .box {
          position: relative;
          width: 200px;
          height: 200px;
        }
        .bg-cover {
          position: absolute;
          top: 0;
          left: 0;
          right: 0;
          bottom: 0;
    
          /* 毛玻璃效果 */
          background-color: transparent;
          backdrop-filter: blur(5px);
        }
      </style>
    </head>
    <body>
      <div class="box">
        <img src="../images/avatar.jpeg" alt="">
        <div class="bg-cover"></div>
      </div>
    </body>
    </html>
    
    image.png
  • filter:直接将模糊或颜色偏移等模糊效果应用于指定的元素

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
      <style>
        body {
          margin: 0;
          bottom: 0;
        }
        .box {
          position: relative;
          width: 200px;
          height: 200px;
          /* 超出去的模糊效果 隐藏掉 */
          overflow: hidden;
        }
        img {
          /* 毛玻璃效果 */
          filter: blur(5px);
        }
      </style>
    </head>
    <body>
      <div class="box">
        <img src="../images/avatar.jpeg" alt="">
      </div>
    </body>
    </html>
    
    image.png

1.2.2 svg实现

  • <filter>:元素作为滤镜操作的容器,该元素定义的滤镜效果需要在SVG元素上的 filter 属性引用。

    • x ,y, width, height 定义了在画布上应用此过滤器的矩形区域。x, y 默认值为 -10%(相对自身);width ,height 默认值为 120% (相对自身
  • <feGaussianBlur>:该滤镜专门对输入图像进行高斯模糊

    • stdDeviation 熟悉指定模糊的程度
  • <feOffset> :该滤镜可以对输入图像指定它的偏移量

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
      <style>
        body {
          margin: 0;
          padding: 0;
        }
      </style>
    
    </head>
    <body>
    
      <svg width="400" height="200" xmlns="http://www.w3.org/2000/svg" >
    
        <defs>
          <!-- 高斯模糊效果 -->
          <filter id="blurFilter">
            <feGaussianBlur stdDeviation="8"></feGaussianBlur>
          </filter>
    
          <filter id="blurFilter1" x="50%" y="50%" width="100%" height="25%">
            <feGaussianBlur stdDeviation="8"></feGaussianBlur>
          </filter>
        </defs>
    
        <image 
          href="../images/avatar.jpeg"
          width="200"
          height="200"
          filter="url(#blurFilter)"
        ></image>
        <image 
          x="200"
          y="0"
          href="../images/avatar.jpeg"
          width="200"
          height="200"
          filter="url(#blurFilter1)"
        ></image>  
      </svg>
    
    </body>
    </html>
    
    image.png

二、SVG的形变

  • transform 属性用来定义元素及其子元素的形变的列表。

    • 此属性可以与任何一个 SVG 中的元素一起使用
    • 从 SVG2 开始,transform它是一个 Presentation Attribute,意味着它可以用作 CSS 属性
    • 但是transform作为CSS 属性和元素属性之间的语法会存在一些差异
      • 比如作为元素属性时:支持2D变换,不需单位,rotate可指定旋转原点
  • transform属性支持的函数:

    • translate(x,y) 平移
    • rotate(z) / rotate(z,cx,cy) :旋转
    • scale(x, y) :缩放
    • skew(x, y) :倾斜
    • matrix(a, b, c, d, e) : 2*3 的形变矩阵
  • 注意:形变会修改坐标系:形变元素内部会建立一个新的坐标系,后续的绘图或形变都会参照新的坐标系

2.1 translate

  • 平移:把元素移动一段距离, 使用transform属性的 translate()函数来平移元素。

    • 与CSS的translate相似但有区别,这里只支持2D变换,不需单位。
  • translate(x, y)函数

    • 一个值时,设置x轴上的平移,而第二个值默认赋值为0
    • 二个值时,设置x轴和y轴上的平移
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
      <style>
        body {
          margin: 0;
          padding: 0;
          background-image: url(../images/grid.png);
        }
        svg {
          background-color: rgba(255, 0, 0, 0.1);
        }
      </style>
    
    </head>
    <body>
    
      <svg width="300" height="300" xmlns="http://www.w3.org/2000/svg" >
    
        <!-- 平移一个元素 -->
        <!-- <rect x="0" y="0" width="100" height="50" transform="translate(100, 100)"></rect> -->
    
        <!-- 平移一个元素, 在元素的内部会创建一个新的坐标系统 -->
        <!-- <rect x="10" y="10" width="100" height="50" transform="translate(100, 100)"></rect> -->
    
        <!-- 平移一个元素, 在元素的内部会创建一个新的坐标系统 -->
        <g transform="translate(100, 100)">
          <rect x="10" y="10" width="100" height="50"></rect>
        </g>
    
      </svg>
    
    </body>
    </html>
    
    image.png

2.2 rotate

  • 旋转:把元素旋转指定的角度, 使用transform属性的 rotate(deg,cx, cy) 函数来旋转元素。

    • 与CSS的rotate相似但有区别。区别是:支持2D变换,不需单位,可指定旋转原点
  • rotate(deg, cx, cy) 函数

    • 一个值时,设置z轴上的旋转的角度
  • 注意:

    • 旋转会修改坐标系,坐标轴也会跟着旋转了
    • 如何指定旋转原点?
      • 直接在rotate中指定 cx ,cy(相对于自身
      • 使用CSS样式写动画
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
      <style>
        body {
          margin: 0;
          padding: 0;
          background-image: url(../images/grid.png);
        }
        svg {
          background-color: rgba(255, 0, 0, 0.1);
        }
      </style>
    
    </head>
    <body>
    
      <svg width="300" height="300" xmlns="http://www.w3.org/2000/svg" >
    
        <!-- 旋转一个元素 -->
        <rect 
          x="0" 
          y="0" 
          width="100" 
          height="50" 
          transform="rotate(45, 50, 25) translate(100,50)"
        ></rect>
    
        <!-- 顺序不一致,效果不一致 -->
        <rect 
          x="0" 
          y="0" 
          width="100" 
          height="50" 
          transform="translate(100,50) rotate(45, 50, 25)"
        ></rect>
    
      </svg>
    
    </body>
    </html>
    
    image.png

2.3 scale

  • 缩放:改变元素尺寸,使用transform属性的 scale() 函数来缩放元素。

    • 与CSS的scale相似但有区别,这只支持2D变换,不需单位
  • scale(x, y)函数

    • 二个值时:它需要两个数字,作为比率计算如何缩放。0.5 表示收缩到 50%
    • 一个值时:第二个数字被忽略了,它默认等于第一个值
  • 注意:

    • 缩放会修改坐标系,坐标轴被缩放了
    • 如何指定缩放的原点?
      • SVG属性实现需要 平移坐标 和 移动图形
      • 直接使用CSS来写动画
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
      <style>
        body {
          margin: 0;
          padding: 0;
          background-image: url(../images/grid.png);
        }
        svg {
          background-color: rgba(255, 0, 0, 0.1);
        }
      </style>
    
    </head>
    <body>
    
      <svg width="300" height="300" xmlns="http://www.w3.org/2000/svg" >
    
        <!-- 缩放一个元素 -->
        <!-- <rect 
          x="0" 
          y="0" 
          width="100" 
          height="50" 
          transform="translate(100,100) scale(1, 2)"
        ></rect> -->
    
        <!-- 平移坐标,修改原点 -->
        <!-- <rect 
          x="-25" 
          y="-25" 
          width="50" 
          height="50" 
          transform="translate(100,100) scale(2)"
        ></rect> -->
    
        <!-- 缩放会改变原点 -->
        <g transform="scale(2)">
          <rect 
            x="0" 
            y="0" 
            width="50" 
            height="50" 
            transform="translate(10,0) "
          ></rect>
        </g>
    
      </svg>
    
    </body>
    </html>
    
    image.png

三、描边动画

  • stroke 是描边属性,专门给图形描边。如果想给各种描边添加动画效果,需用到下面两个属性:

    • stroke-dasharray =“number [, number , ….]”: 将虚线类型应用在描边上。

    • 该值必须是用逗号分割的数字组成的数列,空格会被忽略。比如 3,5 :

      • 第一个表示填色区域的长度为 3
      • 第二个表示非填色区域的长度为 5
    • stroke-dashoffset:指定在dasharray模式下路径的偏移量(往左偏移)。

      • 值为number类型,除了可以正值,也可以取负值。
  • 描边动画实现步骤:

    1. 先将描边设置为虚线
    2. 接着将描边偏移到不可见处
    3. 通过动画让描边慢慢变为可见,这样就产生了动画效果

3.1 直线的描边

  • 不可见 -> 可见

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
      <style>
        body, ul{
          margin: 0;
          padding: 0;
          background-image: url(../images/grid.png);
        }
        svg {
          background-color: rgba(255, 0, 0, 0.1);
        }
    
        #line {
          /* 指定为虚线 */
          stroke-dasharray: 100px;
          /* 可见 */
          stroke-dashoffset: 0;
          animation: lineMove 2s linear;
        }
    
        @keyframes lineMove {
          0% {
            /* 不可见 */
            stroke-dashoffset: 100px;
          }
          100% {
            /* 可见 */
            stroke-dashoffset: 0;
          }
        }
    
      </style>
    
    </head>
    <body>
    
      <svg width="300" height="300" xmlns="http://www.w3.org/2000/svg" >
    
        <line 
          id="line"
          x1="100" y1="70"  x2="200" y2="70" 
          stroke="red" 
          stroke-width="10"
        >
        </line>
    
      </svg>
    
    </body>
    </html>
    
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
      <style>
        body, ul{
          margin: 0;
          padding: 0;
          background-image: url(../images/grid.png);
        }
        svg {
          background-color: rgba(255, 0, 0, 0.1);
        }
    
        #line {
          /* 指定为虚线 */
          stroke-dasharray: 100px;
          /* 不可见 */
          stroke-dashoffset: 100px;
          /* 动画结束保持最后一帧的状态 */
          animation: lineMove 2s linear forwards;
        }
    
        @keyframes lineMove {
          100% {
            /* 可见 */
            stroke-dashoffset: 0;
          }
        }
    
      </style>
    
    </head>
    <body>
    
      <svg width="300" height="300" xmlns="http://www.w3.org/2000/svg" >
    
        <line 
          id="line"
          x1="100" y1="70"  x2="200" y2="70" 
          stroke="red" 
          stroke-width="10"
        >
        </line>
    
      </svg>
    
    </body>
    </html>
    
  • 如果虚线长度大于线条或者路径的长度,会使动画效果加速

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
      <style>
        body, ul{
          margin: 0;
          padding: 0;
          background-image: url(../images/grid.png);
        }
        svg {
          background-color: rgba(255, 0, 0, 0.1);
        }
    
        #line {
          /* 指定为虚线 长度大于 线条长度 */
          stroke-dasharray: 500px;
          /* 不可见 */
          stroke-dashoffset: 500px;
          /* 动画结束保持最后一帧的状态 */
          animation: lineMove 2s linear forwards;
        }
    
        @keyframes lineMove {
          100% {
            /* 可见 */
            stroke-dashoffset: 0;
          }
        }
      </style>
    
    </head>
    <body>
    
      <svg width="300" height="300" xmlns="http://www.w3.org/2000/svg" >
    
        <line 
          id="line"
          x1="100" y1="70"  x2="200" y2="70" 
          stroke="red" 
          stroke-width="10"
        >
        </line>
    
      </svg>
    
    </body>
    </html>
    

3.2 折线的描边

  • 计算出折线的长度即可
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
      <style>
        body {
          margin: 0;
          padding: 0;
          background-image: url(../images/grid.png);
        }
        svg {
          background-color: rgba(255, 0, 0, 0.1);
        }
    
        #line {
          /* 指定为虚线 */
          stroke-dasharray: 130px;
          /* 不可见 */
          stroke-dashoffset: 130px;
          /* 动画结束保持最后一帧的状态 */
          animation: lineMove 2s linear forwards;
        }
    
        @keyframes lineMove {
          100% {
            /* 可见 */
            stroke-dashoffset: 0;
          }
        }
    
      </style>
    
    </head>
    <body>
    
      <svg width="300" height="300" xmlns="http://www.w3.org/2000/svg" >
    
        <path 
          id="line"
          d="M 100 70, L 200 70, L 200 100"
          fill="transparent"
          stroke="red" 
          stroke-width="10"
        >
        </path>
    
      </svg>
    
    </body>
    </html>
    

3.3 雪糕的描边

  • 实现步骤:

    1. 找到一个雪糕的SVG图片(设计师提供 | 网站下载)
    2. 将雪糕的每一个路径都改成虚线
    3. 将每个路径的描边都移动到虚线的空白处(不可见)
    4. 给每个路径添加动画,将路径描边慢慢移动到虚线填充处,即可
  • 获取路径长度的方法:

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
        <style>
          .outline {
            stroke-dasharray: 1020px;
            stroke-dashoffset: 1020px;
            animation: lineMove 2s linear forwards;
          }
    
          .stick {
            stroke-dasharray: 500px;
            stroke-dashoffset: 500px;
            animation: lineMove 2s linear forwards;
            animation-delay: 0.5s;
          }
    
          .drop {
            stroke-dasharray: 200px;
            stroke-dashoffset: 200px;
            animation: lineMove 2s linear forwards;
            animation-delay: 2s;
          }
    
          .inside-l {
            stroke-dasharray: 800px;
            stroke-dashoffset: 800px;
            animation: lineMove 2s linear forwards;
            animation-delay: 1.5s;
          }
    
          .inside-r {
            stroke-dasharray: 700px;
            stroke-dashoffset: 700px;
            animation: lineMove 2s linear forwards;
          }
    
          @keyframes lineMove {
            100% {
              /* 可见 */
              stroke-dashoffset: 0;
            }
          }
    
        </style>
      </head>
      <body>
        <svg
          id="popsicle"
          width="300"
          height="400"
          xmlns="http://www.w3.org/2000/svg"
          viewBox="0 0 177.3 449.1"
        >
          <g stroke="black" stroke-width="5px">
            <!-- 手柄 -->
            <path
              class="stick"
              d="M408.8,395.9V502.4a18.8,18.8,0,0,1-18.8,18.8h0a18.8,18.8,0,0,1-18.8-18.8V415.3"
              transform="translate(-301.2 -73.5)"
              fill="none"
            />
            <!-- 水滴 -->
            <path
              class="drop"
              d="M359.1,453.5c0,3.1-2.1,5.6-4.7,5.6s-4.7-2.5-4.7-5.6,2.1-8.3,4.7-8.3S359.1,450.4,359.1,453.5Z"
              transform="translate(-301.2 -73.5)"
              fill="none"
            />
            <!-- 外层 -->
            <path
              class="outline"
              d="M389.9,75h0a87.4,87.4,0,0,0-87.2,87.2v218a15.7,15.7,0,0,0,15.7,15.7h12a4.3,4.3,0,0,1,4.1,4.8h0.1v17c0,8.2,9.1,7.9,9.1,0v-6c0-5.2,5.8-5.2,5.8,0v20.5c0,7.7,9.8,7.7,9.8,0V407.2c0-5.2,6.4-5.2,6.4,0v2.7c0,7.7,8.8,7.7,8.8,0v-6c0-6.4,3.9-7.8,6-8.1h80.9a15.7,15.7,0,0,0,15.7-15.7v-218A87.4,87.4,0,0,0,389.9,75Z"
              transform="translate(-301.2 -73.5)"
              fill="none"
            />
    
            <!-- 里面左边 -->
            <path
              class="inside-l"
              d="M55.5,68h0A20.2,20.2,0,0,1,75.7,88.2V276.9a4.5,4.5,0,0,1-4.5,4.5H39.8a4.5,4.5,0,0,1-4.5-4.5V88.2A20.2,20.2,0,0,1,55.5,68Z"
              fill="none"
            />
            <!-- 里面右边 -->
            <path
              class="inside-r"
              d="M121.8,68h0A20.2,20.2,0,0,1,142,88.2V277a4.4,4.4,0,0,1-4.4,4.4H106.1a4.4,4.4,0,0,1-4.4-4.4V88.2A20.2,20.2,0,0,1,121.8,68Z"
              fill="none"
            />
          </g>
        </svg>
    
        <script>
          window.onload = function () {
            getPathLength("stick");
            getPathLength("drop");
            getPathLength("outline");
            getPathLength("inside-l");
            getPathLength("inside-r");
          };
    
          function getPathLength(className) {
            let stickEl = document.getElementsByClassName(className)[0];
            // getTotalLength 返回指示路径总长度(以用户单位为单位)的浮点数
            let stickLength = stickEl.getTotalLength();
            console.log(className + "Length=", stickLength);
          }
        </script>
      </body>
    </html>
    
    image.png

四、SVG中SMIL动画

4.1 SMIL 介绍

  • SMIL(Synchronized Multimedia Integration Language 同步多媒体集成语言)是W3C推荐的可扩展标记语言,用于描述多媒体演示

    • SMIL 标记是用 XML 编写的,与HTML有相似之处。

    • SMIL 允许开发多媒体项目,例如:文本、图像、视频、音频等。

    • SMIL 定义了时间、布局、动画、视觉转换和媒体嵌入等标记,比如:<head> <body> <seq> <par> <excl> 等元素

  • SMIL的应用

    • 目前最常用的Web浏览器基本都支持 SMIL 语言
    • SVG 动画元素是基于SMIL实现(SVG中使用SMIL实现元素有:<set><animate><animateMotion>
    • Adobe Media Player implement SMIL playback
    • QuickTime Player implement SMIL playback

4.2 SVG动画实现方式

  • SVG是一种基于XML的开放标准矢量图形格式,动画可以通过多种方式实现:

    1. 用JS脚本实现:可以直接通过 JavaScript 在来给 SVG 创建动画和开发交互式的用户界面

    2. 用CSS样式实现:自 2008 年以来,CSS动画已成为WebKit中的一项功能,使得我们可以通过CSS动画的方式来给文档对象模型(DOM) 中的 SVG 文件编写动态效果。

    3. 用SMIL实现:一种基于SMIL语言实现的SVG动画

4.3 SMIL动画的优势

  • SVG用SMIL方式实现动画,SMIL允许你做下面这些事情:

    • 变动一个元素的数字属性(x、y……)
    • 变动变形属性(translation 或 rotation)
    • 变动颜色属性
    • 物件方向与运动路径方向同步等等
  • SMIL方式实现动画的优势:

    • 只需在页面放几个animate元素就可以实现强大的动画效果,无需任何CSS和JS代码

    • SMIL支持声明式动画。声明式动画不需指定如何做某事的细节,而是指定最终结果应该是什么,将实现细节留给客户端软件

    • 在 JavaScript 中,动画通常使用 setTimeout() 或 setInterval() 等方法创建,这些方法需要手动管理动画的时间。而SMIL声明式动画可以让浏览器自动处理,比如:动画轨迹直接与动画对象相关联、物体和运动路径方向、管理动画时间等等

    • SMIL 动画还有一个令人愉快的特点是,动画与对象本身是紧密集成的,对于代码的编写和阅读性都非常好

4.4 SMIL动画的元素

  • SVG 中支持SMIL动画的元素:

    • <set> <animate> <animateColor> <animateMotion>
    • 更多
  • Set

    • <set>元素提供了一种简单的方法,可以在指定的时间内设置属性的值。

      • set元素是最简单的 SVG 动画元素。它是在经过特定时间间隔后,将属性设置为某个值(不是过度动画效果)。因此,图像不是连续动画,而是改变一次属性值。
      • 支持所有属性类型,包括那些无法合理插值的属性类型,例如:字符串 和 布尔值。而对于可以合理插值的属性通常首选<animate>元素。
    • <set>元素常用属性:

      • attributeName:指示将在动画期间更改的目标元素的 CSS 属性( property )或属性( attribute )的名称
      • attributeType: (已过期) 指定定义目标属性的类型(值为:CSS | XML | auto)
      • to : 定义在特定时间设置目标属性的值。该值必须与目标属性的要求相匹配。 值类型:;默认值:无
      • begin:定义何时开始动画或何时丢弃元素,默认是 0s ( begin支持多种类型的值 )
    • <set>案例:

      • 1)在3秒后自动将长方形瞬间移到右边
      • 2)点击长方形后,长方形瞬间移到右边
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
      <style>
        body {
          margin: 0;
          padding: 0;
          background-image: url(../images/grid.png);
        }
        svg {
          background-color: rgba(255, 0, 0, 0.1);
        }
      </style>
    
    </head>
    <body>
    
      <svg width="300" height="300" xmlns="http://www.w3.org/2000/svg" >
        <rect x="0" y="0" width="100" height="50" fill="red">
          <set attributeName="x" to="200" begin="3"></set>
        </rect>
      </svg>
    
      <svg width="300" height="300" xmlns="http://www.w3.org/2000/svg" >
        <rect id="rectangle" x="0" y="0" width="100" height="50" fill="red">
          <set attributeName="x" to="200" begin="rectangle.click"></set>
        </rect>
      </svg>
    
    </body>
    </html>
    
  • Animate

    • <animate>元素给某个属性创建过渡动画效果。需将animate元素嵌套在要应用动画的元素内。

    • <animate>元素常用属性:

      • attributeName:指将在动画期间更改目标元素的 property (CSS 属)或 attribute的名称。

      • 动画值属性:

        • from:在动画期间将被修改的属性的初始值。没有默认值
        • to :在动画期间将被修改的属性的最终值。没有默认值
        • values:该属性具有不同的含义,具体取决于使用它的上下文(没有默认值)
          • 它定义了在动画过度中使用的一系列值,值需要用分号隔开,比如:values=“2 ; 3; 4; 5”
          • 当values属性定义时,from、to会被忽略
      • 动画时间属性:

        • begin:定义何时开始动画或何时丢弃元素。默认是 0s
        • dur:动画的持续时间,该值必须,并要求大于 0。单位可以用小时 ( h)、分钟 ( m)、秒 ( s) 或毫秒 ( ms) 表示
        • fill:定义动画的最终状态。 freeze(保持最后一个动画帧的状态) | remove(保持第一个动画帧的状态)
        • repeatCount:指示动画将发生的次数:<number> | indefinite。没有默认值
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
      <style>
        body {
          margin: 0;
          padding: 0;
          background-image: url(../images/grid.png);
        }
        svg {
          background-color: rgba(255, 0, 0, 0.1);
        }
      </style>
    
    </head>
    <body>
    
      <svg width="300" height="300" xmlns="http://www.w3.org/2000/svg" >
        <rect x="0" y="0" width="100" height="50" fill="red">
          <!-- <animate
            attributeName="x"
            from="0"
            to="200"
            dur="2s"
            fill="freeze"
            repeatCount="2"
          ></animate> -->
    
          <!-- 必须的三个属性 -->
          <animate
            attributeName="x"
            to="200"
            dur="2s"
          ></animate>
        </rect>
      </svg>
    
      <svg width="300" height="300" xmlns="http://www.w3.org/2000/svg" >
        <rect x="0" y="0" width="100" height="50" fill="red">
          <!-- values 代替 from to -->
          <animate
            attributeName="x"
            values="0; 170; 200"
            dur="3s"
            repeatCount="indefinite"
          ></animate>
    
          <animate
            attributeName="fill"
            values="skyblue; blue"
            dur="3s"
            repeatCount="indefinite"
          ></animate>
        </rect>
      </svg>
    
      <svg width="300" height="300" xmlns="http://www.w3.org/2000/svg" >
        <rect x="0" y="0" width="100" height="50" fill="pink">
          <animate
            id="firstAnimate"
            attributeName="x"
            values="0; 200"
            dur="3s"
            fill="freeze"
          ></animate>
    
          <animate
            attributeName="y"
            values="0; 100"
            dur="3s"
            fill="freeze"
            begin="firstAnimate.end"
          ></animate>
        </rect>
      </svg>
    
    </body>
    </html>
    
    image.png
  • AnimateTransform

    • <animateTransform>元素

      • 指定目标元素的形变(transform)属性,从而允许控制元素的平移、旋转、缩放或倾斜动画(类似于 CSS3 的形变)。

      • 在一个动画元素中,只能用一个< animateTransform >元素创建动画;存在多个时,后面会覆盖前面的动画。

    • <animateTransform>元素常用属性:

      • attributeName:指示将在动画期间更改的目标元素的 CSS 属性( property )或属性( attribute )的名称

      • type :一个指定类型的属性,在不同的使用场景下,有不同的意思:

        • 在元素,只支持 translate(x, y) | rotate(deg, cx, cy) | scale(x, y) | skewX(x) | skewY(y)
        • 在 HTML 中的 <style><script> 元素,它定义了元素内容的类型
      • 动画值属性:from、to 、values

      • 动画时间属性:begin、dur、fill、repeatCount

    • 平移

      <svg width="300" height="300" xmlns="http://www.w3.org/2000/svg" >
        <rect x="0" y="0" width="100" height="50" fill="red">
          <animateTransform
            attributeName="transform"
            type="translate"
            from="0, 0"
            to="200, 0"
            dur="2s"
            begin="1s"
            repeatCount="indefinite"
          ></animateTransform>
        </rect>
      </svg>
      
      <svg width="300" height="300" xmlns="http://www.w3.org/2000/svg" >
        <rect x="0" y="0" width="100" height="50" fill="red">
          <animateTransform
            attributeName="transform"
            type="translate"
            values="0, 0; 200, 0"
            dur="2s"
            begin="1s"
            repeatCount="indefinite"
          ></animateTransform>
        </rect>
      </svg>
      
    • 旋转

      <svg width="300" height="300" xmlns="http://www.w3.org/2000/svg" >
        <rect x="0" y="0" width="100" height="50" fill="red">
          <animateTransform
            attributeName="transform"
            type="rotate"
            from="0, 50, 25"
            to="360, 50, 25"
            dur="2s"
            begin="1s"
            repeatCount="indefinite"
          ></animateTransform>
        </rect>
      </svg>
      
      <svg width="300" height="300" xmlns="http://www.w3.org/2000/svg" >
        <rect x="0" y="0" width="100" height="50" fill="red">
          <animateTransform
            attributeName="transform"
            type="rotate"
            values="0 50 25; -360 50 25"
            dur="2s"
            begin="1s"
            repeatCount="indefinite"
          ></animateTransform>
        </rect>
      </svg>
      
    • 缩放

      <svg width="300" height="300" xmlns="http://www.w3.org/2000/svg" >
        <rect x="0" y="0" width="100" height="50" fill="red">
          <animateTransform
            attributeName="transform"
            type="scale"
            from="1 1"
            to="2 3"
            dur="2s"
            begin="1s"
            repeatCount="indefinite"
          ></animateTransform>
        </rect>
      </svg>
      
      <svg width="300" height="300" xmlns="http://www.w3.org/2000/svg" >
        <rect x="0" y="0" width="100" height="50" fill="red">
          <animateTransform
            attributeName="transform"
            type="scale"
            values="1; 0.5"
            dur="2s"
            begin="1s"
            repeatCount="indefinite"
          ></animateTransform>
        </rect>
      </svg>
      
  • AnimateMotion

    • <animateMotion> 定义了一个元素如何沿着运动路径进行移动

      • 动画元素的坐标原点,会影响元素运动路径,建议从(0, 0)开始
      • 要复用现有路径,可<animateMotion>元素中使用<mpath>元素
    • <aniamteMotion>元素常用属性:

      • path:定义运动的路径,值和<path>元素的 d 属性一样,也可用 href 引用 一个 <path>

      • rotate :动画元素自动跟随路径旋转,使元素动画方向和路径方向相同,值类型:<数字> | auto | auto-reverse; 默认值:0

      • 动画值属性: from、to 、values

      • 动画时间属性: begin、dur、fill、repeatCount

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
      <style>
        body {
          margin: 0;
          padding: 0;
          background-image: url(../images/grid.png);
        }
        svg {
          background-color: rgba(255, 0, 0, 0.1);
        }
      </style>
    
    </head>
    <body>
    
      <svg width="300" height="300" xmlns="http://www.w3.org/2000/svg" >
        <!-- 一条路径 -->
        <path d="M 0 100, L 100 30, L 200 100, L 300 30" fill="transparent" stroke="red"></path>
    
        <!-- 一辆车 -->
        <rect x="-10" y="-5" width="20" height="10" rx="4" ry="4" fill="red">
          <animateMotion
            path="M 0 100, L 100 30, L 200 100, L 300 30"
            rotate="auto"
            dur="4s"
            fill="freeze"
            repeatCount="indefinite"
          ></animateMotion>
        </rect>
      </svg>
    
      <svg width="300" height="300" xmlns="http://www.w3.org/2000/svg" >
        <!-- 一条路径 -->
        <path id="linePath" d="M 0 100, L 100 30, L 200 100, L 300 30" fill="transparent" stroke="red"></path>
    
        <!-- 一辆车 -->
        <rect id="car" x="-10" y="-5" width="20" height="10" rx="4" ry="4" fill="red"></rect>
    
        <!-- 动画 -->
        <animateMotion
          href="#car"
          rotate="auto"
          dur="4s"
          fill="freeze"
        >
          <!-- 引用路径 -->
          <mpath href="#linePath"></mpath>
        </animateMotion>
      </svg>
    
    </body>
    </html>
    
    image.png

五、SVG + SMIL动画案例

5.1 飞机飞行轨迹

<svg width="400" height="200" viewBox="0 0 3387 1270" xmlns="http://www.w3.org/2000/svg">
  <defs>
    <style>
      svg {
        background-color: #28505d;
      }
      /**飞机飞行路线**/
      .planePath {
        stroke: #d9dada;
        stroke-width: 0.5%;
        stroke-dasharray: 1% 2%;
        stroke-linecap: round;
        fill: none;
      }
      /**飞机颜色**/
      .fil1 {
        fill: #d9dada;
      }
      .fil2 {
        fill: #c5c6c6;
      }
      .fil4 {
        fill: #9d9e9e;
      }
      .fil3 {
        fill: #aeafb0;
      }
    </style>
  </defs>

  <!-- 飞行路径 -->
  <path
    id="planePath"
    class="planePath"
    d="M-226 626c439,4 636,-213 934,-225 755,-31 602,769 1334,658 562,-86 668,-698 266,-908 -401,-210 -893,189 -632,630 260,441 747,121 1051,91 360,-36 889,179 889,179"
  />

  <!-- 飞机图形-->
  <g id="plane">
    <polygon
      class="fil1"
      points="-141,-10 199,0 -198,-72 -188,-61 -171,-57 -184,-57 "
    />
    <polygon class="fil2" points="199,0 -141,-10 -163,63 -123,9 " />
    <polygon
      class="fil3"
      points="-95,39 -113,32 -123,9 -163,63 -105,53 -108,45 -87,48 -90,45 -103,41 -94,41 "
    />
    <path
      class="fil4"
      d="M-87 48l-21 -3 3 8 19 -4 -1 -1zm-26 -16l18 7 -2 -1 32 -7 -29 1 11 -4 -24 -1 -16 -18 10 23zm10 9l13 4 -4 -4 -9 0z"
    />
    <polygon
      class="fil1"
      points="-83,28 -94,32 -65,31 -97,38 -86,49 -67,70 199,0 -123,9 -107,27 "
    />
  </g>

  <!-- 动画效果 -->
  <animateMotion
    href="#plane"
    rotate="auto"
    dur="5"
    repeatCount="indefinite"
  >
    <mpath href="#planePath"></mpath>
  </animateMotion>
</svg>
image.png

5.2 播放器

<svg
  xmlns="http://www.w3.org/2000/svg"
  width="80"
  height="100"
  viewBox="0 0 80 100"
  style="background: #e74c3c;"
>
  <!-- line 1 -->
  <rect
    fill="#fff"
    width="3"
    height="100"
    transform="rotate(180,3,50)"
  >
    <animate
      attributeName="height"
      values="30; 100; 30"
      dur="1s"
      repeatCount="indefinite"
      begin="0s"
    >
    </animate>
  </rect>
  <!-- line2 -->
  <rect
    x="17"
    fill="#fff"
    width="3"
    height="100"
    transform="rotate(180 20 50)"
  >
    <animate
      attributeName="height"
      values="30; 100; 30"
      dur="1s"
      repeatCount="indefinite"
      begin="0.1s"
    >
    </animate>
  </rect>
  <!-- line3 -->
  <rect
    x="40"
    fill="#fff"
    width="3"
    height="100"
    transform="rotate(180,40,50)"
  >
  <animate
    attributeName="height"
    values="30; 100; 30"
    dur="1s"
    repeatCount="indefinite"
    begin="0.3s"
  >
  </animate>

  </rect>
  <!-- line4 -->
  <rect
    x="60"
    fill="#fff"
    width="3"
    height="100"
    transform="rotate(180 58 50)"
  >
    <animate
      attributeName="height"
      values="30; 100; 30"
      dur="1s"
      repeatCount="indefinite"
      begin="0.5s"
    >
    </animate>
  </rect>
  <!-- line4 -->
  <rect
    x="80"
    fill="#fff"
    width="3"
    height="100"
    transform="translate(0) rotate(180 76 50)"
  >
    <animate
      attributeName="height"
      values="30; 100; 30"
      dur="1s"
      repeatCount="indefinite"
      begin="0.1s"
    >
    </animate>
  </rect>
</svg>

image.png

5.3 加载动画

<svg
  xmlns="http://www.w3.org/2000/svg"
  width="100"
  height="100"
  viewBox="0 0 100 100"
  style="background: #e74c3c;"
>
  <circle fill="#fff" stroke="none" cx="6" cy="50" r="6">
    <animate
      attributeName="opacity"
      values="0;1;0"
      dur="1s"
      begin="0s"
      repeatCount="indefinite"
    >
    </animate>
  </circle>
  <circle fill="#fff" stroke="none" cx="26" cy="50" r="6">
    <animate
      attributeName="opacity"
      values="0;1;0"
      dur="1s"
      begin="0.1s"
      repeatCount="indefinite"
    >
    </animate>
  </circle>
  <circle fill="#fff" stroke="none" cx="46" cy="50" r="6">
    <animate
      attributeName="opacity"
      values="0;1;0"
      dur="1s"
      begin="0.2s"
      repeatCount="indefinite"
    >
    </animate>
  </circle>
</svg>    
image.png

5.4 转动动画

<svg
  xmlns="http://www.w3.org/2000/svg"
  width="100"
  height="100"
  viewBox="0 0 100 100"
>
  <defs>
    <style>
      svg {
        background-color: #e74c3c;
      }
    </style>
  </defs>
  <!-- big circle -->
  <circle
    fill="none"
    stroke="#fff"
    stroke-width="6"
    stroke-miterlimit="15"
    stroke-dasharray="14.2472,14.2472"
    cx="50"
    cy="50"
    r="47"
  >
    <animateTransform
      attributeName="transform"
      type="rotate"
      values="0 50 50;360 50 50"
      dur="5s"
      repeatCount="indefinite"
      >
    </animateTransform>
  </circle>

  <!-- small circle -->
  <circle
    fill="none"
    stroke="#fff"
    stroke-width="1"
    stroke-miterlimit="10"
    stroke-dasharray="10,10"
    cx="50"
    cy="50"
    r="39"
  >
    <animateTransform
      attributeName="transform"
      type="rotate"
      values="0 50 50;-360 50 50"
      dur="5s"
      repeatCount="indefinite"
    >
    </animateTransform>
  </circle>

  <!-- rect -->
  <g fill="#fff">
    <rect x="30" y="35" width="5" height="30">
      <animateTransform
        attributeName="transform"
        type="translate"
        values="0 -5; 0 5; 0 -5"
        dur="1s"
        begin="0s"
        repeatCount="indefinite"
      >
      </animateTransform>
    </rect>
    <rect x="40" y="35" width="5" height="30">
      <animateTransform
      attributeName="transform"
      type="translate"
      values="0 -5; 0 5; 0 -5"
      dur="1s"
      begin="0.1s"
      repeatCount="indefinite"
      >
      </animateTransform>
    </rect>
    <rect x="50" y="35" width="5" height="30">
      <animateTransform
      attributeName="transform"
      type="translate"
      values="0 -5; 0 5; 0 -5"
      dur="1s"
      begin="0.2s"
      repeatCount="indefinite"
      >
      </animateTransform>
    </rect>
    <rect x="60" y="35" width="5" height="30">
      <animateTransform
      attributeName="transform"
      type="translate"
      values="0 -5; 0 5; 0 -5"
      dur="1s"
      begin="0.3s"
      repeatCount="indefinite"
      >
      </animateTransform>
    </rect>
    <rect x="70" y="35" width="5" height="30">
      <animateTransform
      attributeName="transform"
      type="translate"
      values="0 -5; 0 5; 0 -5"
      dur="1s"
      begin="0.4s"
      repeatCount="indefinite"
      >
      </animateTransform>
    </rect>
  </g>
</svg>

image.png

5.5 城市定位

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
      .first-box {
        width: 600px;
        height: 314px;
        background: url(./images/bg-location.png);
        background-size: 100% 100%;
      }
      
      /* 定位的 icon */
      #loc1 {
        animation: moveLoc1Icon1 1s linear infinite alternate;
      }
      #loc2 {
        animation: moveLoc1Icon2 1s linear infinite alternate;
        animation-delay: 0.3s;
      }
      
      @keyframes moveLoc1Icon1 {
        0%{
          transform: translateY(-20px);
        }
        100%{
          transform: translateY(0px);
        }
      }
      @keyframes moveLoc1Icon2 {
        0%{
          transform: translateY(0px);
        }
        100%{
          transform: translateY(10px);
        }
      }

      
      #c1 {
        animation: scaleEllipse1 2s linear infinite;
      }

      #c2 {
        animation: scaleEllipse2 2s linear infinite;
        animation-delay: 1s;
      }

      #c3, #c4 {
        animation: scaleEllipse3 2s linear infinite;
      }

      #c4 {
        animation-delay: 1s;
      }


      @keyframes scaleEllipse1 {
        0%{
          rx:0;
          ry:0;
          opacity: 1;
        }
        100% {
          rx:16;
          ry:8;
          opacity: 0;
        }
      }
      @keyframes scaleEllipse2 {
        0%{
          rx:0;
          ry:0;
          opacity: 1;
        }
        100%{
          rx:12;
          ry:6;
          opacity: 0;
        }
      }
      @keyframes scaleEllipse3 {
        0%{
          rx:0;
          ry:0;
          opacity: 1;
        }
        100%{
          rx:10;
          ry:5;
          opacity: 0;
        }
      }

    </style>
  </head>
  <body>
    <div class="box-left first-box">
      <svg
        id="location"
        height="400"
        version="1.1"
        width="744"
        xmlns="http://www.w3.org/2000/svg"
      >
        <desc>Created with Snap</desc>
        <defs>
          <linearGradient
            x1="100%"
            y1="40.7087699%"
            x2="4.9797314%"
            y2="60.8683027%"
            id="linearGradient-1"
          >
            <stop stop-color="#F5533D" stop-opacity="0" offset="0%"></stop>
            <stop stop-color="#F5533D" offset="44.5180532%"></stop>
            <stop stop-color="#F5533D" stop-opacity="0" offset="100%"></stop>
          </linearGradient>
        </defs>
        <image
          id="loc1"
          xlink:href="./images/redPoint.png"
          preserveAspectRatio="none"
          x="265"
          y="50.275"
          width="55"
          height="74"
        ></image>
        <image
          id="loc2"
          xlink:href="./images/smPoint.png"
          preserveAspectRatio="none"
          x="155"
          y="69.97776888888889"
          width="25"
          height="34"
        ></image>
        <image
          id="loc3"
          xlink:href="./images/smPoint.png"
          preserveAspectRatio="none"
          x="435"
          y="90.35983743115371"
          width="25"
          height="34"
        ></image>
        <image
          xlink:href=" ./images/bg-red.png"
          preserveAspectRatio="none"
          x="278"
          y="94"
          width="30"
          height="100"
        ></image>
        <image
          xlink:href=" ./images/bg-red.png"
          preserveAspectRatio="none"
          x="160"
          y="94"
          width="15"
          height="50"
        ></image>
        <image
          xlink:href="./images/first-smbg.png"
          preserveAspectRatio="none"
          x="438"
          y="125"
          width="20"
          height="20"
        ></image>
        <ellipse
          id="c1"
          cx="293"
          cy="187"
          rx="8.106666666666667"
          ry="3.546666666666667"
          fill="rgba(0,0,0,0)"
          stroke="#ff0000"
          style="opacity: 0.493333"
        ></ellipse>
        <ellipse
          id="c2"
          cx="293"
          cy="187"
          rx="0"
          ry="0"
          fill="rgba(0,0,0,0)"
          stroke="#ff0000"
          style="opacity: 1"
        ></ellipse>
        <ellipse
          id="c3"
          cx="168"
          cy="143"
          rx="0"
          ry="0"
          fill="rgba(0,0,0,0)"
          stroke="#ff0000"
          style="opacity: 1"
        ></ellipse>
        <ellipse
          id="c4"
          cx="168"
          cy="143"
          rx="6.08"
          ry="2.5333333333333337"
          fill="rgba(0,0,0,0)"
          stroke="#ff0000"
          style="opacity: 0.493333"
        ></ellipse>
      </svg>
    </div>
  </body>
</html>    

image.png

5.6 水球动画

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
      *,
      *:before,
      *:after {
        box-sizing: border-box;
        outline: none;
      }
      body {
        background: #020438;
        font: 14px/1 "Open Sans", helvetica, sans-serif;
      }
      .box {
        height: 280px;
        width: 280px;
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        background: #020438;
        border-radius: 100%;
        overflow: hidden;
      }
      .box .percent {
        position: absolute;
        left: 0;
        top: 0;
        z-index: 3;
        width: 100%;
        height: 100%;
        display: -webkit-box;
        display: -ms-flexbox;
        display: flex;
        display: -webkit-flex;
        align-items: center;

        justify-content: center;
        color: #fff;
        font-size: 64px;
      }
      .box .water {
        position: absolute;
        left: 0;
        top: 0;
        z-index: 2;
        width: 100%;
        height: 100%;
        transform: translate(0, 50%);
        background: #4d6de3;
      }
      .box .water_wave {
        width: 200%;
        position: absolute;
        bottom: 100%;
      }
      .box .water_wave_back {
        right: 0;
        fill: #c7eeff;
        animation: wave-back 1.4s linear infinite;
      }
      .box .water_wave_front {
        left: 0;
        fill: #4d6de3;
        margin-bottom: -1px;
        animation: wave-front 0.7s linear infinite;
      }

      @keyframes wave-back {
        0% {
          transform: translateX(0);
        }
        100% {
          transform: translateX(50%);
        }
      }

      @keyframes wave-front {
        0% {
          transform: translateX(0);
        }
        100% {
          transform: translateX(-50%);
        }
      }
    </style>
  </head>
  <body>
    <div class="water-ball">
      <svg
        version="1.1"
        xmlns="https://www.w3.org/2000/svg"
        xmlns:xlink="https://www.w3.org/1999/xlink"
        x="0px"
        y="0px"
        style="display: none"
      >
        <symbol id="wave">
          <path
            d="M420,20c21.5-0.4,38.8-2.5,51.1-4.5c13.4-2.2,26.5-5.2,27.3-5.4C514,6.5,518,4.7,528.5,2.7c7.1-1.3,17.9-2.8,31.5-2.7c0,0,0,0,0,0v20H420z"
          ></path>
          <path
            d="M420,20c-21.5-0.4-38.8-2.5-51.1-4.5c-13.4-2.2-26.5-5.2-27.3-5.4C326,6.5,322,4.7,311.5,2.7C304.3,1.4,293.6-0.1,280,0c0,0,0,0,0,0v20H420z"
          ></path>
          <path
            d="M140,20c21.5-0.4,38.8-2.5,51.1-4.5c13.4-2.2,26.5-5.2,27.3-5.4C234,6.5,238,4.7,248.5,2.7c7.1-1.3,17.9-2.8,31.5-2.7c0,0,0,0,0,0v20H140z"
          ></path>
          <path
            d="M140,20c-21.5-0.4-38.8-2.5-51.1-4.5c-13.4-2.2-26.5-5.2-27.3-5.4C46,6.5,42,4.7,31.5,2.7C24.3,1.4,13.6-0.1,0,0c0,0,0,0,0,0l0,20H140z"
          ></path>
        </symbol>
      </svg>
      <div class="box">
        <div class="percent">
          <div class="percentNum" id="count">0</div>
          <div class="percentB">%</div>
        </div>
        <div id="water" class="water">
          <svg viewBox="0 0 560 20" class="water_wave water_wave_back">
            <use xlink:href="#wave"></use>
          </svg>
          <svg viewBox="0 0 560 20" class="water_wave water_wave_front">
            <use xlink:href="#wave"></use>
          </svg>
        </div>
      </div>
    </div>
    <script>
      window.onload = function() {
        const waterEl = document.getElementById('water')
        const countEl = document.getElementById('count')
        
        let targetPercentage = 65
        let currentPercentage = 0
        let timeId = null

        timeId = setInterval(() => {
          currentPercentage ++
          if(currentPercentage >= targetPercentage) {
            clearInterval(timeId)
          }
          countEl.innerHTML = currentPercentage
          // 防止溢出
          if(currentPercentage <= 100) {
            waterEl.style.transform = `translateY(${100 - currentPercentage}%)`
          }
        }, 60);
      }
    </script>
  </body>
</html>
image.png