The canvas painting shape (round, oval, square) project REACT

DrawShape.jsx components as follows:

import React, { Component } from 'react'
// import ClassNames from 'classnames'
import PropTypes from 'prop-types'
import _ from 'lodash'
import './index.less'

class DrawShape extends Component {
  static propTypes = {
    style: PropTypes.object,
    width: PropTypes.number,
    height: PropTypes.number,
    onAddShape: PropTypes.func,
    type: PropTypes.string,
    shapeWidth: PropTypes.number,
    color: PropTypes.string,
  }

  static defaultProps = {
    style: {},
    width: 1000,
    height: 1000,
    onAddShape: _.noop,
    type: 'square',
    shapeWidth: 2,
    color: '#ee4f4f',
  }

  state = {
  }

  componentDidMount() {
    const { canvasElem } = this
    this.writingCtx = canvasElem.getContext('2d')

    if (canvasElem) {
      canvasElem.addEventListener('mousedown', this.handleMouseDown)
      canvasElem.addEventListener('mousemove', this.handleMouseMove)
      canvasElem.addEventListener('mouseup', this.handleMouseUp)
      canvasElem.addEventListener('mouseout', this.handleMouseOut)
    }
  }

  componentWillUnmount() {
    const { canvasElem } = this
    if (canvasElem) {
      canvasElem.removeEventListener('mousedown', this.handleMouseDown)
      canvasElem.removeEventListener('mousemove', this.handleMouseMove)
      canvasElem.removeEventListener('mouseup', this.handleMouseUp)
      canvasElem.removeEventListener('mouseout', this.handleMouseOut)
    }
  }

  handleMouseDown = (e) => {
    this.isDrawingShape = true
    if (this.canvasElem !== undefined) {
      this.coordinateScaleX = this.canvasElem.clientWidth / this.props.width
      this.coordinateScaleY = this.canvasElem.clientHeight / this.props.height
    }
    this.writingCtx.lineWidth = this.props.shapeWidth / this.coordinateScaleX
    this.writingCtx.strokeStyle = this.props.color
    const {
      offsetX,
      offsetY,
    } = e
    this.mouseDownX = offsetX
    this.mouseDownY = offsetY
  }

  handleMouseMove = (e) => {
    if (this.isDrawingShape === true) {
      switch (this.props.type) {
        case 'square':
          this.drawRect(e)
          break
        case 'circle':
          this.drawEllipse(e)
          break
      }
    }
  }

  handleMouseUp = () => {
    this.isDrawingShape = false
    this.props.onAddShape({
      type: this.props.type,
      color: this.props.color,
      width: this.squeezePathX(this.props.shapeWidth / this.coordinateScaleX),
      positionX: this.squeezePathX(this.positionX),
      positionY: this.squeezePathY(this.positionY),
      dataX: this.squeezePathX(this.dataX),
      dataY: this.squeezePathY(this.dataY),
    })
    this.writingCtx.clearRect(0, 0, this.props.width, this.props.height)
  }

  handleMouseOut = (e) => {
    this.handleMouseUp(e)
  }

  drawRect = (e) => {
    const {
      offsetX,
      offsetY,
    } = e
    this.positionX = this.mouseDownX / this.coordinateScaleX
    this.positionY = this.mouseDownY / this.coordinateScaleY
    this.dataX = (offsetX - this.mouseDownX) / this.coordinateScaleX
    this.dataY = (offsetY - this.mouseDownY) / this.coordinateScaleY
    this.writingCtx.clearRect(0, 0, this.props.width, this.props.height)
    this.writingCtx.beginPath()
    this.writingCtx.strokeRect(this.positionX, this.positionY, this.dataX, this.dataY)
  }

  drawCircle = (e) => {
    const {
      offsetX,
      offsetY,
    } = e
    const rx = (offsetX - this.mouseDownX) / 2
    const ry = (offsetY - this.mouseDownY) / 2
    const radius = Math.sqrt(rx * rx + ry * ry)
    const centreX = rx + this.mouseDownX
    const centreY = ry + this.mouseDownY
    this.writingCtx.clearRect(0, 0, this.props.width, this.props.height)
    this.writingCtx.beginPath()
    this.writingCtx.arc(centreX / this.coordinateScaleX, centreY / this.coordinateScaleY, radius, 0, Math.PI * 2)
    this.writingCtx.stroke()
  }

  drawEllipse = (e) => {
    const {
      offsetX,
      offsetY,
    } = e
    const radiusX = Math.abs(offsetX - this.mouseDownX) / 2
    const radiusY = Math.abs(offsetY - this.mouseDownY) / 2
    const centreX = offsetX >= this.mouseDownX ? (radiusX + this.mouseDownX) : (radiusX + offsetX)
    const centreY = offsetY >= this.mouseDownY ? (radiusY + this.mouseDownY) : (radiusY + offsetY)
    this.positionX = centreX / this.coordinateScaleX
    this.positionY = centreY / this.coordinateScaleY
    this.dataX = radiusX / this.coordinateScaleX
    this.dataY = radiusY / this.coordinateScaleY
    this.writingCtx.clearRect(0, 0, this.props.width, this.props.height)
    this.writingCtx.beginPath()
    this.writingCtx.ellipse(this.positionX, this.positionY, this.dataX, this.dataY, 0, 0, Math.PI * 2)
    this.writingCtx.stroke()
  }

  // data to be stored is compressed according to the resolution to a value between the canvas [0,1] 
  squeezePathX (value) {
    const {
      width,
    } = this.props
    return value / width
  }

  squeezePathY(value) {
    const {
      height,
    } = this.props
    return value / height
  }

  canvasElem

  writingCtx

  isDrawingShape = false

  coordinateScaleX

  coordinateScaleY

  mouseDownX = 0 // abscissa at mousedown 

  mouseDownY = 0 // when mousedown ordinate 

  positionX // stores shape data X 

  positionY // stores shape data Y 

  dataX // wide shape data stored 

  DATAY // stores shape data height of

  render() {
    const {
      width,
      height,
      style,
    } = this.props

    return (
      <canvas
        width={width}
        height={height}
        style={style}
        className="draw-shape-canvas-component-wrap"
        ref={(r) => { this.canvasElem = r }}
      />
    )
  }
}

export default DrawShape

Corresponding components DrawShape.jsx less follows:

.draw-shape-canvas-component-wrap {
  width: 100%;
  cursor: url('~ROOT/shared/assets/image/vn-shape-cursor-35-35.png') 22 22, nw-resize;
}

The corresponding higher order components DrawShape.jsx DrawShape.js following components:

import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { observer } from 'mobx-react'

import { DrawShape } from '@dby-h5-clients/pc-1vn-components'

import localStore from '../../store/localStore'
import remoteStore from '../../store/remoteStore'

@observer
class DrawShapeWrapper extends Component {
  static propTypes = {
    id: PropTypes.string.isRequired,
    style: PropTypes.object,
  }

  static defaultProps = {
    style: {},
  }

  handleAddShape = (shapeInfo) => {
    remoteStore.getMediaResourceById(this.props.id).state.addShape({
      type: shapeInfo.type,
      color: shapeInfo.color,
      width: shapeInfo.width,
      position: JSON.stringify([shapeInfo.positionX, shapeInfo.positionY]),
      data: JSON.stringify([shapeInfo.dataX, shapeInfo.dataY]),
    })
  }

  render() {
    const {
      slideRenderWidth,
      slideRenderHeight,
    } = remoteStore.getMediaResourceById(this.props.id).state

    const {
      currentTask,
      drawShapeConfig,
    } = localStore.pencilBoxInfo

    if (currentTask !== 'drawShape') {
      return null
    }

    return (
      <DrawShape
        style={this.props.style}
        onAddShape={this.handleAddShape}
        height={slideRenderHeight}
        width={slideRenderWidth}
        type={drawShapeConfig.type}
        shapeWidth={drawShapeConfig.width}
        color={drawShapeConfig.color}
      />
    )
  }
}

export default DrawShapeWrapper

Videos can be realized as local shape, but the above logic is saved to the local unfinished distal end remote data, the local shape of the painting removed. This applies to teachers and students end side of the scene. Then the remote components, we have to traverse the data remoteStore in turn show. code show as below:

import React, { Component } from 'react'
import PropTypes from 'prop-types'
import assign from 'object-assign'
import { autorun } from 'mobx'
import _ from 'lodash'
import { observer } from 'mobx-react'

import {
  drawLine,
  clearPath,
  drawWrapText,
  drawShape,
} from '~/shared/utils/drawWritings'

@observer
class RemoteWritingCanvas extends Component {
  static propTypes = {
    style: PropTypes.object,
    width: PropTypes.number,
    height: PropTypes.number,
    remoteWritings: PropTypes.oneOfType ([
      PropTypes.arrayOf (PropTypes.shape ({
        type: PropTypes.string,
        color: PropTypes.string,
        lineCap: PropTypes.string,
        lineJoin: PropTypes.string,
        points: PropTypes.string, // JSON 数组
        width: PropTypes.number,
      })),
      PropTypes.arrayOf (PropTypes.shape ({
        type: PropTypes.string,
        content: PropTypes.string,
        color: PropTypes.string,
        position: PropTypes.string,
        fontSize: PropTypes.number,
      })),
    ]),
  }

  static defaultProps = {
    style: {},
    width: 1000,
    height: 1000,
    remoteWritings: [],
  }


  componentDidMount() {
    this.writingCtx = this.canvasElem.getContext('2d')

    this.cancelAutoRuns = [
      autorun(this.drawWritingsAutoRun, { name: 'auto draw writings' }),
    ]

    // After recovery scribing resize 
    the this .resizeObserver = new new ResizeObserver (() => {
       the this .drawWritingsAutoRun ()
    })

    this.resizeObserver.observe(this.canvasElem)
  }

  componentWillUnmount() {
    this.resizeObserver.unobserve(this.canvasElem)
    _.forEach(this.cancelAutoRuns, f => f())
  }

  canvasElem

  writingCtx

  drawWritingsAutoRun = () => {
     // TODO performance optimization, has been filtered Videos crossed 
    the this .writingCtx.clearRect (0, 0, the this .props.width, the this .props.height)
    _.map(this.props.remoteWritings, (writing) => {
      if (['markPen', 'eraser', 'pencil'].indexOf(writing.type) > -1) {
        const {
          type,
          color,
          lineCap,
          lineJoin,
          points,
          width,
        } = writing

        const canvasWidth = this.props.width
        switch (type) {
          case 'eraser':
            clearPath(this.writingCtx, this.recoverPath(JSON.parse(points)), width * canvasWidth)
            break
          case 'pencil': // 同 markPen
          case 'markPen':
            drawLine(this.writingCtx, this.recoverPath(JSON.parse(points)), color, width * canvasWidth, lineJoin, lineCap)
            break
        }
      }
      if (writing.type === 'text') {
        const {
          color,
          content,
          fontSize,
          position,
        } = writing

        const [x, y] = this.recoverPath(JSON.parse(position))

        drawWrapText({
          canvasContext: this.writingCtx,
          text: content,
          color,
          fontSize: fontSize * this.props.width,
          x,
          Y,
        })
      }
      if (['square', 'circle'].indexOf(writing.type) > -1) {
        const {
          type,
          color,
          position,
          data,
        } = writing
        const width = this.recoverPathX(writing.width)
        let [positionX, positionY] = JSON.parse(position)
        let [dataX, dataY] = JSON.parse(data)
        positionX = this.recoverPathX(positionX)
        positionY = this.recoverPathY(positionY)
        dataX = this.recoverPathX(dataX)
        dataY = this.recoverPathY(dataY)
        drawShape({
          writingCtx: this.writingCtx,
          type,
          color,
          width,
          positionX,
          positionY,
          dataX,
          dataY,
        })
      }
    })
  }

  // the coordinate point [0,1] scaled according to the resolution canvas 
  recoverPath (path) {
    const {
      width,
      height,
    } = this.props
    return _.map(path, (val, i) => (i % 2 === 0 ? val * width : val * height))
  }

  recoverPathX(value) {
    const {
      width,
    } = this.props
    return value * width
  }

  recoverPathY(value) {
    const {
      height,
    } = this.props
    return value * height
  }

  render() {
    const {
      width,
      height,
      style,
    } = this.props
    const wrapStyles = assign({}, style, {
      width: '100%',
    })

    return (
      <canvas
        className="remote-writing-canvas-component-wrap"
        width={width}
        height={height}
        style={wrapStyles}
        ref={(r) => { this.canvasElem = r }}
      />
    )
  }
}

export default RemoteWritingCanvas

Wherein the drawing of utility functions used from drawWritings: Internal code is as follows:

/**
 * Draw a whole line
 * @param ctx
 * @param points
 * @param color
 * @param width
 * @param lineJoin
 * @param lineCap
 */
export function drawLine(ctx, points, color, width, lineJoin = 'miter', lineCap = 'round') {
  if (points.length >= 2) {
    ctx.lineWidth = width
    ctx.strokeStyle = color
    ctx.lineCap = lineCap
    ctx.lineJoin = lineJoin
    ctx.beginPath ()
    if (points.length === 2) {
      ctx.arc(points[0], points[1], width, 0, Math.PI * 2)
    } else {
      if (points.length > 4) {
        ctx.moveTo(points[0], points[1])
        for (let i = 2; i < points.length - 4; i += 2) {
          ctx.quadraticCurveTo(points[i], points[i + 1], (points[i] + points[i + 2]) / 2, (points[i + 1] + points[i + 3]) / 2)
        }
        ctx.lineTo(points[points.length - 2], points[points.length - 1])
      } else {
        ctx.moveTo(points[0], points[1])
        ctx.lineTo(points[2], points[3])
      }
    }
    ctx.stroke()
    ctx.closePath()
  }
}

/**
 * Draw a point, do optimization based on previously existing line
 * @param ctx
 * @param point
 * @param prevPoints
 * @param color
 * @param width
 * @param lineJoin
 * @param lineCap
 */
export function drawPoint(ctx, point, prevPoints, color, width, lineJoin = 'miter', lineCap = 'round') {
  ctx.lineWidth = width
  ctx.strokeStyle = color
  ctx.lineCap = lineCap
  ctx.lineJoin = lineJoin
  const prevPointsLength = prevPoints.length
  if (prevPointsLength === 0) { // 画一个点
    ctx.arc(point[0], point[1], width, 0, Math.PI * 2)
  } The else  IF (prevPointsLength === 2) { // begin scribing 
    ctx.beginPath ()
    ctx.moveTo(...point)
  } The else { // continue scribing 
    ctx.lineTo (... point)
  }
  ctx.stroke()
}

/**
 * Draw a set of lines, crossed translucent support, each update will remove all the repaint it crossed
 * @param ctx
 * @Param lines two-dimensional array, the array elements are crossed dots, eg [[1,2,3,4], [1,2,3,4,5,6], ...]
 * @param color
 * @param width
 * @param lineJoin
 * @param lineCap
 * @param canvasWith
 * @param canvasHeight
 */
export function drawOpacityLines(ctx, lines, canvasWith = 10000, canvasHeight = 10000) {
  ctx.clearRect(0, 0, canvasWith, canvasHeight)

  for (let i = 0; i < lines.length; i += 1) {
    const {
      points,
      color,
      width,
      lineJoin,
      lineCap,
    } = lines[i]
    const pointsLength = points.length

    if (pointsLength > 2) {
      ctx.strokeStyle = color
      ctx.lineCap = lineCap
      ctx.lineJoin = lineJoin
      ctx.lineWidth = width
      ctx.beginPath ()

      if (pointsLength > 4) {
        ctx.moveTo(points[0], points[1])
        for (let j = 2; j < pointsLength - 4; j += 2) {
          ctx.quadraticCurveTo(points[j], points[j + 1], (points[j] + points[j + 2]) / 2, (points[j + 1] + points[j + 3]) / 2)
        }
        ctx.lineTo(points[pointsLength - 2], points[pointsLength - 1])
      } else {
        ctx.moveTo(points[0], points[1])
        ctx.lineTo(points[2], points[3])
      }

      ctx.stroke()
      ctx.closePath()
    }
  }
}

/**
 * Erase path
 * @param ctx
 * @param {Array} points
 * @param width
 */
export function clearPath(ctx, points, width) {
  const pointsLength = points.length
  if (pointsLength > 0) {
    ctx.beginPath ()
    ctx.globalCompositeOperation = 'destination-out'

    if (pointsLength === 2) { // 一个点
      ctx.arc(points[0], points[1], width / 2, 0, 2 * Math.PI)
      ctx.fill()
    } else if (pointsLength >= 4) {
      ctx.lineWidth = width
      ctx.lineJoin = 'round'
      ctx.lineCap = 'round'
      ctx.moveTo(points[0], points[1])
      for (let j = 2; j <= pointsLength - 2; j += 2) {
        ctx.lineTo(points[j], points[j + 1])
      }
      ctx.stroke()
    }
    ctx.closePath()
    ctx.globalCompositeOperation = 'source-over'
  }
}

/**
 * Write
 * @param {object} textInfo
 * @param textInfo.canvasContext
 * @param textInfo.text
 * @param textInfo.color
 * @param textInfo.fontSize
 * @param textInfo.x
 * @Param textInfo.y
 */
export function drawText(
  {
    canvasContext,
    text,
    color,
    fontSize,
    x,
    Y,
  },
) {
  canvasContext.font = `normal normal ${fontSize}px Airal`
  canvasContext.fillStyle = color
  canvasContext.textBaseline = 'middle'
  canvasContext.fillText(text, x, y)
}

/**
 * Write beyond the right edge of the canvas wrap
 * @param {object} textInfo
 * @param textInfo.canvasContext
 * @param textInfo.text
 * @param textInfo.color
 * @param textInfo.fontSize
 * @param textInfo.x
 * @Param textInfo.y
 */
export function drawWrapText(
  {
    canvasContext,
    text,
    color,
    fontSize,
    x,
    Y,
  },
) {
  if (typeof text !== 'string' || typeof x !== 'number' || typeof y !== 'number') {
    return
  }
  const canvasWidth = canvasContext.canvas.width
  canvasContext.font = `normal normal ${fontSize}px sans-serif`
  canvasContext.fillStyle = color
  canvasContext.textBaseline = 'middle'

  // character to separate array 
  const = arrText text.split ( '' )
  let line = ''

  let calcY = y
  for (let n = 0; n < arrText.length; n += 1) {
    const testLine = line + arrText[n]
    const metrics = canvasContext.measureText(testLine)
    const testWidth = metrics.width
    if (testWidth > canvasWidth - x && n > 0) {
      canvasContext.fillText(line, x, calcY)
      line = arrText[n]
      calcY += fontSize
    } else {
      line = testLine
    }
  }
  canvasContext.fillText(line, x, calcY)
}

/**
 * Painting shape
 * @param {object} shapeInfo
 * @param shapeInfo.writingCtx
 * @param shapeInfo.type
 * @param shapeInfo.color
 * @param shapeInfo.width
 * @param shapeInfo.positionX
 * @param shapeInfo.positionY
 * @param shapeInfo.dataX
 * @param shapeInfo.dataY
 */
export function drawShape(
  {
    writingCtx,
    type,
    color,
    width,
    positionX,
    positionY,
    dataX,
    dataY,
  },
) {
  writingCtx.lineWidth = width
  writingCtx.strokeStyle = color
  if (type === 'square') {
    writingCtx.beginPath ()
    writingCtx.strokeRect(positionX, positionY, dataX, dataY)
  }
  if (type === 'circle') {
    writingCtx.beginPath ()
    writingCtx.ellipse(positionX, positionY, dataX, dataY, 0, 0, Math.PI * 2)
    writingCtx.stroke()
  }
}

There are two canvas width and height is provided:

1. properties height, width, the canvas is set resolution, i.e. the canvas coordinate range. If `canvasElem.height = 200; canvasElem.width = 400;` which corresponds to the bottom right coordinates (200, 400).

2. style style inside height and width, set the actual display size. If the same is canvasElem mentioned above, style is `{width: 100px; height: 100px}`, the monitor canvasElem mouseDown, bottom right click event acquired in the mouse position coordinates `(event.offsetX, event.offsetY) `` should be (100, 100) `.

The mouse click position drawn on a canvas coordinate conversion is required that trans `trans ([100, 100]) == [200, 400]` `trans` make the following coordinate conversion and then returns - x * canvas maximum transverse coordinate / display width - y * canvas coordinate maximum longitudinal / display height reference code trans = ([x, y]) => {const scaleX = canvasElem.width / canvasElem.clientWidth const scaleY = canvasElem.height / canvasElem.clientHeight return [x * scaleX, y * scaleY]} in general, we courseware display area of ​​a fixed size (the 4: 3 or 16: 9), the size and proportions shown courseware is not fixed, the display width of the scribe line canvas filled courseware display area, which the resolution is based on the resolution of the picture courseware load calculations come, so we usually need to coordinate conversion performed when crossed.

 

Summary: If you feel the above too much trouble, just want to achieve a simple drawing a straight line, shape, etc. locally, you can refer to this article: https://blog.csdn.net/qq_31164127/article/details/72929871

 

Guess you like

Origin www.cnblogs.com/chenbeibei520/p/11096319.html