共计 5516 个字符,预计需要花费 14 分钟才能阅读完成。
前言
H5 中利用 cancas 实现一个画板功能很简单,甚至你可以直接用 chatGpt 生成,生成之后简单的效果运行 demo 是可以的,然后在此基础上可以自己修改一下,集成功能,就可以了。
例如如下是 html5 的简单的画板 demo
let canvas = document.getElementById('canvas');
let ctx = canvas.getContext('2d');
// 画板控制开关
let painting = false;
// 第一个点坐标
let startPoint = {x: undefined, y: undefined};
// 初始化画布大小
wh();
// 特性检测
if (document.body.ontouchstart !== undefined) {
// 触屏设备
canvas.ontouchstart = function (e) {//[0] 表示 touch 第一个触碰点
let x = e.touches[0].clientX;
let y = e.touches[0].clientY;
painting = true;
if (EraserEnabled) {ctx.clearRect(x - 20, y - 20, 40, 40)
}
startPoint = {x: x, y: y};
};
canvas.ontouchmove = function (e) {let x = e.touches[0].clientX;
let y = e.touches[0].clientY;
let newPoint = {x: x, y: y};
if (painting) {if (EraserEnabled) {ctx.clearRect(x - 15, y - 15, 30, 30)
} else {drawLine(startPoint.x, startPoint.y, newPoint.x, newPoint.y);
}
startPoint = newPoint;
}
};
canvas.ontouchend = function () {painting = false;};
}else{// 非触屏设备
// 按下鼠标 (mouse)
// 鼠标点击事件(onmousedown)canvas.onmousedown = function (e) {
let x = e.offsetX;
let y = e.offsetY;
painting = true;
if (EraserEnabled) {ctx.clearRect(x - 15, y - 15, 30, 30)
}
startPoint = {x: x, y: y};
};
// 滑动鼠标
// 鼠标滑动事件(onmousemove)canvas.onmousemove = function (e) {
let x = e.offsetX;
let y = e.offsetY;
let newPoint = {x: x, y: y};
if (painting) {if (EraserEnabled) {ctx.clearRect(x - 15, y - 15, 30, 30)
} else {drawLine(startPoint.x, startPoint.y, newPoint.x, newPoint.y);
}
startPoint = newPoint;
}
};
// 松开鼠标
// 鼠标松开事件(onmouseup)
canvas.onmouseup = function () {painting = false;};
}
/***** 工具函数 *****/
// 点与点之间连接
function drawLine(xStart, yStart, xEnd, yEnd) {
// 开始绘制路径
ctx.beginPath();
// 线宽
ctx.lineWidth = 2;
// 起始位置
ctx.moveTo(xStart, yStart);
// 停止位置
ctx.lineTo(xEnd, yEnd);
// 描绘线路
ctx.stroke();
// 结束绘制
ctx.closePath();}
// canvas 与屏幕宽高一致
function wh() {
let pageWidth = document.documentElement.clientWidth;
let pageHeight = document.documentElement.clientHeight;
canvas.width = pageWidth;
canvas.height = pageHeight;
}
// 控制橡皮擦开启
let EraserEnabled = false;
eraser.onclick = function () {
EraserEnabled = true;
eraser.classList.add('active');
brush.classList.remove('active');
canvas.classList.add('xiangpica');
};
brush.onclick = function () {
EraserEnabled = false;
brush.classList.add('active');
eraser.classList.remove('active');
canvas.classList.remove('xiangpica');
};
// 清屏
clear.onclick = function() {
ctx.fillStyle = '#C5C5C5';
ctx.fillRect(0,0,canvas.width,canvas.height);
};
// 保存
save.onclick = function() {let url = canvas.toDataURL('image/jpg');
let a = document.createElement('a');
document.body.appendChild(a);
a.href = url;
a.download = '草稿纸';
a.target = '_blank';
a.click()};
// 画笔颜色及鼠标样式
black.onclick = function () {
ctx.strokeStyle = 'black';
canvas.classList.add('cursor1');
canvas.classList.remove('cursor2');
canvas.classList.remove('cursor3');
canvas.classList.remove('cursor4');
canvas.classList.remove('cursor5');
canvas.classList.remove('xiangpica');
EraserEnabled = false;
eraser.classList.remove('active');
};
red.onclick = function () {
ctx.strokeStyle = 'red';
canvas.classList.add('cursor2');
canvas.classList.remove('cursor1');
canvas.classList.remove('cursor3');
canvas.classList.remove('cursor4');
canvas.classList.remove('cursor5');
canvas.classList.remove('xiangpica');
EraserEnabled = false;
eraser.classList.remove('active');
};
orange.onclick = function () {
ctx.strokeStyle = 'orange';
canvas.classList.add('cursor3');
canvas.classList.remove('cursor2');
canvas.classList.remove('cursor1');
canvas.classList.remove('cursor4');
canvas.classList.remove('cursor5');
canvas.classList.remove('xiangpica');
EraserEnabled = false;
eraser.classList.remove('active');
};
green.onclick = function () {
ctx.strokeStyle = 'green';
canvas.classList.add('cursor4');
canvas.classList.remove('cursor2');
canvas.classList.remove('cursor3');
canvas.classList.remove('cursor1');
canvas.classList.remove('cursor5');
canvas.classList.remove('xiangpica');
EraserEnabled = false;
eraser.classList.remove('active');
};
blue.onclick = function () {
ctx.strokeStyle = 'blueviolet';
canvas.classList.add('cursor5');
canvas.classList.remove('cursor2');
canvas.classList.remove('cursor3');
canvas.classList.remove('cursor4');
canvas.classList.remove('cursor1');
canvas.classList.remove('xiangpica');
EraserEnabled = false;
eraser.classList.remove('active');
};
html 代码:
-
-
-
-
-
假如要新增历史回退,两种思路
1、记录 path,回退就是回退 path
2、记录 imageData, 回退是回退 imageData
方案一:记录 imageData 的方式是:
ctx.getImageData(0, 0, canvas.width, canvas.height)
把这些 push 到历史里面,每次回退拿到相应的画面。回退就可以
ctx.putImageData(putImage, 0, 0)
问题
橡皮假如把背景擦除,那么橡皮的颜色改成背景颜色可以解决这个问题。
画布放到缩小之后,画板里面的内容不变,如何操作?
function scaleImageData(imageData, scale, outCtx) {var scaled = outCtx.createImageData(imageData.width * scale, imageData.height * scale)
outCtx.imageSmoothingEnabled = true
for (var row = 0; row
通过上面方法进行图片数据的缩放。
关于具体的,我之前有篇文章,大家可以参考一下:https://www.haorooms.com/post/canvas_getimagedata
getImageData 可以改变 canvas 一些数据。
react-native-canvas 画板
react-native-canvas 画板其实本质也是用了 react-native-webview,canvas 的 api 和 h5 基本一致,但是使用下来有几个注意点
1、canvas 不支持监听手势,需要外层包一层 View, 然后在 View 上面添加手势,进行监听位置移动,再来绘制画板
2、历史回退假如存放 image 数组的话,会很卡,需要利用 path 形式存放历史记录。
例如如下代码
{paths.length === 0 ? 让大家猜猜你画的啥 : null}
如下方法里面进行手势监听
const panResponder = useRef(
PanResponder.create({onStartShouldSetPanResponder: () => true,
onPanResponderGrant: moveStart,
onPanResponderMove: listenToUser,
onPanResponderRelease: () => {painting = false}
})
).current
通过如下方式记录 path
const {locationX, locationY} = event.nativeEvent
每一步记录一下。
moveStart 函数里记录
let _path = [...getPaths(), {x, y, isStart: true, color: getInitColor(), width: getWidth(), clear: getClear() }]
setPaths(_path)
移动的时候记录
let _path = [...getPaths(), {x, y, isStart: false, ...data}]
setPaths(_path)
历史回退可以通过如下方案
const deleteLastIndexBack = (data) => {if (!data || data.length === 0) {return []
}
const lastIndex = data.findLastIndex((item) => item.isStart === true)
const result = lastIndex !== -1 ? data.slice(0, lastIndex) : data
return result
}
过滤掉 isStart==false 及最后一个 true
react-native-canvas 注意点
另外的注意点就是 canvas 的写法必须完全符合规定,开始一定是 ctx.beginPath(),假如漏掉或者不规范,就不行,这点在 html5 里面不太一样。