30daysJS-07 - HTML5 Canvas
路径生命周期必须和一次绘画行为对齐,而不是和mousemove对齐。
没学过canvas,所以这次跟着教程写代码。
问题
先写了这样的
<script>
const canvas = document.querySelector("#draw");
const ctx = canvas.getContext("2d");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
ctx.strokeStyle = "#aec600";
ctx.lineJoin = "round";
ctx.lineCap = 'round';
let isDrawing = false;
let lastX = 0;
let lastY = 0;
function draw(e) {
if (!isDrawing) return;
console.log(e);
ctx.beginPath();
ctx.moveTo(lastX, lastY);
ctx.lineTo(e.offsetX, e.offsetY);
ctx.stroke()
[lastX, lastY] = [e.offsetX, e.offsetY];
}
canvas.addEventListener("mousedown", (e) => {
isDrawing = true
[lastX, lastY] = [e.offsetX, e.offsetY];
});
canvas.addEventListener("mousemove", draw);
canvas.addEventListener("mouseup", () => isDrawing = false);
canvas.addEventListener("mouseout", () => isDrawing = false);
console.log('Canvas size:', canvas.width, canvas.height);
console.log('Canvas CSS size:', canvas.clientWidth, canvas.clientHeight);
console.log('Canvas offset:', canvas.offsetLeft, canvas.offsetTop);
</script>
结果:
![[Pasted image 20260126114524.png]]
我仔细和老师的代码对比了好多遍,怎么看都是一模一样啊,哪里不对了?最后发现原来是我有两个地方没有加分号😓(是的我自己写就习惯不加分号)难怪使用prettier优化后总是把 ctx.lineTo(e.offsetX, e.offsetY)和ctx.stroke()黏在一起,我还以为是插件出错······
最后去查了自动插入分号([[ASI]], Automatic Semicolon Insertion)机制,了解了下js里面什么时候必须加分号,什么时候不能换行。
JavaScript 语法:到底要不要写分号?一文吃透 ASI 与坑点清单
什么时候必须加分号?
以下列符号开头的
() // 可能会与上一行形成IIFE调用
[] // 可能会被当作上行表达式的下标/逗号表达式
/ // 可能会被当成除号
` // 可能与上一行函数/变量粘连
什么时候不能换行?
//后置自增/自减:
i /* no LT here */ ++
i /* no LT here */ --
//return、throw、yield、await、async 后面紧跟的实体:
return /* no LT here */ value
throw /* no LT here */ new Error()
yield /* no LT here */ i++
async /* no LT here */ function f() {}
const f = async /* no LT here */ x => x * x
//箭头函数箭头前:
const f = x /* no LT here */ => x * x
//带标签的 break/continue 与标签名之间:
break /* no LT here */ outer
continue/* no LT here */ outer
答案
const canvas = document.querySelector("canvas")!;
const ctx = canvas?.getContext("2d")!;
let isDrawing = false;
let lineStart = 0;
let lineEnd = 0;
ctx.strokeStyle = "orange";
let hue = 0;
let direction = true;
ctx.lineWidth = 100;
function draw(e: MouseEvent) {
if (!isDrawing) return;
let x = position(canvas, e).x;
let y = position(canvas, e).y;
ctx.strokeStyle = `hsl(${hue} 50% 70%)`;
ctx.beginPath();
ctx.moveTo(lineStart, lineEnd);
ctx.lineTo(x, y);
ctx.stroke();
[lineStart, lineEnd] = [x, y];
hue++;
if (hue > 360) {
hue = 0;
}
if (ctx.lineWidth > 100 || ctx.lineWidth <= 1) {
direction = !direction;
}
if (direction) {
ctx.lineWidth--;
} else if (!direction) {
ctx.lineWidth++;
}
console.log(hue);
}
function position(canva: HTMLCanvasElement, e: MouseEvent) {
const pos = canva.getBoundingClientRect();
return {
x: e.clientX - pos.left,
y: e.clientY - pos.top,
};
}
canvas.addEventListener("mousemove", draw);
canvas.addEventListener("mousedown", (e: MouseEvent) => {
isDrawing = true;
ctx.beginPath();
[lineStart, lineEnd] = [position(canvas, e).x, position(canvas, e).y];
});
canvas.addEventListener("mouseup", () => {
isDrawing = false;
});
试着用新学的ts写了一下.
思路:
- 功能:①画线功能,②颜色宽度变化功能;
- 方法:①状态机管理,isDrawing布尔值控制绘制状态,②鼠标事件(mousedown, mousemove, mouseup)驱动。
其他都很顺利,到了宽度变化部分卡住了,仔细研究了下老师代码发现实在巧妙。
let direction = true
if(ctx.lineWidth > 100 || ctx.lineWidth < 1){
direction = !direction
}
if(direction){
ctx.lineWidth++
} else if (!direction){
ctx.lineWidth--
}
有两个条件,一个是ctx.lineWidth > 100,一个是ctx.lineWidth < 1,只要满足其一就使direction = !direction,不论此刻的direction是true还是false,总之只需要它反转。
接下来就是根据direction是T还是F进行数值增减,非常清晰明了。
借用ai的说法,这是双向边界控制:
上限检查:ctx.lineWidth > 100 → 超过最大值就反向
下限检查:ctx.lineWidth <= 1 → 低于最小值就反向
这样就创建了一个自动在1-100之间来回振荡的线宽变化。
发散一下,这个可以用在颜色明暗循环,图像大小变化,透明度闪烁上,真聪明啊。
结果:
