历史文章
欢迎前往集智学园官网体验
前情回顾:
上一篇文章中,我介绍了知识星空核心功能的实现方法:如何使用canvas模拟地图的平移和缩放。在这篇文章中,我们紧接着来说一说其他一些精彩的功能。
如何对canvas中一个元素设置事件
canvas其实是一张大画布,当你在上面开始绘图之后,元素就和画布变成了一个整体。而我们的需求是需要对绘制的每个点都要设置事件(这里主要指点击事件)。
各种地图都是使用dom生成地图上的标记(marker),所以可以很方便地对标记绑定事件。但在我们这里,所有的点都由是canvas绘制,并没有独立的“元素”概念。所以想要为某个元素添加事件就成了一件比较蛋疼的事情。
这里使用了一个比较笨的办法,就是监听整个画布,然后去比对这个点击时刻的位置,和每个圆心
的距离是否小于这个圆的半径。
听上去运算量就很大,那最终效果怎么样呢?我们来试一试
clickHandler: e => {
let { x, y } = { e.clientX, e.clientY }
let point = null
this.pointList.forEach(ele => {
<!--计算当前鼠标点击的位置和圆心的距离,并与半径比较-->
if (Math.sqrt(Math.pow(x - ele.x), 2)) + Math.pow((y - ele.y), 2)) <= ele.r) {
//鼠标选中了这个圆
point = ele
}
})
}
这样看下来感觉也还行,只是在每次点击的时候去遍历一遍所有的点。当clickHandler
函数返回不为null时,说明当前选中了一个点。
接下去还想加一个需求:监听到hover事件,hover到一个点上之后改变鼠标样式。
emm...
按照上面的思路应该是这样写
hoverhandler: e => {
let { x, y } = { e.clientX, e.clientY }
let point = null
this.pointList.forEach(ele => {
<!--计算当前鼠标点击的位置和圆心的距离,并与半径比较-->
if (Math.sqrt(Math.pow(x - ele.x), 2)) + Math.pow((y - ele.y), 2)) <= ele.r) {
//鼠标hover到了这个圆
point = ele
}
})
}
上面的函数本身没问题,问题就在于调用频次上,对画布监听mousemove
事件的话,会以非常高的频率调用这个函数。假设鼠标一直在移动,1s中触发5次mousemove
事件的话,那1s内就会遍历比较所有的点5遍。这就就会比较明显地对性能产生影响了。
这该怎么办?
后来想了一个优化的办法:就是只去遍历当前视图
中的点。即把点位位置和当前浏览器窗口去比较,如果在屏幕之外的话,就不用去和这个点去比。
另外还可能对点的大小进行过滤,半径太小的点也不用去比较,对最后效果影响并不大。
最后实现是这样写。
hoverhandler: e => {
let { x, y } = { e.clientX, e.clientY }
let point = null
this.pointList.forEach(ele => {
<!--加上过滤条件-->
if (ele.x- ele.value <= window.width && ele.x + ele.value >= 0 && ele.y - ele.value <= window.height && ele.y + ele.value >= 0 && ele.value > 15) {
<!--计算当前鼠标点击的位置和圆心的距离,并与半径比较-->
if (Math.sqrt(Math.pow(x - ele.x), 2)) + Math.pow((y - ele.y), 2)) <= ele.r) {
//鼠标hover到了这个圆
point = ele
}
}
})
}
到这里就算是完成了对画布中元素设置事件的过程。如果有需求需要加上的别的事件,也可以使用这个思路去写。
(我觉得还不是最优解决方案,大佬们有想法欢迎和我讨论)
接下去我们来说一说窗口的弹出动画部分的实现
窗口弹出动画
网站有很多的动画效果,从性能上考虑,原则是能用css实现的就绝不用js,实现效果如下:
[图片上传失败...(image-b9644b-1554187119372)]
看起来好像很复杂,其实都是由基本的keframe
组成的,原理一点都不难。这里结合实际的业务逻辑,给大家复习下css3
的动画实现。
点击点位后的生成动画分为三个步骤:
这是原始没有弹窗的状态
1. 生成和当前点相同大小的圆
这里需要用到上面所说的点击事件,步骤如下:
a. 点击某个点
b. 把这个点移动到视图的中心位置
c. 在当前位置生成一个和这个圆半径相同的圆形窗口
点击一个圆获取它的参数,在上一个模块中已经实现。
生成圆的部分也比较简单,只要获取到当前点击的是哪个圆,根据当前圆的半径,在屏幕中心
生成一个和它完全重叠的圆就可以。可以使用一个固定的dom,控制它是否显示,以及根据选中圆的半径控制它的宽高。
所以重点是点击某个点后,把这个圆移动到视图中心的过程应该如何实现。
上一篇文章集智学园知识星空——前端技术实现分析(一)中已经说到了移动地图的实现方式。核心是transform(x, y)
函数,参数x, y为横纵坐标分别要移动的距离。肯定不能直接把中心坐标和当前点的位置做差直接传入,否则就是一个突变的现象,我们是要做一个丝滑的移动动画实现最终的功能。
所以需要把中心坐标
和当前点
的坐标做差后,分段传入tranform函数
。至于分几段比较合适,就需要把它作为一个超参细致地进行调整。
最终我是调整出了一种计算分段的方法,让分段参数和移动距离成负相关,这样就可以让移动距离长的点移动得快一些,而移动距离短的过程进行得慢一些。
<!--point为要移动到中心位置的点-->
panToCenter (point) {
<!--选中的点和中心点之间的距离-->
let dis = Math.sqrt(Math.pow(point.x - window.center, 2) + Math.pow(point.y - window.center, 2));
<!--根据距离调整出的一个函数,让分段和距离成负相关-->
let count = f(dis)
//每个分段的距离:perLength = length / count
let perLength = { x: (point.x - window.center) / count, y: (point.y - window.center) / count };
let panToTime = setInterval(() => {
transform(-perLength.x, -perLength.y);
count--;
if (count <= 0) {
clearInterval(panToTime);
this.panToTime = null;
}
}, 20);
}
到为止这里为止我们就完成了点位的移动和窗口的打开。
接下去就是css的事情
2. 圆半径变大,随后变成矩形窗口,并显示内容
在keyframe
里面定义一个初始状态,中间状态和最终状态,设置动画时间,动画效果等参数
@keyframes openUp {
<!--初始状态是一个园-->
from {
opacity: 0.7;
border-radius: 50%;
}
<!--放大过程中间状态依旧是圆-->
50% {
opacity: 0.8;
border-radius: 50%;
}
<!--最后变成一个矩形-->
100% {
opacity: 0.95;
border-radius: 20px;
height: 90%;
width: 60%;
}
}
.openUp {
-webkit-animation-timing-function: ease-in-out;
-webkit-animation-duration: 0.5s;
-webkit-animation-name: openUp;
animation-timing-function: ease-in-out;
animation-name: openUp;
animation-duration: 0.5s;
}
最后在窗口中加载内容即可
ps:本来还想加入路径的实现过程,觉得篇幅太长,路径的实现还是放在下一篇中。后续还会继续探讨点位生成背后的算法思路,尽情期待