canvas 刻度尺

使用React和Canvas动态绘制响应式刻度尺
该代码示例展示了如何在React应用中使用CanvasAPI创建一个可自定义的刻度尺,并使其根据窗口大小变化调整刻度间隔。通过监听窗口尺寸,动态计算并更新Canvas的宽度以及刻度线的间隔,确保刻度尺始终均匀分布。

参考链接:https://codepen.io/luren/pen/yEagYO

画布

<canvas
  id="rule"
  height="40"
  width="1020"
  style={{ position: 'absolute' }}></canvas>

配置项

const [config, setConfig] = useState({
  size: 101, // 刻度尺总刻度数,(100个间隔需要101个刻度线)
  x: 0, // 刻度尺x坐标位置,单位px
  y: 0, // 刻度尺y坐标位置,单位px
  w: 10, // 刻度线的间隔,单位px
  h: 10, // 刻度线基础长度,单位px
  width: 1020, // 刻度尺宽度(写长20px是为了最后一个刻度值能被显示出来)
  height: 40, // 刻度尺高度
});

画刻度方法

  const draw = (cxt: CanvasRenderingContext2D, config: configVo) => {
    const size = config.size || 101;
    const x = config.x || 0;
    const y = config.y || 0;
    const w = config.w || 10;
    const h = config.h || 10;
    let offset = 3; // 数字的偏移量

    // 画之前清空画布
    cxt.clearRect(0, 0, config.width, config.height);

    // 设置画笔属性
    cxt.strokeStyle = '#aaa'; // 画笔颜色
    cxt.lineWidth = 1; // 画笔大小
    cxt.font = '12'; //字体大小

    // 画刻度线
    for (let i = 0; i < size; i++) {
      // 开始一条路径
      cxt.beginPath();
      // 移动到指定位置
      cxt.moveTo(x + i * w, y);
      // 满10刻度时刻度线长一些 并且在上方表明刻度
      if (i % 10 == 0) {
        // 计算偏移量
        offset = (String(i / 10).length * 6) / 2; // 数字的长度为:String(i / 10).length, /2 是为了居中
        cxt.fillText(i + '', x + i * w, y + h * 3.5); // 数字
        cxt.lineTo(x + i * w, y + h * 2); // 刻度线长(2h)
      } else {
        // 满5刻度(刻度线长:1.5h)时的刻度线略长于1刻度(刻度线长1h)的
        cxt.lineTo(x + i * w, y + (i % 5 === 0 ? 1.5 : 1) * h);
      }
      // 画出路径
      cxt.stroke();
    }
  };

初始化

useEffect(() => {
  const canvas = document.getElementById('rule') as HTMLCanvasElement;
  if (!canvas) return;

  const cxt = canvas.getContext('2d') as CanvasRenderingContext2D;
  draw(cxt, config);
}, [config]);

完整代码

请添加图片描述

import { useState, useEffect } from 'react';

interface configVo {
  size: number;
  x: number;
  y: number;
  w: number;
  h: number;
  width: number;
  height: number;
}

const Rule = () => {
  const [config, setConfig] = useState({
    size: 101, // 刻度尺总刻度数,(100个间隔需要101个刻度线)
    x: 0, // 刻度尺x坐标位置,单位px
    y: 0, // 刻度尺y坐标位置,单位px
    w: 10, // 刻度线的间隔,单位px
    h: 10, // 刻度线基础长度,单位px
    width: 1020, // 刻度尺宽度(写长20px是为了最后一个刻度值能被显示出来)
    height: 40, // 刻度尺高度
  });

  const draw = (cxt: CanvasRenderingContext2D, config: configVo) => {
    const size = config.size || 101;
    const x = config.x || 0;
    const y = config.y || 0;
    const w = config.w || 10;
    const h = config.h || 10;
    let offset = 3; // 数字的偏移量

    // 画之前清空画布
    cxt.clearRect(0, 0, config.width, config.height);

    // 设置画笔属性
    cxt.strokeStyle = '#aaa'; // 画笔颜色
    cxt.lineWidth = 1; // 画笔大小
    cxt.font = '12'; //字体大小

    // 画刻度线
    for (let i = 0; i < size; i++) {
      // 开始一条路径
      cxt.beginPath();
      // 移动到指定位置
      cxt.moveTo(x + i * w, y);
      // 满10刻度时刻度线长一些 并且在上方表明刻度
      if (i % 10 == 0) {
        // 计算偏移量
        offset = (String(i / 10).length * 6) / 2; // 数字的长度为:String(i / 10).length, /2 是为了居中
        cxt.fillText(i + '', x + i * w, y + h * 3.5); // 数字
        cxt.lineTo(x + i * w, y + h * 2); // 刻度线长(2h)
      } else {
        // 满5刻度(刻度线长:1.5h)时的刻度线略长于1刻度(刻度线长1h)的
        cxt.lineTo(x + i * w, y + (i % 5 === 0 ? 1.5 : 1) * h);
      }
      // 画出路径
      cxt.stroke();
    }
  };

  useEffect(() => {
    const canvas = document.getElementById('rule') as HTMLCanvasElement;
    if (!canvas) return;

    const cxt = canvas.getContext('2d') as CanvasRenderingContext2D;
    draw(cxt, config);
  }, [config]);

  return (
    <canvas
      id="rule"
      height="40"
      width="1020"
      style={{ position: 'absolute' }}></canvas>
  );
};

export default Rule;

监听窗口变化(窗口宽度均分为100份)

1.使用width变量存储窗口宽度

const [width, setWidth] = useState(0);

2.监听窗口变化

useEffect(() => {
  getWrapperSize();
  window.addEventListener('resize', getWrapperSize);
  return () => {
    window.removeEventListener('resize', getWrapperSize);
  };
}, [getWrapperSize]);
const getWrapperSize = useCallback(() => {
  const canvas = document.getElementById('rule');
  if (!canvas) return;
  setConfig({
    ...config,
    w: window.innerWidth / 100,
  });
  canvas.setAttribute('width', window.innerWidth + 'px');
  setWidth(window.innerWidth);
}, [config]);

3.当config变化/width变化,都需要重新绘制尺子

useEffect(() => {
  const canvas = document.getElementById('rule') as HTMLCanvasElement;
  if (!canvas) return;

  const cxt = canvas.getContext('2d') as CanvasRenderingContext2D;
  draw(cxt, config);
}, [width, config]);

完整代码如下:此时窗口大小变化,尺子大小也会发生变化

请添加图片描述
请添加图片描述

import { useState, useCallback, useEffect } from 'react';

interface configVo {
  size: number;
  x: number;
  y: number;
  w: number;
  h: number;
  width: number;
  height: number;
}

const Rule = () => {
  const [width, setWidth] = useState(0);
  const [config, setConfig] = useState({
    size: 101, // 刻度尺总刻度数,(100个间隔需要101个刻度线)
    x: 0, // 刻度尺x坐标位置,单位px
    y: 0, // 刻度尺y坐标位置,单位px
    w: 10, // 刻度线的间隔,单位px
    h: 10, // 刻度线基础长度,单位px
    width: width, // 刻度尺宽度(写长20px是为了最后一个刻度值能被显示出来)
    height: 40, // 刻度尺高度
  });

  const draw = (cxt: CanvasRenderingContext2D, config: configVo) => {
    const size = config.size || 101;
    const x = config.x || 0;
    const y = config.y || 0;
    const w = config.w || 10;
    const h = config.h || 10;
    let offset = 3; // 数字的偏移量

    // 画之前清空画布
    cxt.clearRect(0, 0, config.width, config.height);

    // 设置画笔属性
    cxt.strokeStyle = '#aaa'; // 画笔颜色
    cxt.lineWidth = 1; // 画笔大小
    cxt.font = '12'; //字体大小

    // 画刻度线
    for (let i = 0; i < size; i++) {
      // 开始一条路径
      cxt.beginPath();
      // 移动到指定位置
      cxt.moveTo(x + i * w, y);
      // 满10刻度时刻度线长一些 并且在上方表明刻度
      if (i % 10 == 0) {
        // 计算偏移量
        offset = (String(i / 10).length * 6) / 2; // 数字的长度为:String(i / 10).length, /2 是为了居中
        cxt.fillText(i + '', x + i * w, y + h * 3.5); // 数字
        cxt.lineTo(x + i * w, y + h * 2); // 刻度线长(2h)
      } else {
        // 满5刻度(刻度线长:1.5h)时的刻度线略长于1刻度(刻度线长1h)的
        cxt.lineTo(x + i * w, y + (i % 5 === 0 ? 1.5 : 1) * h);
      }
      // 画出路径
      cxt.stroke();
    }
  };

  const getWrapperSize = useCallback(() => {
    const canvas = document.getElementById('rule');
    if (!canvas) return;

    setConfig({
      ...config,
      w: window.innerWidth / 100,
    });
    canvas.setAttribute('width', window.innerWidth + 'px');
    setWidth(window.innerWidth);
  }, []);

  useEffect(() => {
    setTimeout(() => {
      getWrapperSize();
    }, 100);

    window.addEventListener('resize', getWrapperSize);
    return () => {
      window.removeEventListener('resize', getWrapperSize);
    };
  }, []);

  useEffect(() => {
    const canvas = document.getElementById('rule') as HTMLCanvasElement;
    if (!canvas) return;

    const cxt = canvas.getContext('2d') as CanvasRenderingContext2D;
    draw(cxt, config);
  }, [width, config]);

  return (
    <canvas
      id="rule"
      height="40"
      width="1020"
      style={{ position: 'absolute' }}></canvas>
  );
};

export default Rule;
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值