react-native实现画板功能

18,277次阅读
没有评论

共计 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 里面不太一样。

    正文完
     0
    Yojack
    版权声明:本篇文章由 Yojack 于1970-01-01发表,共计5516字。
    转载说明:
    1 本网站名称:优杰开发笔记
    2 本站永久网址:https://yojack.cn
    3 本网站的文章部分内容可能来源于网络,仅供大家学习与参考,如有侵权,请联系站长进行删除处理。
    4 本站一切资源不代表本站立场,并不代表本站赞同其观点和对其真实性负责。
    5 本站所有内容均可转载及分享, 但请注明出处
    6 我们始终尊重原创作者的版权,所有文章在发布时,均尽可能注明出处与作者。
    7 站长邮箱:laylwenl@gmail.com
    评论(没有评论)