制作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";
}
}
});