最终效果
一个全屏的画布,当你移动鼠标时,会留下彩色的轨迹,点击鼠标可以清除画布。

(图片来源网络,侵删)
完整代码
你可以直接将以下代码复制到一个 .html 文件中,然后用浏览器打开即可。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">鼠标轨迹绘制器</title>
<style>
/* 基本样式,让画布占满整个窗口 */
body {
margin: 0;
padding: 0;
overflow: hidden; /* 防止出现滚动条 */
background-color: #f0f0f0;
}
canvas {
display: block; /* 移除 canvas 元素默认的内联空白 */
cursor: crosshair; /* 将鼠标光标变为十字准星 */
}
</style>
</head>
<body>
<canvas id="myCanvas"></canvas>
<script>
// 1. 获取 canvas 元素和其 2D 渲染上下文
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
// 2. 设置 canvas 的尺寸为窗口大小
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
resizeCanvas();
// 监听窗口大小变化,以便在调整窗口大小时重新设置 canvas 尺寸
window.addEventListener('resize', resizeCanvas);
// 3. 设置绘制样式
ctx.lineWidth = 5; // 线条宽度
ctx.lineCap = 'round'; // 线条末端为圆形
ctx.lineJoin = 'round'; // 线条连接处为圆形
// 4. 跟踪鼠标状态
let isDrawing = false;
let lastX = 0;
let lastY = 0;
// 5. 定义颜色数组,用于绘制彩色轨迹
const colors = [
'#FF6B6B', '#4ECDC4', '#45B7D1', '#FFBE0B', '#FB5607',
'#8338EC', '#3A86FF', '#FF006E', '#C77DFF', '#7209B7'
];
let currentColorIndex = 0;
// 6. 绘制函数
function draw(e) {
// 如果鼠标没有按下,则不绘制
if (!isDrawing) {
return;
}
// 获取鼠标在 canvas 上的坐标
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
// 设置当前颜色
ctx.strokeStyle = colors[currentColorIndex];
// 开始绘制路径
ctx.beginPath();
// 移动到上一个点
ctx.moveTo(lastX, lastY);
// 画一条线到当前点
ctx.lineTo(x, y);
// 描边(绘制线条)
ctx.stroke();
// 更新上一个点的坐标为当前点
lastX = x;
lastY = y;
// 更换颜色,实现彩虹效果
currentColorIndex = (currentColorIndex + 1) % colors.length;
}
// 7. 事件监听器
// 鼠标按下事件:开始绘制
canvas.addEventListener('mousedown', (e) => {
isDrawing = true;
// 当鼠标按下时,记录当前鼠标位置作为路径的起点
const rect = canvas.getBoundingClientRect();
lastX = e.clientX - rect.left;
lastY = e.clientY - rect.top;
});
// 鼠标移动事件:执行绘制
canvas.addEventListener('mousemove', draw);
// 鼠标松开或离开画布事件:停止绘制
canvas.addEventListener('mouseup', () => {
isDrawing = false;
});
canvas.addEventListener('mouseout', () => {
isDrawing = false;
});
// 8. 清除画布功能(点击任意位置清除)
canvas.addEventListener('click', () => {
// 清除整个画布区域
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 重置颜色索引,从第一个颜色开始
currentColorIndex = 0;
});
</script>
</body>
</html>
代码分步详解
HTML 结构 (<body>)
<body>
<canvas id="myCanvas"></canvas>
</body>
- 我们只需要一个核心元素:
<canvas>,给它一个id(这里是myCanvas),方便 JavaScript 找到它。
CSS 样式 (<style>)
body {
margin: 0;
padding: 0;
overflow: hidden;
background-color: #f0f0f0;
}
canvas {
display: block;
cursor: crosshair;
}
body的margin和padding设为0,overflow: hidden是为了让页面没有边距,并且窗口大小改变时不会出现滚动条,让画布能完美填充整个视口。canvas的display: block可以移除<canvas>作为内联元素时自带的底部空白。cursor: crosshair将鼠标指针在画布上变为十字准星,提示用户可以在这里绘图。
JavaScript 逻辑 (<script>)
步骤 1 & 2:获取 Canvas 并设置尺寸
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
resizeCanvas();
window.addEventListener('resize', resizeCanvas);
document.getElementById('myCanvas')获取我们 HTML 中的 canvas 元素。canvas.getContext('2d')获取一个用于在画布上绘制 2D 图形的“上下文”(Context),所有的绘图操作(如画线、画圆、填充颜色)都是通过这个ctx对象来完成的。resizeCanvas()函数将 canvas 的宽度和高度设置为当前浏览器窗口的宽高,确保画布是全屏的。- 我们还添加了一个
resize事件监听器,这样当用户调整浏览器窗口大小时,画布会自动适应新的尺寸。
步骤 3:设置绘制样式
ctx.lineWidth = 5; ctx.lineCap = 'round'; ctx.lineJoin = 'round';
lineWidth设置线条的粗细。lineCap = 'round'让线条的端点是圆形的,而不是方形。lineJoin = 'round'让两条线相交的连接处是圆滑的。
步骤 4:跟踪鼠标状态
let isDrawing = false; let lastX = 0; let lastY = 0;
isDrawing是一个“开关”,用来判断用户当前是否正在按下鼠标并拖动。lastX和lastY用来记录上一次鼠标的位置,因为画线需要起点和终点,所以我们必须知道从哪里画到哪里。
步骤 5:定义颜色数组
const colors = [...]; let currentColorIndex = 0;
- 我们定义了一个漂亮的颜色数组,每次画线时,我们按顺序从这个数组中取颜色,形成彩虹轨迹的效果。
步骤 6:核心绘制函数 draw(e)
function draw(e) {
if (!isDrawing) return;
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
ctx.strokeStyle = colors[currentColorIndex];
ctx.beginPath();
ctx.moveTo(lastX, lastY);
ctx.lineTo(x, y);
ctx.stroke();
lastX = x;
lastY = y;
currentColorIndex = (currentColorIndex + 1) % colors.length;
}
- 坐标计算:
e.clientX和e.clientY是鼠标相对于浏览器窗口左上角的坐标,如果页面有滚动或者 canvas 不是从(0,0)开始,我们需要用canvas.getBoundingClientRect()来获取 canvas 元素自身的偏移量,然后用clientX - rect.left和clientY - rect.top得到在 canvas 内部的准确坐标。 ctx.beginPath():开始一条新的、独立的路径,这非常重要,确保每次画线都是独立的,不会和之前的路径混在一起。ctx.moveTo(lastX, lastY):将“画笔”移动到上一个记录的点。ctx.lineTo(x, y):从当前画笔位置(即moveTo的点)画一条直线到新的鼠标位置(x, y)。ctx.stroke():将上面定义的路径用strokeStyle设置的颜色和lineWidth设置的宽度“描边”出来,也就是画出我们看到的线条。- 更新状态:绘制完成后,将
lastX和lastY更新为当前点,为下一次绘制做准备,颜色索引currentColorIndex加 1,如果超出数组长度则取模,实现循环。
步骤 7:事件监听器
canvas.addEventListener('mousedown', (e) => { ... });
canvas.addEventListener('mousemove', draw);
canvas.addEventListener('mouseup', () => { ... });
canvas.addEventListener('mouseout', () => { ... });
mousedown:当鼠标在 canvas 上按下时,设置isDrawing = true,并记录当前的鼠标位置作为lastX和lastY。mousemove:当鼠标在 canvas 上移动时,调用draw()函数,这是触发绘制的关键。mouseup:当鼠标松开时,设置isDrawing = false,停止绘制。mouseout:当鼠标移出 canvas 区域时,也设置isDrawing = false,防止鼠标移出后松开,但isDrawing状态没有重置。
步骤 8:清除画布功能
canvas.addEventListener('click', () => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
currentColorIndex = 0;
});
- 我们监听
click事件(即按下并松开)。 ctx.clearRect(x, y, width, height)是一个非常有用的方法,它会清除指定矩形区域内的所有像素,我们用它来清除整个画布。- 重置
currentColorIndex,这样下次点击后,颜色会从数组的第一个开始。
如何扩展和改进?
- 改变线条粗细:可以增加一个滑动条(
<input type="range">)来动态改变ctx.lineWidth。 - 选择颜色:可以添加一个颜色选择器(
<input type="color">)来让用户自定义颜色。 - 保存图片:可以添加一个按钮,点击后使用
canvas.toDataURL()方法将画布内容保存为一张 PNG 图片。 - 绘制模式:可以增加“绘制”和“擦除”模式,擦除模式其实就是将
globalCompositeOperation设置为'destination-out',这样画出的内容会变成透明。

(图片来源网络,侵删)
