圆弧和二次贝塞尔曲线组成的容器中的波浪特效

 

 

<!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>定位图标内的双重波浪特效</title>
  </head>
  <body>
    <canvas id="canvas-online-rate" width="80" height="80"></canvas>
    <script>
      var ctx = document.querySelector("#canvas-online-rate").getContext("2d");
      var k0 = 0;
      var k1 = Math.PI / 2;
      // 开始
      function init() {
        k0 -= 0.05; // X轴偏移量
        k1 -= 0.025; // X轴偏移量
        // 清空重绘
        ctx.clearRect(0, 0, 80, 80);
        // 外轮廓
        ctx.beginPath();
        const circle = circleCurve(ctx);
        const bc = quadraticBesselCurve(ctx);
        ctx.closePath();
        ctx.strokeStyle = "#09bac0";
        ctx.stroke();
        // 绘制函数图像1
        ctx.beginPath();
        deawWave(ctx, bc, circle, k0);
        ctx.closePath();
        ctx.fillStyle = "#086873aa";
        ctx.fill();
        // 绘制函数图像2
        ctx.beginPath();
        deawWave(ctx, bc, circle, k1);
        ctx.fillStyle = "#09bac0";
        ctx.fill();
        // 动画
        window.requestAnimationFrame(init);
      }
      /**
       * 创建半圆弧
       * 返回半圆弧点集合和圆曲线方程
       */
      function circleCurve(ctx) {
        const o = { x: 40, y: 30 }; // 圆心
        const r = 20; // 半径
        // 圆的曲线方程:根据x求y
        const circleY = (x) => {
          const y = Math.sqrt(r * r - (x - o.x) * (x - o.x)) + o.y;
          if (y > 30) {
            return 30 - (y - 30);
          }
          return y;
        };
        ctx.arc(o.x, o.y, r, 0, Math.PI, true);
        // 保存圆的曲线点集合
        const circle = [];
        for (let i = 0; i < 2 * r; i++) {
          const x = o.x - r + i;
          const y = circleY(x);
          circle.push({ x, y });
        }
        return circle;
      }
      /**
       * 创建二次贝塞尔曲线
       * 返回二次贝塞尔曲线点集合
       */
      function quadraticBesselCurve(ctx) {
        const bcList = [];
        // 左侧函数点
        const lp0 = { x: 20, y: 30 };
        const lp1 = { x: 20, y: 50 };
        const lp2 = { x: 40, y: 70 };
        // 右侧函数点
        const rp0 = { x: 40, y: 70 };
        const rp1 = { x: 60, y: 50 };
        const rp2 = { x: 60, y: 30 };
        // 根据设定好的点绘制曲线并保存点坐标
        ctx.quadraticCurveTo(lp1.x, lp1.y, lp2.x, lp2.y);
        for (let i = 0; i < 40; i++) {
          const t = i / 40;
          const x = (1 - t) * (1 - t) * lp0.x + 2 * t * (1 - t) * lp1.x + t * t * lp2.x;
          const y = (1 - t) * (1 - t) * lp0.y + 2 * t * (1 - t) * lp1.y + t * t * lp2.y;
          bcList.push({ x, y });
        }
        ctx.quadraticCurveTo(rp1.x, rp1.y, rp2.x, rp2.y);
        for (let i = 1; i <= 40; i++) {
          const t = i / 40;
          const x = (1 - t) * (1 - t) * rp0.x + 2 * t * (1 - t) * rp1.x + t * t * rp2.x;
          const y = (1 - t) * (1 - t) * rp0.y + 2 * t * (1 - t) * rp1.y + t * t * rp2.y;
          bcList.push({ x, y });
        }
        return bcList;
      }
      /**
       * 绘制波浪区域范围
       */
      function deawWave(ctx, bc, circle, k) {
        const a = 3; // 振幅
        const w = (3 * Math.PI) / 40; // 角速度
        const y = 30; // 偏距 10~70
        // 正弦函数
        const sinFunc = (x, k) => {
          return a * Math.sin(w * x + k) + y;
        };

        let bcStartIdx = 0;
        let bcEndIdx = bc.length;
        if (y >= 30) {
          for (let i = 0; i < bc.length; i++) {
            const ix = bc[i].x;
            const iy = sinFunc(ix, k);
            if (iy <= bc[i].y) {
              if (!bcStartIdx) {
                bcStartIdx = i + 1;
              }
              ctx.lineTo(ix, iy);
            } else if (i > bc.length / 2) {
              bcEndIdx = i;
              break;
            }
          }
        } else {
          for (let i = 0; i < circle.length; i++) {
            const ix = circle[i].x;
            const iy = sinFunc(ix, k);
            if (iy > circle[i].y) {
              ctx.lineTo(ix, iy);
            } else {
              ctx.lineTo(ix, circle[i].y);
            }
          }
        }

        const newBc = bc.slice(bcStartIdx, bcEndIdx).reverse();
        for (let i = 0; i < newBc.length; i++) {
          ctx.lineTo(newBc[i].x, newBc[i].y);
        }
        ctx.closePath();
      }
      init();
    </script>
  </body>
</html>

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值