canvas应用——实现一个简单的绘画板(2)

接上一篇:canvas应用——实现一个简单的绘画板(1)

功能2:鼠标拖动矩形变更位置

  在鼠标按下时要先判断该操作是绘制还是拖拽,这里以鼠标按下时的坐标是否在矩形内来判断,在矩形外则为绘制(isDrawing=true),在矩形内则为拖拽(isDragging=true)。若是为拖拽还要判断是哪一个矩形,选中的矩形设置isSelected=true。为了方便区分,为正在操作的矩形添加黑色边框。

function mouseDown(e) {
  startX = e.offsetX
  startY = e.offsetY
  rectIndex = rectList.findIndex(item => { // 是否在矩形内,findIndex()查找数组内的对象,找到符合条件的则返回该对象的下标,没有则返回-1
    if (item.startX < item.endX) {
      if (item.startY < item.endY) {
        return startX > item.startX && startX < item.endX && startY > item.startY && startY < item.endY
      } else {
        return startX > item.startX && startX < item.endX && startY > item.endY && startY < item.startY
      }
    } else {
      if (item.startY < item.endY) {
        return startX > item.endY && startX < item.startY && startY > item.startY && startY < item.endY
      } else {
        return startX > item.startX && startX < item.endX && startY > item.endY && startY < item.startY
      }
    }
  })
  if (rectIndex !== -1) {
    currentRect = rectList[rectIndex]
    isDragging = true
    currentRect.isSelected = true
  } else {
    isDrawing = true
  }
  color = colors[randomFromTo(0, 8)] // colors为存储颜色的数组,randomFromTo为一个方法,返回随机整数
}
function mouseMove(e) {
  endX = e.offsetX
  endY = e.offsetY
  if (isDrawing) {
    drawRects()
    context.globalAlpha = 0.3
    context.beginPath()
    context.moveTo(startX, startY)
    context.lineTo(endX, startY)
    context.lineTo(endX, endY)
    context.lineTo(startX, endY)
    context.lineTo(startX, startY)
    context.fillStyle = color
    context.strokeStyle = 'black'
    context.fill()
    context.stroke()
  } else if (isDragging) {
    const w = Math.abs(startX - endX)
    const h = Math.abs(startY - endY)
    if (endX < startX) {
      startX -= w
      endX -= w
      currentRect.startX -= w
      currentRect.endX -= w
    }
    if (endX >= startX) {
      startX += w
      endX += w
      currentRect.startX += w
      currentRect.endX += w
    }
    if (endY < startY) {
      startY -= h
      endY -= h
      currentRect.startY -= h
      currentRect.endY -= h
    }
    if (endY >= startY) {
      startY += h
      endY += h
      currentRect.startY += h
      currentRect.endY += h
    }
    drawRects()
  }
}

function mouseUp(e) {
  if (isDrawing) {
    rectList.unshift(new Rect(startX, startY, endX, endY, color))
    isDrawing = false
  }
  if (isDragging) {
    rectList.forEach(item => {
      item.isSelected = false
    })
    isDragging = false
  }
}

function drawRects() {
  context.clearRect(0, 0, canvas.width, canvas.height)
  for (let i = 0; i < rectList.length; i++) {
    let rect = rectList[i]
    context.globalAlpha = 0.3
    context.beginPath()
    context.moveTo(rect.startX, rect.startY)
    context.lineTo(rect.endX, rect.startY)
    context.lineTo(rect.endX, rect.endY)
    context.lineTo(rect.startX, rect.endY)
    context.lineTo(rect.startX, rect.startY)
    context.fillStyle = rect.color
    context.fill()
    if (rect.isSelected) {
      context.strokeStyle = 'black'
      context.stroke()
    }
  }
}

功能3:保存当前画布的图像

  利用HTMLCanvasElement.toDataURL()将画布图像转换成base64格式,并动态生成节点将图片展示出来,也可使用file-saver将图片保存到本地

function save () {
  const data = canvas.toDataURL('image/png', 1)
  const chileNode =document.createElement('img')
  chileNode.src = data
  document.getElementById('img-container').appendChild(chileNode)
}

功能4:清空画布

  清空画布在之前很多代码中都有使用过,为了方便把它抽离出来

function clearCanvas() {
  context.clearRect(0, 0, canvas.width, canvas.height)
}

  但是要完全清空还要把存储矩形对象的数组也清空掉,如果没有清空矩形数组的话后续绘制会把之前的矩形都显示出来

function clearAll () {
  rectList = []
  clearCanvas()
}

功能5:撤销与反撤销

撤销

  用undoArray存储每一步的操作,把每次绘制后的状态存储到数组中,每撤销一次就把数组末尾的pop出来,恢复到前一个状态,注意要加上判断,以免溢出报错

function mouseDown(e) {
  // 省略重复代码
  if (rectIndex !== -1) {
    currentRect = rectList[rectIndex]
    isDragging = true
    currentRect.isSelected = true
    undoArray.pop()
    const tempRectList = rectList.slice()
    const tempCurrentRect = Object.assign({}, currentRect)
    tempRectList.splice(rectIndex, 1, tempCurrentRect)
    undoArray.push(tempRectList)
  } else {
    isDrawing = true
  }
}
function mouseUp(e) {
  if (isDrawing) {
    rectList.unshift(new Rect(startX, startY, endX, endY, color))
  }
  if (isDragging) {
    rectList.forEach(item => {
      item.isSelected = false
    })
  }
  undoArray.push(rectList.slice()) // 在鼠标松开的时候把当前状态即rectList存进undoArray
  isDrawing = false
  isDragging = false
}
function undo () {
  if (undoArray.length > 0) {
    undoArray.pop()
    rectList = undoArray[undoArray.length - 1].slice()
  } else {
    rectList = []
  }
  drawRects()
}

反撤销

  反撤销其实和撤销差不多,这里用一个redoArray数组来存储反撤销的状态,把撤销时pop出来的队形push进redoArray里面,每执行一次反撤销也是把数组末尾的pop出来,恢复到前一个状态,pop出来的对象在存回undoArray里面,就可以实现连续的撤销与反撤销,这里也要加上判断,以免溢出报错

function undo () {
  // context.clearRect(0, 0, canvas.width, canvas.height)
  if (undoArray.length > 0) {
    redoArray.push(undoArray.pop())
    rectList = undoArray[undoArray.length - 1].slice()
  } else {
    rectList = []
  }
  drawRects()
}
function redo () {
  // context.clearRect(0, 0, canvas.width, canvas.height)
  if (redoArray.length > 0) {
    rectList = redoArray[redoArray.length - 1].slice()
    undoArray.push(redoArray.pop())
  }
  drawRects()
}

以上代码看起来会迷糊,之后会做优化

参考文献

  1. canvas实现鼠标拖拽矩形移动改变大小
  2. HTML5 - Canvas的使用样例14(图形增加鼠标点击、拖动交互)
  3. 探究 canvas 绘图中撤销(undo)功能的实现方式)

源码地址

https://github.com/13660539818/canvas-demo

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值