-
Notifications
You must be signed in to change notification settings - Fork 7
๐ชต 4. ์บ๋ฒ์ค ์ฑ๋ฅ ์ต์ ํ ์๋ โ requestAnimationFrame, throttle, offscreenCanvas
D.Joung edited this page Dec 5, 2024
·
2 revisions
- requestAnimationFrame (๋ฏธ์ ์ฉ)
- offscreenCanvas (๋ฏธ์ ์ฉ)
- throttle (์์ ํ์ด์ง ์บ๋ฒ์ค์๋ง ์ ์ฉ)
- drawingBuffer ๋ฐฐ์ด์ ๋ง๋ค์ด์ ๋๋ก์ ํ ์์
๋ค์ ์ ์ฅํ ํ,
requestAnimationFrame
์ ํตํด ์์ฐจ์ ์ผ๋ก ์ฒ๋ฆฌํ๋ ์ต์ ํ ๋ก์ง์ ์๋ํด ๋ณด์์ต๋๋ค. ํ์ง๋ง ์๋์ ์ ์ ๋ฐ์์ ๋ ์ ์ฒด ์ ์ redrawํ๋ ๊ณผ์ ์์,ctx.stroke()
ํธ์ถ ๋จ์๋ก ๋งค ํ๋ ์๋ง๋ค ๋ ๋๋ง๋๋ ์ด์๊ฐ ๋ฐ์ํ์ต๋๋ค. - ์ด๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด drawingBuffer์ ๋ฃ์ ๋ type ๋ถ์ฌํ์ต๋๋ค.
-
line
orredraw
-
- ์ดํ
OffscreenCanvas
๋ฅผ ์ฌ์ฉํด ๋ฉํฐ ์ค๋ ๋์์ ๋๋ก์ ๋ก์ง์ ์ํ ํ ๋ณํฉํ๋ ๋ฐฉ์๊น์ง ์๋ํด๋ดค์ง๋ง, ํด๋น ๋ฐฉ์์ ๊ฒฝ์ฐ ๋ฆฌ๋ ๋๋ง์ด ๋ฐ์ํ ๋ ๋ง๋ค ํ๋ฉด ๊น๋นก์์ด ๋ฐ์ํด ์ฌ์ฉํ ์ ์์์ต๋๋ค.- ๋ฆฌ๋ ๋๋ง์ด ๋น๋ฒํ ๋๋ ๋ถ์ ํฉํ ๊ฒ์ผ๋ก ๋ณด์ ๋๋ค.
interface DrawingLine {
type: 'line';
data: DrawingData;
}
interface DrawingRedraw {
type: 'redraw';
data: DrawingData[];
}
type DrawingBuffer = DrawingLine | DrawingRedraw;
const drawingBufferRef = useRef<DrawingBuffer[]>([]);
- drawStroke์ ๋ฐ๋ก ์์ ์ ๋งก๊ธฐ๋ ๋์ drawingBuffer์ ์ ์ฅ ํ requestAnimationFrame ๋ฉ์๋๋ก ์ต์ ์์ ์์ผ๋ก ์ํํ์์ต๋๋ค.
state.drawingBufferRef.current.push({ type: 'line', data: updatedDrawing });
useEffect(() => {
let lastTime: DOMHighResTimeStamp = 0;
const drawingBuffer = state.drawingBufferRef.current;
const drawAnimation = (timestemp: DOMHighResTimeStamp) => {
if (timestemp - lastTime > 16) {
if (drawingBuffer.length >= 0) {
const currentDraw = drawingBuffer.shift();
if (currentDraw) {
if (currentDraw.type === 'line') drawStroke(currentDraw.data);
else if (currentDraw.type === 'redraw') {
const drawDataList = currentDraw.data;
const { ctx } = getCanvasContext(canvasRef);
const offScreen = new OffscreenCanvas(MAINCANVAS_RESOLUTION_WIDTH, MAINCANVAS_RESOLUTION_HEIGHT);
const worker = new Worker('canvasWorker.js');
worker.postMessage({ canvas: offScreen, dataList: drawDataList }, [offScreen]);
worker.onmessage = (event) => {
const bitmap = event.data; // ImageBitmap ๊ฐ์ฒด
ctx.drawImage(bitmap, 0, 0); // ๋ฉ์ธ ์บ๋ฒ์ค ์์ ๋ณํฉ
};
}
}
}
lastTime = timestemp;
}
requestAnimationFrame(drawAnimation);
};
const aniId = requestAnimationFrame(drawAnimation);
return () => {
if (aniId) cancelAnimationFrame(aniId);
};
- react ๊ฐ๋ฐ ํ๊ฒฝ์์๋ public ํด๋ ์๋์ ์์ด์ผ ์ ์ ์๋ํฉ๋๋ค.
self.onmessage = (event) => {
const {canvas, dataList} = event.data;
const ctx = canvas.getContext('2d');
ctx.beginPath();
dataList.forEach((drawingData) => {
const { points, style } = drawingData;
ctx.save();
ctx.strokeStyle = style.color;
ctx.lineWidth = style.width;
points.forEach((point, idx) => {
const { x, y } = point;
if (idx === 0) ctx.moveTo(x, y);
else ctx.lineTo(x, y);
});
});
ctx.stroke();
const bitmap = canvas.transferToImageBitmap();
// ์์
์๋ฃ ๋ฉ์์ง ์ ์ก
self.postMessage(bitmap, [bitmap]);
};
- ํ์ง๋ง ์ ์ฉ ์ํ๋๋ ๋ชปํ ๊ฒ ๊ฐ์ ๋ฏธ์ ์ฉ ์ํ๋ก ๋กค๋ฐฑํ์์ต๋๋ค.
- ์๋ฌด๋๋ ํ๋ ์ ๋น ์ฒ๋ฆฌ๋์ ๋๋ฌด ๋ง์ด ๋ถ์ฌํ ๊ฒ ๊ฐ์ต๋๋ค.
useEffect(() => {
let lastTime: DOMHighResTimeStamp = 0;
const drawingBuffer = state.drawingBufferRef.current;
const drawAnimation = (timestemp: DOMHighResTimeStamp) => {
if (timestemp - lastTime > 16) {
if (drawingBuffer.length >= 0) {
const currentDraw = drawingBuffer.shift();
if (currentDraw) {
if (currentDraw.type === 'line') drawStroke(currentDraw.data); //type์ด line์ผ ๊ฒฝ์ฐ๋ ์ผ๋ฐ ๋๋ก์
if (currentDraw.type === 'redraw') //type์ด redraw์ผ ๊ฒฝ์ฐ์๋ ์ ์ฒด ๋๋ก์ ๋ผ์ธ๋ค์ ๋ฐฐ์ด
currentDraw.data.forEach((drawingData) => {
drawStroke(drawingData);
});
}
}
lastTime = timestemp;
}
requestAnimationFrame(drawAnimation);
};
const aniId = requestAnimationFrame(drawAnimation);
return () => {
if (aniId) cancelAnimationFrame(aniId);
};
}, [drawStroke, state.drawingBufferRef.current]);
- ์์ํ๋ฉด background์ ๋ง์ฐ์ค ์ปค์คํฐ๋ง์ด์ง์ ๊ตฌํํ ๋ก์ง์ throttle์ ์ ์ฉํด ์ต์ ํ๋ฅผ ์๋ํ์์ต๋๋ค.
- Test ํ๊ฒฝ
- ๊ฐค๋ญ์๋ถ2 - intel CORE i7 - RAM 16GB - x64
- windows 11 Home
- chrome ๋ธ๋ผ์ฐ์
-
mousemove ๋ฐ์ ์ฃผ๊ธฐ
-
performance.now()
๋ฅผ ์ฌ์ฉํด mousemove ์ด๋ฒคํธ ๊ฐ๊ฒฉ์ console.log๋ก ์ถ๋ ฅํ์ต๋๋ค.- ์ค๊ฐ์ 100๋จ์ ๋๋ ์๋ค์ ๋ง์ฐ์ค๋ฅผ ์ ๊น ๋ฉ์ท๋ค๊ฐ ๋ค์ ์์ง์์ ๋ ๋ฐ์
-
๋๋ต
7~8ms
๊ฐ๊ฒฉ์ผ๋ก ๋ฐ์ํ์ต๋๋ค.
currentTimestamp.current = performance.now(); console.log(currentTimestamp.current - lastTimestamp.current); lastTimestamp.current = currentTimestamp.current;
-
-
๊ฐ์ด๋ฐ ๋ก๊ณ ๋ฐ ๋ฒํผ ๊ธฐ์ค์ผ๋ก ๋ ๋ฐํด ๊ทธ๋ ธ์ ๋ ๋ฒํผ ์ ์ขํ ์๋ฅผ ์ธก์ ํ์์ต๋๋ค.
- 400~600๊ฐ ๊ฐ๋ ์ขํ๊ฐ ์์ ๋๋ค.
- 16ms ์ด์ ๊ฐ๊ฒฉ์์๋ ๋๋ก์์ด ์๊ฐ์ ์ผ๋ก ์กฐ๊ธ ๋ถ์์ ํ์ต๋๋ค. ๋ฐ๋ผ์ 16ms ๊ฐ๊ฒฉ์ผ๋ก ์ขํ๋ฅผ ์ํ๋งํฉ๋๋ค.
currentTimestamp.current = performance.now();
if (currentTimestamp.current - lastTimestamp.current < 16) return;
console.log(currentTimestamp.current - lastTimestamp.current); //๊ฐ๊ฒฉ ์ถ๋ ฅ
lastTimestamp.current = currentTimestamp.current;
-
mousemove ๋ฐ์ ์ฃผ๊ธฐ
-
performance.now()
๋ฅผ ์ฌ์ฉํด mousemove ์ด๋ฒคํธ ๊ฐ๊ฒฉ์ console.log๋ก ์ถ๋ ฅํด๋ด ๋๋ค.- ์ค๊ฐ์ 100๋จ์ ๋๋ ์๋ค์ ๋ง์ฐ์ค๋ฅผ ์ ๊น ๋ฉ์ท๋ค๊ฐ ๋ค์ ์์ง์์ ๋ ๋ฐ์
- ๋๋ต
16~25ms
์ฌ์ด๋ก ๋ฐ์
-
-
๊ฐ์ด๋ฐ ๋ก๊ณ ๋ฐ ๋ฒํผ ๊ธฐ์ค์ผ๋ก ๋ ๋ฐํด ๊ทธ๋ ธ์ ๋ ๋ฒํผ ์ ์ขํ ์๋ฅผ ์ธก์ ํ์์ต๋๋ค.
- 200~300๊ฐ ๊ฐ๋ ์ขํ๊ฐ ์์์ต๋๋ค.
-
ํ ์คํธ PC ๊ธฐ์ค throttle, requestAnimationFrame ๋ ๋ค
16ms
๊ฐ๊ฒฉ์ผ๋ก ๋ฐ์์ํค๋ฉด ์ ์ด ๋งค๋๋ฝ๊ฒ ์ด์ด์ง๋ฏ๋ก, ์ํ๋ง ๊ฐ๊ฒฉ์ด16ms
๋ก ๊ท์น์ ์ผ๋ก ๋ฐ์ํ ์ ์๋๋ก ๊ตฌํํ์์ต๋๋ค.
- 1. ๊ฐ๋ฐ ํ๊ฒฝ ์ธํ ๋ฐ ํ๋ก์ ํธ ๋ฌธ์ํ
- 2. ์ค์๊ฐ ํต์
- 3. ์ธํ๋ผ ๋ฐ CI/CD
- 4. ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์์ด Canvas ๊ตฌํํ๊ธฐ
- 5. ์บ๋ฒ์ค ๋๊ธฐํ๋ฅผ ์ํ ์์ CRDT ๊ตฌํ๊ธฐ
-
6. ์ปดํฌ๋ํธ ํจํด๋ถํฐ ์น์์ผ๊น์ง, ํจ์จ์ ์ธ FE ์ค๊ณ
- ์ข์ ์ปดํฌ๋ํธ๋ ๋ฌด์์ธ๊ฐ? + Headless Pattern
- ํจ์จ์ ์ธ UI ์ปดํฌ๋ํธ ์คํ์ผ๋ง: Tailwind CSS + cn.ts
- Tailwind CSS๋ก ๋์์ธ ์์คํ ๋ฐ UI ์ปดํฌ๋ํธ ์ธํ
- ์น์์ผ ํด๋ผ์ด์ธํธ ๊ตฌํ๊ธฐ: React ํ๊ฒฝ์์ ํจ์จ์ ์ธ ์น์์ผ ์ํคํ ์ฒ
- ์น์์ผ ํด๋ผ์ด์ธํธ ์ฝ๋ ๋ถ์ ๋ฐ ๊ณต์
- 7. ํธ๋ฌ๋ธ ์ํ ๋ฐ ์ฑ๋ฅ/UX ๊ฐ์
- 1์ฃผ์ฐจ ๊ธฐ์ ๊ณต์
- 2์ฃผ์ฐจ ๋ฐ๋ชจ ๋ฐ์ด
- 3์ฃผ์ฐจ ๋ฐ๋ชจ ๋ฐ์ด
- 4์ฃผ์ฐจ ๋ฐ๋ชจ ๋ฐ์ด
- 5์ฃผ์ฐจ ๋ฐ๋ชจ ๋ฐ์ด
- WEEK 06 ์ฃผ๊ฐ ๊ณํ
- WEEK 06 ๋ฐ์ผ๋ฆฌ ์คํฌ๋ผ
- WEEK 06 ์ฃผ๊ฐ ํ๊ณ