1. canvas介绍
Canvas API 提供了一个通过JavaScript 和 HTML的<canvas>元素来绘制图形的方式。它可以用于动画、游戏画面、数据可视化、图片编辑以及实时视频处理等方面。 Canvas API 主要聚焦于 2D 图形。 canvas的原点在左上角,webgl是在中心,这点需要区分下。
2. canvas绘制的基础示例
<canvas id='myCanvas' />
const canvas = document.getElementById("myCanvas");
const ctx = canvas.getContext("2d");
ctx.fillStyle = "red";
ctx.fillRect(10, 10, 100, 100);
3. canvas动态绘制
-
3.1 数据格式部分
const rectArea = { startX: 0, startY: 0, areaWidth: 0, areaHeight: 0 } -
3.2 事件监听部分
使用鼠标的mousedown、mousemove、mouseup三个事件监听就可以轻松实现图形的新绘制,这边依旧使用矩形举例说明。
整个实现过程大概是这样的,在鼠标按下时获取起始坐标,并且记录,在鼠标移动的时候与起始坐标进行计算,算出矩形的长和宽,鼠标抬起的时候结束绘制。
整体实现比较简单,对于图形的绘制难点在于计算,矩形的计算较为简单,如果遇到多边形等绘制,就有可能需要了解下交叉检测(快速排斥实验或者跨立实验等)。 下面根据代码简单说明下这个过程:
- mousedown
// 记录按下去那个点的坐标 const mousedownEvent = (e) => { isMouseDown = true; mousedown.x = e.offsetX; mousedown.y = e.offsetY; }- mousemove
const mousemoveEvent = (e) => { let x = e.offsetX; let y = e.offsetY; // 获取canvas的一个大小 const canvasDom = document.getElementById("myCanvas"); let bbox = canvasDom.getBoundingClientRect(); // 边界检测,如果鼠标移动到canvas外部,不应该继续增加矩形的长宽,因为图形绘制是在当期canvas的显示区域 x = x > bbox.right ? bbox.right : x; x = x < bbox.left ? bbox.left : x; y = y > bbox.bottom ? bbox.bottom : y; y = y < bbox.top ? bbox.top : y; const minWidth = 6; const minHeight = 6; // 矩形的左上顶点 rectArea.startX = x < this.mousedown.x ? x : this.mousedown.x; rectArea.startY = y < this.mousedown.y ? y : this.mousedown.y; // 矩形的长宽 rectArea.areaWidth = Math.max(Math.abs(x - mousedown.x), minWidth); rectArea.areaHeight = Math.max(Math.abs(y - mousedown.y), minHeight); };- mouseup
const mouseUpEvent = (e) => { isMouseDown = false }新绘制大概就是以上这些就能实现了,但通常情况下也需要对已经绘制的图形进行编辑,于是就可以对以上代码进行一个调整,适配下编辑的情况。
- 首先,需要检测下当前鼠标是否在我们之前已经绘制的矩形内部/边界
const moveIsInArea = (e) => { const x = e.offsetX; const y = e.offsetY; // 是否在矩形内部/边界 let isInArea = false; // 事件类型 let drawEvent = 0; // 鼠标样式 let cursor = ''; const { startX, startY, areaWidth, areaHeight } = rectArea; const endX = startX + areaWidth; const endY = startY + areaHeight; if (startX <= x && endX >= x && startY <= y && endY >= y) { if (x - startX < 5 && y - startY < 5) { // 在矩形的左上顶点 cursor = "nw-resize"; drawEvent = 2; } else if (x - startX < 5 && endY - y < 5) { // 在矩形的左下顶点 cursor = "sw-resize"; drawEvent = 8; } else if (endX - x < 5 && y - startY < 5) { // 在矩形的右上顶点 cursor = "ne-resize"; drawEvent = 4; } else if (endX - x < 5 && endY - y < 5) { // 在矩形的右下顶点 cursor = "se-resize"; drawEvent = 6; } else if (x - startX < 3) { // 在矩形的左边界 cursor = "w-resize"; drawEvent = 1; } else if (endX - x < 3) { // 在矩形的右边界 cursor = "e-resize"; drawEvent = 5; } else if (y - startY < 3) { // 在矩形的上边界 cursor = "n-resize"; drawEvent = 3; } else if (endY - y < 3) { // 在矩形的下边界 cursor = "s-resize"; drawEvent = 7; } else { // 在矩形内部 cursor = "move"; drawEvent = 9; } } return { isInArea, drawEvent, }; };- 编辑可以看做另类的新绘制,比如拖动左边界,可以看做 右上顶点为起始绘制点的一个新绘制, 其他边界或顶点的拉伸也都可以类推,mousedown代码就可以相对应改成:
const mousedownEvent = (e) => { isMouseDown = true; let x = e.clientX let y = e.clientY if (this.drawEvent !== 0) { const { startX, startY, areaWidth, areaHeight } = rectArea; const endX = startX + areaWidth; const endY = startY + areaHeight; switch (this.drawEvent) { // 左边界&左下顶点 case 1: case 8: x = endX; y = startY; break; // 上边界&左上顶点 case 2: case 3: x = endX; y = endY; break; // 右边界&右上顶点 case 4: case 5: x = startX; y = endY; break; // 下边界&右下顶点 case 6: case 7: x = startX; y = startY; break; } } // 设置起始顶点 mousedown.x = x; mousedown.y = y; }- 改写之前的mousemove方法
const mousemoveEvent = (e) => { // 鼠标还没按下,检测下是否在矩形内部/边界 if (!isMouseDown) { moveIsInArea(e) } else { // 获取canvas的一个大小 let bbox = canvasDom.getBoundingClientRect(); // 边界检测,如果鼠标移动到canvas外部,不应该继续增加矩形的长宽,因为图形绘制是在当期canvas的显示区域 x = x > bbox.right ? bbox.right : x; x = x < bbox.left ? bbox.left : x; y = y > bbox.bottom ? bbox.bottom : y; y = y < bbox.top ? bbox.top : y; const minWidth = 6; const minHeight = 6; // 矩形的左上顶点 const left = x < this.mousedown.x ? x : this.mousedown.x; const top = y < this.mousedown.y ? y : this.mousedown.y; // 矩形的长宽 const width = Math.max(Math.abs(x - mousedown.x), minWidth); const weight = Math.max(Math.abs(y - mousedown.y), minHeight); switch (this.drawEvent) { // 左右边界拉伸 case 1: case 5: rectArea.startX = left; rectArea.areaWidth = width; break; // 上下边界拉伸 case 3: case 7: rectArea.startY = top; rectArea.areaHeight = height; break; // 拖动整个矩形到新的位置(移动) case 9: rectArea.startX += x - mousedown.x; rectArea.startY += y - mousedown.y; rectArea.startX = Math.max(rectArea.startX, 0); rectArea.startX = Math.min(rectArea.startX, canvasW - rectArea.areaWidth); rectArea.startY = Math.max(rectArea.startY, 0); rectArea.startY = Math.min(rectArea.startY, canvasH - rectArea.areaHeight); mousedown.y = y; mousedown.x = x; break; // 四个顶点拉伸以及新绘制 default: rectArea.startX = left; rectArea.startY = top; rectArea.areaWidth = width; rectArea.areaHeight = height; break; } } };经过上面修改,就可以通过鼠标对绘制数据进行更改了
-
3.3 数据格式部分
绘制数据已经有了,接下来就是按照绘制数据直接将图形在canvas上绘制出来即可。
const canvasDom = document.getElementById("myCanvas"); const ctx = canvasDom.getContext("2d"); // 设置线宽 ctx.lineWidth = 2; // 清除画布,重新绘制 ctx.clearRect(0, 0, canvasDom.width, canvasDom.height); const { startX, startY, areaHeight, areaWidth } = rectArea; // 设置边框颜色 ctx.strokeStyle = '#FF0000'; // 绘制非实心矩形 ctx.strokeRect(startX, startY, areaWidth, areaHeight); // 绘制非实心矩形 // ctx.fillRect(startX, startY, areaWidth, areaHeight);
4. 结语
以上就是一个简单绘制的全过程了。其实绘制还有其他的细节,比如窗口大小变化导致canvas画布大小变化,这个时候往往需要对canvas进行大小变化监听,可以使用ResizeObserver或者MutationObserver,监听到大小变化重新计算坐标即可,这部分有时间再补充吧。 canvas的功能很强大,感兴趣的话也是可以玩一玩的。
待补充......