canvas怎么做点击事件,canvas事件绑定

  canvas怎么做点击事件,canvas事件绑定

  作为前端,给元素添加事件是很常见的事情。但是,在Canvas中,它所绘制的东西什么都得不到,更别说添加事件了,那我们又能怎么办呢?当然不是!我们在平时的项目中肯定用过很多Canvas框架,发现这些框架中的事件都很成熟,没有特别严重的问题。那么我们就可以确定事件不是画布中不可触及的东西。

  一个傻瓜式的方式

  我们都知道,当一个元素触发一个事件的时候,它的鼠标位置基本上是在这个元素的上方,所以我们很自然的会想到将当前的鼠标位置和对象所占据的位置进行比较,这样就可以算出对象是否应该触发事件。这个方法比较简单,我就不用代码演示了。但是,既然我称之为傻瓜式的方法,显然不是有效的解决方法。因为物体所占据的位置不一定很容易获得,如果是矩形、圆形等。我们仍然可以通过一些简单的公式得到它的位置。然而,具有复杂点的多边形,或者甚至多边形的一些边是弯曲的。显然,此时我们获取它的位置是极其复杂和困难的,所以这种方法只适合我们自己在一些demo中使用,而不适用于大多数情况。

  一个较聪明的方式

  既然上面的方法碰壁了,那就只能另辟蹊径了。翻遍CanvasAPI,找到一个方法,isPointInPath,好像就是我们要找的药。

  介绍isPointInPath

  isPointInPath的作用:顾名思义,我们可以直观的知道这个方法是用来判断一个点是否在路径中的。

  isPointInPath的输入输出参数:ctx.ispointpath ([path,] x,y [,fillRule])。这个方法有四个参数,其中path和fillrule是可选的,x和y是必需的。下面我们依次介绍四个参数。

  Path:看到这个参数,我开始以为是beginPath或者closePath的返回值。不幸的是,这两个方法没有返回值。查阅资料后发现是Path2D构造函数new的对象。Path2D构造函数的具体用法。遗憾的是,这种方法可能是因为兼容性问题,有些开源框架还没用过。

  X,Y:这两个参数很好理解,就是X轴和Y轴之间的距离。需要注意的是,它们的相对位置是画布的左上角。

  FillRule:非零值(默认),偶数奇数。非零包围规则和奇偶规则是图形中判断一个点是否在多边形中的规则,非零包围规则是画布的默认规则。如果想详细了解这两条规则,可以自己查阅资料,这里就不用介绍了。

  引入了参与之后,那么isPointInPath方法的参与一定是大家猜出来的,也就是真假。

  使用isPointInPath

  在前一节介绍了isPointInPath方法之后,现在让我们使用它。

  让我们从一个简单的演示开始:

  const canvas=document . getelementbyid( canvas )const CTX=canvas . get context( 2d )CTX . begin path()CTX . move to(10,10) ctx.lineTo(10,50) ctx.lineTo(50,50)CTX . fill style= black CTX . fill()CTX . close path()canvas . addevent listener( click ,function(e){ const canvasInfo=canvas . getboun

  如图,灰色部分是Canvas占据的区域,黑色部分是我们实际添加事件的区域。我们点击黑色区域后,其实如我们所愿,打印出来的值是真的。看似Canvas的事件监控这么简单,其实真的那么简单。显然不可能!我们再举一个例子。这时,有两个区域,我们需要将不同的事件绑定到它们:

  const canvas=document . getelementbyid( canvas )const CTX=canvas . get context( 2d )CTX . begin path()CTX . move to(10,10) ctx.lineTo(10,50) ctx.lineTo(50,50)CTX . fill style= black CTX . fill()CTX . close path()CTX . begin path()CTX . move to(100,100) ctx。

  这个时候,结果已经不像我们预料的那样了。当点击黑色区域时,打印值为假,当点击红色区域时,打印值为真。

  其实原因很简单。因为上面的代码,我们实际上创建了两条路径,而ispointPath方法实际上只检测当前点是否在最后一条路径中,而例子中的红色区域就是最后一条路径,所以ispointPath方法只有在红色区域被点击时才能判断为真。现在让我们修改代码:

  const canvas=document . getelementbyid( canvas )const CTX=canvas . get context( 2d )let draw array=[]函数draw 1(){ CTX . begin path()CTX . move to(10,10) ctx.lineTo(10,50) ctx.lineTo(50,10)CTX . fill style= black CTX . fill()}函数draw 2(){ CTX . begin path()CTX . move to(10我们将每条路径放入一个单独的函数中,并将它们放入一个数组中。当点击事件被触发时,我们清除画布,遍历数组并重新绘制它。我们每画一条路径,就做一个判断,这样在调用isPointInPath方法的时候,就可以实时得到当前最后一条路径,然后判断当前点在哪条路径。

  现在已经间接实现了对每条路径的单独事件监控,但是实现方法需要一次又一次的重绘,有什么方法可以在不重绘的情况下监控事件?

  首先我们要知道一次次重绘的原因是isPointInPath方法是最后一个要监控的路径。然而,当我们介绍这个方法时,我们说它的第一个参数是一个Path对象。当我们传递这个参数时,路径停止采用最后一个路径,而是使用我们传入的路径。现在让我们来论证它的可行性:

  const canvas=document . getelementbyid( canvas )const CTX=canvas . get context( 2d )const path 1=new path 2d();path1.rect(10,10,100,100);CTX . fill(path 1)const path 2=new path 2d();path2.moveTo(220,60);path2.arc(170,60,50,0,2 *数学。PI);CTX . stroke(path 2)canvas . addevent listener( click ,function(e){ console . log(CTX . ispointinpath(path 1,e.clientX,e . clienty))console . log(CTX . ispointinpath(path 2,e.clientX,e.clientY)) })

  如上图所示,我们点击左边的图,打印真假;点击右边的图形,打印错误和正确。打印结果显示没有问题,但由于其兼容性有待加强,目前推荐使用重绘来监控事件。

  结语

  Canvas的事件监控在这里基本相同。原理很简单,大家应该都能掌握。

  Github地址,欢迎入手

  附录

  我自己写了一个演示。

  const canvas=文档。getelementbyid( canvas )类rectangle {构造函数(CTX,{ top=0,left=0,width=30,height=50,background= red }){ this。CTX=CTX这个。顶=顶这个。左=左这个。宽度=宽度this。高度=高度this。background=background }画(){这个。CTX。开始路径()this。CTX。移到(这个。左边,这个。顶)这个。CTX。行到(这个。宽度。这个顶){这个。左=左这个。top=top } } class circle { constructor(CTX,{ center=[],radius=10,background= blue }){ this。CTX=CTX这个。center=[center[0]===未定义?半径:中心[0],中心[1]===未定义?radius:center[1]]this . radius=radius this。background=background }画(){这个。CTX。开始路径()this。CTX。弧(这个。中心[0],这。中心[1],这。半径,0,数学.* 2,假)这个。CTX。填充样式=this。背景这个。CTX。填()这个。CTX。close path()} adjust(left,top){ this。center[0]=离开这个。center[1]=top } } class demo { constructor(canvas){ this。canvasinfo=画布。getboundingclientrect()this。渲染列表=[]this。CTX=画布。获取上下文( 2d )此。canvas=(config)={ let target=new rectangle(this。CTX,{.config })这个。addrenderlist(target)return this } this。circle=(config)={ let target=new circle(this。CTX,{.config })这个。addRenderList(target)return this } this。addEvent()} addrender list(target){ this。渲染列表。push(target)} item to last(index){ const last item=this。渲染列表。【拼接(索引,1)】0本。渲染列表。推(最后一项)}画(){这个。CTX。清除rect(0,this.canvasInfo.width,this。canvasinfo。身高)这个。渲染列表。foreach(it=it。painting())} addEvent(){ const==null){ this。最后一个(选择的索引)}文档的项目。addevent侦听器( mousemove ,mousemoveEvent)文档。addevent listener( mouseup ,mouseevent)this。绘画()})函数mousemoveEvent(e){ const target=that。渲染列表。length-1]const currentX=e . clienty const currentY=e . clienty target。adjust(currentX,currentY-startY)startX=currentX startY=currentY that。绘画()}矩形({})。矩形({上:60,左:60,背景:蓝色 })。矩形({上:30,左:20,背景:绿色 })。圆圈()。圆({圆心:[100,30],背景:红色,半径:5})。绘画()

  这就是本文的全部内容。希望对大家的学习和支持有帮助。

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: