博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
集智学园知识星空——前端技术实现分析(一)
阅读量:6413 次
发布时间:2019-06-23

本文共 5061 字,大约阅读时间需要 16 分钟。

在上一篇文章 中我们讲了产品新版本的特点,简单来说就是三点:

  1. 使用二维展示方式,展示的信息更多维,更丰富。
  2. 使用层级化展示,每个层级有对应的信息重点,在展示更多信息的同时,不产生视觉负担。
  3. 高手可便捷地自行探索学习路径,同时也为初学者提供了推荐的学习路径。

那既然作为一个程序员,从本篇文章开始就要剖析产品中用到的技术了。整个产品前后端交互不多,核心在于后端算法生成数据,和前端酷炫的交互实现两部分。

算法过程还涉及到机密啊专利啊等等乱七八糟的事情,不能说的太详细,但前端部分本身就完全对外公开,所以也谈不上技术保护。所以我们会着重对前端的实现部分进行分享和分析。

还没有体验过的同学,可以前往体验后再继续往下看。

模拟地图功能

所有的课程以分布在二维坐标系上的点的形式呈现。那就有对视图在二维平面中上下左右移动的需求。而且为了展示内部细节,还需要支持缩放。本质上就是一个地图。所以我们首先需要实现地图的基本交互,移动 + 缩放

之所以不使用google或者百度地图这类现有的地图框架,一是因为我们其实只需要地图的部分交互,其实没必要引入庞大的地图库;二是我们希望能更灵活地对这个"地图"进行自定义开发,后续可能会在现有基础上增加更多的交互或者元素。

另外地图组件本质是图片的分片加载,所以难免在移动和缩放的时候出现中间加载时刻。所以在经过了一段时间的尝试之后我们放弃了对地图库的引入。

1. 核心绘图

整个视图的组成主要元素是那些课程点,这些点都是绘制在一个canvas上 核心绘图函数很简单

drawPoint (point) {  ctx.arc(point.x, point.y, point.r, 0, 2 * Math.PI);}复制代码

点位的坐标生成是另外的技术话题,大致流程是将课程信息(包括资料,文本,标签等)提取出来转化为高维课程特征矩阵,再通过聚类和降维技术映射成二维坐标。具体实现将另开篇幅。本文针对前端实现方式,不对此展开讨论。

2. 引入监听事件
  1. 移动功能用到了
  • mousedown, // 鼠标移动
  • mousestart // 鼠标点下
  • mouseup // 鼠标抬起
  1. 缩放功能用到了
  • dblclick // 鼠标双击
  • mousewheel // 鼠标滚轮
  • DOMMouseScroll // firfox的鼠标滚轮 设置事件函数,将所有事件绑定在视图的canvas上
//设置事件    setHandler(dom) {      //鼠标双击      dom.addEventListener( 'dblclick',e => {          onDocumenDblClick(e, this, false);        }, { passive: true });      //鼠标按下      dom.addEventListener('mousedown', e => {          moveDown(e, this, false);        }, { passive: true });      //鼠标移动      dom.addEventListener('mousemove', e => {        moveMouse(e, this, point);      });      //鼠标抬起      dom.addEventListener( 'mouseup', e => {         moveUP(e, this);        }, { passive: true });      //鼠标滚轮      dom.onmousewheel = e => { e.stopPropagation();        mouseScroll(e, this, false);      };      // 鼠标滚轮事件firfox      dom.addEventListener('DOMMouseScroll', e => {        mouseScroll(e, this, false);      });    },复制代码

设置好事件后,就是地图功能实现的核心了。移动 + 缩放

3. 拖拽移动功能

移动主要监听mousemove事件,这就需要对单纯的“鼠标移动”,和按下后的“拖拽”做一个区分,所以需要mousedownmouseup事件的配合,来判断当前是否为拖拽状态。

let dragFlag = false; // 拖拽标识   /*鼠标点下事件   @param {*} e event */  moveDown (e) => {    dragFlag = true; // 鼠标被按下,准备拖拽  }  /*鼠标抬起事件   @param {*} e event */  moveUP (e) => {    dragFlag = false; //结束拖拽标识  },  /** 拖拽事件  @param {*} e event */  moveMouse (e) => {    if (dragFlag) {      ...      transform(x, y);  // x, y为地图移动的距离    }  },复制代码

至于拖拽的距离,则取决于上一时刻的位置,和当前位置的差值。所以在移动的过程中,需要去记录上一时刻的位置。初始位置,为鼠标按下的位置

let lastPointPos = [];// 鼠标按下 moveDown (e) => {    dragFlag = true; // 鼠标被按下,准备拖拽    lastPointPos = [e.clientX, e.clientY]  }// 鼠标拖拽 moveMouse (e) => {   if (dragFlag) {     let x = e.clientX - lastPoint[0];     let y = e.clientY - lastPoint[1];     lastPoint = [e.clientX, e.clientY];     transform(x, y);  }}复制代码

这样一来, transform函数就能专注实现移动点位

//  移动点位函数transform (x, y) => {    this.x = this.x + x;    this.y = this.y + y    drawPoint();  })}复制代码

到这里,拖拽移动地图的功能基本完成

接下去,我们来说一说稍微复杂的缩放操作。

4. 缩放功能

有很多操作会触发缩放:

  1. 双击地图
  2. 鼠标滚动
  3. 笔记本触控板

双击触发dbclick事件 鼠标滚动和触控板的行为基本一致,都是触发鼠标滚轮mousewheel(firfox触发的是DOMMouseScroll事件)

// 双击事件onDocumenDblClick (e) => { ...  let flag = 'large';  scale(x, y, flag)  // scale为缩放函数,传入缩放中心,和放大还是缩小标志}// 滚动事件mouseScroll (e) => {  ...  scale(x, y, flag)  // scale为缩放函数,传入缩放中心,和放大还是缩小标志}复制代码

因为每次双击的缩放尺度,和每次滚轮的缩放尺度,显然是不一样的。所以两个行为的缩放倍数。肯定不一样。我们可以设置,每触发一次双击事件,就相当于触发了n次的scale(n为一个自定义的参数), 即

onDocumenDblClick (e) => {  ...  let flag = 'large';  let count = 0;  let time = setInterval(() => {  if (count <= n) {      scale(x, y, flag)  // scale为缩放函数,传入缩放中心,和放大还是缩小标志    } else {      clearInterval(time)      }  }, 100)}复制代码

这么写当然可以实现功能,但是一点都不优雅,而且使用setInterval做动画对浏览器来说并不是一个最佳的渲染方案,点位多的时候容易有失帧现象。这里钻一下细节,使用requestAnimationFrame改写下。

let scaleStartTime = 0; // 开始放大的起始时间// 双击事件onDocumenDblClick (e) => {  ...  let flag = 'large';  scaleStartTime = performance.now();  scaleOnceAnimation(e,  time,  flag);  //  time是自定义参数,自行设置动画要运行的时间。}// 循环动画scaleOnceAnimation (e, time, flag) => {   // 使用当前时间和起始时间做对比,每次循环都判断是否已经达到设置的动画运行时间。   if (performance.now() - scaleStartTime > time) {     scaleStartTime = 0;      return;    }    scale(x,  y,  flag);    window.requestAnimationFrame(() => {      scaleOnceAnimation(e, time, flag);    });}复制代码

最后就是scale函数的实现。在直接写代码之前,我们先来做个简单的数学题。

以p(1, 1)为中心,把圆(2, 2, r = 1)放大为原来的两倍,求圆放大后的坐标和半径

第一步,移动整个坐标,直至p位于(0, 0)点,此时圆坐标为(1, 1, r = 1)

第二步,放大整个坐标系至相应倍数,这里为2倍, 得到圆(2, 2, r = 2)

第三步,把坐标系移回原来的位置,让p回到初始点,得到圆(3, 3, r = 2)

从这道题中可以看出,要把一个点以某一中心进行缩放,还需要借助平移的方法,所以讲了这么一堆,可以得出缩放函数应该这么写

// 缩放函数scale (x, y, flag) => {  let scale = flag === 'large' ? 110 / 100 ? 100 / 110; // 缩放比例  transform(-x, -y);  this.x = this.x * scale;  this.y = this.y * scale;  transform(x, y);  this.drawPoint()  })}复制代码

到此为止,缩放的功能就也已经基本实现。一个模拟地图行为的产品也已经实现了最核心的功能。

在此基础上,我们还可以模拟其他衍伸功能,比如:

  • viewPort (pointArray):把传入的点放置于视图中合适的位置;
  • panTo (x, y):把视图移动到某个位置,并以传入的坐标为视图中心(或任何一个你想要的位置点)
  • openWindow (point) :打开点位的信息窗口 除了模拟地图API的基本功能以外,还能根据需求开发自己的地图新功能
  • scaleToValue(point, value):对某个点移动到视图中心,并放大到指定大小
  • scaleToRange(range) :缩放地图,直到满足传入到视图范围内 ....

由于是完全canvas手撸的地图,所以完全可以根据需求开发想要的功能,虽然可能一开始如果选择了地图框架来实现功能,前期进展肯定会比现在快,但到了后期开发,我相信一定是我们自己的框架更加灵活,更有利于实现我们的想法,而不会被技术所局限。

本篇主要介绍了地图的基础操作移动缩放是如何实现的。 在下一篇,我们来介绍一下更加精彩的“窗口”打开的过程,期间涉及到panTo函数的实现,即把视图移动到选中点为中心的状态。 敬请期待。

转载于:https://juejin.im/post/5c95eadcf265da60f56116d3

你可能感兴趣的文章
MySQL 千万级数据表 partition 实战应用
查看>>
PAT A1043
查看>>
es6声明类实现继承
查看>>
2019年Java面试-并发容器篇
查看>>
MongoDB
查看>>
Terraform入门 - 1. 安装Terraform
查看>>
被七牛云OSS对象存储测试域名回收后正确数据迁移姿势!
查看>>
ES6 Proxy的学习与理解
查看>>
JDBC
查看>>
比特币Pow
查看>>
CAP 一致性协议及应用解析
查看>>
由安装yarn引发的血案
查看>>
深入理解 lambda表达式 与 MethodReference(四)
查看>>
Golang之轻松化解defer的温柔陷阱
查看>>
Redux入门教程(快速上手)_day_01
查看>>
Django搭建个人博客:给文章加个漂亮的标题图
查看>>
《前端十年心路》书稿规划
查看>>
SpringBoot 实战 (十三) | 整合 MyBatis (XML 版)
查看>>
docker环境下,使用phpstorm进行debug
查看>>
236. Lowest Common Ancestor of a Binary Tree
查看>>