前言
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代码:
<canvas id="canvas" class="cursor1" width="500" height="500"></canvas>
<div id="actions" class="actions">
<svg id="brush" class="icon active">
<use xlink:href="#icon-pencil"></use>
</svg>
<svg id="eraser" class="icon">
<use xlink:href="#icon-xiangpica2"></use>
</svg>
<svg id="save" class="icon">
<use xlink:href="#icon-xiazai"></use>
</svg>
<svg id="clear" class="icon">
<use xlink:href="#icon-delete"></use>
</svg>
</div>
<ol class="colors">
<li id="black" class="black"></li>
<li id="red" class="red"></li>
<li id="orange" class="orange"></li>
<li id="green" class="green"></li>
<li id="blue" class="blue"></li>
</ol>
<script src="canvas-demo.js"></script>
假如要新增历史回退,两种思路
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 < imageData.height; row++) {
for (var col = 0; col < imageData.width; col++) {
var sourcePixel = [imageData.data[(row * imageData.width + col) * 4 + 0], imageData.data[(row * imageData.width + col) * 4 + 1], imageData.data[(row * imageData.width + col) * 4 + 2], imageData.data[(row * imageData.width + col) * 4 + 3]]
for (var y = 0; y < scale; y++) {
var destRow = Math.floor(row * scale) + y
for (var x = 0; x < scale; x++) {
var destCol = Math.floor(col * scale) + x
for (var i = 0; i < 4; i++) {
scaled.data[(destRow * scaled.width + destCol) * 4 + i] = sourcePixel[i]
}
}
}
}
}
return scaled
}
通过上面方法进行图片数据的缩放。
关于具体的,我之前有篇文章,大家可以参考一下: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形式存放历史记录。
例如如下代码
<View {...panResponder.panHandlers} style={{ width: wrapWidth, height: wrapWidth }}>
{paths.length === 0 ? <Text className={Style.toptips}>让大家猜猜你画的啥</Text> : null}
<Canvas style={{ width: wrapWidth, height: wrapWidth }} ref={canvasRef}></Canvas>
</View>
如下方法里面进行手势监听
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里面不太一样。