京东6.18大促主会场领京享红包更优惠

 找回密码
 立即注册

QQ登录

只需一步,快速开始

如何使用canvas绘制可移动网格的示例代码

2024-11-2 22:21| 发布者: ae2942d9| 查看: 54| 评论: 0

摘要: 本文重要先容了如何使用canvas绘制可移动网格的示例代码,分享给各人,具体如下: 结果 阐明 这个是真实项目中碰到的需求,我把它抽离出来,屏蔽了那些业务相干的东西,仅从代码角度来思量这个题目。首先网格大小可

本文重要先容了如何使用canvas绘制可移动网格的示例代码,分享给各人,具体如下:

结果

阐明

这个是真实项目中碰到的需求,我把它抽离出来,屏蔽了那些业务相干的东西,仅从代码角度来思量这个题目。首先网格大小可配置,每个极点是可以移动的。看到这个题目,不知道各位是怎么去思考的。就先来说说我本身的思绪。

分析

首先必要有一个起点,这样就能确定网格所在的位置,其次就是网格中的每个正方形(我们就按正方形来思考,这样简单一点)的边长是多少,另外每个极点移动的时候,边也必要跟着移动。

所以实在要存储的就只有两类对象,一类就是线,另外就是极点。

如何存储极点和线呢?这里用了一个库[code]fabric.js[/code],就比较轻易的创建极点和边的对象,并且它也提供了移动边的方法,但是题目也同时出现了:按照上面的表现,一个点最多关联4条边,最少也关联了2条边,如何表现这种极点和边的关联关系呢?

先想到就是使用数组来存储极点和线,然后再根据线中包含的极点坐标来判定这个线是否和某个极点相连,如果是的话,则将将其到场到极点的关联属性中。后面当移动极点的时候,根据极点拿到其关联的线,去动态改变线的坐标,这样就能实现上面的那种结果了。

实现

下面根据以上分析,我们来实现代码。首先必要存储的对象有极点、边。然后根据起点坐标以及每个小矩形的边长,很轻易就可以盘算出所有的极点坐标。

[code] function Grid({node, unit, row, col, matrix = []}) { // 存储极点 this.vertexes = []; // 存储边 this.lines = []; // 根据起点坐标以及单位边长盘算 for (let i = 0; i <= row; i++) { for (let j = 0; j <= col; j++) { const newVertex = makeRect(node.x + i * unit, node.y + j * unit); this.vertexes.push(newVertex); } } // 添加极点对象的事件监听器 this.addListener(); }[/code]

那么边怎么盘算呢,构造边的话,只必要两个极点就可以连成边,因此我们可以选择遍历极点来构造边,但是这样的话会造成重复的边,而我们只必要一条边就可以了,不然移动的话,你会发现移动完,下面还会表现一条重叠的边。固然实在最重要的原因就是效率题目,如果不去重的话,会导致盘算的时间复杂度过高。

如今有两种方法来解决,一种就是给极点做标记,当前做线的两头的极点已经标记过了,那么就跳过当前轮的遍历。另外一种方法,就是可以根据网格这种特定的外形来获取边,如下图,按照两种不同的颜色来盘算程度的边和垂直的边。

这样的话,程度方向,就每行两两构成边,垂直方向,就按照一定的间隔连接两个极点构成边。这里由于后面必要传给算法的格式是二维数组,因此就使用了这个方法。

[code] // ...省略了 // 构造矩阵 this.matrix = []; let index = -1; for (let i = 0; i < this.vertexes.length; i++) { if (i % (col + 1) === 0) { index++; this.matrix[index] = []; } this.matrix[index].push(this.vertexes[i]); } // 根据矩阵添加边 let idx = 0; for (let i = 0; i < this.matrix.length; i++) { for (let j = 0; j < this.matrix[i].length; j++) { // 交叉渲染边,这样能够在可视区内优先展示 this.matrix[i][j+1] && this.makeLine(this.matrix[i][j], this.matrix[i][j+1]); this.vertexes[idx + col + 1] && this.makeLine(this.vertexes[idx], this.vertexes[idx + col + 1]); idx++; } }[/code]

后面就是找每个极点关联了几条边

[code] for (let i = 0; i < this.vertexes.length; i++) { const vertex = this.vertexes[i]; // 根据极点的坐标是否是边的两头的开始或结束坐标来判定极点是否与这条边关联 const associateLines = this.lines.filter(item => { return (item.x1 === vertex.left && item.y1 === vertex.top) || (item.x2 === vertext.left && item.y2 === vertex.top); }); vertex.lines = associateLines; }[/code]

眼精的同学肯定一眼就看出来啦,这个时间复杂度太高了。所以虽然网格画出来了,但是当极点数目过多的时候,盘算时间太长,导致欣赏器卡住了了差不多2s往上,当程度方向有50个极点,垂直方向有50个极点,就能显着看到欣赏器的卡顿,此时如果有输入框之类的交互UI,是无法做任何操作的,这肯定也是不行滴。

改进

那么有什么方法能够高效的找到极点和边之间的关联呢?这里就不卖关子了,固然大概还有其他更好的方法,但是笔者知识所限,只能到这啦。

解决办法就是图这种布局,由于图的边可以使用毗邻表大概是毗邻矩阵来存储,这样如果我存储了一个极点,那么与这个极点关联的边实在就确定了,也就是说,我们在添加极点的时候,就[code]顺便[/code]解决了这种极点的关联题目,不再必要再次遍历所有的边来找关联了。(这里就不具体先容图这种数据布局了,有兴趣的同学可以本身查找资料,实际这里运用图的地方也就是这个边和极点的关联关系,其他什么图的遍历都没有用到)

我们来改进一下我们的代码。

[code] function Grid({node, unit, row, col, matrix = []}) { this.vertexes = []; this.lines = []; this.edges = new Map(); this.addEdges = addEdges; this.addVertexes = addVertexes; }[/code]

这里添加了一个新的属性[code]edges[/code],来存储极点和边的映射关系。其他的步骤和先前都是一样的,只是更换了添加极点和边的方法,什么意思呢,看代码实在明白了:

[code] function Grid({node, unit, row, col, matrix = []}) { // ...省略 // 根据矩阵添加边 let idx = 0; for (let i = 0; i < this.matrix.length; i++) { for (let j = 0; j < this.matrix[i].length; j++) { // 交叉渲染边,这样能够在可视区内优先展示 this.matrix[i][j+1] && this.addEdges(this.matrix[i][j], this.matrix[i][j+1]); this.vertexes[idx + col + 1] && this.addEdges(this.vertexes[idx], this.vertexes[idx + col + 1]); idx++; } } // 将边关联到极点 this.edges.forEach((value, key) => { key.lines = value; }); }[/code]

这里我们就将复杂度为[code]O(mn)[/code]的盘算低落为了[code]O(n)[/code],这里[code]m[/code]为[code]lines[/code]的长度,[code]n[/code]为[code]vertexes[/code]的长度。然后再来看下此时盘算100*100的极点数,盘算时间只有[code]200ms[/code],已经能够满足我的需求了。那么图是如何实现这种关联的呢,实在就是每次添加边的时候,将边的两个极点同时添加进关联关系中,也就是[code]Map[/code]的布局中。

[code] function addEdges(v, w) { const line = makeLine({point1: v, point2: w}); // 极点v关联了边line this.edges.get(v).push(line); // 极点w也同时关联了边line this.edges.get(w).push(line); this.lines.push(line); } function addVertexes(v) { this.vertexes.push(v); // 给每个极点设置一个Map布局 this.edges.set(v, []); }[/code]

这样盘算完所有的极点之后,实际极点关联的边也都确定了,末了只必要遍历一下这些[code]edges[/code]就可以了。

完成了这些之后,开开心心的调用[code]fabric[/code]的api,将这些对象添加进[code]canvas[/code]中就可以了。

[code] // fabric的API,添加fabric对象到画布中 canvas.add(...this.vertexes); canvas.add(...this.lines);[/code]

好了,大功告成,可以交差了。运行页面,打开一看,好家伙,盘算速度是快了许多,但是渲染的速度惨不忍睹,30*30的极点数目,页面照旧有卡顿的情况,这是怎么回事呢?

过细想想,添加这么多的对象到画布中,盘算量确实黑白常大的,但是这里我们也无法改变这种渲染消耗。于是想到了一个折中的方法,就是使用时间切片,简单来说,就是使用[code]requestAnimationFrame[/code]这个API,将渲染使命分割为一个一个的片段,在欣赏器空闲时去渲染,这样就不会去壅闭其他欣赏器的使命。这里就涉及了一些欣赏器渲染的相干知识。

[code] function renderIdleCallback(canvas) { // 使命切片 const points = this.points.slice(); const lines = this.lines.slice(); const task = () => { // 清理canvas的时候,中断后面的渲染 if (this.interrupt) return; if (!points.length && !lines.length) return; let slicePoint = [], sliceLine = []; for (let i = 0; i < 10; i++) { if (points.length) { const top = points.shift(); slicePoint.push(top); } if (lines.length) { const top = lines.shift(); sliceLine.push(top); } } canvas.add(...slicePoint); canvas.add(...sliceLine); window.requestAnimationFrame(task); } task(); }[/code]

上面的代码到场了一个标识符来中断渲染,由于存在这样一种情况,本次网格还没有渲染完,就被清理掉又重新渲染,那么就必要停止上次的渲染,重新开始新的渲染了。

总结

好了,到这里也就结束了。由于笔者知识浮浅,只能做到这种满足需求的优化了,更极致的优化就要看各位大佬辅导。同时此次尝试也是笔者第一次将所学的数据布局、优化手段结合到项目中,成就感照旧非常多的,也是感受到数据布局算法对于程序员的重要性,如果想要突破本身的技能瓶颈,那么这也是绕不开的一个点。

到此这篇关于如何使用canvas绘制可移动网格的示例代码的文章就先容到这了,更多相干canvas 可移动网格内容请搜索脚本之家以前的文章或继承欣赏下面的相干文章,希望各人以后多多支持脚本之家!


来源:https://www.jb51.net/html5/756492.html
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
关闭

站长推荐上一条 /6 下一条

QQ|手机版|小黑屋|梦想之都-俊月星空 ( 粤ICP备18056059号 )|网站地图

GMT+8, 2025-7-2 02:12 , Processed in 0.034927 second(s), 19 queries .

Powered by Mxzdjyxk! X3.5

© 2001-2025 Discuz! Team.

返回顶部