canvas绘图

该博客介绍了如何在React项目中利用canvas进行图片批注,并实现撤回功能。通过设置canvas和图片的层级关系,以及在组件挂载后加载图片并动态调整canvas尺寸。在绘图过程中,记录每一步轨迹,撤回时重新渲染前一状态的轨迹。最后,将绘图后的canvas与原图合并生成新图片。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在react项目中使用canvas绘图,在原有图片上绘制批阅痕迹,
支持撤回功能,保留前面的绘图痕迹,在绘制完毕后合成一张新的图片,

pic可以随便找一张图片

import React, { Component } from 'react';
import "./index.less";
let pic = '...';

class Canvas extends Component {
  constructor(props) {
    super(props);
    this.state = {
      imgObj: {},
      isAllowDrawLine: false, //是否允许划线
      finalImg: ''
    }
  }
  componentDidMount() {
    let that = this;
    let canvasPic = new Image();
    canvasPic.setAttribute("crossOrigin", 'Anonymous')
    canvasPic.onload = function () {
      let width = parseInt(canvasPic.width);
      let height = parseInt(canvasPic.height);
      let imgObj = {
        width,
        height,
        url: pic,
        step: -1,
        historyCanvas: []
      }
      that.setState({
        imgObj
      })
    }
    canvasPic.src = pic
  }
  render() {
    let { imgObj,finalImg } = this.state;
    return (
      <div className='canvas_page'
        onMouseUp={(e) => { this.canvasMouseUp(e) }}>
        <button onClick={this.canvasBack}>撤回</button>
        <button onClick={this.hecheng}>合成</button>
        {imgObj && imgObj.url
          ? <div className='canvas_box'>
            <img src={imgObj.url} className='bg_img'
              style={{ width: '990px', height: parseInt(990 * imgObj.height / imgObj.width) + 'px' }} alt="" />
            <canvas id='myCanvas' width='990'
              height={parseInt(990 * imgObj.height / imgObj.width)}
              onMouseDown={(e) => { this.canvasMouseDown(e) }}
              onMouseMove={(e) => { this.canvasMouseMove(e) }}
              onMouseUp={(e) => { this.canvasMouseUp(e) }}
            ></canvas>
          </div>
          : null}
        {finalImg
        ?<img src={finalImg} className='final_img'/>:null}
      </div>
    )
  }
}
export default Canvas;

less文件如下

将作为背景的img底图设置为absolute,z-index:-1;

作为操作层的canvas的z-index:1,

.canvas_page {
    position: relative;
    width: 100%;
    padding: 15px 2%;
    box-sizing: border-box;
    .final_img{
        width: 300px;
    }
    >button {
        height: 30px;
        line-height: 28px;
        padding: 0 20px;
        letter-spacing: 2px;
        color: #333;
        border: 1px solid #333;
        border-radius: 5px;
        margin-right: 14px;
        font-size: 12px;
        margin-bottom: 10px;
        background-color: #fff;
    }
    .canvas_box{
      border: 1px solid #999;
      padding: 1px;
      position: relative;
      >img{
          z-index: -1;
          position: absolute;
          top: 0;
          left: 0;
      }
      >canvas{
        z-index: 1;
        cursor:crosshair;
      }
    }

}

首先取一张底图,如上的pic

在 componentDidMount中设置图片对象,

用onload获取图片的宽和高,用来动态设置canvas的宽和高,做到和图片保持一致.

撤回实际上是将每次的轨迹保存起来,撤回的时候取上一次的轨迹重新渲染

 historyCanvas 就是画笔的轨迹数组,step是步骤

以下是获取canvas内部鼠标的位置

 //获取canvas内鼠标位置
  getCanvasXY = (canvas, x, y) => {
    let rect = canvas.getBoundingClientRect()
    //x和y参数分别传入的是鼠标距离窗口的坐标,然后减去canvas距离窗口左边和顶部的距离。
    return {
      x: x - rect.left * (canvas.width / rect.width),
      y: y - rect.top * (canvas.height / rect.height) - 1
    }
  }

在按下鼠标的时候设置画笔类型和起点,

  canvasMouseDown = (e) => {
    e.preventDefault();
    let that = this;
    let myCanvas = document.getElementById('myCanvas');
    let context = myCanvas.getContext('2d');
    let ele = that.getCanvasXY(myCanvas, e.clientX, e.clientY)
    that.setState({
      isAllowDrawLine: true
    });
    context.save();
    context.setLineDash([5, 5]); //画虚线
    context.strokeStyle = "rgb(230,11,9)";
    context.moveTo(ele.x, ele.y);
  }

鼠标移动的时候,判断是否鼠标被按下,如果被按下就画

由isAllowDrawLine判断 ,鼠标按下的时候设为true,松开的时候设为false

  canvasMouseMove = (e) => {
    let that = this;
    let { isAllowDrawLine, imgObj } = that.state;
    let myCanvas = document.getElementById('myCanvas');
    let context = myCanvas.getContext('2d');
    let ele = that.getCanvasXY(myCanvas, e.clientX, e.clientY);
    if (isAllowDrawLine) {
      context.lineTo(ele.x, ele.y)
      context.stroke();
    }
  }

鼠标松开的时候,将你的画笔步数加1,并将画面保存到imgObj对象的historyCanvas数组中

  canvasMouseUp = e => {
    let that = this;
    e.stopPropagation();
    let { isAllowDrawLine, imgObj } = that.state;
    let { step, historyCanvas } = imgObj;
    let myCanvas = document.getElementById('myCanvas');
    let context = myCanvas.getContext('2d');
    if (!isAllowDrawLine) return
    if (isAllowDrawLine) {
      that.setState({
        isAllowDrawLine: false
      });
      step++;
      if (step < historyCanvas.length) {
        historyCanvas.length = step; // 截断数组
      }
      historyCanvas.push(myCanvas.toDataURL()); // 添加新的绘制到历史记录
      imgObj = { ...imgObj, step, historyCanvas }
      console.log(imgObj)
      that.setState({
        imgObj
      })
      context.restore();
    }
  }

撤回的时候先判断是否有轨迹

如果有的话,先清空画布,即context.clearRect();

beginPath() 方法开始一条路径或重置当前的路径,一定不能忘了,

不然不能清除之前的轨迹

  canvasBack = () => {
    let that = this;
    let { imgObj, isAllowDrawLine } = that.state;
    let { step, historyCanvas } = imgObj;
    let myCanvas = document.getElementById('myCanvas');
    if (!myCanvas) return
    let context = myCanvas.getContext('2d');
    if (step >= 0) {
      context.clearRect(0, 0, myCanvas.width, myCanvas.height);
      context.beginPath();
      if (step < historyCanvas.length) {
        historyCanvas.length = step; // 截断数组
      }
      step--;
      imgObj = { ...imgObj, step, historyCanvas }
      if (historyCanvas.length > 0) {
        let canvasPic = new Image();
        canvasPic.onload = function () {
          context.drawImage(canvasPic, 0, 0, myCanvas.width, myCanvas.height);
        }
        canvasPic.src = historyCanvas[step];
      }
      that.setState({
        imgObj
      })
    } else {
      alert('撤回到最后了')
    }
  }

在绘制完轨迹后将你的canvas画布和底图进行合并,制成一张新图,

首先拿到最后一个画布的轨迹 lastCanvas

新建一个和底图大小一致canvas,在底图onload之后将底图绘制到新建的canvas上,

然后再将我们的lastCanvas绘制到新的canvas上,实现两张图片的重叠合并,

拼成一张新的并展示

  hecheng = () => {
    let that = this;
    let { imgObj } = that.state;
    let { width, height, step, historyCanvas, url } = imgObj;
    if (historyCanvas.length == 0) {
      //没有经过绘图
      that.setState({
        finalImg: url
      })
    } else {
     //合并canvas和图片
     let lastCanvas = historyCanvas[historyCanvas.length - 1];
     var Newcanvas = document.createElement('canvas');
     var newContext = Newcanvas.getContext('2d');
     Newcanvas.width = width;
     Newcanvas.height = height;
     let newImg = new Image();
     newImg.setAttribute("crossOrigin", 'Anonymous')
     newImg.onload = function () {
         newContext.drawImage(newImg, 0, 0, width, height, 0, 0, Newcanvas.width, Newcanvas.height);
         var canvasImg = new Image();
         canvasImg.onload = function () {
             newContext.drawImage(canvasImg, 0, 0, 990, parseInt(990 * height / width), 0, 0, Newcanvas.width, Newcanvas.height);
             var dataURL = Newcanvas.toDataURL("image/png");
             that.setState({
              finalImg:dataURL
             })
         }
         canvasImg.src = lastCanvas;
     }
     newImg.src = url;
    }
  }

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值