"集智知识星空"前端技术分析(二)

历史文章

集智学园知识星空——产品介绍篇

集智学园知识星空——前端技术实现分析(一)

欢迎前往集智学园官网体验

前情回顾:
上一篇文章中,我介绍了知识星空核心功能的实现方法:如何使用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的动画实现。

点击点位后的生成动画分为三个步骤:
这是原始没有弹窗的状态

image

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);
}

到为止这里为止我们就完成了点位的移动和窗口的打开。

image

接下去就是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;
  }

最后在窗口中加载内容即可

image

ps:本来还想加入路径的实现过程,觉得篇幅太长,路径的实现还是放在下一篇中。后续还会继续探讨点位生成背后的算法思路,尽情期待

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容