react + canvas实现图片框选

613 阅读2分钟

1. 对应的react的js文件

import React, { useEffect, useRef, useState } from 'react'
import { Button } from 'antd'
import DefaultImage from 'layout/images/oranges.jpg'

export default function CanvasTag() {
  const defaultSrc = DefaultImage

  const [canvasWidth, setCanvasWidth] = useState(0)
  const [canvasHeight, setCanvasHeight] = useState(0)
  const [historyRect, setHistoryRect] = useState([])
  const [isDrawing, setIsDrawing] = useState(false)
  const [rectInfo, setRectInfo] = useState({})


  const canvasWrapRef = useRef(null)
  const nativeImgRef = useRef(null)
  const canvasImgRef = useRef(null)
  const canvasRectRef = useRef(null)


  useEffect(() => {
    initImgDraw()
  }, [])

  useEffect(() => {
    // history 发生变化时重绘背景图层
    const bgCtx = canvasRectRef.current?.getContext('2d')
    if (bgCtx) {
      // 擦除全部矩形
      bgCtx.clearRect(
        0,
        0,
        canvasRectRef.current?.width,
        canvasRectRef.current?.height,
      )
      
      bgCtx.lineWidth = 2
      bgCtx.strokeStyle = '#ff0000'
      historyRect.forEach((rect) => {
        bgCtx.strokeRect(
          rect.startX,
          rect.startY,
          rect.endX - rect.startX,
          rect.endY - rect.startY,
        )
      })
    }
  }, [historyRect])

  useEffect(() => {
    // 绘制信息发生改变时触发, 回执结束时执行, 并且不能是一个点
    if (
      !isDrawing &&
      (rectInfo.startX !== rectInfo.endX || rectInfo.startY !== rectInfo.endY)
    ) {
      setHistoryRect([...historyRect, rectInfo])
    }
  }, [rectInfo, isDrawing])

  // canvas初始化
  const initImgDraw = () => {
    nativeImgRef.current.crossOrigin = 'anonymous'
    const canvasContent = canvasImgRef.current.getContext('2d')
    nativeImgRef.current.onload = () => {
      const imgWidth = nativeImgRef.current.offsetWidth
      const imgHeight = nativeImgRef.current.offsetHeight
      const wrapWidth = canvasWrapRef.current.offsetWidth
      const wrapHeight = (wrapWidth * imgHeight) / imgWidth
      // 图片承载canvas绘制
      canvasContent.drawImage(nativeImgRef.current, wrapWidth, wrapHeight)//绘制图片
      setCanvasWidth(wrapWidth)
      setCanvasHeight(wrapHeight)
      // 矩形canvas长宽设置
      canvasRectRef.current.width = wrapWidth
      canvasRectRef.current.height = wrapHeight
    }
  }

  /**
   * 鼠标落下,开始绘制,记录起始坐标
   * @param e
   */
  const startDraw = (e) => {
    setIsDrawing(true)
    setRectInfo({
      ...rectInfo,
      startX: e.nativeEvent.offsetX,
      startY: e.nativeEvent.offsetY,
    })
  }
    
  /**
     * 鼠标抬起,结束绘制,记录结束坐标
     * @param e
     */
  const overDraw = (e) => {
    setIsDrawing(false)
    setRectInfo({
      ...rectInfo,
      endX: e.nativeEvent.offsetX,
      endY: e.nativeEvent.offsetY,
    })
    // 清除当前图层
    const canvas = canvasRectRef.current
    const ctx = canvas?.getContext('2d')
    if (ctx) {
      ctx.clearRect(0, 0, canvas.width, canvas.height)
    }
  }
    
  /**
     * 鼠标移动
     * @param e
     */
  const drawRect = (e) => {
    if (!isDrawing) {
      return
    }
    const canvas = canvasRectRef.current
    const ctx = canvas?.getContext('2d')
    const { startX, startY } = rectInfo
    if (ctx) {
      ctx.lineWidth = 2;
      ctx.strokeStyle = '#ff0000'
      ctx.clearRect(0, 0, canvas?.width, canvas?.height)
      ctx.strokeRect(
        startX,
        startY,
        (e.nativeEvent.offsetX - startX),
        (e.nativeEvent.offsetY - startY),
      )
    }
  }
    
  /**
     * 撤销上一步绘制
     */
  const abortDraw = () => {
    const history = [...historyRect]
    history.splice(history.length - 1, 1)
    setHistoryRect(history)
  }
    
  /**
     * 保存绘制
     */
  const saveDraw = () => {
    //创建新的canvas
    const newCanvas = document.createElement('canvas')
    newCanvas.width = canvasWidth
    newCanvas.height = canvasHeight
    const newCanvasCtx = newCanvas.getContext('2d')
    newCanvasCtx.drawImage(canvasImgRef.current, canvasWidth, canvasHeight)
    newCanvasCtx.drawImage(canvasRectRef.current, canvasWidth, canvasHeight)
    const exportPdfImg = { url: newCanvas.toDataURL('image/png') }

    /*参数在传递形式写这篇文章的时候还没有确定,这里是只是一个 ajax 请求*/
  }
    
  return (
    <div className="canvas-container">
      <div className="canvasWrap" ref={canvasWrapRef}>
        <img className="img-native" ref={nativeImgRef} src={defaultSrc} alt=""/>
        <canvas id="canvasImg" ref={canvasImgRef}></canvas>
        <canvas 
          id="canvasRect" 
          ref={canvasRectRef}
          onMouseDown={startDraw}
          onMouseUp={overDraw}
          onMouseMove={drawRect}
        >
        </canvas>
      </div>

      <div className="operate-btn">
        <Button type="primary" onClick={abortDraw}>撤销</Button>
        <Button type="primary" onClick={saveDraw}>保存</Button>
      </div>
    </div>
  )
}

2. 样式文件

/*canvas画布*/
.canvas-container{
  width: 100%;
  max-width: 1000px;
  min-height: 500px;
  .canvasWrap{
    width: 100%;
    min-height: 400px;
    margin-bottom: 20px;
    position: relative;
    .img-native{
      width: 100%;
      height: auto;
    }
    #canvasImg{
      position: absolute;
      width: 100%;
      height: 100%;
      z-index: -1;
    }
    #canvasRect{
      position: absolute;
      width: 100%;
      height: 100%;
      z-index: 2;
      top:0;
      left:0;
    }
  }
  .operate-btn{
    width: 100%;
    padding: 20px 150px;
    display: flex;
    justify-content: space-evenly;
    align-items: center;
  }
}

效果如图

image.png

目前两个canvas合并成一张图片暂时没有成功,大家有好方法赶紧帮忙支招吧!!