小程序 canvas type=2d 來做畫畫板,有橡皮擦,選顏色,選粗細,撤銷和還原

 canvas api 文檔:https://www.canvasapi.cn/

組件裏的 canvas

 
 
        <canvas
         class="handWriting"
          id="handWriting"
          canvas-id="handWriting"
          type="2d"
          disable-scroll="true"
          @touchstart="uploadScaleStart"
          @touchmove="uploadScaleMove"
          @touchend="uploadScaleEnd"
          @touchcancel="uploadScaleEnd"
          @tap.stop
        />
this.handwriting = new Handwriting(
      {
        canvasName: 'handWriting',
      },
      this
    )
import { systemObj, loadImgSync, canvasToUrlSync } from '../../utils/utils'

class Handwriting {
  // 內置數據
  ctx: any
  canvas: any
  canvasWidth = 300
  canvasHeight = 600
  canvasSaveImgs: any = []
  drawCurrent = 0
  beginPoint: any = {}
  points: any = []
  canvasName = ''
  that = {}

  firstTouch = true // 第一次觸發
  lineColorVal = ''
  lineColorAlpha = 1
  lineSize = 10
  isClear = false // 橡皮擦

  constructor(opts: any, _this: any) {
    this.canvasName = opts.canvasName || 'handWriting'
    this.that = _this
    this.init()
  }

  init() {
    const query = uni.createSelectorQuery().in(this.that)
    const config = { node: true, size: true }
    query
      .select(`#${this.canvasName}`)
      .fields(config, (res: any) => {
        // console.log('canvas init:=>', res, systemObj)
        const canvas = res.node
        const ctx = canvas.getContext('2d')
        const { pixelRatio: dpr } = systemObj
        const width = res.width * dpr
        const height = res.height * dpr
        canvas.width = width
        canvas.height = height
        ctx.scale(dpr, dpr)
        this.ctx = ctx
        this.canvas = canvas
        this.canvasWidth = width
        this.canvasHeight = height
        // ctx.fillRect(10, 10, 33, 33) // test
      })
      .exec()
  }
  // 筆跡開始
  uploadScaleStart(event: any) {
    // console.log('start', event)
    const e = event.mp
    if (e.type !== 'touchstart') return false
    const { x, y } = e.touches[0]
    this.points.push({ x, y })
    this.beginPoint = { x, y }
    this.ctx.strokeStyle = `rgba(${this.lineColorVal}, ${this.lineColorAlpha})`
    this.ctx.lineWidth = this.lineSize

    if (systemObj.platform !== 'devtools') {
      this.ctx.lineCap = 'round'
      let type = 'source-over'
      this.isClear && (type = 'destination-out')
      this.ctx.globalCompositeOperation = type
    }

    // if (this.firstTouch) {
    //   this.firstTouch = false
    // }
  }
  drawLine(beginPoint: any, controlPoint: any, endPoint: any) {
    this.ctx.beginPath()
    this.ctx.moveTo(beginPoint.x, beginPoint.y)
    this.ctx.quadraticCurveTo(controlPoint.x, controlPoint.y, endPoint.x, endPoint.y)
    this.ctx.stroke()
    this.ctx.closePath()
  }
  // 筆跡移動
  uploadScaleMove(event: any) {
    // console.log('move')
    let e = event.mp
    if (e.type !== 'touchmove') return false
    if (e.cancelable) {
      // 判斷默認行爲是否已經被禁用
      if (!e.defaultPrevented) {
        e.preventDefault()
      }
    }
    const { x, y } = e.touches[0]
    this.points.push({ x, y })
    if (this.points.length > 3) {
      const lastTwoPoints = this.points.slice(-2)
      const controlPoint = lastTwoPoints[0]
      const endPoint = {
        x: (lastTwoPoints[0].x + lastTwoPoints[1].x) / 2,
        y: (lastTwoPoints[0].y + lastTwoPoints[1].y) / 2,
      }
      this.drawLine(this.beginPoint, controlPoint, endPoint)
      this.beginPoint = endPoint
    }
  }
  // 筆跡結束
  async uploadScaleEnd(event: any) {
    const e = event.mp
    if (e.type != 'touchend') return 0
    const { x, y } = e.changedTouches[0]
    this.points.push({ x, y })

    if (this.points.length > 3) {
      const lastTwoPoints = this.points.slice(-2)
      const controlPoint = lastTwoPoints[0]
      const endPoint = lastTwoPoints[1]
      this.drawLine(this.beginPoint, controlPoint, endPoint)
    }
    this.beginPoint = null
    this.points = []

    await this.drawEnd()
  }
  async getCanvasUrl() {
    return await canvasToUrlSync(this.canvas)
  }
  async drawEnd() {
    const saveSteps = this.canvasSaveImgs.length
    if (this.drawCurrent > 0) {
      this.canvasSaveImgs.length = saveSteps - this.drawCurrent
      this.drawCurrent = 0
    }
    this.canvasSaveImgs.push(await this.getCanvasUrl())
  }
  selectColorEvent(val: any) {
    this.lineColorVal = val
    this.isClear = false
  }
  selectLineSize(val: any) {
    this.lineSize = val
  }
  clearEvent() {
    this.isClear = true
  }
  noClearEvent() {
    this.isClear = false
  }
  clearAllCanvas() {
    this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight)
  }
  async stepChange(type: string) {
    const saveSteps = this.canvasSaveImgs.length

    if (type === 'last') {
      if (this.drawCurrent < saveSteps) {
        this.drawCurrent++
      } else {
        return
      }
    }
    if (type === 'next') {
      if (this.drawCurrent > 0) {
        this.drawCurrent--
      } else {
        return
      }
    }

    this.clearAllCanvas()
    this.ctx.globalCompositeOperation = 'source-over'

    if (this.drawCurrent === saveSteps) {
      return
    }

    const index = saveSteps - this.drawCurrent - 1
    const { pixelRatio: dpr } = systemObj
    const img: any = await loadImgSync(this.canvas, this.canvasSaveImgs[index])
    this.ctx.drawImage(
      img,
      0,
      0,
      this.canvasWidth,
      this.canvasHeight,
      0,
      0,
      this.canvasWidth / dpr,
      this.canvasHeight / dpr
    )
  }
  getHelpfulData() {
    return {
      drawCurrent: this.drawCurrent,
      saveSteps: this.canvasSaveImgs.length,
    }
  }
}

export default Handwriting

 

utils.js

export function loadImgSync(canvas: any, url: string) {
  const img = canvas.createImage()
  img.src = url
  return new Promise((resolve, reject) => {
    img.onload = () => {
      resolve(img)
    }
  })
}

export function canvasToUrlSync(canvas: any) {
  return new Promise((resolve, reject) => {
    wx.canvasToTempFilePath({
      canvas,
      success: async (res: any) => {
        // console.log('tools-url:>', res.tempFilePath)
        // uni.previewImage({
        //   urls: [res.tempFilePath]
        // })
        resolve(res.tempFilePath)
      },
      fail: (err) => {
        console.log('圖片生成失敗:' + JSON.stringify(err))
        reject()
      },
    })
  })
}

export const systemInfo = uni.getSystemInfoSync()

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章