全国服务热线:4008-888-888

技术知识

怎样在Canvas中加上恶性事件的方式示例

做为1个前端开发,给元素加上恶性事件是1件习以为常的事儿。但是在Canvas中,其所画的任何物品全是没法获得的,更别说加上恶性事件,那末大家对其就无计可施了吗?自然并不是的!大家在平常新项目中毫无疑问都用过很多Canvas的架构,大家发现恶性事件在这些架构中早已应用的10分为熟了,并且并沒有出現非常比较严重的难题。那末大家能够毫无疑问的是,恶性事件在Canvas中其实不是1个没法碰触的事儿。

1个傻瓜式的方法

大家都了解1个元素在开启1个恶性事件时,其电脑鼠标的部位基础处在该元素之上,那末大家就当然而然的想起根据当今电脑鼠标的部位和物件所占有的部位开展比对,从而大家就可以得出该物件是不是应开启恶性事件。这类方法较为简易,我就无需编码演试了,但是既然我叫它傻瓜式的方法,很显著它并不是1个合理的处理方法。由于物件所占有的部位其实不1定是10分非常容易获得,假如是矩形框、圆形等大家还能根据1些简易的公式获得其占有的部位,但是在繁杂点的多边形,乃至是多边形的一些边是弧线的,不言而喻,大家这时候候再获得其所占有的部位时是1件极为繁杂且难度巨大的事儿,因此这类方法只合适自身在做1些demo中应用,其实不可用于大多数数的状况。

1个较聪慧的方法

既然上面这类方法栽跟头了,那末大家只能独辟蹊径。在翻阅CanvasAPI的情况下,寻找了1个方式isPointInPath,貌似更是大家苦苦找寻的良药。

详细介绍isPointInPath

isPointInPath的功效:说白了,大家很直观的能够了解该方式用以分辨点是不是处在相对路径之中。

isPointInPath的入参出参:ctx.isPointInPath([path, ]x, y [, fillRule]),该方式的主要参数有4个,在其中path和fillRule为选填,x和y为必填。大家先后详细介绍4个主要参数。

path:看到这个主要参数,我刚开始认为是beginPath或closePath的回到值,很可是的是这两个方式并沒有回到值,在查阅了材料后,发现是Path2D结构涵数new的目标。Path2D结构涵数实际用法。但是可是的是该方式将会因为适配性的难题,现阶段看了1些开源系统架构都还未应用。

x,y:这两个主要参数很好了解,便是x轴和y轴的间距,必须留意的是,其相对性部位是Canvas的左上角。

fillRule:nonzero(默认设置),evenodd。非零围绕标准和奇偶数标准是图型学中分辨1个点是不是处在多边形内的标准,在其中非零围绕标准是Canvas的默认设置标准。想实际掌握这两种标准的,能够自身去查阅材料,这里就不提升篇数详细介绍了。

上面详细介绍完了入参,那末isPointInPath方式的出参想必大伙儿都可以以猜到了,便是true和false。

应用isPointInPath

上1节详细介绍完isPointInPath方式后,大家如今就来应用它吧。

先来1个简易的demo:

  const canvas = document.getElementById('canvas')
  const ctx = canvas.getContext('2d')

  ctx.beginPath()
  ctx.moveTo(10, 10)
  ctx.lineTo(10, 50)
  ctx.lineTo(50, 50)
  ctx.lineTo(50, 10)
  ctx.fillStyle= 'black'
  ctx.fill()
  ctx.closePath()

  canvas.addEventListener('click', function (e) {
    const canvasInfo = canvas.getBoundingClientRect()
    console.log(ctx.isPointInPath(e.clientX - canvasInfo.left, e.clientY - canvasInfo.top))
  })

如图所示,灰色一部分为Canvas所占有的地区,黑色为大家具体加上恶性事件的地区,在大家点一下黑色地区后,具体也确实如大家所愿,复印出来的值为true。貌似Canvas的恶性事件监视就这么简易的处理了,但是事儿真有这么简易吗。明显是不能能的!大家再来举个事例,这时候候有两个地区,而且大家必须各自给其关联不一样的恶性事件:

  const canvas = document.getElementById('canvas')
  const ctx = canvas.getContext('2d')

  ctx.beginPath()
  ctx.moveTo(10, 10)
  ctx.lineTo(10, 50)
  ctx.lineTo(50, 50)
  ctx.lineTo(50, 10)
  ctx.fillStyle= 'black'
  ctx.fill()
  ctx.closePath()

  ctx.beginPath()
  ctx.moveTo(100, 100)
  ctx.lineTo(100, 150)
  ctx.lineTo(150, 150)
  ctx.lineTo(150, 100)
  ctx.fillStyle= 'red'
  ctx.fill()
  ctx.closePath()

  canvas.addEventListener('click', function (e) {
    const canvasInfo = canvas.getBoundingClientRect()
    console.log(ctx.isPointInPath(e.clientX - canvasInfo.left, e.clientY - canvasInfo.top))
  })

这个情况下,結果就已不好似大家所预计的1样,当点一下在其中黑色地区时,复印的值为false,点一下鲜红色地区时,复印的值为true。

实际上缘故很简易,由于上述编码,大家具体建立了两个Path,而isPointInPath方式具体只检验当今点是不是处在最终1个Path之中,而事例中鲜红色地区为最终1个Path,因此仅有点一下鲜红色地区时,isPointInPath方式才可以分辨为true。如今大家更新改造1下编码:

  const canvas = document.getElementById('canvas')
  const ctx = canvas.getContext('2d')
  let drawArray = []

  function draw1 () {
    ctx.beginPath()
    ctx.moveTo(10, 10)
    ctx.lineTo(10, 50)
    ctx.lineTo(50, 50)
    ctx.lineTo(50, 10)
    ctx.fillStyle= 'black'
    ctx.fill()
  }

  function draw2 () {
    ctx.beginPath()
    ctx.moveTo(100, 100)
    ctx.lineTo(100, 150)
    ctx.lineTo(150, 150)
    ctx.lineTo(150, 100)
    ctx.fillStyle= 'red'
    ctx.fill()
    ctx.closePath()
  }

  drawArray.push(draw1, draw2)  

  drawArray.forEach(it => {
    it()
  })

  canvas.addEventListener('click', function (e) {
    ctx.clearRect(0, 0, 400, 750)
    const canvasInfo = canvas.getBoundingClientRect()
    drawArray.forEach(it => {
      it()
      console.log(ctx.isPointInPath(e.clientX - canvasInfo.left, e.clientY - canvasInfo.top))
    })
  })

上面的编码大家开展了1个很大的更新改造,大家将每一个Path放入到1个独立的涵数之中,并将它们push到1个数字能量数组之中。当开启点一下恶性事件时,大家清空Canvas,并遍历数字能量数组再次绘图,每当绘图1个Path开展1次分辨,从而在启用isPointInPath方式时,大家能即时的获得当今的最终1个Path,进而分辨出当今点所处的Path之中。

如今大家早已间接性的完成了对每一个Path的独立恶性事件监视,但是实际上现的方法必须1次又1次的重绘,那末有方法不必须重绘就可以监视恶性事件吗?

最先大家必须了解1次又1次重绘的缘故是由于isPointInPath方式是监视的最终1个Path,但是大家在详细介绍这个方式的情况下,说过其第1个主要参数是1个Path目标,当大家传送了这个主要参数后,Path就已不去取最终1个Path而是应用大家传送进去的这个Path,如今大家来个demo来认证其可行性:

  const canvas = document.getElementById('canvas')
  const ctx = canvas.getContext('2d')

  const path1 = new Path2D();
  path1.rect(10, 10, 100,100);
  ctx.fill(path1)
  const path2 = new Path2D();
  path2.moveTo(220, 60);
  path2.arc(170, 60, 50, 0, 2 * Math.PI);
  ctx.stroke(path2)

  canvas.addEventListener('click', function (e) {
    console.log(ctx.isPointInPath(path1, e.clientX, e.clientY))
    console.log(ctx.isPointInPath(path2, e.clientX, e.clientY))
  })

如上图所示,大家点一下了左侧图型,复印true,false;点一下右侧图型,复印false,true。复印的結果说明是沒有难题的,但是因为其适配性也有待提升,因此现阶段提议還是应用重绘方法来监视恶性事件。

结语

Canvas的恶性事件监视讲到这里基础就类似了,基本原理很简易,大伙儿应当都能把握。
github详细地址,欢迎start

附录

自身写的1个demo

  const canvas = document.getElementById('canvas')

  class rectangular {
    constructor (
      ctx, 
      {
        top = 0,
        left = 0,
        width = 30,
        height = 50,
        background = 'red'
      }
    ) {
      this.ctx = ctx
      this.top = top
      this.left = left
      this.width = width
      this.height = height
      this.background = background
    }

    painting () {
      this.ctx.beginPath()
      this.ctx.moveTo(this.left, this.top)
      this.ctx.lineTo(this.left + this.width, this.top)
      this.ctx.lineTo(this.left + this.width, this.top + this.height)
      this.ctx.lineTo(this.left, this.top + this.height)
      this.ctx.fillStyle = this.background
      this.ctx.fill()
      this.ctx.closePath()
    }

    adjust (left, top) {
      this.left += left
      this.top += top
    }
  }

  class circle {
    constructor (
      ctx, 
      {
        center = [],
        radius = 10,
        background = 'blue'
      }
    ) {
      this.ctx = ctx
      this.center = [center[0] === undefined ? radius : center[0], center[1] === undefined ? radius : center[1]]
      this.radius = radius
      this.background = background
    }

    painting () {

      this.ctx.beginPath()
      this.ctx.arc(this.center[0], this.center[1], this.radius, 0, Math.PI * 2, false)
      this.ctx.fillStyle = this.background
      this.ctx.fill()
      this.ctx.closePath()
    }

    adjust (left, top) {
      this.center[0] += left
      this.center[1] += top
    }
  }

  class demo {
    constructor (canvas) {
      this.canvasInfo = canvas.getBoundingClientRect()
      this.renderList = []
      this.ctx = canvas.getContext('2d')
      this.canvas = canvas
      this.rectangular = (config) => {
        let target = new rectangular(this.ctx, {...config})
        this.addRenderList(target)
        return this
      }

      this.circle = (config) => {
        let target = new circle(this.ctx, {...config})
        this.addRenderList(target)
        return this
      }
      this.addEvent()
    }

    addRenderList (target) {
      this.renderList.push(target)
    }

    itemToLast (index) {
      const lastItem = this.renderList.splice(index, 1)[0]

      this.renderList.push(lastItem)
    }

    painting () {
      this.ctx.clearRect(0, 0, this.canvasInfo.width, this.canvasInfo.height)
      this.renderList.forEach(it => it.painting())
    }

    addEvent () {
      const that = this
      let startX, startY

      canvas.addEventListener('mousedown', e => {
        startX = e.clientX
        startY = e.clientY
        let choosedIndex = null
        this.renderList.forEach((it, index) => {
          it.painting()
          if (this.ctx.isPointInPath(startX, startY)) {
            choosedIndex = index
          }
        })
        
        if (choosedIndex !== null) {
          this.itemToLast(choosedIndex)
        }

        document.addEventListener('mousemove', mousemoveEvent)
        document.addEventListener('mouseup', mouseupEvent)
        this.painting()
      })

      function mousemoveEvent (e) {
        const target = that.renderList[that.renderList.length - 1]
        const currentX = e.clientX
        const currentY = e.clientY
        target.adjust(currentX - startX, currentY - startY)
        startX = currentX
        startY = currentY
        that.painting()
      }

      function mouseupEvent (e) {
        const target = that.renderList[that.renderList.length - 1]
        const currentX = e.clientX
        const currentY = e.clientY

        target.adjust(currentX - startX, currentY - startY)
        startX = currentX
        startY = currentY
        that.painting()
        document.removeEventListener('mousemove', mousemoveEvent)
        document.removeEventListener('mouseup', mouseupEvent)
      }
    }
  }

  const yes = new demo(canvas)
    .rectangular({})
    .rectangular({top: 60, left: 60, background: 'blue'})
    .rectangular({top: 30, left: 20, background: 'green'})
    .circle()
    .circle({center: [100, 30], background: 'red', radius: 5})
    .painting()

以上便是本文的所有內容,期待对大伙儿的学习培训有一定的协助,也期待大伙儿多多适用脚本制作之家。



在线客服

关闭

客户服务热线
4008-888-888


点击这里给我发消息 在线客服

点击这里给我发消息 在线客服