Малювання на зображенні в Canvas за допомогою React

Я радий провести вас через створення цікавого маленького проєкту з React.

Ми завантажимо зображення на канвас, а потім дозволимо вам розкрити вашу творчість, малюючи на ньому. Крок за кроком ми налаштуємо канвас, опрацюємо події малювання та будемо відслідковувати вашу роботу. Тож беріть чашку кави (або ваш улюблений напій) і давайте зануримося у світ магії React і канваса!

pic

Передумови (Що ви повинні знати перед тим, як почати цей посібник)

  1. NodeJS.
  2. Як створювати React застосунки.
  3. Трішки CSS (я знаю, це може бути складно).

Нарешті, ми готові почати.

Налаштування компонента React

Спершу імпортуємо кілька речей.

import React, { useRef, useState, useEffect } from 'react';

Ми використовуємо 3 типи хука React. useRef() для посилання на поточний канвас, useState() для збереження поточних змінних стану, useEffect() для виклику певних функцій щоразу, коли відбуваються зміни в певних змінних стану.

Це дуже популярні хуки React. Ви можете знайти більше інформації про них в Інтернеті.

Давайте перейдемо до наступного кроку.

Створення компонента React

Давайте назвемо наш компонент React "DrawingCanvas". Краще дотримуватись стилю camelCase.

const DrawingCanvas = ({ imageSrc }) => {  


}

Створення змінних стану

Від цього моменту додаємо все всередину функції компонента.

const canvasRef = useRef(null);  
 const [ctx, setCtx] = useState(null);  
 const [isDrawing, setIsDrawing] = useState(false);  
 const [points, setPoints] = useState([]);  
 const [allStrokes, setAllStrokes] = useState([]);  
 const [currentPoints, setCurrentPoints] = useState([]);  
 const lastPosRef = useRef({ x: 0, y: 0 });  
 const [penColor, setPenColor] = useState("blue");  
 const [penWidth, setPenWidth] = useState(5);  
 const [originalImageData, setOriginalImageData] = useState(null);

Це список змінних стану, які ми будемо використовувати в нашому коді. Більшість змінних самі за себе говорять.

Створення необхідних функцій для обробки завантаження зображення та подій вказівника/дотику

const loadImageCanvas = (path) => {  
 const canvas = canvasRef.current;  
 const context = canvas.getContext("2d");  
 setCtx(context);  

 const image = new Image();  
 image.crossOrigin = "anonymous";  
 image.src = path;  

 image.onload = () => {  
 canvas.width = image.width;  
 canvas.height = image.height;  
 context.drawImage(image, 0, 0);  
 const baseImageData = canvas.toDataURL("image/png");  
 setOriginalImageData(baseImageData);  
 };  
 };

Ви можете передати будь-який шлях до зображення path в цю функцію, щоб завантажити його на канвас.
Ви навіть можете передавати URL-адреси, щоб завантажити зображення в канвас (не забудьте встановити властивість crossOrigin в значення ‘anonymous’, щоб уникнути проблем з CORS (повірте, це дійсно дратує)).

const getCanvasCoords = (e) => {  
 let clientX, clientY;  
 if (e.touches && e.touches.length > 0) {  
 clientX = e.touches[0].clientX;  
 clientY = e.touches[0].clientY;  
 } else {  
 clientX = e.clientX;  
 clientY = e.clientY;  
 }  
 const canvas = canvasRef.current;  
 const rect = canvas.getBoundingClientRect();  
 const scaleX = canvas.width / rect.width;  
 const scaleY = canvas.height / rect.height;  

 return {  
 x: (clientX - rect.left) * scaleX,  
 y: (clientY - rect.top) * scaleY,  
 };  
 };  

 const handlePointerDown = (e) => {  
 e.preventDefault();  
 if (!ctx) return;  
 setIsDrawing(true);  
 const { x, y } = getCanvasCoords(e);  
 lastPosRef.current = { x, y };  
 };  

 const handlePointerMove = (e) => {  
 e.preventDefault();  
 if (!ctx || !isDrawing) return;  

 const { x, y } = getCanvasCoords(e);  
 setCurrentPoints((prev) => [...prev, { x, y }]);  

 const prevPoints = currentPoints;  
 if (prevPoints.length < 1) return;  

 const last = prevPoints[prevPoints.length - 1];  

 ctx.beginPath();  
 ctx.moveTo(last.x, last.y);  
 ctx.lineTo(x, y);  
 ctx.strokeStyle = penColor;  
 ctx.lineWidth = penWidth;  
 ctx.lineCap = "round";  
 ctx.lineJoin = "round";  
 ctx.globalCompositeOperation = "source-over";  
 ctx.stroke();  
 };  

 const handlePointerUp = (e) => {  
 e.preventDefault();  
 setIsDrawing(false);  
 setAllStrokes((prev) => [...prev, currentPoints]);  
 setCurrentPoints([]);  
 };  

 useEffect(() => {  
 if (imageSrc) {  
 loadImageCanvas(imageSrc);  
 }  
 }, [imageSrc]);

Тепер це інші функції обробки подій, необхідні для малювання на канвасі.

Зосередьтесь на функції getCanvasCoords(). Вона встановлює координати x та y в залежності від типу події, чи це подія дотику (touch event), чи подія вказівника (pointer event), що дуже важливо для сенсорних екранів.

useEffect() в кінці оновлює канвас щоразу, коли змінюється джерело зображення.

Інші функції в основному виконують обчислення для збереження поточних значень точок і відображення їх на канвасі.

Створення функції повернення

Цей крок буде дуже легким для фронтендерів, які вже добре знайомі з хаосом CSS.

return (  

    ); ```  Вірю, що CSS на даному етапі зрозумілий вам. Зосередьтеся на обробці подій миші та дотику. Нам потрібно обробляти кожну з цих подій окремо в нашому коді. Ви також можете додати деякі кнопки для зміни ширини та кольору пензля, налаштовуючи відповідні змінні стану.  `touchAction: “none”` забороняє прокручування сторінки, коли вона відкрита на сенсорному пристрої. Тому це дуже важливо не забути.

## Фінальний код

Ми завершили кодування для цього компонента.
Ось весь код, якщо вам потрібно його просто скопіювати та вставити (як ви, ймовірно, це й зробите).

import React, { useRef, useState, useEffect } from 'react';

const DrawingCanvas = ({ imageSrc }) => {
const canvasRef = useRef(null);
const [ctx, setCtx] = useState(null);
const [isDrawing, setIsDrawing] = useState(false);
const [points, setPoints] = useState([]);
const [allStrokes, setAllStrokes] = useState([]);
const [currentPoints, setCurrentPoints] = useState([]);
const lastPosRef = useRef({ x: 0, y: 0 });
const [penColor, setPenColor] = useState("blue");
const [penWidth, setPenWidth] = useState(5);
const [originalImageData, setOriginalImageData] = useState(null);

const loadImageCanvas = (base64) => {
const canvas = canvasRef.current;
const context = canvas.getContext("2d");
setCtx(context);

const image = new Image();
image.crossOrigin = "anonymous";
image.src = base64;

image.onload = () => {
canvas.width = image.width;
canvas.height = image.height;
context.drawImage(image, 0, 0);
const baseImageData = canvas.toDataURL("image/png");
setOriginalImageData(baseImageData);
};
};

const getCanvasCoords = (e) => {
let clientX, clientY;
if (e.touches && e.touches.length > 0) {
clientX = e.touches[0].clientX;
clientY = e.touches[0].clientY;
} else {
clientX = e.clientX;
clientY = e.clientY;
}
const canvas = canvasRef.current;
const rect = canvas.getBoundingClientRect();
const scaleX = canvas.width / rect.width;
const scaleY = canvas.height / rect.height;

return {
x: (clientX - rect.left) * scaleX,
y: (clientY - rect.top) * scaleY,
};
};

const handlePointerDown = (e) => {
e.preventDefault();
if (!ctx) return;
setIsDrawing(true);
const { x, y } = getCanvasCoords(e);
lastPosRef.current = { x, y };
};

const handlePointerMove = (e) => {
e.preventDefault();
if (!ctx || !isDrawing) return;

const { x, y } = getCanvasCoords(e);
setCurrentPoints((prev) => [...prev, { x, y }]);

const prevPoints = currentPoints;
if (prevPoints.length < 1) return;

const last = prevPoints[prevPoints.length - 1];

ctx.beginPath();
ctx.moveTo(last.x, last.y);
ctx.lineTo(x, y);
ctx.strokeStyle = penColor;
ctx.lineWidth = penWidth;
ctx.lineCap = "round";
ctx.lineJoin = "round";
ctx.globalCompositeOperation = "source-over";
ctx.stroke();
};

const handlePointerUp = (e) => {
e.preventDefault();
setIsDrawing(false);
setAllStrokes((prev) => [...prev, currentPoints]);
setCurrentPoints([]);
};

useEffect(() => {
if (imageSrc) {
loadImageCanvas(imageSrc);
}
}, [imageSrc]);

return (

);   };      

export default DrawingCanvas;
```

Можна додати кнопку для збереження відредагованого зображення. (Це вже окремий пост.)

Сподіваюся, що я допоміг вирішити маленьку проблему вашого щоденного кодування. Успіхів у програмуванні! О, будь ласка, спробуйте самі запустити цей код. Він працює, як гарячий ніж по маслу.

Перекладено з: Drawing on an Image in a React Canvas

Leave a Reply

Your email address will not be published. Required fields are marked *