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.小结 下面我一一展开,让我们看看每一节都说了什么. 前言 语言和数字都是信息传播的载体,他们之间其实存在着天 ...
随机推荐
- Java基础(七)——多线程
一.概述 1.介绍 Java VM 启动的时候会有一个进程Java.exe,该进程中至少有一个线程负责Java程序的执行.而且这个线程运行的代码存在于main方法中,该线程称之为主线程.其实从细节上来 ...
- C#开发BIMFACE系列40 服务端API之模型集成
BIMFACE二次开发系列目录 [已更新最新开发文章,点击查看详细] 随着建筑信息化模型技术的发展,越来越多的人选择在云端浏览建筑模型.现阶段的云端模型浏览大多是基于文件级别,一次只可以浏览一 ...
- Pandas 数据的一些基本操作
一个很偶然的机会,主动出击挑战一个之前没有尝试过的新东西,在做的过程中需要处理一些csv文件的数据,以下是我总结的一些小方法,希望对和我一样的新手朋友们有所帮助,初次尝试,望路过的朋友有更好的方法可以 ...
- 编译原理: FIRST(x) FOLLOW(x) SELECT(x)的计算
目录 First计算 Follow计算 Select计算 已知文法G[S]: S→MH|a H→LSo|ε K→dML|ε L→eHf M→K|bLM 判断G是否是LL(1)文法. First计算 F ...
- 10.6.2 sendfile
1.传统Linux中 I/O 的问题 2.传统的 Linux 系统的标准 I/O 接口( read. write)是基于数据拷贝的,也就是数据都是 copy_to_user 或者 copy_from_ ...
- Java基础之(八):顺序结构与选择结构
顺序结构 Java的基本结构就是顺序结构 顺序结构是最简单的算法结构 它是任何一个算法都离不开的一种基本算法结构. 例子: public static void main(String[] args) ...
- 重庆邮电大学第十一届ACM程序设计竞赛-网络选拔赛 C题
1008: 偷袭 时间限制: 1.000 sec 内存限制: 128 MB 武林要以和为贵,张麻子不讲武德来偷袭马老师的亲传弟子. 马老师有n个亲传弟子,每个弟子有一个武力值a[i]. n个弟子中只有 ...
- 搭载Dubbo+Zookeeper踩了这么多坑,我终于决定写下这篇!
大家好,我是melo,一名大二上软件工程在读生,经历了一年的摸滚,现在已经在工作室里边准备开发后台项目啦. 这篇文章我们不谈数据结构了,来谈谈入门分布式踩过的坑.感觉到了分布式这一层,由于技术更新迭代 ...
- better-scroll快速上手及封装(vue项目)
愿你有诗有梦,有坦荡荡的远方 本文声明:这是一篇学习coderwhy老师的vue2课程的一个笔记,所以本文章是在vue项目中实现,没学过vue的大佬们可以举一反三. 使用场景及介绍 BetterScr ...
- [对对子队]测试报告Beta
一.测试中发现的bug BETA阶段的新bug 描述 提出者(可能需要发现者在会议上复现) 处理人 是否解决 第四关中工作区的循环语句拖动到组件区后成本的大小比原来不一样的问题 梁河览 何瑞 是 循环 ...