美文网首页
"集智知识星空"前端技术分析(二)

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

作者: 如意同学Try | 来源:发表于2019-04-02 14:46 被阅读0次

    历史文章

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

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

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

    前情回顾:
    上一篇文章中,我介绍了知识星空核心功能的实现方法:如何使用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:本来还想加入路径的实现过程,觉得篇幅太长,路径的实现还是放在下一篇中。后续还会继续探讨点位生成背后的算法思路,尽情期待

    相关文章

      网友评论

          本文标题:"集智知识星空"前端技术分析(二)

          本文链接:https://www.haomeiwen.com/subject/qkkgbqtx.html