2

react-native实现画板功能

 8 months ago
source link: https://www.haorooms.com/post/react-native-canvas
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

react-native实现画板功能

2023年12月16日 383次浏览

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK