这里的渲染区域的思路是:以 canvas 元素作为画布背景,节点是以 div 的方式渲染拖拽进去的节点,拖拽的位置将是以 canvas 的相对位置来移动,大概的结构如下:
<template><!-- 渲染区域的祖先元素 --><div><!-- canvas 画布,绝对于父级元素定位,inset: 0; --><canvas></canvas><!-- 节点列表渲染的父级元素,绝对于父级元素定位,inset: 0; --><div><!-- 节点1,绝对于父级元素定位 --><div></div><!-- 节点2,绝对于父级元素定位 --><div></div><!-- 节点3,绝对于父级元素定位 --><div></div><!-- 节点4,绝对于父级元素定位 --><div></div></div></div></template>复制代码而连接线的绘制是根据 next 字段的信息,查找到 targetComponentId 组件的位置,然后在canvas上做两点间的 线条绘制 。
链接的类型分为3种: 直线,折线,曲线
直线的绘制最为简单,取两个点连接就行 。
// 绘制直线const drawStraightLine = (ctx: CanvasRenderingContext2D,points: [number, number][],highlight?: boolean) => {ctx.beginPath()ctx.moveTo(points[0][0], points[0][1])ctx.lineTo(points[1][0], points[1][1])// 是否是当前选中的连接线,当前连接线高亮shadowLine(ctx, highlight)ctx.stroke()ctx.restore()ctx.closePath()}复制代码

文章插图
折线的方式比较复杂,因为折线需要尽可能的不要把连接线和节点重合,所以它要判断每一种连接线的场景,还有两个节点的宽度和高度也需要考虑计算 。如下:

文章插图
起始节点有四个方向,目标节点也有四个方向,还有目标节点相对于起始节点有四个象限,所以严格来说,总共有 4 * 4 * 4 = 64 种场景 。这些场景中的折线点也不一样,最多的有 4 次,最少的折 0 次,单求出这 64 种坐标点就用了 700 行代码 。

文章插图
最后的绘制方法与直线一样:
// 绘制折线const drawBrokenLine = ({ ctx, points }: WF.DrawLineType, highlight?: boolean) => {ctx.beginPath()ctx.moveTo(points[0][0], points[0][1])for (let i = 1; i < points.length; i++) {ctx.lineTo(points[i][0], points[i][1])}shadowLine(ctx, highlight)ctx.stroke()ctx.restore()ctx.closePath()}复制代码
曲线相对于折线来说,思路会简单很多,不需要考虑折线这么多场景 。

文章插图
这里的折线是用三阶的贝塞尔曲线来绘制的,固定的取四个点,两个起止点,两个控制点,其中两个起止点是固定的,我们只需要求出两个控制点的坐标即可 。这里代码不多,可以直接贴出来:
/** * Description: 计算三阶贝塞尔曲线的坐标 */import WF from '../type'const coeff = 0.5export default function calcBezierPoints({ startDire, startx, starty, destDire, destx, desty }: WF.CalcBezierType,points: [number, number][]) {const p = Math.max(Math.abs(destx - startx), Math.abs(desty - starty)) * coeffswitch (startDire) {case 'down':points.push([startx, starty + p])breakcase 'up':points.push([startx, starty - p])breakcase 'left':points.push([startx - p, starty])breakcase 'right':points.push([startx + p, starty])break// no default}switch (destDire) {case 'down':points.push([destx, desty + p])breakcase 'up':points.push([destx, desty - p])breakcase 'left':points.push([destx - p, desty])breakcase 'right':points.push([destx + p, desty])break// no default}}复制代码
推荐阅读
- Spring Boot与JAX-RS框架Jersey的完美搭配
- 详解java中float与double的区别
- VPN 隧道协议的区别
- 端口号的分类和测试方法
- 多年前借鉴b/s优势实现基于.net的c/s框架
- Wifi 数据通信原理
- 食疗跟着年龄走
- 专家支招 腹泻时不可服用的药物
- 五种美容中医药酒的制作
- 老慢支病人夏季保健药膳
