html5中监听canvas内部元素点击事件的三种方法


Posted in HTML / CSS onApril 28, 2019

canvas内部元素不能像DOM元素一样方便的添加交互事件监听,因为canvas内不存在“元素”这个概念,他们仅仅是canvas绘制出来的图形。这对于交互开发来说是一个必经障碍,想要监听图形的点击事件思路很简单,只要监听canvas元素本身的点击事件,再判断点击坐标位于哪一个图形内部,就变相实现了图形点击事件。本文将介绍三种方法,判断坐标点是否位于某个canvas图形内部。

约定

本文介绍的三种方法适用于识别canvas内形状不规则而且位置无规律的图形点击事件,对于形状规则或者位置有规律的场景,肯定有更简便的实现,这里不做讨论。

像素法

像素检测法的思路是,将canvas中的多个图形(如果有多个的话)分别离屏绘制,并用 getImageData() 方法分别获取到像素数据保存起来。当canvas元素监听到点击事件时,通过点击坐标可以直接推算出点击发生在canvas上的第几个像素,然后遍历前面保存的图形数据,看看这个像素的alpha值是不是0,如果是0说明落点不在当前图形内,否则就说明点到了这个图形。

根据点击坐标得到所点击的像素序号的方法:

像素序号 = (纵坐标-1) * canvas宽度 + 横坐标

比如在宽度为 5 的画布上点击坐标 (3,3) ,根据上述公式得到像素序号是 (3-1) * 5 + 3 = 18 ,如图所示:

html5中监听canvas内部元素点击事件的三种方法

因为canvas导出的图形数据是将每个像素以 rgba 的顺序存成4个数字组成的数组,所以想访问指定像素的alpha值,只要读取这个数组的第 pIndex * 4 + 3 个值就可以了,如果这个值不为0,说明该像素可见,也就是点击到了该图形。

这个方法是我认为思路最直接、结果最准确、而且对图形形状没有任何要求的方法,但这个方法有一个致命的局限,当图形需要在画布上移动时,要频繁的创建数据缓存才能保证检测结果准确,受到画布尺寸和图形数量的影响, getImageData() 方法的性能会成为严重的瓶颈。所以如果canvas图形是静态的,这个方法非常适合,否则就不适合用这个方法了。

角度法

角度判断法的原理很容易理解,如果一个点在多边形内部,则该点与多边形所有顶点两两构成的夹角,相加应该刚好等于360°。

html5中监听canvas内部元素点击事件的三种方法

计算过程可以转变为以下三个步骤:

1.已知多边形顶点和已知坐标,将坐标与顶点两两组合成三点队列
2. 已知三点求夹角,可以使用 余玄定理
3.判断夹角之和是否360°

每一步都很简单,实现如下:

//计算两点距离
const getDistence = function (p1, p2) {
  return Math.sqrt((p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y))
};
//角度法判断点在多边形内部
const checkPointInPolyline = (point, polylinePoints) => {
    let totalA = 0;
    const A = point;
    for (let i = 0; i < polylinePoints.length; i++) {
        let B, C;
        if (i === polylinePoints.length - 1) {
            B = {
                x: polylinePoints[i][0],
                y: polylinePoints[i][1]
            };
            C = {
                x: polylinePoints[0][0],
                y: polylinePoints[0][1]
            };
        } else {
            B = {
                x: polylinePoints[i][0],
                y: polylinePoints[i][1]
            };
            C = {
                x: polylinePoints[i + 1][0],
                y: polylinePoints[i + 1][1]
            };
        }
        //计算角度
        const angleA = Math.acos((Math.pow(getDistence(A, C), 2) + Math.pow(getDistence(A, B), 2) - Math.pow(getDistence(B, C), 2)) / (2 * getDistence(A, C) * getDistence(A, B)))
        totalA += angleA
    }
    //判断角度之和
    return totalA === 2 * Math.PI
}

这个方法有一个局限性,就是图形必须是 凸多边形 。如果不是凸多边形需要先切割成凸多边形再计算,这就比较复杂了。

类似的思路还有面积法,如果一个点在多边形内部,那么该点与多边形所有顶点两两构成的三角形,面积相加应该等于多边形的面积,首先计算多边形的面积就很麻烦,所以这种方法可以直接pass掉。

射线法

射线法是一个我讲不清道理但非常好用的方法,只要判断点与多边形一侧的交点个数为奇数,则点在多边形内部。需要注意的是,只要数任何一侧的焦点个数就可以,比如左侧。这个方法不限制多边形的类型,凸多边形、凹多边形甚至环形都可以。

html5中监听canvas内部元素点击事件的三种方法

实现起来也非常简单:

 

const checkPointInPolyline = (point, polylinePoints) => {
    //射线法
  let leftSide = 0;
  const A = point;
  for (let i = 0; i < polylinePoints.length; i++) {
    let B, C;
    if (i === polylinePoints.length - 1) {
      B = {
        x: polylinePoints[i][0],
        y: polylinePoints[i][1]
      };
      C = {
        x: polylinePoints[0][0],
        y: polylinePoints[0][1]
      };
    } else {
      B = {
        x: polylinePoints[i][0],
        y: polylinePoints[i][1]
      };
      C = {
        x: polylinePoints[i + 1][0],
        y: polylinePoints[i + 1][1]
      };
    }
    //判断左侧相交
    let sortByY = [B.y, C.y].sort((a,b) => a-b)
    if (sortByY[0] < A.y && sortByY[1] > A.y){
      if(B.x<A.x || C.x < A.x){
        leftSide++
      }
    }
  }
  return leftSide % 2 === 1
}

射线法有一种特殊情况,当点在多变形的一条边上时需要特殊处理。但在工程中我认为也可以不处理,因为如果用户刚好点在图形的边界上,那么程序认为他没有点到也讲的过去。

总结

以上三种方法都可以实现canvas中不规则图形的点击检测。其中,像素法的优势在于不挑形状,而且在静态场景中有一定的性能优势;角度法应该说只有理论价值,实用性不佳;工程中最实用的当属射线法,局限性小,实现简单,多数时候只需要知道射线法就可以了。
 

HTML / CSS 相关文章推荐
css3实例教程 一款纯css3实现的环形导航菜单
Oct 20 HTML / CSS
HTML+CSS3模拟心的跳动实例代码
Sep 05 HTML / CSS
详解CSS3实现响应式手风琴效果
Jun 10 HTML / CSS
HTML5打开本地app应用的方法
Mar 31 HTML / CSS
HTML5实现Notification API桌面通知功能
Mar 02 HTML / CSS
canvas粒子动画背景的实现示例
Sep 03 HTML / CSS
HTML5学习笔记之html5与传统html区别
Jan 06 HTML / CSS
浅析HTML5中的 History 模式
Jun 22 HTML / CSS
基于HTML5实现类似微信手机摇一摇功能(计算摇动次数)
Jul 24 HTML / CSS
html5自定义video标签的海报与播放按钮功能
Dec 04 HTML / CSS
html+css合并表格边框的示例代码
Mar 31 HTML / CSS
HTML5中 rem适配方案与 viewport 适配问题详解
Apr 27 HTML / CSS
Html5实现单张、多张图片上传功能
Apr 28 #HTML / CSS
鲜为人知的HTML5语音合成功能
May 17 #HTML / CSS
Html5内唤醒百度、高德APP的实现示例
May 20 #HTML / CSS
socket.io 和canvas 实现的共享画板功能
May 22 #HTML / CSS
Canvas 文字碰撞检测并抽稀的方法
May 27 #HTML / CSS
利用canvas实现图片下载功能来实现浏览器兼容问题
May 31 #HTML / CSS
HTML5印章绘制电子签章图片(中文英文椭圆章、中文英文椭圆印章)
Jun 03 #HTML / CSS
You might like
利用php+mcDropdown实现文件路径可在下拉框选择
2013/08/07 PHP
php如何解决无法上传大于8M的文件问题
2014/03/10 PHP
微信营销平台系统?刮刮乐的开发
2014/06/10 PHP
PHP模拟asp.net的StringBuilder类实现方法
2015/08/08 PHP
WordPress开发中自定义菜单的相关PHP函数使用简介
2016/01/05 PHP
eclipse php wamp配置教程
2016/06/30 PHP
解决Yii2邮件发送结果返回成功,但接收不到邮件的问题
2017/05/23 PHP
小试JQuery的AutoComplete插件
2011/05/04 Javascript
读jQuery之二(两种扩展)
2011/06/11 Javascript
js 关键词高亮(根据ID/tag高亮关键字)案例介绍
2013/01/21 Javascript
JQGrid的用法解析(列编辑,添加行,删除行)
2013/11/08 Javascript
Node.js模块加载详解
2014/08/16 Javascript
JavaScript获取URL汇总
2015/06/08 Javascript
Bootstrap实现带动画过渡的弹出框
2016/08/09 Javascript
xtemplate node.js 的使用方法实例解析
2016/08/22 Javascript
微信小程序中单位rpx和rem的使用
2016/12/06 Javascript
用JavaScript实现让浏览器停止载入页面的方法
2017/01/19 Javascript
javaScript嗅探执行神器-sniffer.js
2017/02/14 Javascript
vue2.0在没有dev-server.js下的本地数据配置方法
2018/02/23 Javascript
[03:38]2014DOTA2西雅图国际邀请赛 VG战队巡礼
2014/07/07 DOTA
[01:55]2014DOTA2国际邀请赛 BBC正赛第一天总结
2014/07/10 DOTA
[03:28]2014DOTA2国际邀请赛 走近EG战队天才中单Arteezy
2014/07/12 DOTA
[01:10]DOTA2亚洲邀请赛 征战号角响彻全场
2015/01/06 DOTA
[02:38]2018年度DOTA2最佳劣单位选手-完美盛典
2018/12/17 DOTA
用Python制作在地图上模拟瘟疫扩散的Gif图
2015/03/31 Python
python如何获取服务器硬件信息
2017/05/11 Python
python找出一个列表中相同元素的多个索引实例
2019/06/11 Python
Python3内置模块random随机方法小结
2019/07/13 Python
python实现矩阵和array数组之间的转换
2019/11/29 Python
Python 实现一个计时器
2020/07/28 Python
YSL Beauty加拿大官方商城:圣罗兰美妆加拿大
2017/05/15 全球购物
mysql有关权限的表都有哪几个
2015/04/22 面试题
高中学生自我评价范文
2014/09/23 职场文书
海南召开党的群众路线教育实践活动总结大会新闻稿
2014/10/21 职场文书
学校师德师风整改措施
2014/10/27 职场文书
Python的这些库,你知道多少?
2021/06/09 Python