hover 背后的数学和图形学
前端开发中,hover是最常见的鼠标操作行为之一,用起来也很方便,CSS直接提供:hover
伪类,js可以通过mouseover
+mouseout
事件模拟,甚至一些第三方库/框架直接提供了 hover API ,比如 jQuery 的 hover()
函数。大部分前端开发者在使用这些很方便的方法时,可能并没有思考过 hover 背后的实现原理。
hover 是跟 DOM 绑定的,常规 DOM 是一个个矩形(CSS 盒模型),鼠标移动时浏览器需要判断鼠标指针坐标是否在这个 DOM 的矩形范围之内,根本上是一个数学问题,即判断一个点是否位于一个矩形内。
这是跟很简单的计算,对比点坐标和矩形四个角的坐标就行了。
但是对于其他的几种前端图形技术来说,就不一定这么简单了,比如SVG、Canvas、WebGL,因为这几种图形技术中并非只有矩形这一种简单图形。
SVG
SVG 除了 <rect>
矩形之外,还有<circle>
、<line>
等代表某种特定图形的元素,以及<arc>
、<path>
这类绘制任意图形的元素。
SVG 实现 hover 的方式跟普通 HTML 并无二致,SVG 本身就是一种特异的 HTML,可以直接使用绝大部分 DOM API 和 CSS 选择器。
Canvas 2D
Canvas 2D(下文简称Canvas)是比 SVG 更底层的图形技术,只有 rect 这一种特定图形,其他的图形都是通过使用直线、弧线、贝塞尔曲线等路径 API 绘制出来。
Canvas 绘制的图形都是在一个 <canvas>
元素内,并不能向 DOM 或 SVG 一样使用 CSS 伪类或js事件实现某个图形的hover效果。为解决这个问题, Canvas 提供了isPointInPath()
API 来判断某个点是否位于某个闭合路径之内,不过这个 API 并不是很好用,这个方法时挂载到绘制上下文 context
上的,只能判断某个点是否位于当前绘制的路径内,这是个非常操蛋的限制。所以在 Canvas 2D 技术领域也通常会借鉴 WebGL 的实现方案,即通过数学方法判断一个点是否位于一个不规则多边形内。
WebGL
WebGL 是比 Canvas 2D 更底层的图形技术,可以说是现阶段前端领域最底层、最接近图形学的图形技术。
未来可以期待一下 WebGPU。
WebGL 中只有点、线段、三角形三种基本图元,所有视觉可见的形状都是以这三种图元组成。其实主要是三角形,包括绝大多数的线和点也是由三角形组成。因为 WebGL 1.0 不支持宽于1像素的线段,而且折线还要考虑各种 join 效果。
WebGL 2.0 支持宽于 1像素的线段。
WebGL 中实现某个图形的 hover 以及click、mouseover、mouseout等鼠标事件的根本就是上文提到的判断一个点是否位于一个不规则多边形内。这是一个纯粹的几何数学问题,理论上有很多种解法,其中在工程领域使用最普遍的是射线法,这是目前综合计算复杂度和性能消耗的最优解之一。
射线法的原理是以待判断的点坐标画一条水平的直线,然后判断这条直接与多边形各条边的交点数量,如果是奇数则代表点在多边形内,如果是偶数则代表点在多边形之外。射线法可以适用于任意多边形,包括有洞(hole)的多边形,具体的推导过程就不贴了,感兴趣的话可以自己查一下相关资料。
射线法涉及以下三个问题:
- 如何获取多边形的各条边的端坐标?
- 如果多边形的某条边是曲线怎么办?
- 如何判断两条线段有交点?
如何获取多边形的各条边的端坐标?
这其实并不是一个图形绘制领域的问题,而是数据制备领域的问题。以一个简单图形举例:
上图中的六边形是由四个三角形组成,前端从服务端拿到的数据一般只包括六边形的6个顶点坐标,即v1 - v6,而且这6个坐标点是按照顺时针排列(如果有hole,则hole的顶点是逆时针排列),如下:
[v1,v2,v3,v4,v5,v6]
前端拿到顶点数组后需要使用三角剖分算法将其切割成4个三角形,最后才给到 WebGL 绘制。
也就是说,在数据制备阶段就已经将多边形的每个顶点坐标确定了,然后依序两两相接就是多边形的各条边。
当然也不排除有的技术团队在数据制备阶段就进行了三角剖分,但这么干的比较少,因为剖分后数据量会增长很多,会带来额外的存储成本和网络通信耗时。
如果多边形的某条边是曲线怎么办?
这是一个伪命题。WebGL 中不存在曲线,任意图形都是通过点、线段、三角形三种图元组合而成,即便视觉上是一个曲线或圆弧,本质上也是一个个三角形,只不过通过算法处理让人眼看不出明显的折角。所以WebGL中的任何图形本质上都是多边形,既然是多边形就可以按照上文的方案解决点与多边形的相对位置判断问题。
如何判断两条线段有交点?
明确了上面两个问题之后,就只剩下判断两条线段是否相交这一个问题了。这同样是个纯粹的数学问题。
回顾上文提到的多边形顶点数据制备,多边形的边是由相邻两个顶点相连而成,顶点是有序的,也就是说多边形的每条边都是有向线段,所以判断两条线段是否相交这个问题准确的说发应该是:判断两个有模向量是否相交。
这就回到了高中数学哈哈。
第一个知识点是向量叉乘。
t = 向量A x 向量B = |A||B|sin(a)
其中a是向量A和向量B的夹角。为了方便描述,我们把上述计算得到的结果赋值为t
。
严格的说,只有三维向量的叉乘才有几何意义,两个向量叉乘得到的是一个垂直于向量A和向量B、模为t
的三维向量。二维向量的叉乘是从三维向量基础上延展出来的,有以下几何意义:
- t为向量A和向量B为相邻边的平行四边形的面积;
- 如果t>0,那么向量A正旋转到向量B的角度小于180度;
- 如果t<0,那么向量A正旋转到向量B的角度大于180度;
- 如果t=0 ,那么向量A和B平行。
判断两条线段是否相交用到了上述的规则2-4。先看下面这张图:
如果线段AB和CD相交可以推导出以下规则:
- 点A和点B分别位于线段CD的两侧;
- 点C和点D分别位于线段AB的两侧。
把第一条规则转化成数学语言,用向量描述:
- 向量AC位于向量AB的逆时针方向;
- 向量AD位于向量AB的顺时针方向;
- 向量BC位于向量BA的顺时针方向;
- 向量BD位于向量BA的逆时针方向。
进一步转化便是:
AB x AC > 0
AB x AD < 0
BA x BC < 0
BA x BD > 0
同理转化第二条规则,不再赘述。
总结
本文简单总结了前端常用的各种图形技术实现hover效果的方法,水平有限,聊当抛砖引玉。前端很多常用的方法或API底层都很值得玩味,这不比八股文有意思?
hover 背后的数学和图形学的更多相关文章
- GAN背后的数学原理
模拟上帝之手的对抗博弈——GAN背后的数学原理 简介 深度学习的潜在优势就在于可以利用大规模具有层级结构的模型来表示相关数据所服从的概率密度.从深度学习的浪潮掀起至今,深度学习的最大成功在于判别式 ...
- 傅里叶变换:MP3、JPEG和Siri背后的数学
九年前,当我还坐在学校的物理数学课的课堂里时,我的老师为我们讲授了一种新方法,给我留下了深刻映像.我认为,毫不夸张地说,这是对数学理论发现最广泛的应用.应用的领域包括:量子物理.射电天文学.MP3和J ...
- 傅里叶变换--MP3、JPEG和Siri背后的数学
http://blog.jobbole.com/51301/ 九年前,当我还坐在学校的物理数学课的课堂里时,我的老师为我们讲授了一种新方法,给我留下了深刻映像. 我认为,毫不夸张地说,这是对数学理论发 ...
- 速算1/Sqrt(x)背后的数学原理
概述 平方根倒数速算法,是用于快速计算1/Sqrt(x)的值的一种算法,在这里x需取符合IEEE 754标准格式的32位正浮点数.让我们先来看这段代码: float Q_rsqrt( float nu ...
- 吴恩达机器学习笔记43-SVM大边界分类背后的数学(Mathematics Behind Large Margin Classification of SVM)
假设我有两个向量,
- Mathematics for Computer Graphics数学在计算机图形学中的应用 [转]
最近严重感觉到数学知识的不足! http://bbs.gameres.com/showthread.asp?threadid=10509 [译]Mathematics for Computer Gra ...
- OpenGL坐标变换及其数学原理,两种摄像机交互模型(附源程序)
实验平台:win7,VS2010 先上结果截图(文章最后下载程序,解压后直接运行BIN文件夹下的EXE程序): a.鼠标拖拽旋转物体,类似于OGRE中的“OgreBites::CameraStyle: ...
- 在数学建模中学MATLAB
为期三周的数学建模国赛培训昨天正式结束了,还是有一定的收获的,尤其是在MATLAB的使用上. 1. 一些MATLAB的基础性东西: 元胞数组的使用:http://blog.csdn.net/z1137 ...
- 《数学之美》(吴军 著)读书笔记:第1章 文字和语言 vs 数字和信息
第1章有4个小节,以及前言. 前言 1.信息 2.文字和数字 3.文字和语言背后的数学 4.小结 下面我一一展开,让我们看看每一节都说了什么. 前言 语言和数字都是信息传播的载体,他们之间其实存在着天 ...
随机推荐
- 新一代容器平台ACK Anywhere,来了
5G.AR.AIoT 等场景在推动新一代云架构的演进,而容器重塑了云的使用方式. 近日,阿里云容器服务全面升级为ACK Anywhere,让企业在任何需要云的地方,都能获得一致的容器基础设施能力. 早 ...
- python-docx 页面设置
初识word文档-节-的概念 编辑一篇word文档,往往首先从页面设置开始,从下图可以看出,页面设置常操作的有页边距.纸张方向.纸张大小4个,而在word中是以节(section)来分大的块,每一节的 ...
- Pandas高级教程之:时间处理
目录 简介 时间分类 Timestamp DatetimeIndex date_range 和 bdate_range origin 格式化 Period DateOffset 作为index 切片和 ...
- 一个关于MySQL指定编码实现的小坑
写在前面 环境:MySQL5.7+,MySQL数据库字符编码实现为utf8,表也为utf8 场景:微信授权获取用户信息(包括昵称)并保存到数据库,有的用户成功了,少数用户却失败了 那么为什么会失败呢? ...
- 题解 [BJOI2017]开车
题目传送门 题目大意 有\(n\)个汽车和\(n\)个加油站,坐标分别为\(a_{1,2,...,n}\)和\(b_{1,2,...,n}\).每辆汽车会到一个加油站,求出最小移动距离之和.有\(m\ ...
- [转载]CSS3实现文本垂直排列
最近的一个项目中要使文字垂直排列,也就是运用了CSS的writing-mode属性. writing-mode最初时ie中支持的一个属性,后来在CSS3中增添了这一新的属性,所以在ie中和其他浏览器中 ...
- 联想SR658安装显卡驱动【NVIDIA Tesla V100】
1. 安装基础依赖环境 yum -y install gcc kernel-devel kernel-headers 2.查看内核和源码版本是否一致 查看内核版本: ls /boot | grep v ...
- 计算机网络:HTTP
计算机网络基础:HTTP 先验知识 HTTP和其他协议的关系 通过下图,了解IP协议,TCP协议,DNS服务在使用HTTP协议通信过程中各自发挥的作用: 服务器处理流程 接受客户端连接 ------& ...
- SpringCloud 2020.0.4 系列之服务降级
1. 概述 老话说的好:做人要正直,做事要正派,胸怀坦荡.光明磊落,才会赢得他人的信赖与尊敬. 言归正传,之前聊了服务间通信的组件 Feign,今天我们来聊聊服务降级. 服务降级简单的理解就是给一个备 ...
- spring cloud feign的基本使用
在上一节,我们学会了如何使用ribbon进行来进行服务之间的调用,但是那种需要通过RestTemplate来进行调用而且当参数比较多时,使用起来就比较麻烦.那么有没有一种调用远程方法(别的服务)就像调 ...