๐Ÿ’ ์ฒด๋ฆฌ๋ณด๋“œ: ํ™”์ดํŠธ๋ณด๋“œ ํ”„๋กœ์ ํŠธ ํšŒ๊ณ 

dev_heeยท2023๋…„ 1์›” 4์ผ
18

ํ›„๊ธฐ

๋ชฉ๋ก ๋ณด๊ธฐ
3/3
post-thumbnail

๐Ÿ’ Cherry Board ๐Ÿ’

์ฒด๋ฆฌ๋ณด๋“œ๋Š” ํ™”์ดํŠธ๋ณด๋“œ ํˆด๋กœ, ํ”ผ๊ทธ์žผ์ฒ˜๋Ÿผ ์—ฌ๋Ÿฌ ์œ ์ €๋“ค์ด ๋ชจ์—ฌ์„œ ์ž์œ ๋กญ๊ฒŒ ๋“œ๋กœ์ž‰ํ•˜๊ณ  ์†Œํ†ตํ•  ์ˆ˜ ์žˆ์–ด์š”. UI๋Š” ์ตœ๋Œ€ํ•œ ๊ท€์—ฝ๊ณ  ์•„๊ธฐ์ž๊ธฐํ•˜๊ฒŒ ๊ฐ€์ ธ๊ฐ€๊ณ  ์‹ถ์—ˆ์–ด์š”! ์ด๋ชจํ‹ฐ์ฝ˜์„ ๋„์žฅ์ฒ˜๋Ÿผ ์ฐ์„ ์ˆ˜๋„ ์žˆ๊ณ  ํ…์ŠคํŠธ๋ฅผ ์ž…๋ ฅํ•˜๊ฑฐ๋‚˜ ์‚ฌ์ง„์„ ์˜ฌ๋ฆด ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ ํ˜„์žฌ ์บ”๋ฒ„์Šค๋ฅผ ์ด๋ฏธ์ง€ ํŒŒ์ผ๋กœ ๋‚ด๋ณด๋‚ด์–ด ์ €์žฅํ•  ์ˆ˜๋„ ์žˆ์–ด์š”. ๊ทธ๋ฆฌ๊ณ  / ๋‹จ์ถ•ํ‚ค๋ฅผ ํ†ตํ•ด ์ปค์„œ ์˜†์— ๋ฉ”์‹œ์ง€๋ฅผ ์ž…๋ ฅํ•  ์ˆ˜ ์žˆ์–ด์š”.

์–ด๋–ป๊ฒŒ ๊ฐœ๋ฐœํ–ˆ์„๊นŒ?

โšก๏ธ Svelte + Tailwind CSS

์ฒด๋ฆฌ๋ณด๋“œ๋Š” ์Šค๋ฒจํŠธ๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๊ฐœ๋ฐœํ–ˆ์Šต๋‹ˆ๋‹ค. ์ €๋Š” ์ด์ „๊นŒ์ง€ ์‚ฌ์šฉํ•ด๋ณธ ํ”„๋ ˆ์ž„์›Œํฌ๊ฐ€ ๋ฆฌ์•กํŠธ ๋ฟ์ด์–ด์„œ ์ƒˆ๋กญ์šด ์ปจ์…‰๊ณผ ์žฅ์ ๋“ค์„ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ์Šค๋ฒจํŠธ๋ฅผ ์‚ฌ์šฉํ•ด ๋ณด๊ณ  ์‹ถ์—ˆ์–ด์š”. React์™€ ๋‹ค๋ฅธ ํ”„๋ ˆ์ž„์›Œํฌ๋“ค๊ณผ ์ฐจ๋ณ„์ ์„ ๊ฐ€์ง„ ์Šค๋ฒจํŠธ๋Š” ๊ฐ€์ƒ DOM์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š์•„์„œ ๋น ๋ฅด๊ณ , ๋ฌด์—‡๋ณด๋‹ค๋„ ํ“จ์–ดํ•œ JavaScript, HTML, CSS์™€ ์œ ์‚ฌํ•œ ๋ฌธ๋ฒ•์œผ๋กœ ๊ฐœ๋ฐœ ๊ฒฝํ—˜์ด ๋งค์šฐ ํŽธ๋ฆฌํ–ˆ์Šต๋‹ˆ๋‹ค.

์Šคํƒ€์ผ์€ utility-first ์ปจ์…‰์„ ๊ฐ€์ง„ Tailwind CSS๋ฅผ ์„ ํƒํ•˜์˜€์Šต๋‹ˆ๋‹ค. CSS-in-JS ์ค‘ Styled-component์™€ Emotion์œผ๋กœ ๊ฐœ๋ฐœํ•ด๋ณธ ๊ฒฝํ—˜์œผ๋ก , ์‹œ๋ฉ˜ํ‹ฑํ•œ ํด๋ž˜์Šค ์ด๋ฆ„ ์ง“๊ธฐ์— ๋Œ€ํ•œ ๊ธฐ์ค€์ด ์‚ฌ๋žŒ๋งˆ๋‹ค ๋‹ค๋ฅด๋ฉฐ, ์‹ฌ์ง€์–ด ํ•œ ์‚ฌ๋žŒ์ด ์ž‘๋ช…ํ•˜๋”๋ผ๋„ ์‹œ๊ฐ„์ด ์ง€๋‚จ์— ๋”ฐ๋ผ ๋‹ค๋ฅด๊ฒŒ ์ง“๊ธฐ๋„ ํ•ฉ๋‹ˆ๋‹ค. Component Driven Development์™€ ์—ฌ๋Ÿฌ ํ”„๋ ˆ์ž„์›Œํฌ ๋“ฑ์žฅ์œผ๋กœ ์ธํ•ด์„œ ์˜ˆ์ „์˜ ํด๋ž˜์Šค ์„ ํƒ์ž๋กœ ์Šคํƒ€์ผ์„ ์žฌ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ์ปดํฌ๋„ŒํŠธ์˜ ์žฌ์‚ฌ์šฉ์œผ๋กœ ๋Œ€์ฒด๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์—, ์ด์ „์ฒ˜๋Ÿผ ํด๋ž˜์Šค๋ช…์„ ์‹œ๋ฉ˜ํ‹ฑํ•˜๊ฒŒ ์ง€์„ ํ•„์š”๋„ ๋”ฑํžˆ ์—†์–ด์กŒ๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. ๋ฌด์—‡๋ณด๋‹ค๋„ ์Šคํƒ€์ผ๋ง์„ ์œ„ํ•œ ํด๋ž˜์Šค๋ช…์„ ๋งค๋ฒˆ ์ž‘๋ช…ํ•˜๋Š” ๊ฒƒ์ด ๋„ˆ๋ฌด๋‚˜๋„ ๊ท€์ฐฎ์•˜์Šต๋‹ˆ๋‹ค.

Tailwind CSS๋Š” utility-first ๋ฐฉ์‹์ด ์ฃผ๋Š” ์ด์ ์„ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค.

  1. ํด๋ž˜์Šค ์ด๋ฆ„์„ ์ž‘์„ฑํ•˜๋Š” ๋ฐ ์—๋„ˆ์ง€๋ฅผ ๋‚ญ๋น„ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
    ๋” ์ด์ƒ ์Šคํƒ€์ผ์„ ์ง€์ •ํ•˜๊ธฐ ์œ„ํ•ด sidebar-inner-wrapper์™€ ๊ฐ™์€ ๋ฐ”๋ณด๊ฐ™์€ ํด๋ž˜์Šค ์ด๋ฆ„์„ ์ถ”๊ฐ€ํ•  ํ•„์š”๊ฐ€ ์—†์œผ๋ฉฐ, ์‹ค์ œ๋กœ๋Š” flex container์™€ ๊ฐ™์€ ์š”์†Œ์˜ ์™„์ „ํžˆ ์ถ”์ƒ์ ์ธ ํด๋ž˜์Šค๋ช…์— ๋Œ€ํ•ด ๊ณ ๋ฏผํ•  ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.

  2. CSS ํŒŒ์ผ์˜ ํฌ๊ธฐ๊ฐ€ ๋” ์ด์ƒ ์ปค์ง€์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ „ํ†ต์ ์ธ ์ ‘๊ทผ ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•˜๋ฉด ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ์„ ๊ฐœ๋ฐœํ•  ๋•Œ ๋งˆ๋‹ค CSS ํŒŒ์ผ์ด ์ปค์ง‘๋‹ˆ๋‹ค. ์œ ํ‹ธ๋ฆฌํ‹ฐ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋ชจ๋“  ๊ฒƒ์„ ์žฌ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ์ƒˆ CSS๋ฅผ ์ž‘์„ฑํ•  ํ•„์š”๊ฐ€ ๊ฑฐ์˜ ์—†์Šต๋‹ˆ๋‹ค.

  3. ๋ณ€ํ™”๋ฅผ ์ฃผ๋Š” ๊ฒƒ์ด ์ด์ „๋ณด๋‹ค ๋” ์•ˆ์ „ํ•˜๋‹ค๊ณ  ๋Š๋‚๋‹ˆ๋‹ค. CSS๋Š” ์ „์—ญ ์Šค์ฝ”๋ธŒ์ด๊ธฐ ๋•Œ๋ฌธ์— ์–ด๋–ค ์Šคํƒ€์ผ์„ ๋ณ€๊ฒฝํ•  ๋•Œ ๋‹ค๋ฅธ ์–ด๋–ค๊ฒƒ์—์„œ ๋ฌธ์ œ๊ฐ€ ์ƒ๊ธธ์ง€ ์•Œ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. HTML์˜ ํด๋ž˜์Šค๋Š” ์ง€์—ญ์ ์œผ๋กœ ๊ด€๋ฆฌ๋˜๋ฏ€๋กœ ๋‹ค๋ฅธ ๋ฌธ์ œ์— ๋Œ€ํ•ด ๊ฑฑ์ •ํ•˜์ง€ ์•Š๊ณ  ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋•Œ๋ฌธ์— utility-first ์ปจ์…‰์ค‘์— ์ด์ „์— ์ตํžˆ ๋“ค์—ˆ๊ณ  npm trends๊ฐ€ ๋†’์€ TailwindCSS ๋ฅผ ์„ ํƒํ•˜๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

๐Ÿงถ Fabric.js

๋ฆฌ์–ผํƒ€์ž„ ๋ฉ€ํ‹ฐ์œ ์ € ํ™”์ดํŠธ๋ณด๋“œ๋ฅผ ๊ฐœ๋ฐœํ•˜๊ธฐ ์œ„ํ•ด HTML5 canvas ๋ฅผ ์‰ฝ๊ฒŒ ๋‹ค๋ฃฐ ์ˆ˜ ์žˆ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ๋ฆฌ์„œ์นญํ•˜๋˜์ค‘ Fabric.js ๋ฅผ ์‚ฌ์šฉํ•ด ํ™”์ดํŠธ๋ณด๋“œ๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ์˜ˆ์ œ ๊ธ€์„ ์ฝ๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

HTML5 canvas ๋Š” ๊ฐ„๋‹จํ•œ ๋„ํ˜•์„ ๊ทธ๋ฆฌ๋Š” ๊ฒƒ์€ ์–ด๋ ต์ง€ ์•Š์ง€๋งŒ, ๊ทธ ๋„ํ˜•์„ ์„ ํƒํ•˜๊ฑฐ๋‚˜ ์ด๋™ํ•˜๋Š”๋“ฑ์˜ ๋ณต์žกํ•œ ์ž‘์—…์„ ์œ„ํ•ด ๋ชจ๋“ ๊ฑธ ์ง์ ‘ ๊ฐœ๋ฐœํ•ด์•ผ ํ•œ๋‹ค๋Š” ๋‹จ์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค.
ํŠนํžˆ ๋„ํ˜•์„ ์ด๋™ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ๊ธฐ์กด ๋„ํ˜•์„ ๊ฐ์ฒด๋กœ์„œ ๋‹ค๋ฃจ์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ์ด์ „์— ๊ทธ๋ฆฐ ๋‚ด์šฉ์„ ์ง€์šฐ๊ณ  ์ด๋™ํ•œ ์œ„์น˜์— ์ƒˆ๋กญ๊ฒŒ ๋‹ค์‹œ ๊ทธ๋ ค์•ผํ•ฉ๋‹ˆ๋‹ค. ๋•Œ๋ฌธ์— ๋ณต์žกํ•˜๊ณ  ์„ฌ์„ธํ•œ ์ž‘์—…์—๋Š” canvas ์—์„œ ์ œ๊ณตํ•˜๋Š” API ๋งŒ์œผ๋ก  ์ ํ•ฉํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

var canvasEl = document.getElementById('c');
var ctx = canvasEl.getContext('2d');
ctx.fillStyle = 'red';

ctx.translate(100, 100);
ctx.rotate(Math.PI / 180 * 45);
ctx.fillRect(-10, -10, 20, 20);

// ์ „์ฒด ์บ”๋ฒ„์Šค๋ฅผ ์ง€์šฐ๊ณ  ๋‹ค์‹œ ์‚ฌ๊ฐํ˜•์„ ๊ทธ๋ ค์•ผ ํ•œ๋‹ค.
ctx.clearRect(0, 0, canvasEl.width, canvasEl.height);
ctx.fillRect(20, 50, 20, 20);

Fabric.js๋Š” ๋„ํ˜•์„ ๊ฐ์ฒด๋กœ ๊ด€๋ฆฌํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ƒˆ๋กญ๊ฒŒ ์บ”๋ฒ„์Šค๋ฅผ ์ง€์šฐ๋Š” ๊ณผ์ •์ด ํ•„์š” ์—†์Šต๋‹ˆ๋‹ค.
๊ฐ์ฒด์— ์ง์ ‘ ์ ‘๊ทผํ•ด์„œ ํšŒ์ „ํ•˜๊ฑฐ๋‚˜ ์ด๋™์‹œํ‚ค๋ฉด ๋˜๊ธฐ ๋•Œ๋ฌธ์— ๋งค์šฐ ํŽธ๋ฆฌํ•˜์ฃ !

var canvas = new fabric.Canvas('c');

// ํšŒ์ „๋œ ์‚ฌ๊ฐํ˜•
var rect = new fabric.Rect({
  left: 100,
  top: 100,
  fill: 'red',
  width: 20,
  height: 20,
  angle: 45 // ํšŒ์ „
});

// ์บ”๋ฒ„์Šค์— ์‚ฌ๊ฐํ˜• ์ถ”๊ฐ€
canvas.add(rect); 

// ์‚ฌ๊ฐํ˜• ์œ„์น˜ ์ด๋™
rect.set({ left: 20, top: 50 });
canvas.renderAll();

์ด๋ ‡๋“ฏ Fabric.js๋Š” Vanilla Canvas API ๊ธฐ๋ฐ˜์œผ๋กœ Object ๋ชจ๋ธ์„ ์ œ๊ณตํ•˜์—ฌ ์‰ฝ์ง€๋งŒ ๊ฐ•๋ ฅํ•œ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ž…๋‹ˆ๋‹ค. Fabric.js๋Š” drawing mode๋ฅผ ์ œ๊ณตํ•˜๋ฉฐ, ์บ”๋ฒ„์Šค์— ๊ทธ๋ ค์งˆ ๊ทธ๋ฆผ๋“ค์„ Object๋กœ ์ƒ์„ฑํ•˜๊ณ  ๊ด€๋ฆฌํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๊ฐ„๋‹จํ•˜๊ฒŒ ์ถ”๊ฐ€, ์‚ญ์ œ ๋ฐ ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ ์บ”๋ฒ„์Šค์—์„œ Object๊ฐ€ ์ถ”๊ฐ€, ์‚ญ์ œ ๋“ฑ์˜ ์ด๋ฒคํŠธ๋ฅผ ๊ฐ์ง€ํ•˜์—ฌ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ๋“ฑ๋กํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์‹ค์‹œ๊ฐ„์œผ๋กœ Object์˜ ๋ณ€๊ฒฝ์‚ฌํ•ญ์„ ๊ด€๋ฆฌํ•˜๊ธฐ์—๋„ ์ ํ•ฉํ•˜์˜€์Šต๋‹ˆ๋‹ค.

canvas.on('object:added', function(options) {
  // do someting 
}

๐Ÿ”ฅ Firebase

์‹ค์‹œ๊ฐ„ ๋ฐ์ดํ„ฐ๋ฅผ ์ฃผ๊ณ ๋ฐ›์„ ์ˆ˜ ์žˆ๋Š” ๊ธฐ์ˆ ๋กœ Firebase๋ฅผ ์„ ํƒํ•˜์˜€์Šต๋‹ˆ๋‹ค. ํŒŒ์ด์–ด๋ฒ ์ด์Šค๋Š” ๊ตฌ๊ธ€์ด ์†Œ์œ ํ•˜๊ณ ์žˆ๋Š” ๋ชจ๋ฐ”์ผ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ฐœ๋ฐœ ํ”Œ๋žซํผ์œผ๋กœ ๋ถ„์„, ์ธ์ฆ, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค, ํŒŒ์ผ ์ €์žฅ, ํ‘ธ์‹œ ๋ฉ”์‹œ์ง€ ๋“ฑ์˜ ์—ฌ๋Ÿฌ ์„œ๋น„์Šค๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

ํŒŒ์ด์–ด๋ฒ ์ด์Šค์—์„œ ์ œ๊ณตํ•˜๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋Š” Cloud Firestore ๊ณผ Realtime database ๋‘ ๊ฐ€์ง€๊ฐ€ ์žˆ์—ˆ๋Š”๋ฐ, ์„œ๋น„์Šค์˜ ๊ธฐ๋Šฅ๊ณผ ๋ชฉ์ ์— ๋งž์ถฐ ์„ ํƒํ•˜์—ฌ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋‘ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ์ฐจ์ด์ ์€ ์—ฌ๋Ÿฌ๊ฐ€์ง€๊ฐ€ ์žˆ์ง€๋งŒ, ๊ฐ€์žฅ ํฐ ์ฐจ์ด๋Š” ๊ฐ€๊ฒฉ ์ •์ฑ…๊ณผ ์ง€์—ฐ์‹œ๊ฐ„ ์ธก๋ฉด์—์„œ ๋‰ด์Šค์•ฑ ์ฒ˜๋Ÿผ ํฐ ๋‹จ์œ„์˜ ๋ฐ์ดํ„ฐ์— ๋Œ€ํ•œ ์š”์ฒญ์ด ์ข…์ข… ๋ฐœ์ƒํ•˜๋Š” ์•ฑ์„ ๊ฐœ๋ฐœํ•˜๋Š” ๊ฒฝ์šฐ์—” Firestore๊ฐ€ ์ ํ•ฉํ•˜๊ณ , ๋ฐ˜๋Œ€๋กœ ์ฝ๊ธฐ/์“ฐ๊ธฐ๊ฐ€ ๋งŽ์ด ๋ฐœ์ƒํ•˜๋Š” ํ™”์ดํŠธ๋ณด๋“œ ์•ฑ์€ Realtime Database๊ฐ€ ๋” ์œ ๋ฆฌํ•˜๋‹ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค.

๋”ฐ๋ผ์„œ ์ฒด๋ฆฌ๋ณด๋“œ๋Š” ํŒŒ์ด์–ด๋ฒ ์ด์Šค์˜ Realtime database ๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์บ”๋ฒ„์Šค์˜ ๋ณ€๊ฒฝ์‚ฌํ•ญ์„ ์ €์žฅํ•˜๊ณ  ํด๋ผ์ด์–ธํŠธ๋“ค์—๊ฒŒ ์‹ค์‹œ๊ฐ„์œผ๋กœ ๋ณ€๊ฒฝ์‚ฌํ•ญ์„ ์ „ํŒŒํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

์•„์‰ฌ์› ๋˜ ์  ๐Ÿ˜‚

๐Ÿƒ๐Ÿปโ€โ™€๏ธ ๋™์‹œ์„ฑ ์ œ์–ด์™€ CRDT

์ฒด๋ฆฌ๋ณด๋“œ๋Š” ์‹ค์‹œ๊ฐ„ ๋ฐ์ดํ„ฐ ํŽธ์ง‘์ด ๋ฐœ์ƒํ•˜์ง€๋งŒ ๊ทธ์— ๋Œ€ํ•œ ๋™์‹œ์„ฑ ์ œ์–ด๋ฅผ ํ•˜์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค.

Realtime database์—์„œ ์บ”๋ฒ„์Šค ๋ฐฉ ๋งˆ๋‹ค ๊ฐ์ฒด ํ˜•ํƒœ์˜ CanvasObjects๋ฅผ ์ €์žฅํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ ๊ทธ๋ฆผ์„ ๊ทธ๋ฆฌ๋ฉด ๊ทธ์— ๋Œ€ํ•œ Object๊ฐ€ CanvasObjects์— ์ถ”๊ฐ€๋ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๊ทธ์— ๋Œ€ํ•œ ๋ณ€๊ฒฝ์‚ฌํ•ญ์ด ๋‹ค๋ฅธ ํด๋ผ์ด์–ธํŠธ๋“ค์—๊ฒŒ ์ „ํŒŒ๋ฉ๋‹ˆ๋‹ค. ๋งŒ์•ฝ ๋‘ ์œ ์ €๊ฐ€ ๋™์‹œ์— ํ•˜๋‚˜์˜ Object๋ฅผ ์ˆ˜์ •/์‚ญ์ œ ํ•˜๋Š” ๊ฒฝ์šฐ์—๋Š” ๊ทธ์— ๋Œ€ํ•œ ๋ณ€๊ฒฝ์‚ฌํ•ญ์„ ๋ณ‘ํ•ฉํ•˜์—ฌ ์ ์šฉํ•˜๋Š” ๋™์‹œ์„ฑ ์ œ์–ดํ•˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์‚ฌ์šฉ์ž๊ฐ€ ๋งŽ์•„์ง€๋ฉด ๋ฐ์ดํ„ฐ๊ฐ€ ์†์‹ค๋˜๊ฑฐ๋‚˜ ๋‘ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๊ฐ™์€ ์บ”๋ฒ„์Šค ํ™”๋ฉด์„ ๋ณด์ง€ ๋ชปํ•˜๋Š” ๋ฌธ์ œ๊ฐ€ ์ƒ๊ธธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋™์‹œ์„ฑ ์ œ์–ด๋ฅผ ์œ„ํ•œ ์—ฌ๋Ÿฌ๊ฐ€์ง€ ์•Œ๊ณ ๋ฆฌ์ฆ˜๋“ค์ด ์žˆ์Šต๋‹ˆ๋‹ค. ๋Œ€ํ‘œ์ ์œผ๋กœ OT(Operstional Transformation)์™€ CRDT(Confilct free Replicatied Data Types) ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. OT๋Š” ์„œ๋ฒ„๊ฐ€ ์ค‘๊ฐ„์—์„œ ๋ฐ์ดํ„ฐ ๋ณ€๊ฒฝ์— ๋Œ€ํ•œ ๋ณด์ •์„ ํ†ตํ•ด ๋™์‹œ์„ฑ์„ ์ œ์–ดํ•˜๊ณ , CRDT๋Š” ๊ณ ์œ  id๋ฅผ ๋ถ€์—ฌํ•˜๋Š” ๋ฐฉ์‹์„ ํ†ตํ•ด์„œ ์„œ๋ฒ„ ์—†์ด ํด๋ผ์ด์–ธํŠธ๋ผ๋ฆฌ ๋ณ€๊ฒฝ์‚ฌํ•ญ์„ ์ „๋‹ฌ๋ฐ›๊ณ  ๋™๊ธฐํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. OT๋Š” ์˜ˆ์ „์— ์ฃผ๋กœ ์‚ฌ์šฉ๋œ ๊ธฐ์ˆ ์ด์—ˆ๊ณ , ์ตœ๊ทผ์—๋Š” CRDT๊ฐ€ ์ธ๊ธฐ๋ฅผ ์–ป๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

์ด์— ๋Œ€ํ•œ ์ž์„ธํ•œ ์„ค๋ช…์€ ์‹ค์‹œ๊ฐ„ ๋™์‹œ ํŽธ์ง‘: OT์™€ CRDT๋ฅผ ์ฐธ๊ณ  ํ•ด์ฃผ์„ธ์š”.

CRDT๋ฅผ ์ ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ์›น์†Œ์ผ“๊ณผ ์œ ์‚ฌํ•˜๊ฒŒ ๋ณ€๊ฒฝ์‚ฌํ•ญ์„ ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ์ „ํŒŒํ•  ๋ฐฉ๋ฒ•์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ํŒŒ์ด์–ด๋ฒ ์ด์Šค๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ๋ณ€๊ฒฝ์‚ฌํ•ญ์„ ์ „ํŒŒํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, ํด๋ผ์ด์–ธํŠธ์˜ ๋ณ€๊ฒฝ์‚ฌํ•ญ์„ ์–ด๋”˜๊ฐ€์— ์ €์žฅํ•˜์ง€ ์•Š๊ณ  ์ „ํŒŒํ•˜๊ธฐ์—” ํŒŒ์ด์–ด๋ฒ ์ด์Šค๊ฐ€ ์ ํ•ฉํ•˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.

๋”ฐ๋ผ์„œ CRDT ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜๋Š” Automerge ๋˜๋Š” Yjs๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ ๋„์ž…์„ ๊ณ ๋ คํ•ด๋ณด์•˜์Šต๋‹ˆ๋‹ค. ํŠนํžˆ Yjs๋Š” npm trends ์ˆœ์œ„๊ฐ€ ๊ฐ€์žฅ ๋†’์•˜๊ณ , ๋„คํŠธ์›Œํฌ๋ฅผ ํ†ตํ•ด ํด๋ผ์ด์–ธํŠธ๋ฅผ ์—ฐ๊ฒฐํ•ด์ฃผ๋Š” y-websocket์™€ y-webtc ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋„ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์ฒด๋ฆฌ๋ณด๋“œ์— ๋„์ž…ํ•˜๊ณ  ์‹ถ์—ˆ์Šต๋‹ˆ๋‹ค.

ํ•˜์ง€๋งŒ ํ…์ŠคํŠธ์ฒ˜๋Ÿผ ๊ฐ€๋ณ๊ณ  ๊ฐ„๋‹จํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค๋ฃจ๋Š” ๊ฒƒ์ด ์•„๋‹Œ, fabric.js์˜ ๋ณต์žกํ•˜๊ณ  ๋ฌด๊ฑฐ์šด obejct ๋ฅผ ๋‹ค๋ฃจ์–ด์•ผ ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ๋ฌธ์ œ๊ฐ€ ์ƒ๊ฒผ์Šต๋‹ˆ๋‹ค. CRDT์˜ ๋‹จ์ ์ค‘์˜ ํ•˜๋‚˜์ธ ๊ณ ์œ  id๋ฅผ ์ €์žฅํ•˜๊ณ  ์ด๋ฅผ ํŠธ๋ฆฌ๊ตฌ์กฐ๋กœ ๊ด€๋ฆฌํ•˜๊ธฐ ์œ„ํ•œ ์ถ”๊ฐ€์ ์ธ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ํ•„์š”๋กœ ํ•œ๋‹ค๋Š” ์  ๋•Œ๋ฌธ์— object์˜ ๋ณ€๊ฒฝ์‚ฌํ•ญ์€ ๋”์šฑ ๋ฌด๊ฑฐ์›Œ์ง€๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
์‚ฌ์‹ค ๋ช…ํ™•ํ•˜๊ฒŒ ์›์ธ์„ ํŒŒ์•…ํ•œ๊ฑด ์•„๋‹ˆ์ง€๋งŒ, ๋ณ€๊ฒฝ์‚ฌํ•ญ์˜ ํฐ ์šฉ๋Ÿ‰๊ณผ ์ €์˜ ์•„์ง ๋ฏธ์ˆ™ํ•œ ๊ฐœ๋ฐœ ์‹ค๋ ฅ ๋•Œ๋ฌธ์— ํผํฌ๋จผ์Šค๊ฐ€ ๋งค์šฐ ๋‚ฎ์•„์ง€๊ฒŒ ๋˜์—ˆ๊ณ  ๊ฐ๊ฐ์˜ ํด๋ผ์ด์–ธํŠธ์—์„œ ๋ณ€๊ฒฝ์‚ฌํ•ญ์ด ์ •๋ง ๋Š๋ฆฌ๊ฒŒ ๋™๊ธฐํ™”๋˜๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ๋๋‚ด ๋™์‹œ์„ฑ ์ œ์–ด๋ฅผ ํ•˜์ง€ ๋ชปํ•˜์˜€์Šต๋‹ˆ๋‹ค.

๐Ÿงถ Fabric.js ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ ํ•œ๊ณ„์ 

Fabric.js๋Š” ๋“œ๋กœ์ž‰์— ์ตœ์ ํ™”๋œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ด๊ธฐ ๋•Œ๋ฌธ์— ํ…์ŠคํŠธ๋ฅผ ์—๋””ํŒ… ํ•˜๋Š”๋ฐ๋Š” ์ ํ•ฉํ•˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ํ”ผ๊ทธ์žผ์ฒ˜๋Ÿผ ํฌ์ŠคํŠธ์ž‡ ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•˜๊ธฐ ์œ„ํ•ด์„œ ํ…์ŠคํŠธ ๊ฐ์ฒด๋ฅผ ๋‹ค๋ค„์•ผ ํ–ˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ํ…์ŠคํŠธ ๊ฐ์ฒด๋ฅผ HTML ์—์„œ CSS๋กœ ์Šคํƒ€์ผ๋ง ํ•˜๋“ฏ์ด padding์„ ์ฃผ๊ฑฐ๋‚˜ width height์„ ์ง€์ •ํ•  ์ˆ˜ ์—†์—ˆ์Šต๋‹ˆ๋‹ค. ๋•Œ๋ฌธ์— ์ข…์ด ๋ชจ์–‘์˜ ์‚ฌ๊ฐํ˜• ๋„ํ˜• ๊ฐ์ฒด์™€ ํ…์ŠคํŠธ ๊ฐ์ฒด๋ฅผ ๊ทธ๋ฃนํ™” ํ•˜์—ฌ ํฌ์ŠคํŠธ์ž‡ ์ฒ˜๋Ÿผ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ๊ทธ๋ฃน์„ ํ•ด์ œํ•ด์•ผ๋งŒ ํ…์ŠคํŠธ๋ฅผ ์ˆ˜์ •์ด ๊ฐ€๋Šฅํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ํฌ์ŠคํŠธ์ž‡ ๊ทธ๋ฃน ๊ฐ์ฒด๋ฅผ ์˜ฌ๋ ค๋‘์—ˆ๋Š”๋ฐ, ํ…์ŠคํŠธ๋ฅผ ์ˆ˜์ •ํ•˜๊ธฐ ์œ„ํ•ด ๊ทธ๋ฃน์„ ํ•ด์ œํ•˜๋ฉด ๊ทธ๋ฃน ๊ฐ์ฒด๋Š” ์‚ญ์ œ๋˜๊ณ  ์‚ฌ๊ฐํ˜• ๋„ํ˜• ๊ฐ์ฒด์™€ ํ…์ŠคํŠธ ๊ฐ์ฒด๊ฐ€ ์ƒˆ๋กญ๊ฒŒ ์ƒ์„ฑ๋˜์–ด์„œ ํฌ์ŠคํŠธ์ž‡์ด ์ฐข์–ด์ง€๋Š”(?) ๋ฒ„๊ทธ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.

๋˜ํ•œ ๋งˆํฌ๋‹ค์šด์ฒ˜๋Ÿผ - ๋กœ ๋ฆฌ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•˜๋ฉด ์ž๋™์œผ๋กœ โ— ์œผ๋กœ ๋ฐ”๊ฟ”์ฃผ๋ฉด์„œ ๊ธ€์ž๊ฐ€ ๋“ค์—ฌ์“ฐ๊ธฐ๋˜๋Š” ๊ฒƒ๊ณผ ๊ฐ™์€ ํ…์ŠคํŠธ ์—๋””ํŒ… ๊ธฐ๋Šฅ์€ fabric.js ๋‚ด์—์„œ ์ œ๊ณตํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— fabric์˜ ํ…์ŠคํŠธ ๊ฐ์ฒด๋Š” ๋ถ„๋ช… ํ•œ๊ณ„์ ์ด ์กด์žฌํ•˜์˜€์Šต๋‹ˆ๋‹ค.

๊ทธ๋ž˜์„œ ํ…์ŠคํŠธ ์—๋””ํŒ…์€ HTML textarea ๋ฅผ ์‚ฌ์šฉํ•ด์„œ ํฌ์ŠคํŠธ์ž‡ ์œ„์— ๋„์šฐ๋„๋ก ๊ตฌํ˜„ํ•ด ๋ณด์•˜์ง€๋งŒ, zoom&panning์‹œ textarea ์˜์—ญ์— ์ปค์„œ๊ฐ€ ๋“ค์–ด๊ฐ€์žˆ์œผ๋ฉด zoom&panning ๊ธฐ๋Šฅ์ด ์ž‘๋™ํ•˜์ง€ ์•Š์•˜๊ณ , pointer-events-none ์„ ์‚ฌ์šฉํ•˜๋ฉด textarea ๋‚ด๋ถ€์—์„œ ์ปค์„œ๋กœ ์กฐ์ž‘ํ• ๋•Œ ์ •์ƒ์ ์œผ๋กœ ์ปค์„œ ์ด๋™์ด ์•ˆ๋˜๋Š”๋“ฑ์˜ ๋ฒ„๊ทธ๊ฐ€ ๋ฐœ์ƒํ•˜์˜€์Šต๋‹ˆ๋‹ค. (๋ง๋ถ™์ด์ž๋ฉด ๋ณต์žกํ•ด์ง„ ์ฝ”๋“œ ๊ตฌ์กฐ ํƒ“์— ๋ฒ„๊ทธ์— ๋Œ€ํ•œ ์›์ธ ํŒŒ์•…์ด ์–ด๋ ค์› ๊ณ  ์ •์ƒ์ ์œผ๋กœ ๋™์ž‘ํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ์งœ๊ธฐ ์–ด๋ ค์› ์Šต๋‹ˆ๋‹ค. ใ… ใ… )

๐Ÿคฏ ๊ธฐ๋Šฅ์„ ๋ถ™์ผ์ˆ˜๋ก ๋ณต์žกํ•ด์ง€๋Š” ์ฝ”๋“œ

๊ฐ„๋‹จํžˆ ๋“œ๋กœ์ž‰ ๊ธฐ๋Šฅ๋งŒ ๊ตฌํ˜„ํ–ˆ์„ ๋•Œ๋Š” ์ฝ”๋“œ๊ฐ€ ๊ทธ๋ ‡๊ฒŒ ๋ณต์žกํ•ด์ง€์ง€๋Š” ์•Š์•˜์ง€๋งŒ ๋™์‹œ์„ฑ, ์ปค์„œ, ๋‹จ์ถ•ํ‚ค, zoom & panning ๋“ฑ์˜ ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ์„ ๋ถ™์ผ ์ˆ˜๋ก ์ฝ”๋“œ๊ฐ€ ์ •๋ˆ๋˜์ง€ ์•Š๊ณ  ๋ณต์žกํ•ด์กŒ์Šต๋‹ˆ๋‹ค. ๋•Œ๋ฌธ์— ์ฝ”๋“œ๋ฅผ ์ˆ˜์ •ํ•  ๋•Œ ๋งˆ๋‹ค ์˜ˆ์ธกํ•˜์ง€ ๋ชปํ•œ ๋ฒ„๊ทธ๋“ค์ด ์ƒ๊ฒจ๋‚˜๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

like ์ŠคํŒŒ๊ฒŒํ‹ฐ ์ฝ”๋“œ..?

์ด์ „์— ์‹ค๋ฌด์—์„œ ๊ฐœ๋ฐœ์„ ํ•  ๋•Œ์—๋„ ์‹œ๊ฐ„์ด ์ง€๋‚˜ ๋ ˆ๊ฑฐ์‹œ๊ฐ€ ๋œ ์ฝ”๋“œ์—์„œ ๋ฒ„๊ทธ๊ฐ€ ๋ฐœ๊ฒฌ๋˜๋ฉด, ์›๋ž˜๋Š” ์ •์ƒ ๋™์ž‘ ํ•˜์˜€๋Š”๋ฐ ๋‚˜์ค‘์— ๋ถ€์ˆ˜ํšจ๊ณผ๋กœ ์ธํ•ด ๋ง๊ฐ€์ง„๊ฑด์ง€, ์•„๋‹˜ ์›๋ž˜๋ถ€ํ„ฐ ๊ทธ๋žฌ๋˜๊ฑด์ง€, ๋„๋Œ€์ฒด ์–ธ์ œ ์–ด๋””๋ถ€ํ„ฐ ๋ฌธ์ œ์˜€๋˜๊ฑด์ง€ ํŒŒ์•…ํ•˜๊ธฐ ์–ด๋ ค์› ๋˜ ๊ธฐ์–ต์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ฒด๋ฆฌ๋ณด๋“œ์—์„œ๋„ ์ด์™€ ์œ ์‚ฌํ•œ ๊ฒฝํ—˜์„ ํ•˜๋ฉด์„œ ๊ธฐ๋Šฅ์ด ๋งŽ์•„์ง€๋Š” ํ”„๋กœ์ ํŠธ์—์„œ๋Š” ํด๋ฆฐํ•œ ์ฝ”๋“œ๋ฅผ ์งœ๋Š” ๊ฒƒ์˜ ์ค‘์š”์„ฑ๊ณผ ํ…Œ์ŠคํŠธ์ฝ”๋“œ๋ฅผ ํ†ตํ•œ ์ฝ”๋“œ์˜ ์‹ ๋ขฐ์„ฑ์„ ์–ป์„ ํ•„์š”๊ฐ€ ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ํ”ผ๋ถ€๋กœ ์•Œ ๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

๋์œผ๋กœ...

์ฐธ ์•„์‰ฌ์›€์ด ๋งŽ์ด ๋‚จ์€ ํ”„๋กœ์ ํŠธ์ž…๋‹ˆ๋‹ค. ๊ทธ๋ฆผ ๊ทธ๋ฆฌ๋Š”๊ฑธ ์ข‹์•„ํ•ด์„œ ํ˜ธ๊ธฐ๋กญ๊ฒŒ ์ฒด๋ฆฌ๋ณด๋“œ๋ฅผ ์‹œ์ž‘ํ–ˆ์ง€๋งŒ, ๊ธฐ์ˆ  ๋ถ€์ฑ„์™€ ๋ฏธ์ˆ™ํ•œ ๊ฐœ๋ฐœ ์‹ค๋ ฅ์œผ๋กœ ๊ธฐ๋Šฅ์„ ์™„๋ฒฝํ•˜๊ฒŒ ๋งˆ๋ฌด๋ฆฌ ํ•˜์ง€ ๋ชปํ•˜์˜€์Šต๋‹ˆ๋‹ค. ๊ทธ๋ž˜๋„ ๋‚˜์˜ ๊ฐœ๋ฐœ ์‹ค๋ ฅ์„ ์ธ์ง€ํ•˜์˜€์œผ๋ฉฐ ๋” ์ข‹๊ณ  ์‹ ๋ขฐํ• ๋งŒํ•œ ์ฝ”๋“œ๋ฅผ ์งœ๊ธฐ ์œ„ํ•ด์„œ๋Š” ๋ถ€์ง€๋Ÿฐํ•˜๊ฒŒ ๊ณต๋ถ€ํ•ด์•ผ๊ฒ ๋‹ค๋Š” ๊ตํ›ˆ์„ ์–ป์„ ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

.
.
.

๊ทธ๋ฆฌ๊ณ  ๊ธฐ๋ก ๋‚จ๊ธฐ๋Š”๊ฑธ ์ฐธ ์ข‹์•„ํ•˜๋Š” ํŽธ์ธ๋ฐ ์›Œ๋‚™ ๋ง์žฌ์ฃผ๋„ ์—†๊ณ  ๊ตญ์–ด๋„ 3๋“ฑ๊ธ‰์„ ๋„˜์ง€ ๋ชปํ–ˆ๋˜ 0๊ฐœ ๊ตญ์–ด ๊ฐœ๋ฐœ์ž์ด๊ธฐ ๋•Œ๋ฌธ์— ์ด๋ฒˆ ํฌ์ŠคํŠธ๋„ ์กฐ๋ฆฌ์žˆ๊ฒŒ ์ž˜ ์ž‘์„ฑํ•˜์ง€๋Š” ๋ชปํ•œ๊ฒƒ ๊ฐ™๋„ค์š”. ๊ธ€ ์“ฐ๊ธฐ์— ๊ด€ํ•œ ์ฑ…๋„ ์ฝ์–ด์•ผ๊ฒ ์Šต๋‹ˆ๋‹ค. ๊ณต๋ถ€ํ•˜๊ณ  ์‹ถ์€ ์š•์‹ฌ์€ ๋งŽ์€๋ฐ ์šด๋™๋„ ํ•˜๊ณ ์‹ถ๊ณ  ๋†€๊ณ ๋„ ์‹ถ๊ณ  ํ•ด์•ผ ํ•  ์ผ์„ ์šฐ์„ ์ˆœ์œ„๋ฅผ ๋‘๊ณ  ํ•˜๋‚˜์”ฉ ํ•ด๊ฒฐํ•ด ๋‚˜๊ฐ€๋Š”๊ฑธ ์—ฐ์Šตํ•  ํ•„์š”๋ฅผ ๋Š๋ผ๋„ค์š”. ์•„๋ฌด์ชผ๋ก ์•ž์œผ๋กœ ์กฐ๊ธˆ์”ฉ ๊พธ์ค€ํžˆ ์„ฑ์žฅํ•ด์„œ ๋‚˜์ค‘์ด ๋Œ์ด์ผœ๋ดค์„ ๋•Œ ์ด๋งŒํผ์ด๋‚˜ ๋งŽ์ด ๋Š˜์—ˆ์–ด! ํ•˜๋Š” ์‚ฌ๋žŒ์ด ๋˜๊ณ ์‹ถ์Šต๋‹ˆ๋‹ค.

profile
๐ŸŽจ๊ทธ๋ฆผ์„ ์ข‹์•„ํ•˜๋Š” FE ๊ฐœ๋ฐœ์ž๐Ÿ‘ฉ๐Ÿปโ€๐Ÿ’ป

10๊ฐœ์˜ ๋Œ“๊ธ€

comment-user-thumbnail
2023๋…„ 1์›” 6์ผ

์šฐ์™€! ๋„ˆ๋ฌด ๋„ˆ๋ฌด ๊ณ ์ƒ๋งŽ์•˜์Šต๋‹ˆ๋‹ค! ์•„์‰ฌ์›€์ด ๋งŽ๋‹ค๊ณ  ํ–ˆ์ง€๋งŒ ์—ฌ๋Ÿฌ๊ฐ€์ง€ ๊ธฐ์ˆ ์Šคํƒ์„ ์จ๊ฐ€๋ฉด์„œ ๊ฒช์—ˆ๋˜ ์‹œํ–‰์ฐฉ์˜ค๋“ค์€ ๋‚˜์ค‘์— ์„ฑ์žฅ์— ํฐ ๋ฐ‘๊ฑฐ๋ฆ„์ด ๋ฉ๋‹ˆ๋‹ค. ์ข‹์€ ๊ฒฝํ—˜์ด ๋˜์—ˆ์„ ๊ฑฐ๋ผ๊ณ  ์ด ๊ฒฝํ—˜์„ ์ ์šฉํ•˜๊ฒŒ ๋  ๋˜ ์ƒˆ๋กœ์šด ํ”„๋กœ์ ํŠธ๋ฅผ ๋งŒ๋‚˜๊ฒŒ ๋˜์—ˆ์„๋•Œ์—๋Š” ์ด ๊ฒฝํ—˜์„ ํ† ๋Œ€๋กœ ์„ฑ๊ณต์ ์ธ ๊ฒฝํ—˜์„ ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋˜๊ธฐ๋ฅผ ๊ธฐ๋Œ€ํ•ฉ๋‹ˆ๋‹ค! :)

1๊ฐœ์˜ ๋‹ต๊ธ€
comment-user-thumbnail
2023๋…„ 1์›” 7์ผ

์•ˆ๋…•ํ•˜์„ธ์š”! ์ฒด๋ฆฌ๋ณด๋“œ ๋„ˆ๋ฌด ๊ท€์—ฝ๊ณ  ๋ฉ‹์ ธ์š” !
๋‹ค๋ฆ„์ด ์•„๋‹ˆ๋ผ ์ด๋ฒˆ์— ๊ทธ๋ฆผ์ผ๊ธฐ๋ฅผ ๊ทธ๋ฆฌ๋Š” ํŒ€ ํ”„๋กœ์ ํŠธ๋ฅผ ์ง„ํ–‰ํ•˜๊ณ  ์žˆ๋Š”๋ฐ,
๊ฐœ๋ฐœ์ž๋‹˜๊ณผ ๋น„์Šทํ•œ ๊ณ ๋ฏผ์œผ๋กœ fabric js๋ฅผ ์ ์šฉํ•ด์„œ react ํ™˜๊ฒฝ์—์„œ ๊ตฌํ˜„ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.
๊ทธ๋Ÿฐ๋ฐ ์•„์ง ์ €ํฌ๊ฐ€ ๋„ˆ๋ฌด๋„ˆ๋ฌด ๋ณ‘์•„๋ฆฌ ๊ฐœ๋ฐœ์ž๋ผ์„œ ์–ด๋ ค์šด ๋ถ€๋ถ„์ด ๋งŽ์Šต๋‹ˆ๋‹ค 9ใ……9
์ €ํฌ๋„ ํŒ€ ํ”„๋กœ์ ํŠธ์— ์ฒด๋ฆฌ๋ณด๋“œ์ฒ˜๋Ÿผ ์ด์˜๊ณ  ๋ฉ‹์ง„ ์บ”๋ฒ„์Šค๋ฅผ ๊ตฌํ˜„ํ•ด๋ณด๊ณ  ์‹ถ์Šต๋‹ˆ๋‹ค..!
ํ˜น์‹œ ๊ดœ์ฐฎ์œผ์‹œ๋‹ค๋ฉด ์ž๋ฌธ์„ ๊ตฌํ•  ์ˆ˜ ์žˆ์„๊นŒ์š”?

1๊ฐœ์˜ ๋‹ต๊ธ€
comment-user-thumbnail
2023๋…„ 1์›” 10์ผ

ํšŒ๊ณ  ์ž˜ ์ฝ์—ˆ์–ด์š”~ ์—ฌ๋Ÿฌ๊ฐ€์ง€ ๋‹ค์–‘ํ•œ ๊ธฐ์ˆ ์„ ๋งŽ์ด ํ•ด๋ณด์‹œ๋ฉด์„œ ์„ฑ์žฅํ•˜๊ณ  ๊ณ„์‹œ๋Š”๊ตฐ์š”,,
์Šคํ„ฐ๋””๋•Œ๋„ ๊ธฐ์ˆ ๋ฐœํ‘œ ๋•๋ถ„์— ์ž˜ ๋“ฃ๊ณ ์žˆ์Šต๋‹ˆ๋‹ค. ํŒŒ์ดํŒ…~!๐Ÿ‘

1๊ฐœ์˜ ๋‹ต๊ธ€
comment-user-thumbnail
2023๋…„ 11์›” 2์ผ

์ข‹์€ ๊ธ€ ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค~!

๋‹ต๊ธ€ ๋‹ฌ๊ธฐ