zoukankan      html  css  js  c++  java
  • 原生 Cavans 实现画板功能,包含曲线、直线、矩形、原型、箭头、截图、橡皮擦等功能

    image

    image


    import React, { Component, Fragment } from 'react'
    import { Form, Button, Input, InputNumber } from 'antd'
    import DrawerArrow from './DrawerArrow'
    import styles from './index.less'
    
    const LIST = [
      {
        key: 'pen',
        name: '铅笔',
      },
      {
        key: 'line',
        name: '直线',
      },
      {
        key: 'rect',
        name: '矩形',
      },
      {
        key: 'arc',
        name: '圆形',
      },
      {
        key: 'arrow',
        name: '箭头',
      },
      {
        key: 'robber',
        name: '橡皮檫',
      },
      {
        key: 'screenshot',
        name: '截图',
      },
    ]
    class Main extends Component {
      constructor(props) {
        super(props)
        this.state = {
          bgUrlList: [], // 背景图 历史记录列表
          isDraw: false,
          brushType: null, // 默认为铅笔
          originX: undefined,
          originY: undefined,
          hisImgList: [], // 用于存储历史记录的数组
          step: -1, // 记录的步数
          areaSize: [600, 600], // 画板的尺寸
          screenshotArea: {
            x0: 0,
            y0: 0,
            x1: 600,
            y1: 600,
          }, // 截图区域的大小
          showSreenshoot: false, // 是否正在截图
        }
        this.cavasDom = null
        this.ctx = null
        this.originBgUrl =
          'https://data.znds.com/attachment/forum/201606/09/175354uvk8ck3wmxk5zv3k.jpg' // 背景图
        this.snapImg = new Image() // 用于动态实时记录上次绘制的 canvas 快照(不包含背景图片)
      }
    
      componentDidMount() {
        this.initCanvas()
      }
    
      initCanvas = () => {
        if (document.getElementById('cavasDom')) {
          this.cavasDom = document.getElementById('cavasDom')
          this.ctx = this.cavasDom.getContext('2d')
          this.bindHistory()
        }
      }
    
      // 选择画笔类型
      selectType = brushType => {
        this.setState({
          brushType,
        })
      }
    
      onMouseDown = event => {
        event.stopPropagation()
        const {
          form: { getFieldValue },
        } = this.props
        const { brushType, areaSize } = this.state
    
        if (!brushType) {
          return
        }
    
        const color = getFieldValue('color')
        const lineWidth = getFieldValue('lineWidth')
    
        const originX =
          event.pageX - document.getElementById('canvasWrapper').offsetLeft // 原点x坐标
        const originY =
          event.pageY - document.getElementById('canvasWrapper').offsetTop // 原点y坐标
    
        this.setState({
          originX,
          originY,
          isDraw: true,
        })
    
        if (brushType !== 'robber') {
          this.snapImg.src = this.cavasDom.toDataURL('image/png')
        }
    
        this.ctx.beginPath()
    
        if (brushType === 'screenshot') {
          this.ctx.strokeStyle = '#000'
          this.ctx.lineWidth = 1
          this.ctx.fillStyle = 'rgba(0, 0, 0, 0.2)'
          this.drawRect(0, 0, areaSize[0], areaSize[1], true)
          this.setState({
            screenshotArea: {
              x0: 0,
              y0: 0,
              x1: areaSize[0],
              y1: areaSize[1],
            },
          })
        } else {
          this.ctx.strokeStyle = color
          this.ctx.lineWidth = lineWidth
        }
    
        this.ctx.moveTo(originX, originY)
      }
    
      onMouseMove = event => {
        event.stopPropagation()
        const { originX, originY, isDraw, brushType, areaSize } = this.state
        if (isDraw && brushType) {
          let x = event.pageX - document.getElementById('canvasWrapper').offsetLeft
          let y = event.pageY - document.getElementById('canvasWrapper').offsetTop
    
          if (brushType === 'pen') {
            this.ctx.lineTo(x, y)
            this.ctx.stroke()
          } else if (brushType === 'robber') {
            this.ctx.strokeStyle = '#fff'
            this.ctx.clearRect(x - 10, y - 10, 20, 20)
          } else if (brushType === 'line') {
            this.drawLine(originX, originY, x, y)
          } else if (brushType === 'rect') {
            this.drawRect(originX, originY, x, y)
          } else if (brushType === 'arc') {
            this.drawCircle(originX, originY, x, y)
          } else if (brushType === 'arrow') {
            this.ctx.clearRect(0, 0, areaSize[0], areaSize[1])
            this.ctx.drawImage(this.snapImg, 0, 0, areaSize[0], areaSize[1])
            DrawerArrow(
              this.ctx,
              originX,
              originY,
              x,
              y,
              this.props.form.getFieldValue('color'),
            )
          } else if (brushType === 'screenshot') {
            this.drawRect(originX, originY, x, y, true)
            this.setState({
              screenshotArea: {
                x0: originX,
                y0: originY,
                x1: x,
                y1: y,
              },
            })
          }
        }
      }
    
      onMouseLeave = event => {
        event.stopPropagation()
        const { isDraw, brushType } = this.state
        if (!brushType) {
          return
        }
        if (isDraw) {
          this.setState({
            isDraw: false,
            showSreenshoot: brushType === 'screenshot',
          })
          this.ctx.closePath()
        }
      }
    
      onMouseUp = event => {
        const { brushType } = this.state
        event.stopPropagation()
        if (!brushType) {
          return
        }
        this.setState({
          isDraw: false,
          showSreenshoot: brushType === 'screenshot',
        })
        if (brushType !== 'screenshot') {
          this.bindHistory() // 当绘画结束时,调用history,保存这一步的历史记录
        }
      }
    
      // 取消截图
      cancelShoot = () => {
        const { hisImgList, bgUrlList, areaSize } = this.state
    
        this.ctx.clearRect(0, 0, areaSize[0], areaSize[1]) // 清空画布
        let tempImg = new Image()
        tempImg.src = hisImgList[hisImgList.length - 1]
        this.originBgUrl = bgUrlList[bgUrlList.length - 1]
    
        // 从数组中调取历史记录的最后一条,进行重绘
        tempImg.onload = () => {
          this.ctx.drawImage(tempImg, 0, 0, areaSize[0], areaSize[1])
        }
    
        this.setState({
          showSreenshoot: false,
          brushType: null,
        })
      }
    
      // 截图
      toShoot = () => {
        const { screenshotArea: SA, bgUrlList, hisImgList, areaSize } = this.state
    
        let newOriginX = SA.x0
        let newOriginY = SA.y0
        if (SA.x1 < SA.x0) {
          newOriginX = SA.x1
        }
        if (SA.y1 < SA.y0) {
          newOriginY = SA.y1
        }
    
        this.snapImg.src = hisImgList[hisImgList.length - 1]
    
        let bgImg = new Image()
        bgImg.src = this.originBgUrl
    
        bgImg.onload = () => {
          this.ctx.drawImage(bgImg, 0, 0, areaSize[0], areaSize[1])
          this.ctx.drawImage(this.snapImg, 0, 0, areaSize[0], areaSize[1])
    
          let img2 = new Image()
          img2.src = this.cavasDom.toDataURL('image/png')
    
          img2.onload = () => {
            this.ctx.drawImage(
              img2,
              newOriginX,
              newOriginY,
              Math.abs(SA.x0 - SA.x1),
              Math.abs(SA.y0 - SA.y1),
              0,
              0,
              areaSize[0],
              areaSize[1],
            )
    
            this.setState(
              {
                bgUrlList: [...bgUrlList, this.cavasDom.toDataURL('image/png')],
                hisImgList: [...hisImgList, this.cavasDom.toDataURL('image/png')],
              },
              () => {
                this.cancelShoot()
              },
            )
          }
        }
      }
    
      // 画直线
      drawLine = (originX, originY, x, y) => {
        const { areaSize } = this.state
        this.ctx.clearRect(0, 0, areaSize[0], areaSize[1])
        this.ctx.drawImage(this.snapImg, 0, 0, areaSize[0], areaSize[1])
        this.ctx.beginPath()
        this.ctx.moveTo(originX, originY)
        this.ctx.lineTo(x, y)
        this.ctx.stroke()
        this.ctx.closePath()
      }
    
      // 画矩形
      drawRect = (originX, originY, x, y, screenshot = false) => {
        const { areaSize } = this.state
        this.ctx.clearRect(0, 0, areaSize[0], areaSize[1])
        this.ctx.drawImage(this.snapImg, 0, 0, areaSize[0], areaSize[1])
        this.ctx.beginPath()
    
        let newOriginX = originX
        let newOriginY = originY
        if (x < originX) {
          newOriginX = x
        }
        if (y < originY) {
          newOriginY = y
        }
    
        this.ctx.rect(
          newOriginX,
          newOriginY,
          Math.abs(x - originX),
          Math.abs(y - originY),
        )
        this.ctx.stroke()
    
        if (screenshot) {
          this.ctx.fill()
        }
    
        this.ctx.closePath()
      }
    
      // 画圆形
      drawCircle = (originX, originY, x, y) => {
        const { areaSize } = this.state
        this.ctx.clearRect(0, 0, areaSize[0], areaSize[1])
        this.ctx.drawImage(this.snapImg, 0, 0, areaSize[0], areaSize[1])
        this.ctx.beginPath()
    
        let newOriginX = originX
        let newOriginY = originY
        if (x < originX) {
          newOriginX = x
        }
        if (y < originY) {
          newOriginY = y
        }
        let r = Math.sqrt(
          Math.abs(x - originX) * Math.abs(x - originX) +
            Math.abs(y - originY) * Math.abs(y - originY),
        )
        this.ctx.arc(
          Math.abs(x - originX) + newOriginX,
          Math.abs(y - originY) + newOriginY,
          r,
          0,
          2 * Math.PI,
        )
    
        this.ctx.stroke()
        this.ctx.closePath()
      }
    
      // 上一步
      toPrev = () => {
        const { step, hisImgList, bgUrlList, areaSize } = this.state
        let copyStep = step
    
        if (copyStep >= 0) {
          copyStep--
          this.ctx.clearRect(0, 0, areaSize[0], areaSize[1]) // 清空画布
          let tempImg = new Image()
          tempImg.src = hisImgList[copyStep]
    
          if (copyStep > 0) {
            this.originBgUrl = bgUrlList[copyStep]
          }
    
          tempImg.onload = () => {
            this.ctx.drawImage(tempImg, 0, 0, areaSize[0], areaSize[1])
          } // 从数组中调取历史记录,进行重绘
        } else {
          alert('没有上一步了')
        }
    
        this.setState({
          step: copyStep,
        })
      }
    
      // 下一步
      toNext = () => {
        const { step, hisImgList, bgUrlList, areaSize } = this.state
        let copyStep = step
    
        if (copyStep < hisImgList.length - 1) {
          copyStep++
          this.ctx.clearRect(0, 0, areaSize[0], areaSize[1]) // 清空画布
          let tempImg = new Image()
          tempImg.src = hisImgList[copyStep]
          this.originBgUrl = bgUrlList[copyStep]
    
          tempImg.onload = () => {
            this.ctx.drawImage(tempImg, 0, 0, areaSize[0], areaSize[1])
          } // 从数组中调取历史记录,进行重绘
        } else {
          alert('没有下一步了')
        }
    
        this.setState({
          step: copyStep,
        })
      }
    
      // 保存历史记录
      bindHistory = () => {
        const { step, hisImgList, bgUrlList } = this.state
        let copyStep = step
    
        copyStep += 1
    
        if (copyStep < hisImgList.length) {
          hisImgList.length = copyStep
          bgUrlList.length = copyStep
        }
    
        hisImgList.push(this.cavasDom.toDataURL('image/png'))
        bgUrlList.push(this.originBgUrl)
    
        this.setState({
          step: copyStep,
          hisImgList: [...hisImgList],
          bgUrlList: [...bgUrlList],
        })
      }
    
      // 最终保存
      toSave = () => {
        this.props.form.validateFields(err => {
          if (!err) {
            const { areaSize } = this.state
            this.snapImg.src = this.cavasDom.toDataURL('image/png')
    
            let bgImg = new Image()
            bgImg.src = this.originBgUrl
    
            bgImg.onload = () => {
              this.ctx.drawImage(bgImg, 0, 0, areaSize[0], areaSize[1])
              this.ctx.drawImage(this.snapImg, 0, 0, areaSize[0], areaSize[1])
    
              console.log(bgImg)
              console.log(bgImg.width)
              console.log(bgImg.height)
              console.log(this.cavasDom.toDataURL('image/png'), 2222)
            }
          }
        })
      }
    
      render() {
        const {
          form: { getFieldDecorator },
        } = this.props
    
        const { areaSize, brushType, showSreenshoot } = this.state
    
        return (
          <Fragment>
            <div id="canvasWrapper" className={styles.canvasWrapper}>
              <canvas
                className={styles.canvasStyle}
                id="cavasDom"
                width={areaSize[0]}
                height={areaSize[1]}
                onMouseDown={this.onMouseDown}
                onMouseMove={this.onMouseMove}
                onMouseLeave={this.onMouseLeave}
                onMouseUp={this.onMouseUp}
              />
              <img
                src={this.originBgUrl}
                alt=""
                className={styles.largeBgImg}
                style={{
                   areaSize[0],
                  height: areaSize[1],
                }}
              />
            </div>
            <br />
            <div id="select">
              {LIST.map(item => (
                <Button
                  key={item.key}
                  type={item.key === brushType ? 'primary' : 'default'}
                  onClick={() => this.selectType(item.key)}>
                  {item.name}
                </Button>
              ))}
    
              <Button onClick={this.toPrev}>上一步</Button>
              <Button onClick={this.toNext}>下一步</Button>
              <Button onClick={this.toSave}>保存</Button>
    
              <Form layout="inline">
                <Form.Item>
                  {getFieldDecorator('color', {
                    initialValue: '#f31212',
                  })(<Input type="color" style={{  60 }} />)}
                </Form.Item>
                <Form.Item>
                  {getFieldDecorator('lineWidth', {
                    initialValue: 2,
                  })(<InputNumber style={{  60 }} />)}
                </Form.Item>
              </Form>
    
              {showSreenshoot && (
                <div className={styles.shootWrapper}>
                  <div className={styles.btns}>
                    <Button onClick={this.cancelShoot}>取消截图</Button>
                    <Button onClick={this.toShoot}>确认截图</Button>
                  </div>
                </div>
              )}
            </div>
          </Fragment>
        )
      }
    }
    
    export default Form.create()(Main)
    
    

    画箭头

    let beginPoint = {}
    let stopPoint = {}
    let polygonVertex = []
    let CONST = {
      edgeLen: 50, // 箭头的头部长度
      angle: 30, // 箭头的头部角度
    }
    
    function paraDef(edgeLen, angle) {
      CONST.edgeLen = edgeLen
      CONST.angle = angle
    }
    
    // 封装的作图对象
    let Plot = {
      angle: '',
     
      dynArrowSize() {
        let x = stopPoint.x - beginPoint.x
        let y = stopPoint.y - beginPoint.y
        let length = Math.sqrt(x ** 2 + y ** 2)
    
        if (length < 50) {
          CONST.edgeLen = length / 2
        } else if (length < 250) {
          CONST.edgeLen /= 2
        } else if (length < 500) {
          CONST.edgeLen = (CONST.edgeLen * length) / 500
        }
      },
    
      // getRadian 返回以起点与X轴之间的夹角角度值
      getRadian(beginPoint, stopPoint) {
        Plot.angle =
          (Math.atan2(stopPoint.y - beginPoint.y, stopPoint.x - beginPoint.x) /
            Math.PI) *
          180
    
        paraDef(50, 30)
        Plot.dynArrowSize()
      },
    
      // /获得箭头底边两个点
      arrowCoord(beginPoint, stopPoint) {
        polygonVertex[0] = beginPoint.x
        polygonVertex[1] = beginPoint.y
        polygonVertex[6] = stopPoint.x
        polygonVertex[7] = stopPoint.y
        Plot.getRadian(beginPoint, stopPoint)
        polygonVertex[8] =
          stopPoint.x -
          CONST.edgeLen * Math.cos((Math.PI / 180) * (Plot.angle + CONST.angle))
        polygonVertex[9] =
          stopPoint.y -
          CONST.edgeLen * Math.sin((Math.PI / 180) * (Plot.angle + CONST.angle))
        polygonVertex[4] =
          stopPoint.x -
          CONST.edgeLen * Math.cos((Math.PI / 180) * (Plot.angle - CONST.angle))
        polygonVertex[5] =
          stopPoint.y -
          CONST.edgeLen * Math.sin((Math.PI / 180) * (Plot.angle - CONST.angle))
      },
    
      // 获取另两个底边侧面点
      sideCoord() {
        let midpoint = {}
        midpoint.x = (polygonVertex[4] + polygonVertex[8]) / 2
        midpoint.y = (polygonVertex[5] + polygonVertex[9]) / 2
        polygonVertex[2] = (polygonVertex[4] + midpoint.x) / 2
        polygonVertex[3] = (polygonVertex[5] + midpoint.y) / 2
        polygonVertex[10] = (polygonVertex[8] + midpoint.x) / 2
        polygonVertex[11] = (polygonVertex[9] + midpoint.y) / 2
      },
    
      // 画箭头
      drawArrow(ctx, color) {
        ctx.fillStyle = color
        ctx.beginPath()
        ctx.moveTo(polygonVertex[0], polygonVertex[1])
        ctx.lineTo(polygonVertex[2], polygonVertex[3])
        ctx.lineTo(polygonVertex[4], polygonVertex[5])
        ctx.lineTo(polygonVertex[6], polygonVertex[7])
        ctx.lineTo(polygonVertex[8], polygonVertex[9])
        ctx.lineTo(polygonVertex[10], polygonVertex[11])
        ctx.closePath()
        ctx.fill()
      },
    }
    
     
    
    const todo = (ctx, originX, originY, x, y, color) => {
      beginPoint.x = originX
      beginPoint.y = originY
      stopPoint.x = x
      stopPoint.y = y
      Plot.arrowCoord(beginPoint, stopPoint)
      Plot.sideCoord()
      Plot.drawArrow(ctx, color)
    }
    
    export default todo
    
    
  • 相关阅读:
    innodb count优化测试
    基于HTML5 Canvas生成粒子效果的人物头像
    基于HTML5 SVG炫酷文字爆炸特效
    一款基于jQuery轮播切换焦点图,可播放多张图片
    基于Bootstrap的jQuery开关按钮组合
    基于jQuery上下切换的焦点图—带缩略图悬浮
    基于HTML5 Canvas实现的图片马赛克模糊特效
    基于jQuery的宽屏可左右切换的焦点图插件
    基于HTML5的捕鱼达人游戏网页版
    基于HTML5实现的中国象棋游戏
  • 原文地址:https://www.cnblogs.com/cckui/p/14277487.html
Copyright © 2011-2022 走看看