本文详解如何使用HTML5 Canvas与JavaScript实现月饼图案绘制功能。通过监听PointerEvent处理鼠标与触摸事件,解决坐标偏移问题,并基于Canvas变换方法实现多笔对称图案绘制,完整代码与逻辑解析供开发者参考。
中秋佳节,皓月当空,除了传统的赏月、猜灯谜和赏花灯习俗外,品尝月饼也是不可或缺的传统。月饼表面通常印有精美的图案与纹路,本文将探讨如何利用技术还原这一视觉效果。
效果图

HTML
该HTML结构主要包含一个Canvas画布、背景图片、文字展示区域以及一组功能按钮。CSS样式部分较为复杂,建议直接参考完整代码仓库以获取详细样式配置。
<div class="banner">
<canvas id="canvas" width="500" height="500"> </canvas>
<div class="basebg"> </div>
<img src="./bg.png" alt="" class="bg">
<img src="./sc1.png" alt="" class="sc1">
<div class="text">———— 但愿人长久,千里共婵娟 ————</div>
<div class="btn">
<div class="model" onclick="model(1)">单笔</div>
<div class="model" onclick="model(8)">对称</div>
<div class="reset" onclick="reset()">清空画布</div>
</div>
</div>初步绘制
绘制逻辑的核心在于事件监听器的绑定,主要涉及pointerdown、pointermove和pointerup三个关键事件。
当用户按下鼠标左键或触摸屏幕时,触发pointerdown事件,此时将isDrawing变量设置为true,标识绘制状态已开启。
在Canvas上移动鼠标或手指时,触发pointermove事件。系统首先检查isDrawing的值以确认是否处于绘制状态。若已开启,则获取鼠标或触摸事件的坐标信息(e.clientX 和 e.clientY),并通过getBoundingClientRect()方法计算Canvas的相对位置,将屏幕坐标转换为Canvas内部坐标(以左上角为原点)。随后,将这些坐标赋值给point对象(记录线段的起点与终点),并调用draw函数执行绘制操作。
当用户释放鼠标左键或手指时,触发pointerup事件,此时将isDrawing变量设置为false,表示绘制动作结束。
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存在细微差异,需在代码中分别处理。
e.touches[0].clientX
e.touches[0].clientYgetBoundingClientRect()
在获取坐标数据时,执行了一项关键操作:从原始坐标中减去getBoundingClientRect()返回的top和left值。
getBoundingClientRect方法返回一个DOMRect对象,该对象由元素的getClientRects()方法生成,代表元素的CSS边框集合。返回值包含描述完整元素最小矩形的属性,如left, top, right, bottom, x, y, width, height,单位为像素。除width和height外,其他属性均相对于视口左上角计算。

此步骤看似可选,实则源于早期未编写* {margin: 0;padding: 0;}重置样式时发现的坐标偏移问题。若CSS初始化不完整,直接忽略此步骤可能导致绘制位置错误。


绘制函数
绘制函数接收Canvas对象、画笔颜色和线条宽度作为参数。函数利用point对象中记录的线段起点与终点,执行具体的绘图指令。
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坐标进行相应的偏移处理。
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,专门用于同步绘制边框效果。
本文介绍了使用JS Canvas实现月饼绘制的完整流程,包括事件处理、坐标转换及对称算法。更多相关技术细节可参考本站过往文章或继续浏览后续内容,感谢支持。