๊ฐœ๋ฐœ ๊ณต๋ถ€/Projects

Project) Olchaeneez NFT Preview(23.08.19 ์ˆ˜์ •)

Ryomi 2023. 8. 6. 22:13
728x90
๋ฐ˜์‘ํ˜•

 

 

๐Ÿ”—๋ฐฐํฌ๋งํฌ / ๐Ÿ”—gif test ๋ฐฐํฌ๋งํฌ

์ž‘์—…๊ธฐ๊ฐ„: 2023.07.26 - 2023.08.04(2์ฃผ)

 

# ํšŒ๊ณ  

์ข‹์€ ๊ธฐํšŒ๋กœ GGNZ ์ด๋ฒคํŠธ ํŽ˜์ด์ง€๋ฅผ ์ œ์ž‘ํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค. ๊ฐœ๋ฐœ์— ์•ž์„œ Canvas๋ฅผ ํ•™์Šตํ•˜์˜€์ง€๋งŒ, ์—ญ์‹œ๋‚˜ ์‰ฝ์ง€ ์•Š์€ ์ž‘์—…์ด์—ˆ๋‹ค. gif ํŒŒ์ผ์„ ๋‹ค๋ฃจ๊ธฐ๊ฐ€ ์‰ฝ์ง€ ์•Š์•„์„œ ๋”์šฑ ๊ทธ๋žฌ๋‹ค. ์ฒ˜์Œ ๊ณ„ํš๋Œ€๋กœ gif๋กœ ๋งŒ๋“ค๊ณ  animated gif ํ˜•ํƒœ๋กœ ์ €์žฅํ•˜๋Š” ํ˜•ํƒœ๊ฐ€ ์•„๋‹Œ png๋กœ ๋ ˆ์ด์–ด๋ฅผ ์Œ“์•„ customized Olchaeneez๋ฅผ ๋งŒ๋“ค๊ณ  png ์ €์žฅํ•˜๋Š” ํ˜•ํƒœ๊ฐ€ ๋˜์–ด ์•„์‰ฌ์›€์ด ๋งŽ์ด ๋‚จ๋Š”๋‹ค. ํ˜„์žฌ๋Š” sub domain์„ ๋”ฐ๋กœ ๋ฐ›์•„, gif๋กœ ์ž‘์—…ํ•  ์ˆ˜ ์žˆ๋Š” ํ˜•ํƒœ๋ฅผ ์‹œ๋„ํ•ด๋ณด๊ณ  ์žˆ๋Š” ์ค‘์ด๋‹ค. 

 

## ๊ธฐ์ˆ ์ ์ธ ๋ฌธ์ œ์  1 - canvas animated gif renderinng

canvas๋Š” animated gif๋ฅผ ์ง€์›ํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ ์ •์ ์ธ ์ด๋ฏธ์ง€๋งŒ ๋ณด์—ฌ์ฃผ๋Š” ์ด์Šˆ๊ฐ€ ์žˆ์—ˆ๋‹ค. ๋””์ž์ด๋„ˆ๋ถ„๊ป˜ ๋ฐ›์€ ๋ฐ์ดํ„ฐ๋Š” animated gif์ด๋ฏ€๋กœ, ์ด๋ฅผ ๋ Œ๋”๋ง ํ•˜๊ธฐ ์œ„ํ•ด ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•ด ํ•ด๊ฒฐํ•˜๊ณ ์ž ํ–ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ถ”๊ฐ€์ ์ธ ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ๋‹ค. ๋‹จ์ˆœํžˆ ํ•˜๋‚˜์˜ gif ํŒŒ์ผ์„ ๋ Œ๋”๋ง ํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹Œ, ์—ฌ๋Ÿฌ์žฅ์˜ animated gif ์ด๋ฏธ์ง€๋ฅผ canvas์— ์Œ“์•„์•ผ ํ–ˆ๊ณ  ์Œ“์ธ ์ด๋ฏธ์ง€๊ฐ€ ๋™์‹œ์— ํŠน์ • ์˜์—ญ์— ๋ณด์—ฌ์ ธ์•ผํ–ˆ๋‹ค.
gif๋ฅผ ์ด๋ ‡๊ฒŒ ๋‹ค๋ค„๋ณธ๊ฑด ์ฒ˜์Œ์ด์–ด์„œ ์•ฝ 3์ผ๊ฐ„ ์—ฌ๋Ÿฌ gif๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‹œ๋„ํ•ด๋ณด์•˜๋‹ค. ๊ทธ ์ค‘ gifencoder์˜ ๊ฒฝ์šฐ, ๊ฐ๊ฐ์˜ ์ด๋ฏธ์ง€๊ฐ€ ์‹œ๊ฐ„์ฐจ๋ฅผ ๋‘๊ณ  ์ˆœ์ฐจ์ ์œผ๋กœ ๋ Œ๋”๋ง ๋˜์–ด ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์—ˆ๋‹ค. ์—ฌ๋Ÿฌ ์‹œ๋„ ๋์— gifuct์™€ fabric ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•ด ์—ฌ๋Ÿฌ animated gif ์ด๋ฏธ์ง€๋ฅผ ๋ Œ๋”๋ง ํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

 

## ๊ธฐ์ˆ ์ ์ธ ๋ฌธ์ œ์  2 - canvas animated gif motion

23.08.04

 canvas์— gif animation์„ ๋ Œ๋”๋งํ•˜๋Š”๋ฐ๋Š” ์„ฑ๊ณตํ–ˆ์ง€๋งŒ, ๊ฐ๊ฐ์˜ gif ์ด๋ฏธ์ง€๋“ค์˜ ๋™์ž‘์ด ์„œ๋กœ ์ผ์น˜ํ•˜์ง€ ์•Š๋Š” ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค. setTimeout์„ ์‚ฌ์šฉํ•ด๋ณด๊ธฐ๋„ ํ•˜๊ณ  ์ด๋ฏธ์ง€๋ฅผ ๋ชจ๋‘ ๊ฐ€์ ธ์˜จ ํ›„ ์ด๋ฏธ์ง€๋ฅผ ๋ Œ๋”๋ง ํ•˜๊ฒŒ ํ•ด ๋ณด๊ธฐ๋„ ํ–ˆ์ง€๋งŒ, ๋ฌธ์ œ๊ฐ€ ํ•ด๊ฒฐ๋˜์ง€ ์•Š์•˜๋‹ค(dev ํ™˜๊ฒฝ์—์„œ๋Š” setTimeout์œผ๋กœ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์—ˆ์ง€๋งŒ, ๋ฐฐํฌ์‹œ์—๋Š” ๋‹ค์‹œ ๋™์ผํ•œ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค).
 ์ถ”๊ฐ€๋กœ canvas์— animated gif์˜ ๋ ˆ์ด์–ด๊ฐ€ ์Œ“์ผ ์ˆ˜๋ก, ์บ๋ฆญํ„ฐ์˜ ๋™์ž‘์ด ๋А๋ ค์ง€๊ณ  ๋ Œ๋”๋ง ๋˜๋Š”๋ฐ ์‹œ๊ฐ„์ด ์˜ค๋ž˜ ๊ฑธ๋ฆฐ๋‹ค๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ๋‹ค. ํ•ด๋‹น ํ”„๋กœ์ ํŠธ๋ฅผ ์œ„ํ•ด ์ถ”๊ฐ€์ ์œผ๋กœ canvas์— ๋Œ€ํ•ด ํ•™์Šตํ–ˆ์ง€๋งŒ, canvas ์‚ฌ์šฉ์„ ํฌ๊ธฐํ•˜๊ณ  figure tag์— ์ด๋ฏธ์ง€๋ฅผ ๋ Œ๋”๋ง ํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ–ˆ๋‹ค.(23.08.04)

 

23.08.19 

 

 8์›” ๋ง์— GGNZ ์˜คํ”„๋ผ์ธ ํ–‰์‚ฌ๊ฐ€ ์žˆ๋‹ค๋Š” ๋ง์—, ๋‹ค์‹œ ํ•œ ๋ฒˆ ํ•ด๋‹น ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ณ ์ž ๋„์ „ํ•ด ๋ณด์•˜๋‹ค. ๋จผ์ €, ๋ถ€ํŠธ์บ ํ”„ ๋ฉ˜ํ† ๋‹˜๊ป˜์„œ ์กฐ์–ธํ•ด์ฃผ์‹  ๋‚ด์šฉ์„ ๋ฐ”ํƒ•์œผ๋กœ ๋ชจ๋“  ์ด๋ฏธ์ง€๋ฅผ ๊ฐ€์ ธ์˜จ ํ›„์— canvas์— ๊ทธ๋ฆฌ๊ณ ์ž ํ–ˆ๋‹ค. ์ด์ „์—๋Š” Promise.all()์„ ์‚ฌ์šฉํ•ด์„œ canvas์— ์—ฌ๋Ÿฌ์žฅ์˜ animated gif๋ฅผ ๊ทธ๋ฆฌ๋Š” ์‹œ๋„๋ฅผ ํ–ˆ์—ˆ์ง€๋งŒ, ๋™์ž‘์ด ์ œ ๊ฐ๊ฐ์ด์–ด์„œ ์ด๋ฏธ์ง€ ์ž์ฒด๊ฐ€ ๋ถˆ์•ˆ์ •ํ•ด๋ณด์ด๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ด๋ฒˆ์—๋Š” fabric์˜ ๋‚ด์žฅ ํ•จ์ˆ˜์ธ 'requestAnimFrame()'์„ ์‚ฌ์šฉํ•ด ๋ชจ๋“  gif ํŒŒ์ผ์„ ๊ฐ€์ ธ์˜จ ํ›„ canvas์— ๊ทธ๋ฆฌ๋Š” ์ฝ”๋“œ๋ฅผ ์งฐ๋‹ค. ์™„๋ฒฝํ•˜์ง„ ์•Š์ง€๋งŒ, ์ด์ „๋ณด๋‹ค ๊ฐ gif  frame์ด ๋”ฐ๋กœ ๋…ผ๋‹ค๋Š” ๋А๋‚Œ์€ ์ค„์—ˆ๋‹ค. 

* fabric.util.requestAnimFrame
 ๋ธŒ๋ผ์šฐ์ €์—๊ฒŒ ๋‹ค์Œ ํ™”๋ฉด ๊ทธ๋ฆฌ๊ธฐ๊ฐ€ ์ผ์–ด๋‚˜๊ธฐ ์ „์— ํŠน์ • ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰ํ•˜๋„๋ก ์š”์ฒญํ•˜๋Š” ๊ธฐ๋Šฅ์œผ๋กœ ์ฃผ๋กœ ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ๋งŒ๋“ค ๋•Œ ์‚ฌ์šฉ๋œ๋‹ค.
 ์˜ˆ๋ฅผ ๋“ค์–ด, ์šฐ๋ฆฌ๊ฐ€ ์›€์ง์ด๋Š” ๊ณต์„ ํ™”๋ฉด์— ๊ทธ๋ฆฌ๊ณ  ์‹ถ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด๋ณด์ž. ๊ณต์ด ์›€์ง์ด๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์ด๊ฒŒ ํ•˜๋ ค๋ฉด ๊ณต์„ ๊ณ„์†ํ•ด์„œ ์ƒˆ๋กœ์šด ์œ„์น˜์— ๊ทธ๋ ค์•ผ ํ•œ๋‹ค. ์ด๋ฅผ ์œ„ํ•ด requestAnimationFrame์„ ์‚ฌ์šฉํ•˜๋ฉด, ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ํ™”๋ฉด์„ ๋‹ค์‹œ ๊ทธ๋ฆฌ๊ธฐ ์ง์ „์— ๊ณต์„ ์ƒˆ๋กœ์šด ์œ„์น˜์— ๊ทธ๋ฆฌ๋Š” ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰ํ•˜๋„๋ก ์š”์ฒญํ•  ์ˆ˜ ์žˆ๋‹ค.
 ์ด๋Ÿฐ ์‹์œผ๋กœ requestAnimationFrame์„ ์‚ฌ์šฉํ•˜๋ฉด, ์• ๋‹ˆ๋ฉ”์ด์…˜์ด ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ๋ณด์ด๊ฒŒ ํ•  ์ˆ˜ ์žˆ๋‹ค. ์™œ๋ƒํ•˜๋ฉด ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ํ™”๋ฉด์„ ๊ทธ๋ฆฌ๋Š” ํƒ€์ด๋ฐ์— ๋งž์ถฐ ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ์—…๋ฐ์ดํŠธํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.
 fabric.util.requestAnimFrame์€ fabric.js ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์—์„œ ์ œ๊ณตํ•˜๋Š” requestAnimationFrame์˜ wrapper ํ•จ์ˆ˜๋‹ค. ์ด ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด fabric.js๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ Canvas์— ๊ทธ๋ ค์ง„ ๊ฐ์ฒด๋“ค์˜ ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ์‰ฝ๊ฒŒ ์ œ์–ดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

 

## ์ƒˆ๋กœ ๋ฐฐ์šด stack 1 -  ํด๋” ๋‚ด ๋ชจ๋“  ํŒŒ์ผ import  (Webpack require.context)

layer ํด๋” ํ•˜์œ„์˜ ํด๋” ๋‚ด์— ์žˆ๋Š” ๋ชจ๋“  ์ด๋ฏธ์ง€ ํŒŒ์ผ์„ import ํ•ด์™€์•ผ ํ–ˆ๋‹ค. ํŒŒ์ผ ์–‘์ด ๋ณดํ†ต 50๊ฐœ๋ผ ํ•˜๋‚˜์”ฉ import ํ•˜๊ธฐ์—๋Š” ๋ฌด๋ฆฌ๊ฐ€ ์žˆ์—ˆ๊ธฐ์— ์ดํ‹€ ๋™์•ˆ ๊ตฌ๊ธ€๋งํ•˜์—ฌ ๋ฐฉ๋ฒ•์„ ์ฐพ์„ ์ˆ˜ ์žˆ์—ˆ๋‹ค. webpack์˜ require.context()๋ฅผ ์‚ฌ์šฉํ•ด ํŠน์ • ์œ„์น˜์˜ ํด๋”์— ์ ‘๊ทผํ•ด ํ•˜์œ„์˜ ๋ชจ๋“  ํŒŒ์ผ์„ importํ•  ์ˆ˜ ์žˆ๊ฒŒํ•ด ์ฃผ๋‹ˆ, ์ •๋ง ํŽธ๋ฆฌํ•˜๊ฒŒ ์‚ฌ์šฉํ–ˆ๋‹ค.

const imgUrl: IImgUrl = {
    base: {
      base: require.context("/public/ํŒŒ์ผ๊ฒฝ๋กœ", false, /\.(png|gif)$/),
    },
...
}

const markdownContext: __WebpackModuleApi.RequireContext =
    imgUrl[์ƒ์œ„ํด๋”][ํ•˜์œ„ํด๋”];

  const importAll = (requireContext: __WebpackModuleApi.RequireContext) => {
    const keys = requireContext.keys();
    const images: IModule[] = keys.map((key: string) => {
      return requireContext(key) as IModule;
    });

    return images.map((el) => el.default.src).slice(images.length / 2);
  };

  const importFile = () => {
    return require(`/ํŒŒ์ผ๊ฒฝ๋กœ)
      .default;
  };

  return  fileName ? importFile() : importAll(markdownContext);

reference) https://eams.dev/post/require-context

 

## ์ƒˆ๋กœ ๋ฐฐ์šด stack 2 -  canvas API (Github,  ๋ฐฐํฌ์‚ฌ์ดํŠธ)

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

 ์ฒ˜์Œ์—๋Š” canvas, context ๋“ฑ ์ฒ˜์Œ ๋‹ค๋ค„๋ณด๋Š” ๊ฒƒ๋“ค์ด๋ผ ์ƒ์†Œํ–ˆ์ง€๋งŒ, ํ† ์ดํ”„๋กœ์ ํŠธ์— ํ•˜๋‚˜, ๋‘˜ ์”ฉ ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•˜๋ฉด์„œ ์ง์ ‘ ๊ทธ๋ฆผํŒ์˜ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์ด ํฅ๋ฏธ๋กญ๊ฒŒ ๋‹ค๊ฐ€์™”๋‹ค. 

* ๊ธฐ๋Šฅ : ๋ธŒ๋Ÿฌ์‰ฌ ํฌ๊ธฐ์กฐ์ ˆ, ๋ฐฐ๊ฒฝ์ƒ‰ ๋„ฃ๊ธฐ, ์ง€์šฐ๊ฐœ, ์บ”๋ฒ„์Šค ๋น„์šฐ๊ธฐ, ์ด๋ฏธ์ง€ ํŒŒ์ผ ๋„ฃ๊ธฐ, ์บ”๋ฒ„์Šค ์ด๋ฏธ์ง€ ์ €์žฅ

 

## ์ƒˆ๋กœ ๋ฐฐ์šด stack 3 -  canvas image ์ €์žฅ - png

canvas์— animated gif๋ฅผ ์—๋Ÿฌ์—†์ด ๋ Œ๋”๋ง ํ•˜์ง€ ๋ชปํ•ด, ๋ณด๋‹ค ์•ˆ์ •์ ์ธ png ํ˜•ํƒœ์˜ ์ด๋ฏธ์ง€๋ฅผ ํ™”๋ฉด์— ๋ณด์—ฌ์ฃผ๊ณ  ์ด๋ฅผ ์‚ฌ์šฉ์ž๊ฐ€ ๋กœ์ปฌ์— ์ €์žฅํ•  ์ˆ˜ ์žˆ๊ฒŒ ๊ฐœ๋ฐœํ–ˆ๋‹ค. ํ•ด๋‹น ๋ถ€๋ถ„์€ html2canvas ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•ด ์‰ฝ๊ฒŒ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค. ํŠนํžˆ ์ด๋ฏธ์ง€๊ฐ€ ๋“ค์–ด์žˆ๋Š” tag๊ฐ€ canvas tag๊ฐ€ ์•„๋‹ˆ๋”๋ผ๋„ png ํ˜•ํƒœ๋กœ ์ด๋ฏธ์ง€๋ฅผ ์ €์žฅํ•  ์ˆ˜ ์žˆ๋Š” ๋ถ€๋ถ„์ด ์‹ ๊ธฐํ–ˆ๋‹ค. 

if (figureRef.current) {
    html2canvas(figureRef.current).then((canvas) => {
      const link = document.createElement("a");
      link.download = "image.png";
      link.href = canvas.toDataURL();
      link.click();
    });
}

 

 

## ์ƒˆ๋กœ ๋ฐฐ์šด stack 4 -  canvas image ์ €์žฅ - webm

animated gif๋ฅผ canvas์— ๊ทธ๋ฆฐ ํ›„, animation ํ˜•ํƒœ๋กœ ๋กœ์ปฌ์— ์ €์žฅํ•  ์ˆ˜ ์žˆ๋„๋ก ์‹œ๋„ํ•ด ๋ณด์•˜๋‹ค. ์ดˆ๊ธฐ์—๋Š” ccapture ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์‚ฌ์šฉ์— ๋Œ€ํ•ด ์•Œ์•„๋ณด๋‹ค๊ฐ€ stackoverflow์—์„œ canvas capturestream๊ณผ mediaRecoder๋ฅผ ์‚ฌ์šฉํ•ด canvas์— ๊ทธ๋ ค์ง„ animation์„ webm ํ˜•ํƒœ๋กœ ์ €์žฅํ•˜๊ณ  ์žˆ๋‹ค๋Š” ๊ธ€์—์„œ ํžŒํŠธ๋ฅผ ์–ป์–ด ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ–ˆ๋‹ค.

HTMLCanvasElement์˜ capturestream()์„ ์‚ฌ์šฉํ•ด canvas์— ๊ทธ๋ ค์ง„ midia stream์„ captureํ•œ ํ›„ ์ด๋ฅผ video ํ˜•์‹์œผ๋กœ ๊ธฐ๋กํ–ˆ๋‹ค. ์ดํ›„ chunks ๋ฐฐ์—ด์— ๋‹ด์•„ Blob๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•œ ํ›„ ์‚ฌ์šฉ์ž์˜ ๋กœ์ปฌ์— ์ €์žฅํ•˜๋„๋ก ํ–ˆ๋‹ค.

const canvas = canvasRef.current as HTMLCanvasElement;
if (canvas) {
  const stream = canvas.captureStream(); // ์บ”๋ฒ„์Šค์—์„œ ๋ฏธ๋””์–ด ์ŠคํŠธ๋ฆผ์„ ์บก์ฒ˜
  const mediaRecorder = new MediaRecorder(stream, { // ์บก์ฒ˜ํ•œ ์ŠคํŠธ๋ฆผ์„ ์‚ฌ์šฉํ•ด ์ƒˆ๋กœ์šด MediaRecorder instance ์ƒ์„ฑ
    mimeType: "video/webm", // instance๋Š” video/webm ํ˜•์‹์„ ๋น„๋””์˜ค๋กœ ๊ธฐ๋กํ•จ
  });

  const chunks: Blob[] = []; // ๊ธฐ๋ก๋œ ๋น„๋””์˜ค ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•  ๋ฐฐ์—ด ์ƒ์„ฑ
  
  // dataavailable ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ ์„ค์ •
  // ํ•ธ๋“ค๋Ÿฌ๋Š” media data๊ฐ€ ์‚ฌ์šฉ๊ฐ€๋Šฅํ•ด์งˆ ๋•Œ ๋งˆ๋‹ค ํ˜ธ์ถœ๋˜์–ด ๋ฐ์ดํ„ฐ๋ฅผ chunks ๋ฐฐ์—ด์— ์ถ”๊ฐ€
  mediaRecorder.ondataavailable = (event) => {
    chunks.push(event.data); 
  };

// stop() ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ ์„ค์ •
// ๋ฏธ๋””์–ด ๊ธฐ๋ก์ด ์ค‘์ง€๋  ๋•Œ ํ˜ธ์ถœ๋˜๋ฉฐ, ์ด๋•Œ, chunks ๋ฐฐ์—ด์— ์ €์žฅ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•ด ์ƒˆ๋กœ์šด Blob ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑ ๋ฐ ๋‹ค์šด๋กœ๋“œ ๋งํฌ ์ œ๊ณต
  mediaRecorder.onstop = () => { 
    const blob = new Blob(chunks, { type: "video/webm" });

    const downloadLink = document.createElement("a");
    downloadLink.href = URL.createObjectURL(blob);
    downloadLink.download = "์ œ๋ชฉ์—†์Œ.webm";
    downloadLink.click();
  };

  mediaRecorder.start(); // ๋ฏธ๋””์–ด ๊ธฐ๋ก ์‹œ์ž‘

  setTimeout(() => {
    mediaRecorder.stop();
  }, 1600);
}

reference)

 

Record animation of canvas with a long time

I have to record animation from canvas and the animation takes about 2 minutes. I'm using MediaRecorder and canvas captureStream to record the canvas, everything works well, but we have to wait for 2

stackoverflow.com

 

 

## ์ƒˆ๋กœ ๋ฐฐ์šด stack 5 -  canvas image ์ €์žฅ - gif

 webm ํ˜•ํƒœ๋กœ ์ €์žฅํ•˜๋Š” ๊ฒƒ์œผ๋กœ test ํŽ˜์ด์ง€ ๊ฐœ๋ฐœ์„ ๋งˆ๋ฌด๋ฆฌํ•˜๋ ค ํ–ˆ๋‹ค. ํ•˜์ง€๋งŒ, ์‚ฌ์šฉ์ค‘์ธ ์•„์ดํฐ safari์—์„œ๋Š” webm ํŒŒ์ผ์„ ๋‹ค์šด๋กœ๋“œํ•  ์ˆ˜ ์—†๋Š”๊ฒƒ์œผ๋กœ ๋ณด์—ฌ์กŒ๋‹ค. ๊ทธ๋ž˜์„œ ์–ด์ฉ” ์ˆ˜ ์—†์ด gif๋กœ ์ €์žฅํ•˜๋Š” ํ˜•ํƒœ๋กœ ๋‹ค์‹œ ๊ฐœ๋ฐœ์„ ํ–ˆ๋‹ค. webm์˜ ๊ฒฝ์šฐ ์‚ฌ์šฉ์ž๊ฐ€ ์ €์žฅ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด ํ‰๊ท  1์ดˆ ๋‚ด๋กœ canvas์— ๊ทธ๋ ค์ง„ animation์„ ๋กœ์ปฌ์— ์ €์žฅํ•  ์žˆ์—ˆ๋‹ค. ํ•˜์ง€๋งŒ, gif์˜ ๊ฒฝ์šฐ, ์ƒ๊ฐ๋ณด๋‹ค ๊ธด ์‹œ๊ฐ„์ด ์ง€๋‚œ ํ›„์—์•ผ animated gif๊ฐ€ ๊ทธ๋ ค์กŒ๊ธฐ์—, ux๋ฅผ ๊ณ ๋ คํ•œ๋‹ค๋ฉด, gif๋กœ ์ €์žฅํ•˜๋Š” ๊ฒƒ์€ ์ง€์–‘ํ•˜๊ณ  ์‹ถ์—ˆ๋‹ค. ํ•˜์ง€๋งŒ, ๋ถˆ๊ฐ€ํ”ผํ–ˆ๋‹ค.

gif.js ๋ฅผ ์‚ฌ์šฉํ•ด canvas์˜ animated gif๋ฅผ ์ €์žฅํ•˜๋Š” ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ–ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ €์žฅ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋‹ˆ ๋‹ค์Œ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค. 

 Failed to load resource: the server responded with a status of 404 (Not Found)

 

node_modules ํด๋” ๋‚ด @types ํด๋”์˜ gif.js ํด๋”์— gif.worker.js ํŒŒ์ผ์ด ์žˆ์ง€๋งŒ, ์„œ๋ฒ„๊ฐ€ ์š”์ฒญํ•œ ๋ฆฌ์†Œ์Šค๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†๋‹ค๋Š” ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค. ์ฒซ ๋ฒˆ์งธ ๋ฐฉ๋ฒ•์œผ๋กœ web-loader๋ฅผ ์„ค์น˜ํ•˜๊ณ  next.config.js ํŒŒ์ผ์„ ์ˆ˜์ •ํ•œ ํ›„ ๋‹ค์‹œ save ๋ฒ„ํŠผ์„ ํด๋ฆญํ•ด๋ดค์ง€๋งŒ, ์—ฌ์ „ํžˆ ๋™์ผํ•œ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค. 

๋‘ ๋ฒˆ์งธ ๋ฐฉ๋ฒ•์œผ๋กœ public ํด๋”์— gif.worker.js๋ฅผ ๋„ฃ์–ด์ฃผ๋‹ˆ save ๋ฒ„ํŠผ์˜ ๊ธฐ๋Šฅ์ด ์ •์ƒ์ ์œผ๋กœ ๋™์ž‘ํ–ˆ๋‹ค. 

 

728x90
๋ฐ˜์‘ํ˜•