如安在Canvas中增加事宜

如安在Canvas中增添事宜

作为一个前端,给元素增添事宜是一件屡见不鲜的事变。但是在Canvas中,其所画的任何东西都是没法猎取的,更别说增添事宜,那末我们对其就一筹莫展了吗?固然不是的!我们在日常平凡项目中肯建都用过很多Canvas的框架,我们发明事宜在这些框架中已运用的非常红熟了,而且并没有涌现特别严重的题目。那末我们能够一定的是,事宜在Canvas中并非一个没法触及的事变。

一个傻瓜式的体式格局

我们都晓得一个元素在触发一个事宜时,其鼠标的位置基础处于该元素之上,那末我们就自然而然的想到经由过程当前鼠标的位置以及物体所占有的位置举行比对,从而我们就可以得出该物体是不是应触发事宜。这类体式格局比较简朴,我就不必代码演示了,不过既然我叫它傻瓜式的体式格局,很明显它不是一个有用的处理体式格局。因为物体所占有的位置并不一定是非常轻易猎取,如果是矩形、圆形等我们还能经由过程一些简朴的公式猎取其占有的位置,但是在庞杂点的多边形,以至是多边形的某些边是弧线的,不言而喻,我们这时刻再猎取其所占有的位置时是一件极为庞杂且难度极大的事变,所以这类体式格局只合适本身在做一些demo中运用,并不适用于大多数的状况。

一个较智慧的体式格局

既然上面这类体式格局受阻了,那末我们只能另辟蹊径。在翻阅CanvasAPI的时刻,找到了一个要领isPointInPath,貌似恰是我们苦苦寻觅的良药。

引见isPointInPath

isPointInPath的作用:望文生义,我们很直观的能够晓得该要领用以推断点是不是处于途径当中。

isPointInPath的入参出参:ctx.isPointInPath([path, ]x, y [, fillRule]),该要领的参数有4个,个中path和fillRule为选填,x和y为必填。我们顺次引见4个参数。

path:看到这个参数,我最先以为是beginPath或许closePath的返回值,很惋惜的是这两个要领并没有返回值,在查阅了材料后,发明是Path2D组织函数new的对象。Path2D组织函数详细用法。不过惋惜的是该要领能够因为兼容性的题目,如今看了一些开源框架都还未运用。

x,y:这两个参数很好明白,就是x轴和y轴的间隔,须要注重的是,其相对位置是Canvas的左上角。

fillRule:nonzero(默许),evenodd。非零围绕划定规矩和奇偶划定规矩是图形学中推断一个点是不是处于多边形内的划定规矩,个中非零围绕划定规矩是Canvas的默许划定规矩。想详细相识这两种划定规矩的,能够本身去查阅材料,这里就不增添篇幅引见了。

上面引见完了入参,那末isPointInPath要领的出参想必人人都能够猜到了,就是true和false。

运用isPointInPath

上一节引见完isPointInPath要领后,我们如今就来运用它吧。

先来一个简朴的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中增加事宜》

如图所示,灰色部分为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))
  })

《如安在Canvas中增加事宜》

这个时刻,效果就不再犹如我们所估计的一样,当点击个中黑色地区时,打印的值为false,点击赤色地区时,打印的值为true。

实在缘由很简朴,因为上述代码,我们现实创建了两个Path,而isPointInPath要领现实只检测当前点是不是处于末了一个Path当中,而例子中赤色地区为末了一个Path,所以只要点击赤色地区时,isPointInPath要领才推断为true。如今我们革新一下代码:

  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))
    })
  })

上面的代码我们举行了一个很大的革新,我们将每一个Path放入到一个零丁的函数当中,并将它们push到一个数组当中。当触发点击事宜时,我们清空Canvas,并遍历数组从新绘制,每当绘制一个Path举行一次推断,从而在挪用isPointInPath要领时,我们能及时的猎取当前的末了一个Path,进而推断出当前点所处的Path当中。

如今我们已间接的完成了对每一个Path的零丁事宜监听,但是实在现的体式格局须要一次又一次的重绘,那末有方法不须要重绘就可以监听事宜吗?

起首我们须要晓得一次又一次重绘的缘由是因为isPointInPath要领是监听的末了一个Path,不过我们在引见这个要领的时刻,说过其第一个参数是一个Path对象,当我们通报了这个参数后,Path就不再去取末了一个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))
  })

《如安在Canvas中增加事宜》

如上图所示,我们点击了左侧图形,打印true,false;点击右侧图形,打印false,true。打印的效果表明是没有题目的,不过因为其兼容性还有待增强,所以如今发起照样运用重绘体式格局来监听事宜。

结语

Canvas的事宜监听讲到这里基础就差不多了,道理很简朴,人人应当都能控制。
github地点,迎接start

附录

本身写的一个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()

《如安在Canvas中增加事宜》

    原文作者:kejiacheng
    原文地址: https://segmentfault.com/a/1190000019239594
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞