皓月当空,月圆中秋,在这个传统节日里,除了赏月、猜灯谜、赏花灯等习俗外,还有就是品尝美味的月饼。关于月饼,大家一定都知道月饼上会印着一个精美的图案与纹路。
HTML
这个 HTML 片段的主要组成部分包括一个canvas画布、一个背景图片、文字以及一组按钮功能。 关于css部分过于冗长大家可以直接去码上掘金阅读。
js复制代码
初步绘制
我们来看一下绘制的逻辑,首先通过了事件监听器绑定了pointerdown、pointermove、pointerup。
pointerdown:当用户按下鼠标左键或触摸屏幕时,isDrawing 的变量设置为 true,表示用户开启绘制。
pointermove:当用户在Canvas上移动鼠标或手指时,首先判断isDrawing的值,确定用户是否开启绘制,如果为开启绘制,则获取鼠标或触摸事件的坐标信息(e.clientX 和 e.clientY)并根据Canvas的相对位置(使用 getBoundingClientRect() 方法计算)将坐标信息转换为Canvas内部的坐标(以左上角为原点)。将其赋值给 point对象(point对象中的表示一条线段的起点与终点),调用draw函数绘制线条。
pointerup:用户释放鼠标左键或手指时触发将 isDrawing 变量设置为 false,表示用户停止绘制。
js复制代码 const canvas = document.getElementById(‘canvas’);
const ctx = canvas.getContext(‘2d’);
const width = canvas.width;
const height = canvas.height;
let point = {};
let lineNum = 8;
let isDrawing = false;
canvas.addEventListener(‘pointerdown’, (e) => {
isDrawing = true;
});
canvas.addEventListener(‘pointermove’, (e) => {
if (!isDrawing) return;
const x = e.clientX - canvas.getBoundingClientRect().left
const y = e.clientY - canvas.getBoundingClientRect().top
point.x1 = x;
point.y1 = y;
draw(ctx, "#fdbb07", 5);
point.x2 = x;
point.y2 = y;
});
canvas.addEventListener(‘pointerup’, (e) => {
isDrawing = false;
});
pointerevnet与touchevent
一般来讲在电脑上我们都会使用MouseEvent,但是如果想要在手机上也能进行绘制,就需要使用PointerEvent和TouchEvent。
PointerEvent是一个通用的事件类型,用于处理多种输入设备(包括鼠标、触摸屏、触控笔等)的输入事件。TouchEvent用于处理触摸屏输入事件,如触摸、滑动等。所有在这里我们使用了PointerEvent,因为它更加具备通用性。
不过在实际开发中,pointermove事件出现绘制中断的情况,于是只能再添加一个touchmove监听器。
需要注意的是TouchEvent获取坐标的方式与PointerEvent略有不同。
js复制代码e.touches[0].clientX
e.touches[0].clientY
getBoundingClientRect()
在获取坐标时,我们还做了一个操作,减去getBoundingClientRect()的top和left。
getBoundingClientRect返回值是一个 DOMRect 对象,这个对象是由该元素的 getClientRects() 方法返回的一组矩形的集合,就是该元素的 CSS 边框大小。返回的结果是包含完整元素的最小矩形,并且拥有 left, top, right, bottom, x, y, width, 和 height 这几个以像素为单位的只读属性用于描述整个边框。除了 width 和 height 以外的属性是相对于视图窗口的左上角来计算的。
这步其实可有可无,是因为在一开始还没写css时发现的一个没写* {margin: 0;padding: 0;}导致的问题。直接来看一下区别,一开始写了css还可能发现不了。
绘制函数
接下来是绘制的函数,传入了canvas对象、画笔颜色和线条宽度,再通过point中线段的起点与终点进行绘制。
js复制代码 function draw(canvas, color, lineWidth) {
canvas.strokeStyle = color;
canvas.lineWidth = lineWidth;
canvas.lineCap = “round”;
canvas.moveTo(point.x1, point.y1);
canvas.lineTo(point.x2, point.y2);
canvas.stroke();
}
对称绘制
上一步简单了实现了绘制功能,已经可以绘制月饼的图案,不过有的月饼上都是一些对称的的图案,简单的靠一支画笔完全不可能画出对称的效果。所以还需要一个能绘制对称图案的功能。
大致的方案是通过rotate进行旋转,为了保证画笔的点位正常,需要使用translate将坐标原点移动到 Canvas 的中心位置,对point坐标都要进行偏移操作。
js复制代码 function draw(canvas, color, lineWidth) {
canvas.strokeStyle = color;
canvas.lineWidth = lineWidth;
canvas.lineCap = “round”;
var r = 360 / lineNum * Math.PI / 180;
for (let i = 0; i < lineNum; i++) {
canvas.save();
canvas.translate(width / 2, height / 2);
canvas.rotate(r * i);
canvas.beginPath();
canvas.moveTo(point.x1 - width / 2, point.y1 - height / 2);
canvas.lineTo(point.x2 - width / 2, point.y2 - height / 2);
canvas.stroke();
canvas.restore();
}
}
结语
至此,绘制的相关功能写完了,还有一个额外的操作是,对于图案线条添加了边框,本意是想增加线条的立体感,但是写完发现一点也感觉不到。由于在绘制过程中线条交叉会导致边框覆盖之前线条的情况,于是使用了一个简单粗暴的方案,在这个canvas中下面在添加一个canvas进行同步绘制单独的边框效果。
最后,祝大家中秋节快乐 !阖家欢乐,月圆人团圆,共享中秋月!