场景:大概21年4月份的时候,项目预规划了“自定义流程图需求”,并且能够根据后端返回数据,实现动态流程图(节点状态、流程线状态、节点数量等动态变化)。当时项目正有2-3天的空档期,于是就利用这几天,熟悉了一下uni-app中canvas api,基于设计稿,先行绘制出了静态图。并且,基于movable-area和movable-view组件,对流程图添加了手势缩放与拖拽功能。不过到目前,该需求也没有在项目中实际落实。本文将将基于该静态流程图,对uni-app中canvas基本使用做一个介绍。
官网API文档:Canvas API
Canvas组件文档:Canvas
设计图原图展示:
①:首先是视图层代码,主要的是Canvas的一些配置,其中canvas-id必须配置,且要唯一;并且如果不设置宽高,默认宽高是300px与225px,我这边是根据设计图大小设置的。此外,Canvas开发最佳、最简便的是在vue页面开发,nvue页面需要兼容,并且性能可能不佳。具体见官网Canvas组件介绍。
②:注意事项:
1. 绘制顺序要注意,比如我画一个流程节点,要先画背景,然后画字,否则字会被遮挡。
2. Canvas的draw()方法只需在所有元素绘制完成后调用一次就行。
3.其余还没想到。
③:下面介绍一下关键的绘制函:
1. drawImage:图片绘制,入参:Canvas 上下文、x、y、宽、高;
drawImage(ctx, path, x, y, dWidth, dHeight) {
ctx.drawImage(path, x, y, dWidth, dHeight)
},
2. roundNode:圆角矩形节点绘制,入参:Canvas 上下文、x、y、宽、高、弧度、填充色值;主要原理是通过arcTo方法绘制圆弧,最后填充颜色;
//圆角矩形
roundNode(ctx, x, y, width, height, radius, color) {
ctx.beginPath()
if (width < 2 * radius) radius = width / 2;
if (height < 2 * radius) radius = height / 2;
ctx.moveTo(x + radius, y);
ctx.arcTo(x + width, y, x + width, y + height, radius);
ctx.arcTo(x + width, y + height, x, y + height, radius);
ctx.arcTo(x, y + height, x, y, radius);
ctx.arcTo(x, y, x + width, y, radius);
ctx.setFillStyle(color)
ctx.fill()
},
3. diamondNode:菱形节点绘制,入参:Canvas 上下文、x、y、宽、高、线宽、填充色值;菱形节点绘制是基于圆角节点进行的,arcTo方法相当于一阶贝塞尔曲线,通过控制控制点的位置实现圆弧位置变动。但这个方法绘制出来有个缺口,存在一点瑕疵,后续项目正式将该需求提上计划后再优化
//菱形绘制
diamondNode(ctx, x, y, width, height, lineColor, bgColor) {
ctx.beginPath()
ctx.setLineDash([])
ctx.setLineWidth(4)
ctx.moveTo(x + 20, y + 20);
ctx.arcTo(x + width / 2, y, x + width, y + height / 2, 5);
ctx.arcTo(x + width, y + height / 2, x + width / 2, y + height, 8);
ctx.arcTo(x + width / 2, y + height, x, y + height / 2, 5);
ctx.arcTo(x, y + height / 2, x + width / 2, y, 8);
ctx.setStrokeStyle(lineColor)
ctx.stroke()
ctx.setFillStyle(bgColor)
ctx.fill()
},
4. drawTriangle:三角形绘制,该方法与线段绘制组合使用,入参:Canvas 上下文、x、y、填充色、箭头方向;这边为了方便起见,箭头大小是写死的,我这边的箭头绘制原理是:基于指定点,绘制线段填充实现。
//绘制三角形 type:箭头朝向:bottom、right、left
drawTriangle(ctx, x, y, color, type) {
ctx.beginPath()
let height = 10 //计算等边三角形的高
ctx.moveTo(x, y); //x y开始
switch (type) {
case 'bottom':
ctx.lineTo(x - height / 2, y)
ctx.lineTo(x, y + height)
ctx.moveTo(x, y)
ctx.lineTo(x + height / 2, y)
ctx.lineTo(x, y + height)
break;
case 'left':
ctx.lineTo(x, y - height / 2)
ctx.lineTo(x - height, y)
ctx.moveTo(x, y)
ctx.lineTo(x, y + height / 2)
ctx.lineTo(x - height, y)
break;
case 'right':
ctx.lineTo(x, y - height / 2)
ctx.lineTo(x + height, y)
ctx.moveTo(x, y)
ctx.lineTo(x, y + height / 2)
ctx.lineTo(x + height, y)
break;
default:
break;
}
ctx.setFillStyle(color) //以纯色绿色填充
ctx.fill();
}
5. drawText:文字绘制,入参:Canvas 上下文、x、y、填充色、文字大小;这边文字绘制在水平方向,通过measureText计算文字宽度,进行了适配;而文字竖直方向适配,试了几种网上的方法,一直没有成功,暂时做了统一偏移量处理。
drawText(ctx, text, x, y, color, size) {
//文字部分
ctx.beginPath()
ctx.setTextAlign('center')
ctx.setFillStyle(color)
ctx.setFontSize(size)
const metrics = ctx.measureText(text)
console.log(metrics.width)
//文字统一偏移
ctx.fillText(text, x + metrics.width / 2, y + 17)
},
6. drawLine: 绘制线段,入参:Canvas 上下文、起始点x、起始点y、目标点x、目标点y、填充色、文字大小、箭头朝向、是否带箭头、是否虚线;线段绘制方法比较简单。
drawLine(ctx, fromX, fromY, toX, toY, color, type, isArrow = true, isDash = false) {
ctx.beginPath()
if (isDash) {
ctx.setLineDash([10]);
} else {
ctx.setLineDash([]);
}
ctx.moveTo(fromX, fromY)
ctx.lineTo(toX, toY)
ctx.setLineWidth(1)
ctx.setStrokeStyle(color)
ctx.stroke()
//是否绘制箭头
if (isArrow) {
this.drawTriangle(ctx, toX, toY, color, type)
}
},
7. touchstart:Canvas触摸事件监听,用于实现流程图中节点的点击。实际使用需要记录每个节点的x、y坐标点、宽、高,通过这些参数对点击区域进行计算与判断,并且还要考虑缩放而带来的坐标点变化。
touchstart(e) {
//点击事件有点复杂,要根据点击点、绘制位置、缩放比例判断点击了哪个节点,
let x = e.touches[0].x
let y = e.touches[0].y
this.node.forEach(item => {
// console.log("item.x * this.scale:"+item.x * this.scale)
// console.log("item.y * this.scale:"+item.y * this.scale)
if (x > item.x * this.scale && x < (item.x + item.w) * this.scale
&& y > item.y * this.scale && y < (item.y + item.h) * this.scale) {
//在范围内,根据标记定义节点类型
console.log(item.targe)
uni.showToast({
icon:'none',
title:item.name
})
}
})
console.log("x:"+x + " y:"+y)
},
④:逻辑层代码量比较多,略微复杂,这边全部贴出来,供参考。部分静态图片资源就不提供了,运行的时候替换一下或注释掉都行。
总结:个人观点来看,uni-app端的canvas,与Android原生相比,核心原理都是找点,找到点位后执行相关绘制方法进行绘制,只不过部分方法有所不同。作为一个Android开发者,虽然暂时没具体使用过原生canvas,但对其也有了大致的了解。此外,Canvas还能实现各种效果的动画,这边初步了解了一点,后续实现了默写效果会继续更新文章。