๋ฐ˜์‘ํ˜•

์บ”๋ฒ„์Šค ๊ธฐ๋ณธ ์„ธํŒ… for React


๐Ÿ’ก ์บ”๋ฒ„์Šค๋Š” HTML ์š”์†Œ ์ค‘ ํ•˜๋‚˜๋กœ ์Šคํฌ๋ฆฝํŠธ ์–ธ์–ด๋กœ ๊ทธ๋ฆผ์„ ๊ทธ๋ฆฌ๋Š”๋ฐ ์‚ฌ์šฉํ•œ๋‹ค.

 

canvas๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„  ์บ”๋ฒ„์Šค context์— DOM์œผ๋กœ ์ ‘๊ทผํ•ด์•ผ ํ•œ๋‹ค. React์—์„  useRef()๋ฅผ ์‚ฌ์šฉํ•ด์„œ Ref ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค๊ณ  ์ ‘๊ทผํ•˜๊ณ  ์‹ถ์€ DOM์˜ ref ๊ฐ’์œผ๋กœ ์„ค์ •ํ•ด์ฃผ๋ฉด ๋œ๋‹ค. ๊ทธ๋Ÿผ Ref ๊ฐ์ฒด์˜ .current ๊ฐ’์€ ํ•ด๋‹น DOM์„ ๊ฐ€๋ฆฌํ‚จ๋‹ค.

 

์บ”๋ฒ„์Šค ์—˜๋ฆฌ๋จผํŠธ ์ƒ์„ฑ ๋ฐ useRef ์„ค์ •

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

const Canvas = () => {
  const canvasRef = useRef(null);

  // ์ƒ๋žต
  return <DrawingArea ref={canvasRef} />;
};

/* ----- ์Šคํƒ€์ผ ์˜์—ญ ----- */
const DrawingArea = styled.canvas`
  // ์ƒ๋žต
`;

 

์บ”๋ฒ„์Šค ์‚ฌ์ด์ฆˆ ์„ค์ • ๋ฐ ๋“œ๋กœ์ž‰ ์ปจํ…์ŠคํŠธ ์ฐธ์กฐ

canvasRef์˜ ๊ธฐ๋ณธ๊ฐ’์€ null์ด๊ธฐ ๋•Œ๋ฌธ์— ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋งˆ์šดํŠธ ๋œ ํ›„ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•˜๋„๋ก useEffect ์•ˆ์—๋‹ค ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•œ๋‹ค.

 

์•„์ง ๊ฒฝํ—˜ํ•˜์ง„ ๋ชปํ–ˆ์ง€๋งŒ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ Œ๋”๋˜๊ณ  ๊ฐ€๋” useRef ์ฐธ์กฐ๊ฐ’์ด null์ธ ๊ฒฝ์šฐ๊ฐ€ ์žˆ๋‹ค๊ณ . useState๋ฅผ ํ™œ์šฉํ•ด ๋“œ๋กœ์ž‰ ์ปจํ…์ŠคํŠธ๋ฅผ ์ƒํƒœ๋กœ ์ €์žฅํ•˜๋ฉด ์ด๋Ÿฐ ๊ฒฝ์šฐ๋ฅผ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค๊ณ  ํ•จ.

const Canvas = () => {
  const canvasRef = useRef(null);
  const [ctx, setCtx] = useState();

  useEffect(() => {
    // ์บ”๋ฒ„์Šค ์‚ฌ์ด์ฆˆ ์„ค์ •
    const canvas = canvasRef.current;
    canvas.width = window.innerHeight; // ํ˜น์€ ์›ํ•˜๋Š” ์‚ฌ์ด์ฆˆ px์—†์ด ์ˆซ์ž๋งŒ ์ž…๋ ฅ
    canvas.height = window.innerWidth; // ํ˜น์€ ์›ํ•˜๋Š” ์‚ฌ์ด์ฆˆ px์—†์ด ์ˆซ์ž๋งŒ ์ž…๋ ฅ

    // ์บ”๋ฒ„์Šค context ์ ‘๊ทผ ํ›„ ๊ธฐ๋ณธ ์„ค์ •
    const context = canvas.getContext('2d'); // "๊ทธ๋ฆฌ๊ธฐ ๋ฉ”์„œ๋“œ์™€ ์†์„ฑ์„ ๊ฐ–๋Š”" 2์ฐจ์› ๋“œ๋กœ์ž‰ ์ปจํ…์ŠคํŠธ ์ฐธ์กฐ
    context.strokeStyle = 'black'; // line ์ƒ‰
    context.lineWidth = 3; // line ๊ตต๊ธฐ
    context.lineJoin = 'round'; // ์„  ์—ฐ๊ฒฐ ๋ชจ์–‘(๊ธฐ๋ณธ ๊ฐ’ miter ์ผ๋ฐ˜ ๋ชจ์–‘)
    context.lineCap = 'round'; // ์„  ๋ ๋ชจ์–‘
    context.save(); // ๋“œ๋กœ์ž‰ ์ปจํ…์ŠคํŠธ ์„ค์ • ์ €์žฅ
    setCtx(context); // ์„ค์ •ํ•œ ๋“œ๋กœ์ž‰ ์ปจํ…์ŠคํŠธ๋ฅผ ์ƒํƒœ๋กœ ์ €์žฅ
  }, []);
};

 

๋งˆ์šฐ์Šค ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ ์„ค์ •

๐Ÿ’ก offset ์ขŒํ‘œ๋Š” ์ด๋ฒคํŠธ๊ฐ€ ๊ฑธ๋ ค ์žˆ๋Š” DOM ๊ฐ์ฒด๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์ขŒํ‘œ๋ฅผ ์ถœ๋ ฅํ•œ๋‹ค. canvas ์š”์†Œ์— ์ด๋ฒคํŠธ๋ฅผ ๊ฑธ์—ˆ๋‹ค๋ฉด ๋ธŒ๋ผ์šฐ์ € ํ™”๋ฉด์ด ์•„๋‹Œ canvas ์š”์†Œ๊ฐ€ ๊ธฐ์ค€์ด ๋œ๋‹ค. offset ์ขŒํ‘œ์˜ ๊ธฐ๋ณธ๊ฐ’์€ ์™ผ์ชฝ ์ƒ๋‹จ (0, 0)

 

์บ”๋ฒ„์Šค์—์„œ ๋งˆ์šฐ์Šค๋กœ ๊ทธ๋ฆผ์„ ๊ทธ๋ฆด ๋•Œ ์ด๋ฒคํŠธ ๋Œ€์ƒ์˜ x(๊ฐ€๋กœ), y(์„ธ๋กœ) ์ขŒํ‘œ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” offsetX offsetY๊ฐ€ ํ•„์š”ํ•˜๋‹ค. ๋ฆฌ์•กํŠธ์—์„  ์„ฑ๋Šฅ ์ตœ์ ํ™”๋ฅผ ์œ„ํ•ด ๋ž˜ํ•‘๋œ ํ•ฉ์„ฑ ์ด๋ฒคํŠธ(Synthetic Event)๋ฅผ ์ „๋‹ฌํ•˜๋ฉฐ ์žฌ์‚ฌ์šฉ๋œ๋‹ค.

 

ํ•˜์ง€๋งŒ ์ด ํ•ฉ์„ฑ ์ด๋ฒคํŠธ์—์„  offset ์ขŒํ‘œ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋‹ค. ํ•ธ๋“ค๋Ÿฌ์—์„œ ์ด๋ฒคํŠธ ๊ฐ์ฒด๋ฅผ ์ฝ˜์†”๋กœ ์ฐ์–ด๋ณด๋ฉด offsetX, offsetY๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†๋‹ค. — ์ฐธ๊ณ  ๊ธ€

 

๋ฆฌ์•กํŠธ์—์„œ offset ์ขŒํ‘œ๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๋ฉด event.nativeEvent๋ฅผ ํ†ตํ•ด ๋ธŒ๋ผ์šฐ์ €์˜ ๊ณ ์œ  ์ด๋ฒคํŠธ์— ์ ‘๊ทผํ•ด์•ผ ํ•œ๋‹ค. ๋งค๊ฐœ๋ณ€์ˆ˜ ๊ตฌ์กฐ๋ถ„ํ•ด๋ฅผ ์ด์šฉํ•ด ({ nativeEvent })์ด๋Ÿฐ์‹์œผ๋กœ ๋ช…์‹œํ•˜๋ฉด ์‚ฌ์šฉํ•˜๊ธฐ ํŽธํ•˜๋‹ค.

const Canvas = () => {
  const [isDrawing, setIsDrawing] = useState(false); // ๊ทธ๋ฆฌ๊ธฐ ์ƒํƒœ ์„ค์ •
  const [path, setPath2D] = useState(new Path2D()); // path ๊ฐ์ฒด ์ƒ์„ฑ

  const startDrawing = ({ nativeEvent }) => {};
  const drawing = ({ nativeEvent }) => {};
  const finishDrawing = ({ nativeEvent }) => {};

  return (
    <DrawingArea
      ref={canvasRef}
      onMouseDown={startDrawing}
      onMouseUp={finishDrawing}
      onMouseMove={drawing}
      // onMouseLeave={finishDrawing} // ๋งˆ์šฐ์Šค๊ฐ€ ์ด๋ฒคํŠธ ์˜์—ญ ๋ฒ—์–ด๋‚ฌ์„ ๋•Œ
    />
  );
};

 

  1. ์บ”๋ฒ„์Šค ์œ„์—์„œ ๋งˆ์šฐ์Šค๋ฅผ ์›€์ง์ด๋ฉด ์‹œ์ž‘์ ์„ ๋งˆ์šฐ์Šค๊ฐ€ ์œ„์น˜ํ•œ ์ขŒํ‘œ๋กœ ์ด๋™
    ctx.moveTo(x, y)
  2. ๋งˆ์šฐ์Šค๋ฅผ ํด๋ฆญํ•˜๋ฉด ๊ทธ๋ฆฌ๊ธฐ ์ƒํƒœ๋กค true๋กœ ๋ณ€๊ฒฝํ•˜๊ณ 
  3. ํด๋ฆญํ•œ ์ƒํƒœ์—์„œ ๋งˆ์šฐ์Šค๋ฅผ ๋“œ๋ž˜๊ทธํ•˜๋ฉด ์„ ์„ ๊ทธ๋ฆฐ๋‹ค.
    ctx.lineTo(x, y) ctx.stroke(x, y)
  4. ํด๋ฆญ ๋ฒ„ํŠผ์„ ๋–ผ๋ฉด ๊ทธ๋ฆฌ๊ธฐ ์ƒํƒœ๋ฅผ false๋กœ ๋ณ€๊ฒฝํ•˜๊ณ  path๋ฅผ ๋‹ซ๊ณ , ๋งˆ์ง€๋ง‰ path๊นŒ์ง€ ์„ ์„ ๊ทธ๋ฆฐ๋‹ค.
    ctx.closePath(x, y) ctx.stroke(x, y)

 

๊ทธ๋ฆฌ๊ธฐ ์‹œ์ž‘ startDrawing

์บ”๋ฒ„์Šค์—์„œ ๋งˆ์šฐ์Šค๋ฅผ ํด๋ฆญํ•˜๋ฉด ๊ทธ๋ฆฌ๊ธฐ ์ƒํƒœ๋ฅผ true๋กœ ๋ณ€๊ฒฝํ•œ๋‹ค

// ๋งˆ์šฐ์Šค๋ฅผ ํด๋ฆญํ•˜๋ฉด ์‹คํ–‰๋˜๋Š” ํ•ธ๋“ค๋Ÿฌ
const startDrawing = ({ nativeEvent }) => {
  const { offsetX, offsetY } = nativeEvent;
  setIsDrawing(true); // ๊ทธ๋ฆฌ๊ธฐ ์ƒํƒœ ๋ณ€๊ฒฝ
  console.log('start offset: ', offsetX, offsetY);
};

 

๊ทธ๋ฆฌ๋Š” ์ค‘ drawing

์บ”๋ฒ„์Šค์—์„œ ๋งˆ์šฐ์Šค๋ฅผ ํด๋ฆญํ•˜์ง€ ์•Š๊ณ  ์›€์ง์ด๊ธฐ๋งŒ ํ•˜๋ฉด ๋งˆ์šฐ์Šค๊ฐ€ ์œ„์น˜ํ•œ ์ขŒํ‘œ๋กœ ์‹œ์ž‘์ ์„ ์ด๋™ํ•œ๋‹ค. ๋งˆ์šฐ์Šค๋ฅผ ํด๋ฆญํ•œ ํ›„(startDrawing ํ•ธ๋“ค๋Ÿฌ ํ˜ธ์ถœ → ๊ทธ๋ฆฌ๊ธฐ ์ƒํƒœ true๋กœ ๋ณ€๊ฒฝ) ๋“œ๋ž˜๊ทธํ•˜๋ฉด ์‹œ์ž‘์ ๋ถ€ํ„ฐ ํ˜„์žฌ ๋งˆ์šฐ์Šค ์ขŒํ‘œ๊นŒ์ง€ ์„ ์„ ๊ธ‹๋Š”๋‹ค.

// ๋งˆ์šฐ์Šค๋ฅผ ์บ”๋ฒ„์Šค ์œ„์—์„œ ์›€์ง์ด๋ฉด(ํ˜น์€ ๋“œ๋ž˜๊ทธ) ์‹คํ–‰๋˜๋Š” ํ•ธ๋“ค๋Ÿฌ
const drawing = ({ nativeEvent }) => {
  // offsetX, offsetY๋Š” ์ด๋ฒคํŠธ๊ฐ€ ๊ฑธ๋ ค์žˆ๋Š” DOM ๊ฐ์ฒด๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์ขŒํ‘œ ์ถœ๋ ฅ
  const { offsetX, offsetY } = nativeEvent;
  if (ctx) {
    if (!isDrawing) {
      // ํด๋ฆญํ•˜์ง€ ์•Š์€ ์ƒํƒœ๋ผ๋ฉด
      path.moveTo(offsetX, offsetY); // ์‹œ์ž‘์  ์„ค์ •
    } else {
      // ํด๋ฆญํ•œ ์ƒํƒœ๋ผ๋ฉด
      path.lineTo(offsetX, offsetY); // ์‹œ์ž‘์ ์—์„œ(ํ˜น์€ ํ˜„์žฌ ์ขŒํ‘œ) ์ด์–ด์งˆ ๋ถ€๋ถ„ ์„ค์ •
      ctx.stroke(path); // ์„  ๊ธ‹๊ธฐ(ํ™”๋ฉด ์ถœ๋ ฅ)
    }
  }
};

 

๊ทธ๋ฆฌ๊ธฐ ์ข…๋ฃŒ finishDrawing

๋งˆ์šฐ์Šค ํด๋ฆญ(onMouseUp)์„ ๋–ผ๋ฉด finishDrawing ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ์‹คํ–‰๋˜๊ณ , ๊ทธ๋ฆฌ๊ธฐ ์ƒํƒœ๋Š” false๋กœ ๋ณ€๊ฒฝ๋œ๋‹ค. path๋Š” ๋‹ซํžˆ๋ฉด์„œ ์‹œ์ž‘์ ๊ณผ ๋์ ์„ ์ž๋™์œผ๋กœ ์—ฐ๊ฒฐํ•˜๊ณ , ๋‹ค์Œ ๊ทธ๋ฆด ๋„ํ˜•์„ ์œ„ํ•ด ์ƒˆ๋กœ์šด path ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•œ๋‹ค.

// ๋งˆ์šฐ์Šค ํด๋ฆญ์„ ํ•ด์ œํ•˜๋ฉด ์‹คํ–‰๋˜๋Š” ํ•ธ๋“ค๋Ÿฌ
const finishDrawing = () => {
  setIsDrawing(false); // ๊ทธ๋ฆฌ๊ธฐ ์ƒํƒœ ๋ณ€๊ฒฝ
  path.closePath(); // ์„  ์ข…๋ฃŒ ์„ ์–ธ(์‹œ์ž‘์ ๊ณผ ๋์ ์„ ์ž๋™์œผ๋กœ ์—ฐ๊ฒฐํ•œ๋‹ค)
  setPath2D(new Path2D()); // ์ƒˆ๋กœ์šด path ๊ฐ์ฒด ์ƒ์„ฑ
};

 

(๋‹ค๊ฐํ˜•)๋„ํ˜• ์ €์žฅ ๋ฐ ๋‹ค์‹œ ๊ทธ๋ฆฌ๊ธฐ

Path2D ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š์•˜์„ ๋•Œ

๋„ํ˜•๋งˆ๋‹ค ๋ชจ๋“  offset ์ขŒํ‘œ๋ฅผ ์ €์žฅํ•ด์„œ ํ•„์š” ์‹œ ํ•ด๋‹น ๋„ํ˜•์˜ ์ขŒํ‘œ๋ฅผ ์ด์šฉํ•ด ๋‹ค์‹œ ๊ทธ๋ฆด ์ˆ˜ ์žˆ๋‹ค. 1๊ฐœ ๋„ํ˜•์„ ๊ทธ๋ฆด ๋•Œ ๋งˆ๋‹ค ์ขŒํ‘œ ์ •๋ณด๋ฅผ ์ž„์‹œ๋กœ ์ €์žฅํ•˜๋Š” ์ƒํƒœ๋„ ํ•„์š”ํ•˜๋‹ค.

// Canvas.js
const Canvas = () => {
  // ์—ฌ๋Ÿฌ ๋„ํ˜•์˜ ์ขŒํ‘œ ์ •๋ณด๋ฅผ ์ €์žฅํ•˜๋Š” ์ƒํƒœ [[...], [...], [...]]
  const [savedPolygon, setSavedPolygon] = useState([]);
  // 1๊ฐœ ๋„ํ˜•์„ ๊ทธ๋ฆด ๋•Œ ์ขŒํ‘œ ์ •๋ณด๋ฅผ ์ž„์‹œ๋กœ ๋‹ด์•„๋‘๋Š” ์ƒํƒœ [...]
  const [drawingOffset, setDrawingOffset] = useState([]);

  const startDrawing = ({ nativeEvent }) => {
    const { offsetX, offsetY } = nativeEvent;
    // ์ƒ๋žต
    setDrawingOffset([offsetX, offsetY]);
  };

  const drawing = ({ nativeEvent }) => {
    const { offsetX, offsetY } = nativeEvent;
    if (ctx) {
      if (!isDrawing) {
        // ์ƒ๋žต(ํด๋ฆญํ•˜์ง€ ์•Š์€ ์ƒํƒœ์—์„œ ๋งˆ์šฐ์Šค ๋ฌด๋ธŒ)
      } else {
        // ์ƒ๋žต(ํด๋ฆญํ•œ ์ƒํƒœ์—์„œ ๋“œ๋ž˜๊ทธ)
        setDrawingOffset([...drawingOffset, offsetX, offsetY]);
      }
    }
  };

  const finishDrawing = () => {
    // ์ƒ๋žต
    setSavedPolygon([...savedPolygon, drawingOffset]); // ์™„์„ฑํ•œ ๋„ํ˜•์˜ ์ขŒํ‘œ ์ •๋ณด๋ฅผ ๋ฐฐ์—ด์— ์ €์žฅ
    setDrawingOffset([]); // ์ž„์‹œ ์ขŒํ‘œ ์ •๋ณด ์ƒํƒœ ์ดˆ๊ธฐํ™”
  };
};

 

// reDraw.js
export default (ctx, savedPolygon) => {
  savedPolygon.forEach((polygon) => {
    const copied = polygon.slice();

    ctx.beginPath();
    ctx.moveTo(copied.shift(), copied.shift());
    // shift/pop ๋ฉ”์†Œ๋“œ๋Š” ์ œ๊ฑฐํ•œ ์š”์†Œ๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

    while (copied.length) {
      ctx.lineTo(copied.shift(), copied.shift());
    }
    
    ctx.closePath();
    ctx.stroke();
  });
};

 

Path2D ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ–ˆ์„ ๋•Œ(๋” ๊ฐ„๋‹จํ•จ)

๊ฐ ๋„ํ˜•์˜ ์ขŒํ‘œ ์ •๋ณด๋ฅผ ์ €์žฅํ•  ์ƒํƒœ ๋ฐฐ์—ด์„ ๋งŒ๋“  ํ›„ ๋„ํ˜•์„ ์™„์„ฑํ•  ๋•Œ๋งˆ๋‹ค ์ด ์ƒํƒœ์— ์ €์žฅํ•œ๋‹ค. reDraw ํ•จ์ˆ˜์—์„  ctx์™€ ๊ฐ ๋„ํ˜•์˜ path ์ •๋ณด๊ฐ€ ๋‹ด๊ธด savedPolygon ๋ฐฐ์—ด์„ ๋ฐ›์•„์„œ ํ™”๋ฉด์— ๊ทธ๋ฆฐ๋‹ค. Path2D ์ธ์Šคํ„ด์Šค์— ๋‹ด๊ธด path ์ •๋ณด๋Š” ctx.stroke(path) ๋ช…๋ น์–ด ํ•œ ๋ฒˆ์œผ๋กœ ํ•ด๋‹น path์˜ ๋ชจ๋“  ์ขŒํ‘œ๋ฅผ ๊ทธ๋ ค์ฃผ๊ธฐ ๋•Œ๋ฌธ์— ๋” ๊น”๋”ํ•˜๋‹ค.

// Canvas.js
const Canvas = () => {
  const [savedPolygon, setSavedPolygon] = useState([]);
  // savedPolygon [{ path }, { path }, { path }]

  const finishDrawing = () => {
    // ์ƒ๋žต
    setSavedPolygon([...savedPolygon, path]); // ๊ทธ๋ฆฐ ๋„ํ˜• ์ƒํƒœ ๋ฐฐ์—ด์— ์ €์žฅ
  };
};

 

// reDraw.js
export default (ctx, savedPolygon) => {
  savedPolygon.forEach((path) => ctx.stroke(path));
};

 

๊ธฐํƒ€ ์ฐธ๊ณ  ๋‚ด์šฉ

  • ์บ”๋ฒ„์Šค ์‚ฌ์ด์ฆˆ ๊ธฐ๋ณธ๊ฐ’์€ width 300(px), height 150(px)
  • ์บ”๋ฒ„์Šค์—์„œ 1๋‹จ์œ„๋Š” 1ํ”ฝ์…€
  • ์œˆ๋„์šฐ ์‚ฌ์ด์ฆˆ ๊ด€๋ จ (์ฐธ๊ณ  ๋งํฌ)
    • window.innerWidth window.innerHeight
    • ์Šคํฌ๋กค๋ฐ”๊ฐ€ ์ฐจ์ง€ํ•˜๋Š” ๊ณต๊ฐ„ ํฌํ•จ
    • document.body.clientWidth/clientHeight (body)
      document.documentElement.clientWidth/clientHeight (html)
    • ์Šคํฌ๋กค๋ฐ”๊ฐ€ ์ฐจ์ง€ํ•˜๋Š” ๊ณต๊ฐ„ ์ œ์™ธ
    • getBoundingClientRect() ๋ฉ”์„œ๋“œ๋กœ ์—˜๋ฆฌ๋จผํŠธ์˜ ์‚ฌ์ด์ฆˆ ์ •๋ณด๋ฅผ ์ฐธ์กฐํ•  ์ˆ˜ ์žˆ๋‹ค.

 

๊ทธ๋ฆฌ๊ธฐ ๊ด€๋ จ


์บ”๋ฒ„์Šค์˜ ๊ธฐ๋ณธ ์ขŒ์ƒ๋‹จ ์ขŒํ‘œ๋Š”(์›์ ) 0, 0์ด๋‹ค. ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์•„๋‹Œ ์บ”๋ฒ„์Šค ์—˜๋ฆฌ๋จผํŠธ์˜ ์›์ ์ธ ๊ฒƒ ์ฃผ์˜. X์ถ•(๊ฐ€๋กœ)์€ ์˜ค๋ฅธ์ชฝ์œผ๋กœ, Y์ถ•(์„ธ๋กœ)์€ ์•„๋ž˜์ชฝ์œผ๋กœ ์ฆ๊ฐ€ํ•˜๋Š” ๋ฐ์นด๋ฅดํŠธ ์ขŒํ‘œ๊ณ„๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

 

์„  Stroke

๐Ÿ’ก `lineTo()`๋Š” ์ง์„ ์„ ๊ทธ์€ ํ›„ ํ˜„์žฌ ์ขŒํ‘œ๋ฅผ ๋์ ์œผ๋กœ ์˜ฎ๊ธด๋‹ค. `moveTo()`๋ฅผ ํ˜ธ์ถœํ•˜์ง€ ์•Š์•„๋„ `lineTo()`๋ฅผ ๊ณ„์† ํ˜ธ์ถœํ•˜์—ฌ ์ด์–ด์ง„ ๋‹ค๊ฐํ˜•์„ ๊ทธ๋ฆด ์ˆ˜ ์žˆ๋‹ค.

 

๊ธฐ๋ณธ์ ์œผ๋กœ ์บ”๋ฒ„์Šค์—์„œ ์„ ์„ ๊ทธ๋ฆฌ๋Š” ์ˆœ์„œ๋Š” ์•„๋ž˜์™€ ๊ฐ™๋‹ค. ์™ธ๊ฐ์„ ์„ ๊ทธ๋ฆฐ ํ›„ fill()์„ ์ด์šฉํ•ด ์•ˆ์ชฝ ์ƒ‰์ƒ์„ ์ฑ„์šธ ์ˆ˜๋„ ์žˆ๋‹ค. ์ด๋•Œ fillStyle = 'lightgray' ๋“ฑ์œผ๋กœ ์ƒ‰์ƒ์„ ๋ฏธ๋ฆฌ ์ง€์ •ํ•ด์ค˜์•ผ ํ•œ๋‹ค. fill()์„ ์‚ฌ์šฉํ•˜๋ฉด path๊ฐ€ ๋‹ซํ˜€์žˆ์ง€ ์•Š์•˜์„ ๊ฒฝ์šฐ ์ž๋™์œผ๋กœ ๋‹ซ์€ ํ›„ ์ฑ„์šด๋‹ค.

ctx.beginPath(); // ๋นˆ ๊ฒฝ๋กœ ์ƒ์„ฑ
ctx.moveTo(x, y); // ์‹œ์ž‘์  ์„ค์ •(์ขŒํ‘œ๋งŒ ์ด๋™)
ctx.lineTo(x, y); // ์‹œ์ž‘์ (ํ˜„์žฌ ์ขŒํ‘œ)์—์„œ x, y ์ขŒํ‘œ๊นŒ์ง€ ์„  ๊ธ‹๊ธฐ(์ขŒํ‘œ๋งŒ ๊ธฐ๋ก)
ctx.closePath(); // path๋ฅผ ๋‹ซ๋Š”๋‹ค(์‹œ์ž‘์ ๊ณผ ๋์  ์—ฐ๊ฒฐ)
ctx.stroke(); // ์™ธ๊ณฝ์„  "๊ทธ๋ฆฌ๊ธฐ"(ํ™”๋ฉด ์ถœ๋ ฅ)

 

Path2D ์ธ์Šคํ„ด์Šค ๊ฐ์ฒด๋ฅผ ์ƒˆ๋กœ ์ƒ์„ฑํ•˜์—ฌ ๊ทธ๋ฆฌ๋Š” ๋ฐฉ๋ฒ•๋„ ์žˆ๋‹ค. Path2D๋Š” ๋”ฐ๋กœ beginPath() ๋ฉ”์†Œ๋“œ๋ฅผ ๊ฐ–์ง€ ์•Š๋Š”๊ฑธ ๋ด์„  ๋นˆ ๊ฒฝ๋กœ๋ฅผ ์ˆ˜๋™์œผ๋กœ ์ƒ์„ฑํ•  ํ•„์š”๊ฐ€ ์—†๋‚˜๋ณด๋‹ค. Path2D ๊ฐ์ฒด๋ฅผ ์ด์šฉํ•ด ๊ทธ๋ฆฐ ์™ธ๊ณฝ์„ ์€ fill()์„ ์ด์šฉํ•ด ์•ˆ์ชฝ ์ƒ‰์ƒ์„ ์ฑ„์šธ ์ˆ˜ ์—†๋‹ค(์ฐธ๊ณ  ๋งํฌ)

const path = new Path2D() // ์ƒˆ๋กœ์šด Path ๊ฐ์ฒด ์ƒ์„ฑ
path.moveTo(x, y) // ์‹œ์ž‘์  ์„ค์ •(์ขŒํ‘œ๋งŒ ์ด๋™)
path.lineTo(x, y) // ์‹œ์ž‘์ (ํ˜„์žฌ ์ขŒํ‘œ)์—์„œ x, y ์ขŒํ‘œ๊นŒ์ง€ ์„  ๊ธ‹๊ธฐ(์ขŒํ‘œ๋งŒ ๊ธฐ๋ก)
path.closePath() // path๋ฅผ ๋‹ซ๋Š”๋‹ค(์‹œ์ž‘์ ๊ณผ ๋์  ์—ฐ๊ฒฐ)
ctx.stroke(path) // ์™ธ๊ณฝ์„  "๊ทธ๋ฆฌ๊ธฐ"(ํ™”๋ฉด ์ถœ๋ ฅ)

 

์„ ์„ ๊ทธ๋ฆด ๋•Œ ๋งˆ๋‹ค ๋งค๋ฒˆ ์œ„ ๊ฐ™์€ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๊ธฐ ๋ฒˆ๊ฑฐ๋กญ๊ธฐ ๋•Œ๋ฌธ์— ๋ณดํ†ต x, y ์ขŒํ‘œ๋ฅผ ๋ฐ›์•„ ์„ ์„ ๊ทธ๋ฆฌ๋Š” drawLine ๊ฐ™์€ ํ•จ์ˆ˜๋ฅผ ์„ ์–ธํ•ด์„œ ์‚ฌ์šฉํ•œ๋‹ค(๋“œ๋กœ์ž‰ ์ปจํ…์ŠคํŠธ๊ฐ€ ์ „์—ญ์ด ์•„๋‹ˆ์–ด์„œ ์ ‘๊ทผํ•  ์ˆ˜ ์—†๋‹ค๋ฉด ctx๋„ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ๋„˜๊ฒจ์•ผ ํ•œ๋‹ค)

// ์ฑ„์›Œ์ง€์ง€ ์•Š์€ ๋‹ค๊ฐํ˜• ๊ทธ๋ฆฌ๊ธฐ ์˜ˆ์ œ
ctx.beginPath();
ctx.moveTo(170, 110);
ctx.lineTo(220, 110);
ctx.lineTo(220, 160);
ctx.closePath();
ctx.stroke();

 

 

// ์ƒ‰์ƒ์ด ์ฑ„์›Œ์ง„ ๋‹ค๊ฐํ˜• ๊ทธ๋ฆฌ๊ธฐ ์˜ˆ์ œ
ctx.fillStyle = 'lightgray'; // ์ฑ„์šฐ๊ธฐ ์ƒ‰์ƒ ์ง€์ •
ctx.beginPath();
ctx.moveTo(170, 110);
ctx.lineTo(220, 110);
ctx.lineTo(220, 160);
ctx.closePath();
ctx.fill();

 

 

์‚ฌ๊ฐํ˜• Rect

x, y ์ขŒํ‘œ๋ฅผ ๋„˜๊ธด ๊ณณ ๋ถ€ํ„ฐ width, height์˜ ํฌ๊ธฐ๋ฅผ ๊ฐ€์ง€๋Š” ์‚ฌ๊ฐํ˜•์„ ๊ทธ๋ฆฐ๋‹ค. clearRect()๋„ ๊ฐ™์€ ์›๋ฆฌ๋กœ ์บ”๋ฒ„์Šค์— ๊ทธ๋ฆฐ ์‚ฌ๊ฐํ˜•์„ ์ง€์šฐ๋Š”๋ฐ ์บ”๋ฒ„์Šค์— ๊ทธ๋ฆฐ ๋ชจ๋“  ๋„ํ˜•์„ ์ง€์šฐ๊ณ  ์‹ถ์„ ๋•Œ ์œ ์šฉํ•˜๋‹ค. ์ด๋•Œ x, y๋Š” 0์„ width, height๋Š” ์บ”๋ฒ„์Šค์˜ ํฌ๊ธฐ๋ฅผ ๋„˜๊ธฐ๋ฉด ๋œ๋‹ค.

ctx.strokeRect(x, y, width, height); // ์ฑ„์›Œ์ง€์ง€ ์•Š์€ ์‚ฌ๊ฐํ˜•(์™ธ๊ณฝ์„ ๋งŒ ์žˆ๋Š”)
ctx.fillRect(x, y, width, height); // ์ฑ„์›Œ์ง„ ์‚ฌ๊ฐํ˜•
ctx.clearRect(x, y, width, height); // ์‚ฌ๊ฐํ˜• ์‚ญ์ œ

 

์› Arc

๊ธฐ๋ณธ ์‚ฌ์šฉ๋ฒ•

arc(x, y, r, st, ed, anticlockwise);
// x, y : ์›์˜ ์ค‘์‹ฌ์ 
// r : ๋ฐ˜์ง€๋ฆ„
// st, ed : ์‹œ์ž‘๊ฐ๊ณผ ๋๊ฐ(3์‹œ ๋ฐฉํ–ฅ์œผ๋กœ 0๋„ํ•˜์—ฌ ์‹œ๊ณ„ ๋ฐฉํ–ฅ์œผ๋กœ ์ฆ๊ฐ€ํ•˜๋Š” ๋ผ๋””์•ˆ ๊ฐ’)
// anticlockwise : ์›์„ ๊ทธ๋ฆฌ๋Š” ๋ฐฉํ–ฅ true๋Š” ๋ฐ˜์‹œ๊ณ„, false๋Š” ์‹œ๊ณ„(๊ธฐ๋ณธ๊ฐ’) ๋ฐฉํ–ฅ

 

์› ๊ทธ๋ฆฌ๊ธฐ ์˜ˆ์ œ. ์› ์ „์ฒด๋ฅผ ๊ทธ๋ฆฌ๋ฏ€๋กœ ์› ๋ฐฉํ–ฅ์€ ๋ช…์‹œํ•˜์ง€ ์•Š์•„๋„ ๋œ๋‹ค(์–ด๋Š ์ชฝ์ด๋“  ์› ์ „์ฒด๊ฐ€ ๊ทธ๋ ค์ง). 

ctx.beginPath();
ctx.arc(200, 200, 100, 0, 2 * Math.PI);
ctx.stroke();

 

์›์„ ๊ทธ๋ฆด ๋•Œ ์‹œ์ž‘/๋ ๊ฐ์€ ๋ผ๋””์•ˆ(ํ˜ธ๋„๋ฒ•; ๅผงๅบฆ) ๊ฐ’์ด๋‹ค. ๋ผ๋””์•ˆ์€ ๋ฐ˜์ง€๋ฆ„๊ณผ ํ˜ธ(ๅผง)์˜ ๊ธธ์ด๊ฐ€ ๊ฐ™์•„์ง€๋Š” ๊ฐ๋„๋ฅผ ์˜๋ฏธํ•˜๋Š”๋ฐ ์ผ๋ฐ˜์ ์œผ๋กœ ๋ผ๋””์•ˆ๋ณด๋‹จ ๊ฐ๋„°์— ์ต์ˆ™ํ•˜๋ฏ€๋กœ ๊ฐ๋„ * Math.PI/180์œผ๋กœ ๋Œ€์‹  ๊ณ„์‚ฐํ•  ์ˆ˜ ์žˆ๋‹ค.

 

  • 1 * Math.PI 180๋„
  • 2 * Math.PI 360๋„

 

 

๊ณก์„  / ํ˜ธ

์ž์„ธํ•œ ๋‚ด์šฉ์€ ํ•œ๊ธ€ ๋งํฌ ์ฐธ๊ณ .

quadraticCurveTo(cpx, cpy, x, y); // ๊ณก์„  ๊ทธ๋ฆฌ๊ธฐ
arcTo(x1, y1, x2, y2, radius); // ์ œ์–ด์  ์‚ฌ์ด์˜ ํ˜ธ/๊ณก์„  ๊ทธ๋ฆฌ๊ธฐ

 

ํ˜„์žฌ ์ขŒํ‘œ๊ฐ€ ๋„ํ˜•/์„  ์•ˆ์— ์žˆ๋Š”์ง€ ํ™•์ธ


 

ctx.isPointInPath([path], x, y)

์ธ์ž๋กœ ์ „๋‹ฌํ•œ x, y ์บ”๋ฒ„์Šค ์ขŒํ‘œ๊ฐ€ ๋„ํ˜•์˜ ์•ˆ์ชฝ ์˜์—ญ์— ์žˆ๋Š”์ง€ ํ™•์ธํ•œ ํ›„ Boolean ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค. Path2D ๊ฐ์ฒด๋ฅผ ์ด์šฉํ–ˆ๋‹ค๋ฉด ์ฒซ๋ฒˆ์งธ ์ธ์ž์— path๋ฅผ ๋„˜๊ฒจ์•ผ ํ•œ๋‹ค. 

 

์•„๋ž˜๋Š” ํด๋ฆญํ•œ ์ขŒํ‘œ๊ฐ€ ๋„ํ˜•์˜ ์•ˆ์ชฝ์ด๋ผ๋ฉด ํ•ด๋‹น ๋„ํ˜•์„ ์‚ญ์ œํ•˜๋Š” ์˜ˆ์ œ.

// onMouseDown ๋งˆ์šฐ์Šค ํด๋ฆญ ํ•ธ๋“ค๋Ÿฌ
const deleteHandler = ({ nativeEvent: e }) => {
  const x = e.offsetX;
  const y = e.offsetY;

  // ์ €์žฅํ•œ ๋„ํ˜• path๋ฅผ ํ•˜๋‚˜์”ฉ ๊ฒ€์‚ฌํ•œ๋‹ค
  savedPolygon.forEach((polygon, index) => {
    if (ctx.isPointInPath(polygon, x, y)) {
      const remaningPolygons = [
        ...savedPolygon.slice(0, index),
        ...savedPolygon.slice(index + 1),
      ];
      setSavedPolygon(remaningPolygons);
      reDraw(ctx, remaningPolygons, canvasSize);
    }
  });
};

 

ctx.isPointInStroke([path], x, y)

์ธ์ž๋กœ ์ „๋‹ฌํ•œ x, y ์บ”๋ฒ„์Šค ์ขŒํ‘œ๊ฐ€ Stroke ์˜์—ญ์˜ ์•ˆ์ชฝ์ธ์ง€ ํ™•์ธํ•œ ํ›„ Boolean ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.  Path2D ๊ฐ์ฒด๋ฅผ ์ด์šฉํ–ˆ๋‹ค๋ฉด ์ฒซ๋ฒˆ์งธ ์ธ์ž์— path๋ฅผ ๋„˜๊ฒจ์•ผ ํ•œ๋‹ค.

 

์•„๋ž˜๋Š” ์ขŒํ‘œ๊ฐ€ ์„  ์œ„์— ์žˆ๋‹ค๋ฉด ์ปค์„œ๊ฐ€ grab์œผ๋กœ ๋ฐ”๋€Œ๋Š” ์˜ˆ์ œ. 

// onMouseMove ๋งˆ์šฐ์Šค ์ด๋™ ํ•ธ๋“ค๋Ÿฌ
if (ctx.isPointInStroke(x, y)) {
  e.target.style.cursor = 'grab';
} else {
  e.target.style.cursor = '';
}

 

์บ”๋ฒ„์Šค ์ด๋ฏธ์ง€ ํฌ๋งท ๋ณ€ํ™˜ / ๋‹ค์šด๋กœ๋“œ / ๊ทธ๋ฆฌ๊ธฐ


์ด๋ฏธ์ง€ ๋‹ค์šด๋กœ๋“œ

์บ”๋ฒ„์Šค์— ๊ทธ๋ฆฐ ๋‚ด์šฉ๋“ค์„ canvas.toDataURL(type, option)์„ ์ด์šฉํ•ด ์›ํ•˜๋Š” ํฌ๋งท์œผ๋กœ ๋ณ€ํ™˜ํ•œ ํ›„ ๋‹ค์šด๋กœ๋“œํ•  ์ˆ˜ ์žˆ๋‹ค. type์—๋Š” ์ด๋ฏธ์ง€ ํฌ๋งท์„ ์ž…๋ ฅํ•˜๊ณ , option์—๋Š” 0๊ณผ 1์‚ฌ์ด์— ์ˆซ์ž๋ฅผ ์ž…๋ ฅํ•˜์—ฌ ์ด๋ฏธ์ง€์˜ ํ€„๋ฆฌํ‹ฐ๋ฅผ ์ง€์ •ํ•  ์ˆ˜ ์žˆ๋‹ค(๋†’์„ ์ˆ˜๋ก ์ข‹์€ ํ€„๋ฆฌํ‹ฐ). option ํŒŒ๋ผ๋ฏธํ„ฐ์— ์•„๋ฌด๊ฒƒ๋„ ์ž…๋ ฅํ•˜์ง€ ์•Š์œผ๋ฉด 0.92๊ฐ€ ๊ธฐ๋ณธ๊ฐ’

 

  • toDataURL('image/png') ๊ธฐ๋ณธ๊ฐ’(ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ๋”ฐ๋กœ ๋ช…์‹œํ•˜์ง€ ์•Š์œผ๋ฉด ๊ธฐ๋ณธ๊ฐ’)
  • "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAH0AAAB9C..."
  • toDataURL('image/webp')
  • toDataURL('image/jpg')

 

// ๋‹ค์šด๋กœ๋“œ ๋ฒ„ํŠผ ํด๋ฆญ์‹œ ์ด๋ฏธ์ง€ ๋‹ค์šด๋กœ๋“œ
export default (canvasRef) => {
  const image = canvasRef.current
    .toDataURL('image/png') // ๊ธฐ๋ณธ๊ฐ’
    .replace('image/png', 'image/octet-stream'); // 2์ง„ ๋ฐ์ดํ„ฐ ํ‘œํ˜„
  // window.location.href = image;
  
  const a = document.createElement('a'); // aํƒœ๊ทธ ์ƒ์„ฑ
  a.style.display = 'none'; // (์„ ํƒ)ํ™”๋ฉด์—์„œ ๋ณด์ด์ง€ ์•Š๋„๋ก display ์„ค์ •
  a.href = image; // ์™ธ๋ถ€ ๋ฆฌ์†Œ์Šค์˜ URL ํ• ๋‹น
  a.download = 'yourImage.png'; // ๋‹ค์šด๋กœ๋“œ ์ง„ํ–‰ ์‹œ ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ์ง€์ •ํ•  ํŒŒ์ผ๋ช…
  a.click(); // ํด๋ฆญ ์ด๋ฒคํŠธ
};

 

๐Ÿ’ก ๋งŒ์•ฝ ์ด๋ฏธ์ง€๊ฐ€ Blob ๊ฐ์ฒด๋ผ๋ฉด window.URL.createObjectURL()์„ ์ด์šฉํ•ด url(DOM String)๋กœ ๋ฐ”๊ฟ€ ์ˆ˜ ์žˆ๋‹ค. ๋ณ€ํ™˜ํ•œ url์€ window ์ฐฝ์ด ์‚ฌ๋ผ์ง€๋ฉด ํ•จ๊ป˜ ์‚ฌ๋ผ์ง€๋ฏ€๋กœ ๋‹ค๋ฅธ window์—์„œ ์žฌ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋‹ค. (์ฐธ๊ณ  ๋ธ”๋กœ๊ทธ)

// blob:http://localhost:1234/28ff8746-94eb-4dbe-9d6c-2443b581dd30
const url = window.URL.createObjectURL(blob);
a.href = url;

 

์ด๋ฏธ์ง€ ๋ฐ์ดํ„ฐ ๊ทธ๋ฆฌ๊ธฐ

๋ฐ˜๋Œ€๋กœ ์ด๋ฏธ์ง€ DataURL์„ ์บ”๋ฒ„์Šค์— ๊ทธ๋ฆฌ๋ ค๋ฉด onload ์†์„ฑ์„ ์ด์šฉํ•ด์•ผ ํ•œ๋‹ค. ์ด๋ฏธ์ง€๋Š” ํ…์ŠคํŠธ์— ๋น„ํ•ด ์šฉ๋Ÿ‰์ด ํฌ๋‹ค. ์šฉ๋Ÿ‰์ด ํฌ๋ฏ€๋กœ ๋กœ๋“œ ์†๋„๋„ ๋Š๋ฆฌ๋‹ค. ๋น„๋™๊ธฐ๋กœ ์ž‘๋™ํ•˜๋Š” ํŠน์„ฑ ๋•Œ๋ฌธ์— ์ด๋ฏธ์ง€๋ฅผ ์™„์ „ํžˆ ๋กœ๋“œํ•  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฌ์ง€ ์•Š๊ณ , ํ™”๋ฉด์— ์ด๋ฏธ์ง€ ํƒœ๊ทธ๋ฅผ ๋จผ์ € ํ‘œ์‹œํ•œ ํ›„, ์ด๋ฏธ์ง€ ๋ฐ์ดํ„ฐ๋Š” ๋”ฐ๋กœ ์ฝ๋Š”๋‹ค.

 

onload ์†์„ฑ์„ ์‚ฌ์šฉํ•˜๋ฉด DOM ํŠธ๋ฆฌ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ด๋ฏธ์ง€, ์Šคํƒ€์ผ์‹œํŠธ ๊ฐ™์€ ์™ธ๋ถ€ ๋ฆฌ์†Œ์Šค๋ฅผ ๋ชจ๋‘ ๋กœ๋“œํ–ˆ์„ ๋•Œ ํŠน์ • ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋„๋ก ํ•  ์ˆ˜ ์žˆ๋‹ค. ์ฆ‰ ์•„๋ž˜ ์ฝ”๋“œ๋Š” ์ด๋ฏธ์ง€๊ฐ€ ๋ชจ๋‘ ๋กœ๋“œ๋œ ํ›„์— ctx.drawImage()๊ฐ€ ์‹คํ–‰๋œ๋‹ค. ๋งŒ์•ฝ onload ์†์„ฑ์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š์•˜๋‹ค๋ฉด ctx.drawImage()๋ฅผ ํ˜ธ์ถœํ–ˆ์„ ์‹œ์ ์— ์ด๋ฏธ์ง€๊ฐ€ ๋ชจ๋‘ ๋กœ๋“œ๋˜์ง€ ์•Š์•˜๋‹ค๋ฉด ํ™”๋ฉด ์ถœ๋ ฅ์€ ์‹คํŒจํ•œ๋‹ค. — ์ฐธ๊ณ 1, ์ฐธ๊ณ 2

const image = new Image();
image.onload = function () {
  ctx.drawImage(image, 0, 0);
};
image.src = correctAnswer;

// ์œ„ ์ฝ”๋“œ๋Š” ์•„๋ž˜์™€ ๊ฐ™๋‹ค
// <image src={correctAnswer} onload={() => ctx.drawImage(image, 0, 0)} />

 

์‹คํ–‰ ์ทจ์†Œ ๊ธฐ๋Šฅ / imageData


์บ”๋ฒ„์Šค ์ „์ฒด ํ˜น์€ ์ผ๋ถ€ ํ”ฝ์…€ ์ •๋ณด๋ฅผ imageData ๊ฐ์ฒด๋กœ ๋ฐ˜ํ™˜ํ•˜๋Š” getImageData()๋ฅผ ์ด์šฉํ•ด ์‹คํ–‰ ์ทจ์†Œ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•  ์ˆ˜๋„ ์žˆ๋‹ค. ๋งˆ์šฐ์Šค ํด๋ฆญ → ๋“œ๋ž˜๊ทธ → ๋งˆ์šฐ์Šค ํด๋ฆญ ํ•ด์ œ ์ž‘์—…์„ ๋ฐ˜๋ณตํ•  ๋•Œ๋งˆ๋‹ค ์บ”๋ฒ„์Šค ์ „์ฒด๋ฅผ imageData๋กœ ์ €์žฅํ•˜๊ณ , ์‹คํ–‰ ์ทจ์†Œ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด putImageData()๋ฅผ ์ด์šฉํ•ด ๋ฐ”๋กœ ์ด์ „ imageData ๊ฐ์ฒด๋ฅผ ์บ”๋ฒ„์Šค์— ๋ณต์›ํ•˜๋Š” ๋ฐฉ์‹.

 

  • ctx.createImageData(width, height)
    • ๋นˆ imageData ์ƒ์„ฑ
    • width, height : imageData์˜ ๊ฐ€๋กœ/์„ธ๋กœ ํฌ๊ธฐ
  • ctx.createImageData(imageData)
    • ์‚ฌ๋ณธ imageData ์ƒ์„ฑ
  • ctx.getImageData(x, y, width, height)
    • x, y : ์ด๋™ํ•  ์ขŒํ‘œ ๊ฑฐ๋ฆฌ
    • x, y ๋‘˜๋‹ค 0์„ ์ž…๋ ฅํ•˜๊ณ  ์บ”๋ฒ„์Šค์˜ width, height๋ฅผ ์ž…๋ ฅํ•˜๋ฉด ์บ”๋ฒ„์Šค ์ „์ฒด๋ฅผ ์ €์žฅํ•œ๋‹ค.
  • ctx.putImageData(imageData, x, y, [dx], [dy], [dw], [dh])
    • x, y : ์ด๋™ํ•  ์ขŒํ‘œ ๊ฑฐ๋ฆฌ
    • (์˜ต์…˜) dx, dy : imageData์˜ x, y ์ขŒํ‘œ
    • (์˜ต์…˜) dw, dh : imageData์˜ ๊ฐ€๋กœ, ์„ธ๋กœ ํฌ๊ธฐ
    • dx, dy, dw, dh : ์›๋ณธ ์ด๋ฏธ์ง€์˜ ์ผ๋ถ€๋ฅผ ์ž˜๋ผ์„œ ์‚ฌ์šฉํ•  ๋•Œ ์œ ์šฉํ•˜๋‹ค

 

๐Ÿ’ก ctx.getImageData(x, y, 1, 1).data ๋ฅผ ํ†ตํ•ด ํ˜„์žฌ ์ขŒํ‘œ์˜ RGBA ์ƒ‰์ƒ๊ฐ’์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

 

imageData ๊ฐ์ฒด์—” data, width, height ์†์„ฑ์„ ๊ฐ–๋Š”๋‹ค. data ์†์„ฑ์—” ๋ž˜์Šคํ„ฐ(ํ”ฝ์…€ ์ •๋ณด) ์ •๋ณด๊ฐ€ ๋‹ด๊ฒจ ์žˆ๋‹ค. 1๊ฐœ ํ”ฝ์…€์—” R(็บข) G(็ปฟ) B(่“) A(Alpha) 4๊ฐœ์˜ ๊ฐ’์„ ๊ฐ€์ง„๋‹ค. RGB ๊ฐ’์˜ ๋ฒ”์œ„๋Š” 0~255์˜ ์ˆซ์ž์ด๋ฉฐ, ํˆฌ๋ช…๋„๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š”A๋Š” 0์— ๊ฐ€๊นŒ์šธ ์ˆ˜๋ก ํˆฌ๋ช…ํ•ด์ง„๋‹ค.

 

์ด๋ฏธ์ง€ ์ถœ์ฒ˜ - seon

 

const imageData = ctx.createImageData(300, 300);
console.log(imageData);
// {data: Uint8ClampedArray(360000), width: 300, height: 300}

for (let i = 0; i < imageData.data.length; i += 3) {
  // ['R','G','B','A','R','G','B','A']
  // RGB ARG BAR GBA ์ˆœ์„œ๋ฅผ ๋ฐ˜๋ณตํ•œ๋‹ค
  imageData.data[i + 0] = 100; // R/A/B/G...
  imageData.data[i + 1] = 0; // G/R/A/B...
  imageData.data[i + 2] = 0; // B/G/R/A...
}
ctx.putImageData(imageData, 300, 300);

 

 

์บ”๋ฒ„์Šค ์„ค์ •๊ฐ’ ์ €์žฅ/๋ณต์›


์บ”๋ฒ„์Šค ์ƒํƒœ๋Š” ctx.save()๋กœ ์ €์žฅํ•˜๊ณ , ctx.restore()๋กœ ๋ณต์›ํ•  ์ˆ˜ ์žˆ๋‹ค. ์บ”๋ฒ„์Šค์— ๊ทธ๋ฆฐ ์„ /๋„ํ˜•์ด๋‚˜ ์ด๋ฏธ์ง€๋ฅผ ์ €์žฅํ•˜๋Š”๊ฒŒ ์•„๋‹Œ ์บ”๋ฒ„์Šค์˜ matrix, strokeStyle ๋“ฑ์˜ ์†์„ฑ์„ ์ €์žฅํ•œ๋‹ค.

 

ctx.restore()๋Š” ๊ฐ€์žฅ ์ตœ๊ทผ์˜ ์ €์žฅํ•œ ์บ”๋ฒ„์Šค ์ƒํƒœ๋ฅผ ๋ณต์›ํ•œ๋‹ค. save()๋ฅผ 3๋ฒˆํ•˜๊ณ  restore()๋ฅผ 3๋ฒˆํ•˜๋ฉด ๋งˆ์ง€๋ง‰ save()๋ถ€ํ„ฐ ์ฐจ๋ก€๋Œ€๋กœ ๋ณต์›ํ•œ๋‹ค. ๋งˆ์ง€๋ง‰ save()๊นŒ์ง€ ๋ชจ๋‘ ๋ณต์›ํ•œ ํ›„ restore()๋ฅผ ํ•œ ๋ฒˆ ๋” ํ•˜๋ฉด ์ฒซ๋ฒˆ์งธ๋กœ ์ €์žฅํ•œ save() ๊ฐ’์„ ๋ถˆ๋Ÿฌ์˜จ๋‹ค(์ฐธ๊ณ  ๋งํฌ).

ctx.save(); // 1๋ฒˆ ์ €์žฅ
ctx.save(); // 2๋ฒˆ ์ €์žฅ
ctx.save(); // 3๋ฒˆ ์ €์žฅ
ctx.restore(); // 3๋ฒˆ ๋ณต๊ตฌ
ctx.restore(); // 2๋ฒˆ ๋ณต๊ตฌ
ctx.restore(); // 1๋ฒˆ ๋ณต๊ตฌ
ctx.restore(); // 1๋ฒˆ ๋ณต๊ตฌ

 

์บ”๋ฒ„์Šค ๊ทธ๋ฆฌ๊ธฐ ๋น„ํ™œ์„ฑํ™”


์บ”๋ฒ„์Šค ์—˜๋ฆฌ๋จผํŠธ CSS์— ์•„๋ž˜ ์†์„ฑ์„ ์ถ”๊ฐ€ํ•œ๋‹ค.

cursor: not-allowed; /* ์š”์ฒญ๋œ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜์ง€ ์•Š์Œ์„ ๋‚˜ํƒ€๋‚ด๋Š” ์ปค์„œ */
pointer-events: none; /* ๋งˆ์šฐ์Šค ํฌ์ธํ„ฐ ์˜ต์…˜(ํด๋ฆญ, hover, ์ปค์„œ ๋“œ๋ž˜๊ทธ ๋“ฑ) ๋น„ํ™œ์„ฑํ™” */

 

๋ ˆํผ๋Ÿฐ์Šค


 


๊ธ€ ์ˆ˜์ •์‚ฌํ•ญ์€ ๋…ธ์…˜ ํŽ˜์ด์ง€์— ๊ฐ€์žฅ ๋น ๋ฅด๊ฒŒ ๋ฐ˜์˜๋ฉ๋‹ˆ๋‹ค. ๋งํฌ๋ฅผ ์ฐธ๊ณ ํ•ด ์ฃผ์„ธ์š”
๋ฐ˜์‘ํ˜•