canvas画板总结

本文详细介绍了如何使用HTML5的canvas元素创建一个交互式的画板应用,包括从使用div模拟画板开始,逐步过渡到使用canvas进行性能优化。讲解了从点击打点到连线的实现,以及如何处理手机和平板的触摸事件。在遇到线条粗糙的问题时,通过将直线连接处变为圆弧来改善视觉效果。此外,还涵盖了颜色处理、橡皮擦功能、画笔与橡皮切换,以及移动端适配等关键点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

制作Canvas画板

画板预览

源码地址在这里插入图片描述
canvas的使用查mdn文档就可以了
下面主要讲的是实现画板的思路,以及我的踩坑记录

先不用canvas标签

先用div,首先知道用户点了哪里
使用console.log调试

<div id="canvas"></div>
xxx.onClick = (e) => {
  console.log(e)
  //e.clientX
  //e.clientY
}

在点击的地方出现圆点

let div = document.createElement('div')
div.style.position = 'absolute'
div.style.left = e.clientX + 'px'
div.style.top = e.clientY + 'px'
div.style.border = '1px solid red'
div.style.weight = '5px'
div.style.height = '5px'
div.style.borderRadius = '50%'
div.style.backgroundColor = 'black'
xxx.appendChild(div)

把点连起来,点变成线
现在是直接操作dom(操作div的),性能差

改进:使用canvas。

<canvas id="canvas"></canvas>

canvas是inline元素
display: block;
再把宽高设置成页面这么大即可

var ctx = canvas.getContext("2d");

    ctx.fillStyle = "rgb(200,0,0)";
    ctx.fillRect (10, 10, 55, 50);

上面这个是canvas画出一个形状,前面两个参数是起始位置,后面两个是宽高

canvas的宽高是在一开始就确定的,

<canvas id="canvas" width="100" height="100"></canvas>

如果后面css还有宽高,就会覆盖这个宽高,但是就相当于缩放了,放大的话就会变模糊。所以最开始写宽高就会写好(用js获取用户屏幕的宽高)

var canvas = document.getElementById("canvas")
const mainWidth = parseInt(main.clientWidth * 0.95);
const mainHeight = parseInt(main.clientHeight );
canvas.width = `${mainWidth}`;
canvas.height = `${mainHeight}`;
canvas.style.width = `${mainWidth}px`;
canvas.style.height = `${mainHeight}px`;

注意:获取的是文档的宽高,获取body是不行的,因为body的高度是由内容撑起来的,没有内容,高度就没有

下面是做点击就打点的操作

canvas.onClick = (e) => {
  ctx.fillRect(e.clientX - 5, e.clientY - 5, 10, 10)
}

这个是canvas的api

变换鼠标事件
onClick->onmousemove
使用一个变量来看鼠标是否是按下,按下时鼠标移动就继续划线,鼠标松开时就不划线

let painting = false

canvas.onmousedown = () => {
  painting = true
}
canvas.onmousemove = () => {
  painting = false
}
canvas.onClick = (e) => {
  if(painting === true) {
    ctx.fillRect(e.clientX - 5, e.clientY - 5, 10, 10)
  }
}

这样就实现了鼠标按下就画线,鼠标松开就停

画圆点

ctx.beginPath();
ctx.arc(50, 50, 50, 0, 2 * Math.PI, false);
ctx.stroke();
ctx.arc(50, 50, 50, 0, 2 * Math.PI, false);
ctx.arc(e.clientX, e.clientY,10, 0, 2 * Math.PI, false);

前两个参数是起始位置,第三个是半径,后面两个是角度

ctx.fill()//实心,填充颜色

再把边框去掉
ctx.strokeStyle = 'none'
现在就可以慢速的画了,为什么是慢速呢,因为是拼凑的,画快了就断掉了,当速度快一点时,就变成点的形状了

怎么样支持手机,但是手机没有鼠标事件,所以就要判断是手机还是pc。
js detect touch support

var isTouchDevice = 'ontouchstart' in document.documentElement;

判断isTouchDevice是true或者false,就能知道是手机还是pc

难点

这个在手机上是支持的,在电脑上是不支持的,就可以判断是手机还是电脑(搜了半天),(就是上面这个)

手机上ontouchstart就是触屏事件,参数e里面就是有touches,里面包含了我们需要的x和y(位置)
手机上就没有按下,起来的事件,就直接是触摸事件

现在是点,把点变成线就可以了
在canvas里面搜直线,这个是三角形,也就是三个点,画直线的画就要两个点就够了,去掉第三个点即可

//描边三角形
  ctx.beginPath();
  ctx.moveTo(125, 125);
  ctx.lineTo(125, 45);
  ctx.lineTo(45, 125);
  ctx.closePath();
  ctx.stroke();
  //上面的就是三个点,然后把三个点连起来

两个点连上就变成直线了,记住上一个点,然后连起来就好起来了

记住上一次的点,然后把现在的点和上一次的点连起来,就可以了,第一个点没有上一个点,所以从第二个点开始。

把线变粗
ctx.lineWidth = 10

难点

画出来的线又很粗糙

解决:js canvas line join
把line和line之间的链接变成圆弧
ctx.lineCap = "round"

整个画板的实现过程

获取位置->打点->连线->断断续续->记住上一个点连起来->粗糙->变成圆弧就不粗糙了

使用了flex布局

使用正则表达式来替换颜色,把颜色转化为不同的格式

const color = getComputedStyle(e.target).backgroundColor;
获取当前的颜色

用了一个伪类来在显示的时候添加边框

颜色的部分使用了<input type="color"></input>

橡皮檫部分是使用了ctx.globalCompositeOperation = "destination-out";
去mdn上查就能查到这个属性的作用

铅笔是ctx.globalCompositeOperation = "source-over";

这两个相互切换是使用了classList,首先给这个添加selected这个类,加上标签,然后再用forEach和indexOf来判断eraser和source-over确定是铅笔还是橡皮

画布的高度

const mainWidth = parseInt(main.clientWidth * 0.95);
const mainHeight = parseInt(main.clientHeight );
canvas.width = `${mainWidth}`;
canvas.height = `${mainHeight}`;
canvas.style.width = `${mainWidth}px`;
canvas.style.height = `${mainHeight}px`;

鼠标绘图

main.addEventListener("mousedown", (e) => {
  painting = true;
  lastX = e.offsetX;
  lastY = e.offsetY;
  ctx.beginPath();
  ctx.arc(lastX, lastY, ctx.lineWidth / 2, 0, 2 * Math.PI);
  ctx.closePath();
  ctx.fill();
});
main.addEventListener("mousemove", (e) => {
  if (painting) {
    ctx.beginPath();
    ctx.moveTo(lastX, lastY);
    ctx.lineTo(e.offsetX, e.offsetY);
    ctx.closePath();
    ctx.stroke();
    lastX = e.offsetX;
    lastY = e.offsetY;
  }
});
main.addEventListener("mouseup", (e) => {
  painting = false;
});
// 鼠标移出画布停止绘画,防止再次进入产生连线
main.addEventListener("mouseout", (e) => {
  painting = false;
});

移动端绘图

// 触摸绘图
canvas.addEventListener("touchstart", (e) => {
  painting = true;
  lastX = e.touches[0].pageX - e.touches[0].target.offsetLeft;
  lastY = e.touches[0].pageY - e.touches[0].target.offsetTop;
  ctx.beginPath();
  ctx.arc(lastX, lastY, ctx.lineWidth / 2, 0, 2 * Math.PI);
  ctx.closePath();
  ctx.fill();
});
// 使用事件委托优化移动端绘图,在画布上触摸移动时阻止body默认事件(阻止浏览器的滑动)
document.body.addEventListener(
  "touchmove",
  (e) => {
    if (painting && e.target.matches(`canvas`)) {
      e.preventDefault();
      ctx.beginPath();
      ctx.moveTo(lastX, lastY);
      ctx.lineTo(
        e.touches[0].pageX - e.touches[0].target.offsetLeft,
        e.touches[0].pageY - e.touches[0].target.offsetTop
      );
      ctx.closePath();
      ctx.stroke();
      lastX = e.touches[0].pageX - e.touches[0].target.offsetLeft;
      lastY = e.touches[0].pageY - e.touches[0].target.offsetTop;
    }
  },
  { passive: false }
);
canvas.addEventListener("touchend", (e) => {
  painting = false;
});
// 触摸移出画布停止绘画,防止再次进入产生连线
canvas.addEventListener("touchcancel", (e) => {
  painting = false;
});

显示当前的颜色

currentColor.addEventListener("input", (e) => {
  ctx.strokeStyle = e.target.value;
  ctx.fillStyle = e.target.value;
});
thickness.addEventListener("input", (e) => {
  ctx.lineWidth = parseInt(e.target.value);
});

点击切换颜色

// 点击色块改变画笔颜色,将色块背景色赋值给 currentColor.value (RGB 转为 HEX )
brushColor.addEventListener("click", (e) => {
  if (e.target.matches("li")) {
    let color16 = "#";
    const color = getComputedStyle(e.target).backgroundColor;
    [...color.matchAll(/\d{1,3}/g)].forEach((item) => {
      const str = parseInt(item).toString(16);
      color16 += str.length === 2 ? str : "0" + str; // 确保六位数 HEX 颜色值
    });
    ctx.strokeStyle = color16;
    ctx.fillStyle = color16;
    currentColor.value = color16;
  }
});

画笔和橡皮的切换

// 点击图标切换铅笔橡皮,使用 globalCompositeOperation 设置绘图类型
type.addEventListener("click", (e) => {
  if (e.target.matches("img")) {
    const li = e.target.parentNode;
    type.childNodes.forEach((node) =>
      node.classList ? node.classList.remove("selected") : undefined
    );
    li.classList.add("selected");
    if (Array.from(li.classList).indexOf("eraser") >= 0) {
      text.textContent = "橡皮";
      ctx.globalCompositeOperation = "destination-out";
    } else {
      text.textContent = "铅笔";
      ctx.globalCompositeOperation = "source-over";
    }
  }
});
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值