iovxw

多种方式实现波浪动画

基础动画实现

当我知道有些动画效果是纯靠图片拼起来的时候我是绝望的,虽然的确很省计算资源,但是你们让高分屏往哪哭?

大部分动画效果,直接用相应图形库自带的函数就能解决

有些稍微麻烦一点的,就得手写了(当然还是不推荐,毕竟人家的函数一般都带各种加速)

这时候就体现出了一个 客户端/网页端 的前端稍微懂点数学是多么的重要

不过太高级的数学我也不会,只能讲点简单的,就是实现一个波浪动画


首先想到的,是用各种绘图库都有提供的贝塞尔曲线直接画(关于贝塞尔曲线的介绍 可以看这个,讲的很清晰)

下面这个就是用一条三次贝塞尔曲线画出来的(源码请直接查看网页源码)

基本步骤是,先在中间画一条三次贝塞尔曲线,再画三条线把下面包起来填充做成 "水"

最后找规律写个控制算法控制曲线的两个控制点循环就行

不过控制算法写的不好,导致看起来并不是很舒服,也不想继续优化了,所以这个版本弃坑


第二个版本,直接用三角函数画,简单暴力

下面开始复习初中数学

我们都知道,在二维坐标系里,有 x y 就有了一个点,俩点连起来就有了一条线

而要画一条横着的直线,只需要知道三个值,x1 x2y,因为两个点 y 是一样的

画起来是这样的(伪代码):

x1 = 0
x2 = 10
y = 0
ctx = canvas.get_context() // 基本各种绘图库都有这个方法,获取一个 "画笔"
ctx.begin_path() // 开始标记一个路径

ctx.move_to(x0, y) // 将画笔移到起点

for (x=x1+1; x<x2+1; x++) {
    ctx.line_to(x, y) // 将前后两点连起来
}

ctx.stroke() // 将路径画出来

当然可以直接一个 line_tox2,不过这里是为了演示逐点画线

至于曲线,聪明的你肯定知道了,画点的过程中想个办法让 y 根据 x 上下跑就可以了

for (x=x1+1; x<x2+1; x++) {
    y = magic(x)
    ctx.line_to(x, y)
}

把里面的 magic 换成三角函数,比如正弦函数(sin),你就可以画出来这样的曲线:

https://upload.wikimedia.org/wikipedia/commons/thumb/b/b2/Sin.svg/800px-Sin.svg.png

(图片来自维基百科,公有领域)

但这还不够,我们需要让这条曲线看起来更 "柔和" 一些,所以让我们把 x 变小一点,比如:

y = sin(0.3 * x)

https://img.vim-cn.com/f6/8fa1280ff1f6eac4e46afacb57945f4c8afb02.svg

红线是 * 0.3 的结果,看起来好多了

目前为止浪高都是一样的,需要让他再大一些,修改一下 y 的波动幅度,比如两倍(黄线)

y = 2 * sin(0.3 * x)

https://img.vim-cn.com/86/8a94484098b7831f7e1615d2435de82457a0f9.svg

同时需要让曲线能上下移动(控制水量),直接改变 y 的高度,比如向上 1 点(绿线)

y = 2 * sin(0.3 * x) + 1

https://img.vim-cn.com/6c/a819fd1685d8241cb9dd49d59052acad5397ca.svg

最后让波浪向左平移,动起来(offset 在每次动画循环中自增)

y = 2 * sin(0.3 * x + offset) + 1

演示就不做了,好麻烦

总之现在就有了一个波浪动画,改变波浪形状只需要把上面挨个介绍过的量修改就行

上面的双波浪是分别用的正弦和余弦函数做成的,也可以直接给 offset 加个差值


第三个版本,因为在 svg 中靠预先绘制点连线做曲线是相当丑陋的,根本经不起放大,所以这个版本用二次贝塞尔曲线绘制

这次直接讲源码

<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg"
    xmlns:xlink="http://www.w3.org/1999/xlink">
  <defs>
    <g id="wave"> <!--单条波浪,具体怎么画的请看之前的介绍贴-->
      <path d="M0 0 Q 50 10, 100 0 T 200 0 V 50 H 0 V 0" />
    </g>
    <clipPath id="boder"> <!--圆形遮罩,用来做边框-->
      <circle cx="50" cy="50" r="50" />
    </clipPath>
  </defs>
  <g clip-path="url(#boder)">
    <rect x="0" y="0" width="100" height="100" fill="white" /> <!--白色背景-->
    <g>
      <g fill="rgba(10, 132, 255, 0.7)"> <!--多条波浪拼成一条完整波浪-->
        <!--需要的最少波浪数量为:
            向上取整(图宽/波浪长度)+1-->
        <use x="0" y="50" xlink:href="#wave" />
        <use x="200" y="50" xlink:href="#wave" />
      </g>
      <!--第二条波浪,用 translate 加偏移,推荐偏移波浪长的 1/4-->
      <g transform="translate(-50)" fill="rgba(1, 112, 223, 0.7)">
        <!--因为加了偏移,所以还要判断
            向上取整(图宽/波浪长度)*波浪长度-偏移>=图宽
            否则再+1-->
        <use x="0" y="50" xlink:href="#wave" />
        <use x="200" y="50" xlink:href="#wave" />
      </g>
      <!--动画效果,to 等于单条波浪长度,dur 为波浪移动速度-->
      <animateTransform attributeName="transform"
                        attributeType="XML"
                        type="translate"
                        from="0"
                        to="-200"
                        dur="1s"
                        repeatCount="indefinite"/>
    </g>
  </g>
</svg>

拼接波浪的数量也不用真的去算,看见缺了的话再加一条就行了

把动画放慢再展开的效果(蓝色是第一条波浪,红色第二条,可以看见波浪的拼接线):