天天看點

canvas動态畫貝塞爾曲線

canvas實作動态貝塞爾曲線效果,實作如下:

1、接口:

interface lineParams {
  start: Array<number>;
  end: Array<number>;
  curveness: number;
  percent: number;
}
export { lineParams };
           

2、js代碼:

import React, { useRef, useEffect } from 'react';
import { lineParams } from './model';

const LineAnimate = () => {
  let canvas: any = null;
  let ctx: any = null;
  let percent: number = 0;
  const canvasWrapRef = useRef(null);
  const canvasRef = useRef(null);
  // canvas初始化
  const initCanvas = () => {
    const canvasWrap = canvasWrapRef.current;
    canvas = canvasRef.current;
    canvas.height = canvasWrap.offsetHeight;
    canvas.width = canvasWrap.offsetWidth;
    ctx = canvas.getContext('2d');
  };
  /**
   * 繪制一條曲線路徑
   * @param  {Object} ctx canvas渲染上下文
   * @param  {Array<number>} start 起點
   * @param  {Array<number>} end 終點
   * @param  {number} curveness 曲度(0-1)
   * @param  {number} percent 繪制百分比(0-100)
   */
  const drawCurvePath = ({ start, end, curveness, percent }) => {
    const cp: Array<number> = [
      (start[0] + end[0]) / 2 - (start[1] - end[1]) * curveness,
      (start[1] + end[1]) / 2 - (end[0] - start[0]) * curveness,
    ];

    const t: number = percent / 100;

    const p0: Array<number> = start;
    const p1: Array<number> = cp;
    const p2: Array<number> = end;
    // 向量<p0, p1>
    const v01: Array<number> = [p1[0] - p0[0], p1[1] - p0[1]];
    // 向量<p1, p2>
    const v12: Array<number> = [p2[0] - p1[0], p2[1] - p1[1]];

    const q0: Array<number> = [p0[0] + v01[0] * t, p0[1] + v01[1] * t];
    const q1: Array<number> = [p1[0] + v12[0] * t, p1[1] + v12[1] * t];
    // 向量<q0, q1>
    const v: Array<number> = [q1[0] - q0[0], q1[1] - q0[1]];

    const b: Array<number> = [q0[0] + v[0] * t, q0[1] + v[1] * t];

    ctx.moveTo(p0[0], p0[1]);

    ctx.quadraticCurveTo(q0[0], q0[1], b[0], b[1]);
  };
  // 畫坐标軸
  const drawCoordinate = () => {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.beginPath();
    ctx.lineWidth = 1;
    ctx.strokeStyle = '#ccc';
    ctx.fillStyle = '#ccc';
    // 畫x軸
    ctx.moveTo(32, canvas.height - 32);
    ctx.lineTo(canvas.width - 32, canvas.height - 32);
    // 畫x軸坐标三角形
    ctx.moveTo(canvas.width - 40, canvas.height - 32);
    ctx.lineTo(canvas.width - 40, canvas.height - 36);
    ctx.lineTo(canvas.width - 32, canvas.height - 32);
    ctx.lineTo(canvas.width - 40, canvas.height - 28);
    ctx.lineTo(canvas.width - 40, canvas.height - 32);
    // 畫y軸
    ctx.moveTo(32, canvas.height - 32);
    ctx.lineTo(32, 32);
    // 畫y軸坐标三角形
    ctx.moveTo(32, 40);
    ctx.lineTo(28, 40);
    ctx.lineTo(32, 32);
    ctx.lineTo(36, 40);
    ctx.lineTo(32, 40);
    ctx.closePath();
    ctx.fill();
    ctx.stroke();
  };
  // 繪制貝塞爾曲線
  const drawQuadraticLine = () => {
    const params: lineParams = {
      start: [32, canvas.height - 32],
      end: [canvas.width - 32, 48],
      curveness: -0.3,
      percent,
    };
    ctx.beginPath();
    ctx.strokeStyle = '#b17ffe';
    ctx.lineWidth = 1;
    drawCurvePath(params);

    percent = (percent + 1) % 100;
    if (percent === 0) {
      setTimeout(() => {
        percent = 0;
        drawCoordinate();
      }, 500);
    }
    ctx.stroke();
    requestAnimationFrame(drawQuadraticLine);
  };
  useEffect(() => {
    initCanvas();
    drawCoordinate();
    drawQuadraticLine();
  }, []);
  return (
    <div ref={canvasWrapRef}>
      <canvas ref={canvasRef} />
    </div>
  );
};
export default LineAnimate;
           

3、css代碼:

{
    border: 1px solid #e8eaec;
    display: inline-block;
    height: 176px;
    margin-right: 32px;
    vertical-align: top;
    width: 176px;
    &:hover {
      box-shadow: 0 1px 6px rgba(0, 0, 0, 0.2);
    }
}
           

4、效果圖:

canvas動态畫貝塞爾曲線

随筆小結,不喜勿噴,謝謝。