Canvas API๋ฅผ ์ด์ฉํ์ฌ ํ ์คํธ๊ฐ ๋ง์ฐ์ค์ ์ํธ์์ฉํ๋ฉฐ, ๋ชจ๋์ฒ๋ผ ํฉ์ด์ง๋ ํจ๊ณผ์ ๋๋ค.
IntroPage.tsx
์ MainPage.tsx
์์ ์ฌ์ฉ๋๊ธฐ ๋๋ฌธ์, ์ฌ์ฌ์ฉ์ ์ํด ScatterCanvas.tsx
์ปดํฌ๋ํธ๋ก ๋ถ๋ฆฌํ์ฌ ๊ด๋ฆฌํฉ๋๋ค.
*** pointerDiv
- ์ปจ๋ฒ์ค ์์(๋ ๋์ z-index) ์์นํ Div Element
๋ก, ๊ทธ๋ ค์ง text์ ๊ฐ์ ๋๋น, ๋์ด๋ฅผ ๊ฐ์ง๊ณ text์ ๊ฐ์ ์์น์ ์กด์ฌํฉ๋๋ค. Scatter ํจ๊ณผ์ ์งํ์ ์ํด์ ์ฌ์ฉ์์ ๋ง์ฐ์ค์ด๋ฒคํธ(onMouseUp
, onMouseDown
)์ ๋ฐ๊ณ , ์ฌ์ฉ์์ ๋ง์ฐ์ค ํด๋ฆญ์ ์ ๋ํ๊ธฐ ์ํด cursor: pointer
์คํ์ผ์ด ์ ์ฉ๋์ด ์์ต๋๋ค.
1๏ธโฃ canvas์ ์ํ๋ ํฐํธ๋ก text ๊ทธ๋ฆฌ๊ธฐ ๋ฐ pointerDiv ๋๋น์ ๋์ด ์ค์
fillText
๋ฉ์๋๋ฅผ ์ด์ฉํ์ฌ ์ํ๋ ํฐํธ๋ก ์ปจ๋ฒ์ค์ text๋ฅผ ๊ทธ๋ ค์ค๋๋ค.
์ปจ๋ฒ์ค์๋ cssํ์ผ์ ์์ฑํด์ค mediaQuery font-size๊ฐ ์ ์ฉ๋์ง ์๊ธฐ ๋๋ฌธ์ ์ปจ๋ฒ์ค์ ๋๋น์ ๋ฐ๋ผ ํฐํธ ์ฌ์ด์ฆ๊ฐ์ ๋ฐํํด์ฃผ๋ ๊ฐ์ฒด(canvasFontSize
)๋ฅผ ๋ณ๋๋ก ์์ฑํด์ฃผ์์ต๋๋ค.
์ฃผ์ํ ์ ์ ์ฌ์ฉํ๋ คํ๋ ํฐํธ์ load๊ฐ ์๋ฃ๋ ์ดํ์ text๋ฅผ ๊ทธ๋ ค์ฃผ์ด์ผ ํ๋ค๋ ๊ฒ์
๋๋ค. Web API ์ค CSS Font Loading API
๋ฅผ ์ด์ฉํ์ฌ ํฐํธํ์ผ์ ๋ก๋ฉ์ด ์๋ฃ๋ ์ดํ์ text๋ฅผ ๊ทธ๋ ค์ค๋๋ค.
text๋ฅผ ๊ทธ๋ฆฐ ํ pointerDiv
์ ๋๋น์ ๋์ด๋ฅผ ์ค์ ํด์ค๋๋ค. ๋์ด๋ text ๋ฐฐ์ด์ ๊ธธ์ด๋ฅผ ํตํด ๊ณ์ฐํด์ค totalHeight
๊ฐ์ด ๋๊ณ , ๋๋น๋ text ๋ฐฐ์ด์ค ๊ฐ์ฅ ๊ธด text์ ๋๋น๊ฐ์ด ๋ฉ๋๋ค. ๋๋น๊ฐ์ ์ปจ๋ฒ์ค์ measureText
๋ฉ์๋๋ฅผ ํตํด ๊ณ์ฐ๋ ๊ฐ์ ๊ฐ์ ธ์ต๋๋ค.
drawText
ํจ์const drawText = useCallback(() => {
if (!canvas) return;
const FONT_SIZE = canvasFontSize(canvas.CANVAS_WIDTH);
const LINE_VALUE = 5; // ์ค ๊ฐ๊ฒฉ ๊ฐ 5px
const ctx = canvas.ctx as CanvasRenderingContext2D;
const totalHeight = text.length * FONT_SIZE + (LINE_VALUE * text.length - 1);
const x = canvas.CANVAS_WIDTH / 2;
const y = (canvas.CANVAS_HEIGHT - totalHeight) / 2 + FONT_SIZE;
let met;
let maxWidth = 0;
let lineHeight = 0;
const fontFile = new FontFace("KoPubWorld Medium", `url(${font_ttf}) format("truetype")`);
document.fonts.add(fontFile);
fontFile
.load()
.then(() => {
ctx.font = `${FONT_SIZE}px KoPubWorld Medium`;
ctx.fillStyle = "#FFFFFF";
ctx.textAlign = "center";
for (let i = 0; i < text.length; i++) {
ctx.fillText(text[i], x, y + FONT_SIZE * i + lineHeight);
lineHeight += LINE_VALUE;
met = ctx.measureText(text[i]);
if (!maxWidth) maxWidth = met.width;
else if (maxWidth < met.width) maxWidth = met.width;
}
if (speechService.tts)
speechService.synth.speak(text.join(""), {
endEvent: () => {
onMouseDownPointerDiv({ width: maxWidth, height: totalHeight });
onMouseUpPointerDiv();
},
});
else setPointerDiv({ width: maxWidth, height: totalHeight });
})
.catch((err: string) => {
console.log(`font ์๋ฌ ๋ฐ์: ${err}`);
});
}, [canvas, text, speechService, onMouseDownPointerDiv, onMouseUpPointerDiv]);
useEffect(() => {
if (!canvas) return;
canvas.init();
canvas.setFrame(15);
drawText();
const myResize = debounce(() => {
**// resize ์ด๋ฒคํธ๋ animation ์คํ์ค์๋ ์คํ๋์ง ์์ต๋๋ค.**
if (canvas.isAnim) return;
canvas.init();
drawText();
}, 300);
window.addEventListener("resize", myResize);
return () => {
window.removeEventListener("resize", myResize);
};
}, [canvas, drawText]);