1

/src/components/CanvasProgress/index.jsx

import Taro from "@tarojs/taro";
import React from "react";
import { View, Canvas, Image } from "@tarojs/components";

import styles from "./styles.module.less";

class CanvasProgress extends React.Component {
  static defaultProps = {
    style: {}, //容器的样式
    progress: 0.6, //进度 [0-1]
    backgroundColor: "#cccccc", //背景环颜色
    lineColor: "#02a101", //前景环颜色
    lineWidth: 5, //线宽
  };

  state = {
    wrapperId: "_progress_wrapper" + randomStr(),
    canvasId: "_progress_canvas" + randomStr(),
    canvas: null,
    ctx: null,
    width: 0,
    height: 0,
    imageUrl: "",
  };

  componentDidMount() {
    this.init();
  }

  componentDidUpdate(prevProps) {
    if (prevProps.progress !== this.props.progress) {
      this.draw();
    }
  }

  init = async () => {
    const { wrapperId, canvasId } = this.state;
    const { width, height } = await getElementRect(wrapperId);
    const { canvas, ctx } = await getCanvas(canvasId);
    const dpr = Taro.getSystemInfoSync().pixelRatio;
    canvas.width = width * dpr;
    canvas.height = height * dpr;
    ctx.scale(dpr, dpr);
    this.setState({ canvas, ctx, width, height }, this.draw);
  };

  draw = () => {
    const { progress, backgroundColor, lineWidth, lineColor } = this.props;
    const { canvas, ctx, width, height } = this.state;
    const radius = toFixed(0.5 * Math.min(width, height)) - lineWidth;
    const range = 2 * progress * Math.PI;
    const offsetRadian = -0.5 * Math.PI;

    ctx.lineWidth = lineWidth;
    ctx.lineCap = "round";
    ctx.beginPath();
    // 1
    ctx.strokeStyle = backgroundColor;
    ctx.arc(toFixed(width / 2), toFixed(height / 2), radius, 0, 2 * Math.PI);
    ctx.stroke();
    ctx.closePath();
    // 2
    ctx.beginPath();
    ctx.strokeStyle = lineColor;
    ctx.arc(
      toFixed(width / 2),
      toFixed(height / 2),
      radius,
      offsetRadian,
      range + offsetRadian
    );
    ctx.stroke();
    ctx.closePath();

    const base64URL = canvas.toDataURL("image/png", 0.5);
    this.setState({ imageUrl: base64URL });
  };

  render() {
    const { style, children } = this.props;
    const { wrapperId, canvasId, imageUrl } = this.state;
    return (
      <View
        id={wrapperId}
        className={styles.container}
        style={{ width: "200px", height: "200px", ...style }}
      >
        <Image className={styles.img} src={imageUrl} />
        <Canvas id={canvasId} type="2d" className={styles.canvas} />

        <View className={styles.content}>{children || ""}</View>
      </View>
    );
  }
}

// utils
const toFixed = (number = 0) => 0.01 * Math.floor(100 * number);

function randomStr(len = 16) {
  const string =
    "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
  const l = string.length;
  let str = "";
  for (let i = 0; i < len; i++) {
    const index = Math.floor((Math.random() * 100 * l) % l);
    str += string[index];
  }
  return str;
}

const getElementRect = async (eleId = "", delay = 200) => {
  return new Promise((resovle, reject) => {
    const t = setTimeout(() => {
      clearTimeout(t);

      Taro.createSelectorQuery()
        .select(`#${eleId}`)
        .boundingClientRect((rect) => {
          if (rect) {
            resovle(rect);
          } else {
            reject("获取不到元素");
          }
        })
        .exec();
    }, delay);
  });
};

const getCanvas = async (eleId = "", delay = 200) => {
  return new Promise((resolve, reject) => {
    const t = setTimeout(() => {
      clearTimeout(t);
      Taro.createSelectorQuery()
        .select(`#${eleId}`)
        .fields({ node: true })
        .exec((res) => {
          if (res && res[0] && res[0].node) {
            const canvas = res[0].node;
            const ctx = canvas.getContext("2d");
            resolve({ canvas, ctx });
          } else {
            reject("获取canvas失败");
          }
        });
    }, delay);
  });
};

export default CanvasProgress;

/src/components/styles.module.less

.container {
  position: relative;
  overflow: hidden;
}

.canvas,
.img {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}

.canvas {
  transform: translateX(-1000vw);
}

.img {
  display: block;
}

.content {
  position: absolute;
  z-index: 2;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}

使用

<CanvasProgress
  style={{ width:"200rpx", height: "200rpx", margin: "20rpx" }}
  progress={0.65}
>
  <Text>65%</Text>
</CanvasProgress>

darcrand
637 声望20 粉丝