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.小结 下面我一一展开,让我们看看每一节都说了什么. 前言 语言和数字都是信息传播的载体,他们之间其实存在着天 ...
随机推荐
- Batch Normalization和Layer Normalization
Batch Normalization:对一个mini batch的样本,经过一个nueron(或filter)后生成的feature map中的所有point进行归一化.(纵向归一化) Layer ...
- 地心地固坐标系(ECEF)与站心坐标系(ENU)的转换
目录 1. 概述 2. 原理 2.1. 平移 2.2. 旋转 2.3. 总结 3. 实现 4. 参考 1. 概述 我在<大地经纬度坐标与地心地固坐标的的转换>这篇文章中已经论述了地心坐标系 ...
- Java-多态(下)
多态 一种类型的多种状态 还有一个小汽车的例子再理解下 汽车接口(相当于父类) package com.oop.demo10; public interface Car { String getNam ...
- 用C++生成solidity语言描述的buchi自动机的初级经验
我的项目rvtool(https://github.com/Zeraka/rvtool)中增加了生成solidity语言格式的监控器的模块. solidity特殊之处在于,它是运行在以太坊虚拟机环境中 ...
- 初学python-day4 字典(已更新完)
- [no code][scrum meeting] Beta 8
$( "#cnblogs_post_body" ).catalog() 例会时间:5月22日15:30,主持者:赵涛 下次例会时间:5月23日11:30,主持者:肖思炀 一.工作汇 ...
- Spring Cloud Gateway GatewayFilter的使用
Spring Cloud Gateway GatewayFilter的使用 一.GatewayFilter的作用 二.Spring Cloud Gateway内置的 GatewayFilter 1.A ...
- 莱特币(LTC)开发环境搭建
Linux系统下搭建莱特币LTC开发环境 1.简介 2.LTC 客户端下载 3.解压到一个固定的目录中 4.启动客户端 4.1.参数解释: 4.2.更多详细的配置 5.执行命令测试一下 6.获取莱特币 ...
- 使用Egg改造订单系统展示效果,方便快速浏览
素材准备: 1.Egg.js Born to build better enterprise frameworks and apps with Node.js & Koa 为企业级框架和应用而 ...
- hdu 5093 Battle ships(二分图最大匹配)
题意: M*N的矩阵,每个格子上是三个之一:*.o.#. (1 <= m, n <= 50) *:海洋,战船可以停在上面. o:浮冰,战船 ...