Skip to content

new-name/backend

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

18 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

New Name



New Name์€ Text, SVG, Image, GIF๋ฅผ ์ด์šฉํ•ด ์‰ฝ๊ฒŒ ์„ธ๋กœํ˜• ๋ช…ํ•จ์„ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋Š” ๋ชจ๋ฐ”์ผ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์ž…๋‹ˆ๋‹ค.

๋ช…ํ•จ์„ ์ƒ์„ฑํ•˜๊ณ  ์ด๋ฏธ์ง€ ํ˜น์€ GIF๋กœ ์ €์žฅํ•˜์—ฌ ์†์‰ฝ๊ฒŒ ๋ช…ํ•จ์„ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


๋ชฉ์ฐจ



๋™๊ธฐ

์ด๋ฒˆ ํ”„๋กœ์ ํŠธ์˜ ๋ชฉํ‘œ๋Š” ๋ชจ๋ฐ”์ผ์ด๋ผ๋Š” ํŠน์„ฑ์ƒ ๋งˆ์šฐ์Šค๊ฐ€ ์•„๋‹Œ ์†๊ฐ€๋ฝ์„ ์ด์šฉํ•ด GUI ์ž˜ ์ด์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ์ด ์žˆ๋Š” ๋ชจ๋ฐ”์ผ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋งŒ๋“ค์–ด๋ณด๊ณ ์ž ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

์ด ๋ชฉํ‘œ์— ์ ํ•ฉํ•œ ์•„์ด๋””์–ด๋ฅผ ๊ณ ๋ฏผํ•˜๋‹ค ์›ํ•˜๋Š” ์ด๋ฏธ์ง€๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๋ชจ๋ฐ”์ผ ์ด๋ฏธ์ง€ ์—๋””ํ„ฐ ํˆด์„ ๊ตฌํ˜„ํ•  ๊ฒฝ์šฐ ๊ณต๋ถ€ํ•  ์š”์†Œ๊ฐ€ ๋งŽ์„ ๊ฒƒ์ด๋ผ ํŒ๋‹จํ•˜์˜€์Šต๋‹ˆ๋‹ค.

์ด๋ฏธ์ง€๋ผ๋Š” ๋ฒ”์œ„๋ฅผ ํ•ธ๋“œํฐ ํ™”๋ฉด ํฌ๊ธฐ์— ๋งž๋Š” ์ด๋ฏธ์ง€๋กœ ํ•œ์ • ์ง“๊ณ ์ž ํ•˜์˜€๊ณ  ๋ช…ํ•จ์ด๋ผ๋Š” ์ปจํ…์ธ ๊ฐ€ ์ƒ๊ฐ๋‚ฌ์Šต๋‹ˆ๋‹ค.

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

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


์„œ๋น„์Šค ํ™”๋ฉด

์˜์ƒ์œผ๋กœ ํ™•์ธํ•˜๊ธฐ!

๊ณ ๋ฏผํ•œ ๋ถ€๋ถ„

ํ•‘๊ฑฐ ์ œ์Šค์ณ ๋‹ค๋ฃจ๊ธฐ


ํ”„๋กœ์ ํŠธ ์ง„ํ–‰ ์ค‘ ์ œ์ผ ๋จผ์ € ๊ณ ๋ คํ–ˆ๋˜ ๋ถ€๋ถ„์€ ํ„ฐ์น˜์š”์†Œ๋ฅผ ์–ด๋–ป๊ฒŒ ๋‚ด๊ฐ€ ์›ํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ์ปจํŠธ๋กค ํ•  ์ง€ ์˜€์Šต๋‹ˆ๋‹ค.

๋ชจ๋ฐ”์ผ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์ด๋ผ๋Š” ํŠน์„ฑ์ƒ ์†๊ฐ€๋ฝ์˜ ์ œ์Šค์ฒ˜๋ฅผ ์ด์šฉํ•ด ์š”์†Œ๋ฅผ ์›ํ•˜๋Š” ๋Œ€๋กœ ๋‹ค๋ฃจ๋Š” ๊ฒƒ์ด ๊ฐ€์žฅ ์ค‘์š”ํ•œ ์š”์†Œ๋ผ๊ณ  ์ƒ๊ฐ์ด ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

์ด๋Ÿฌํ•œ ์ œ์Šค์ฒ˜๋ฅผ ๋‹ค๋ฃจ๋Š” ๊ธฐ๋Šฅ์„ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋กœ ํ•ด๊ฒฐํ•˜๋ฉด ์˜์กด์„ฑ์ด ๋†’์•„์งˆ ๊ฒƒ์ด๋ผ ํŒ๋‹จํ•˜์˜€์Šต๋‹ˆ๋‹ค.

์ด์— RN์˜ ๋‚ด์žฅ API์ธ PanResponder๋ฅผ ์ด์šฉํ•ด ๋ฉ€ํ‹ฐ ํ„ฐ์น˜๋ฅผ ํ•˜๋‚˜์˜ ์•ก์…˜์œผ๋กœ ๋ฐ”๊ฟ€ ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ์— ๋Œ€ํ•ด ๊ทผ๋ณธ์ ์œผ๋กœ ์•Œ์•„๊ฐ€๋ณด๊ณ ์ž ํ•˜์˜€์Šต๋‹ˆ๋‹ค.


PanResponder๋ฅผ ์ด์šฉํ•ด ์›ํ•˜๋Š” ์š”์†Œ๋ฅผ ์†๊ฐ€๋ฝ์˜ ์ด๋™๋งŒํผ ์ด๋™์‹œํ‚ฌ ์ˆ˜ ์žˆ์„๊นŒ?


PanResponder๋Š” RN์˜ ๋ผ์ดํ”„ ์‚ฌ์ดํด์„ ๊ด€๋ฆฌํ•˜๋Š” Gesture Responder System์— ์˜์•  ํ„ฐ์น˜์˜ ์˜ˆ์ธก์„ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•ด์ค๋‹ˆ๋‹ค.

์ฒ˜์Œ PanResponder ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” PanResponder ๋‚ด๋ถ€์˜ ์ •์  ๋ฉ”์†Œ๋“œ์ธ PanResponder.create๋ฅผ ํ†ตํ•ด ์ƒˆ๋กœ์šด PanResponder ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

์ด๋ฅผ RN์ƒ์—์„œ๋Š” useRef ํ˜น์€ useState๋ฅผ ์ด์šฉํ•ด ์ƒ์„ฑ๋œ ์ธ์Šคํ„ด์Šค์˜ ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์—ฌ๊ธฐ์—์„œ useRef๋ฅผ ์ด์šฉํ•˜์ง€ ์•Š๊ณ  useEffect์™€ useState๋ฅผ ์ด์šฉํ•˜์˜€๋Š”๋ฐ ๊ทธ ์ด์œ ๋Š” useRef๋ฅผ ์ด์šฉํ•  ๊ฒฝ์šฐ ๊ฐ’์ด ๋ณ€๊ฒฝ๋˜์–ด๋„ ์žฌ๋ Œ๋”๋ง์ด ๋˜์ง€ ์•Š๊ณ  ๊ฐ’์„ ์ฐธ์กฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ํ•˜์ง€๋งŒ ์ด๋ฏธ์ง€๋‚˜ GIF ๋˜๋Š” ๋‹ค๋ฅธ ์š”์†Œ๋“ค์˜ ํฌ๊ธฐ๋ฅผ ๋ณ€๊ฒฝํ•˜๋Š” ์ž‘์—…์€ ๋ฆฌํŽ˜์ธํŒ…์ด ๋‹ค์‹œ ๋˜์–ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ํ›„์ž๋ฅผ ์ด์šฉํ•˜๋Š” ๊ฒƒ์ด ๋‚˜์€ ๋ฐฉ๋ฒ•์ด๋ผ ํŒ๋‹จํ•˜์˜€์Šต๋‹ˆ๋‹ค.

RN์˜ ๊ฒฝ์šฐ UI๊ตฌ์ถ•์˜ ๊ฐ€์žฅ ๊ธฐ๋ณธ์ ์ธ ๊ตฌ์„ฑ์š”์†Œ๋กœ์จ ์‚ฌ์šฉ๋˜๋Š” View๋ผ๋Š” ํ˜•ํƒœ๋ฅผ ์‚ฌ์šฉํ•ด ์›ํ•˜๋Š” UI๋ฅผ ํ‘œ์‹œํ•˜๋Š” ๊ธฐ๋Šฅ์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.

์ด View๋ฅผ PanResponder๋ฅผ ์ด์šฉํ•ด ์š”์†Œ๋ฅผ ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ์กฐ์ ˆํ•˜๋ ค๋ฉด Animated๋ผ๋Š” RN์˜ ๋‚ด์žฅ API๋ฅผ ๊ฐ™์ด ์ด์šฉํ•ด Animated.View๋กœ ์ด์šฉํ•ด ์ฃผ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์ด๋ฅผ ์œ„ํ•ด ๊ด€๋ฆฌ๊ฐ€ ๊ฐ€๋Šฅํ•œ ๊ฐ’์ธ Animated.Value๋ฅผ ์ด์šฉํ•ฉ๋‹ˆ๋‹ค.

์ถ”๊ฐ€์ ์œผ๋กœ RN์ƒ์—์„œ๋Š” ์–ด๋–ค View๋ฅผ ํ„ฐ์น˜ํ•  ๊ฒฝ์šฐ ์ด๋ฒคํŠธ ๋ฒ„๋ธ”๋ง์ด ๋ฐœ์ƒํ•ด ์ œ์Šค์ฒ˜์— ๋Œ€์‘ํ•˜๋Š” ์ด๋ฒคํŠธ ํ•ธ๋“ค๋ง์ด ๊ฐ€์žฅ ์•„๋ž˜ View๋กœ ๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค.

์ด๋ฅผ ์ œ์Šค์ฒ˜ ์ปจํŠธ๋กค์ด ๋‚ด๋ถ€์˜ ์ž์‹ View๋ถ€ํ„ฐ ์ด๋ฒคํŠธ๋ฅผ ์ปจํŠธ๋กค ํ•˜๋Š” ๊ฒƒ์„ ์›์น˜ ์•Š๋Š”๋‹ค๋ฉด PanResponder์˜ onStartShouldSetPanResponder ์†์„ฑ์„ true๋กœ ๋งŒ๋“ค์–ด์ค˜์•ผ ํ•ฉ๋‹ˆ๋‹ค.

  onStartShouldSetPanResponder: (evt, gestureState) => true,

๋‹ค์Œ์€ onPanResponder์˜ ์†์„ฑ์ž…๋‹ˆ๋‹ค.

  • onPanResponderGrant : PanResponder์˜ ์†๊ฐ€๋ฝ์ด ๋‹ฟ๊ณ ๋‚˜์„œ ์ฒ˜์Œ ์›€์ง์ผ ์‹œ์˜ ์ด๋ฒคํŠธ๋ฅผ ์•ก์…˜์œผ๋กœ ๋ฐ”๊ฟ€ ์ˆ˜ ์žˆ๋Š” ์†์„ฑ์ž…๋‹ˆ๋‹ค. ์ด์ „์˜ Animated.Value๋ฅผ ์ดˆ๊ธฐํ™”์‹œ์ผœ์ฃผ์–ด์•ผ ๊ทธ ์ „์˜ ์ฐธ์กฐ ๊ฐ’์„ ์ฐธ์กฐํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ฒ˜์Œ ์›€์ง์ž„ ์‹œ ์„ ํƒ๋œ ์š”์†Œ์˜ ์ขŒํ‘œ์™€ ๊ฐ’์„ ์ดˆ๊ธฐํ™” ์‹œ์ผœ์ค€ ๋’ค ์ฒซ๋ฒˆ์งธ ์ธ์ž์ธ event์™€ gestureState๊ฐ์ฒด๋ฅผ ์ด์šฉํ•ด ์†๊ฐ€๋ฝ์˜ ์œ„์น˜๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ํ›„ ์ด๋ฅผ ์ด์šฉํ•ด ์†๊ฐ€๋ฝ์ด ์ด๋™ํ•œ ๊ฑฐ๋ฆฌ๋ฅผ ์ด์šฉํ•ด ์š”์†Œ์˜ ํฌ๊ธฐ ํ˜น์€ ์ขŒํ‘œ๋ฅผ ๋ณ€๊ฒฝํ•˜์˜€์Šต๋‹ˆ๋‹ค.
  • onPanResponderMove: Grant๋Š” ์ฒซ ์›€์ง์ž„ ์‹œ ๋งŒ์˜ ๊ฐ’์„ ์ฐธ์กฐํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๊ทธ ํ›„์˜ ๊ฐ’์€ onPanResponderMove๋ฅผ ์ด์šฉํ•ด ๊ฐ’์„ ์กฐ์ ˆํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ์†๊ฐ€๋ฝ์ด ์ฒซ ์›€์ง์ž„์ด ์ƒ๊ธด ํ›„ ๋–ผ์ง€๊ธฐ ์ „๊นŒ์ง€ ์ผ๋ จ์˜ ์—ฐ์†์ ์ธ ์ œ์Šค์ฒ˜๋ฅผ ์•ก์…˜์œผ๋กœ ์ปจํŠธ๋กค ํ•ฉ๋‹ˆ๋‹ค.
  • onPanResponderRelease: ์†๊ฐ€๋ฝ์ด ๋–จ์–ด์ง€๋Š” ์ˆœ๊ฐ„ ์•ก์…˜์„ ์ •์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐ’์„ ๋ฐ˜์˜ ์‹œ์ผœ์ค€ ํ›„ Animated.Value๋ฅผ ๋‹ค์‹œ ์ดˆ๊ธฐํ™” ์‹œ์ผœ์ฃผ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์•„๋ž˜๋Š” ์˜ˆ์‹œ ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค.

useEffect(() => {
  setMoveResponder(
    PanResponder.create({
      onMoveShouldSetPanResponder: () => true,
      onPanResponderGrant: (_, gestureState) => {
        position = {
          x: gestureState.dx,
          y: gestureState.dy,
        };

        movePan.setOffset(position);
        movePan.setValue({ x: 0, y: 0 });
      },
      onPanResponderMove: Animated.event(
        [null, { dx: movePan.x, dy: movePan.y }],
        { useNativeDriver: false },
      ),
      onPanResponderRelease: ({ nativeEvent }, gestureState) => {
        if (selectedIndexRef.current === null) return;

        positionRef = {
          x: positionRef.x + gestureState.dx,
          y: positionRef.y + gestureState.dy,
        };

        dispatch(
          updateImagePosition({
            index: selectedIndexRef.current,
            x: position.x,
            y: position.y,
          }),
        );

        movePan.setValue({ x: 0, y: 0 });
      },
    }),
  );
}, [movePan]);

๊ฐ’์ด ๋ณ€ํ™”ํ•˜์ง€ ์•Š๋Š” ์ด์Šˆ


์œ„์—์„œ ํ„ฐ์น˜๋ฅผ ํ•  ๊ฒฝ์šฐ ์›ํ•˜๋Š” ๊ฐ’ ๋งŒํผ ์ด๋™ํ•˜์ง€ ์•Š๊ณ  ์˜ˆ์ƒ๋˜๋Š” ๊ฐ’์ด ์•„๋‹Œ ๋‹ค๋ฅธ ๊ฐ’์„ ์–ป๋Š” ์ด์Šˆ๊ฐ€ ๋ฐœ์ƒํ•˜์˜€์Šต๋‹ˆ๋‹ค.

PanResponder๋ฅผ ์ด์šฉํ•  ๊ฒฝ์šฐ useState์˜ ๊ฒฝ์šฐ batch๋ผ๋Š” ์—ฌ๋Ÿฌ ์ƒํƒœ ์—…๋ฐ์ดํŠธ๋ฅผ ์ผ๊ด„ ์ฒ˜๋ฆฌํ•˜๋Š” ์†์„ฑ์„ ์ด์šฉํ•ด ์‹คํ–‰์ด ๋ฉ๋‹ˆ๋‹ค.

์ด๋กœ ์ธํ•ด ํ„ฐ์น˜๊ฐ€ ๋  ๊ฒฝ์šฐ, ํ„ฐ์น˜๊ฐ€ ๋ฐ”๋กœ ๋ˆŒ๋ฆฐ ํ˜„์žฌ์˜ ๊ฐ’๋งŒ ์ด์šฉํ•  ์ˆ˜ ์žˆ์—ˆ๊ณ  PanResponder ์ธ์Šคํ„ด์Šค ์†์— useState๋กœ ๊ด€๋ฆฌํ•œ ๊ฐ’์˜ ๊ฒฝ์šฐ ์ฆ‰๊ฐ์ ์œผ๋กœ ํ”ผ๋“œ๋ฐฑ๋˜์–ด ๋ฐ˜์˜๋  ์ˆ˜ ์—†์—ˆ์Šต๋‹ˆ๋‹ค.

๊ทธ๋ž˜์„œ useRef๋ฅผ ์ด์šฉํ•˜์˜€์Šต๋‹ˆ๋‹ค. useRef๋Š” ํ•ญ์ƒ ์ตœ์‹ ์˜ ์ƒํƒœ๋ฅผ ์ฐธ์กฐํ•  ์ˆ˜ ์žˆ์—ˆ๊ธฐ์— ํ•ด๊ฒฐ ํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

positionRef.current = {
  x: gestureState.dx,
  y: gestureState.dy,
};

์‚ฌ์ด์ฆˆ๋ฅผ ์กฐ์ ˆํ•  ๋•Œ๋Š” ์–ด๋–ป๊ฒŒ ํ•ด์•ผ ํ•  ๊นŒ?


์ด๋™์„ ๋‹ค๋ฃจ๋Š” ๊ฒƒ์€ ํ•˜๋‚˜์˜ ์†๊ฐ€๋ฝ์ด ์ด๋™ํ•œ ๊ฑฐ๋ฆฌ๋งŒํผ์„ useRef๋ฅผ ์ด์šฉํ•ด ๋ณ€๊ฒฝํ•ด์ค„ ์ˆ˜ ์žˆ์—ˆ๋Š”๋ฐ, ์‚ฌ์ด์ฆˆ๋ฅผ ์กฐ์ ˆํ•˜๋Š” ๊ฒƒ์€ ์–ด๋–ป๊ฒŒ ํ•ด์•ผํ•  ๊นŒ? ๋ผ๋Š” ๊ณ ๋ฏผ์— ๋งž๋‹ฅ๋œจ๋ฆฌ๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

์ด์— ์‚ฌ์šฉ์ž๊ฐ€ ์นœ์ˆ™ํ•˜๊ฒŒ ๋А๋‚„ ์ˆ˜ ์žˆ๋Š” ์‚ฌ์ด์ฆˆ ์กฐ์ ˆ ๋ฐฉ๋ฒ•์„ ๋‘๊ฐ€์ง€ ์ƒ๊ฐํ•ด๋ณด๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

  1. ๋‘๊ฐœ์˜ ์†๊ฐ€๋ฝ์„ ์ด์šฉํ•ด ํ•€์น˜ ์คŒ,์•„์›ƒ์„ ์ด์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•
  2. ์Šฌ๋ผ์ด๋” ์Šคํฌ๋กค์„ ์ด์šฉํ•ด ์ •๋ฐ€ํ•˜๊ฒŒ ์กฐ์ ˆํ•˜๋Š” ๋ฐฉ๋ฒ•

์ €๋Š” ์ฒ˜์Œ ๋ฐฉ๋ฒ•์ธ ๋‘ ์†๊ฐ€๋ฝ์˜ ๊ฑฐ๋ฆฌ๊ฐ€ ์ฒ˜์Œ ๋‹ฟ์•˜์„ ๋•Œ๋ฅผ ๊ตฌํ•˜๋Š” ๊ฒƒ๋ถ€ํ„ฐ ์‹œ์ž‘์„ ํ•ด๋ณด์ž๋ผ๊ณ  ์ƒ๊ฐ์„ ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

  • ์ฒซ๋ฒˆ์งธ ์ด๋ฒคํŠธ ์ธ์ž์†์˜ nativeEvent๋ฅผ ๊ตฌ์กฐ๋ถ„ํ•ดํ• ๋‹น์„ ์ด์šฉํ•ด ๋‚ด๋ถ€์˜ ๊ฐ’๋“ค์„ ๊ฐ€์ ธ์˜จ ๋’ค ๊ทธ ์†์—์„œ touches๋ผ๋Š” ํ˜„์žฌ ๋‹ฟ์•„ ์žˆ๋Š” ํ„ฐ์น˜๋ฅผ ๊ตฌํ•  ์ˆ˜ ์žˆ๋Š” ์†์„ฑ์„ ์ด์šฉํ•ด ํ˜„์žฌ touches ๋ฐฐ์—ด์˜ ๊ธธ์ด๋ฅผ ์ด์šฉํ•ด, ์†๊ฐ€๋ฝ์ด ๋‘๊ฐœ๊ฐ€ ๋‹ฟ์ง€ ์•Š์•˜์„ ๋•Œ๋Š” early return ํ•˜๋Š” ์‹์œผ๋กœ ์ฒ˜์Œ ์‹œ์ž‘์„ ์žก์•˜์Šต๋‹ˆ๋‹ค.
  • ์—ฌ๊ธฐ์—์„œ ๋‘ ์†๊ฐ€๋ฝ ์‚ฌ์ด์˜ ๊ฑฐ๋ฆฌ๋ฅผ ๊ตฌํ•˜๋Š” getDistanceํ•จ์ˆ˜๋ฅผ ํ‘œํ˜„ ํ•œ ๋’ค ํ”ผํƒ€๊ณ ๋ผ์Šค์˜ ์ •๋ฆฌ๋ฅผ ์ด์šฉํ•ด ๊ตฌํ•ด๋ณด์•˜์Šต๋‹ˆ๋‹ค.
const getDistance = (touch1, touch2) => {
  const dx = touch1.pageX - touch2.pageX;
  const dy = touch1.pageY - touch2.pageY;

  return Math.sqrt(dx * dx + dy * dy);
};
  onPanResponderGrant: ({ nativeEvent }) => {
    const { touches } = nativeEvent;
    if (selectedIndexRef.current === null || touches.length !== 2) return;

    if (touches.length === 2) {
      const [touch1, touch2] = touches;
      const distance = getDistance(touch1, touch2);

      sizePositionRef.current = {
        xy: distance,
      };

      scaleRefXY._startingDistance = distance;
    }
  },
  • onPanResponderGrant์˜ distance๋ฅผ ์ดˆ๊ธฐ ๊ฐ’์œผ๋กœ ์žก์•„ ์ค€ ๋’ค ๊ทธ ๊ฐ’์„ ์ด๋™ ์ค‘์ผ ๋•Œ ๋น„๊ตํ•ด ์ฃผ๋ ค ํ•˜์˜€์Šต๋‹ˆ๋‹ค.
  onPanResponderMove: ({ nativeEvent }) => {
    const { touches } = nativeEvent;
    if (selectedIndexRef.current === null || touches.length !== 2) return;

    if (touches.length === 2) {
      const [touch1, touch2] = touches;
      const distance = getDistance(touch1, touch2);

      const scaleFactorAll = distance / scaleRefXY._startingDistance;

      scaleRefXY.setValue(scaleFactorAll);
    }
  },
  • onPanResponderMove ๋„์ค‘์˜ distance๋ฅผ onPanResponderGrant์˜ distance์™€ ๋น„๊ตํ•ด scaleFactor๋ฅผ ๊ณ„์‚ฐํ•ด์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค.
return (
  <Animated.View
    key={element[index]?.id}
    style={[
      { position: "absolute" },
      positionStyle,
      { transform: [{ scale: scaleRefXY }] },
    ]}
    onPress={() => handleSelect(index)}
    {...resizeResponder.panHandlers}
  >
    <TouchableOpacity>{shapeElements}</TouchableOpacity>
  </Animated.View>
);
  • ์ด๋ฅผ ์ด์šฉํ•ด Animate.View์˜ scale์†์„ฑ์„ scaleRef๋ฅผ ์ด์šฉํ•ด transform ํ•ด์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค.

X์ถ•๊ณผ Y์ถ• ๊ฐœ๋ณ„ ์กฐ์ ˆ


๊ทธ ์ดํ›„๋Š” X์ถ•๊ณผ Y์ถ•์˜ scale์„ ๊ฐ๊ฐ ์กฐ์ ˆ ํ•  ์ˆ˜๋Š” ์—†์„๊นŒ? ๋ผ๋Š” ๊ณ ๋ฏผ์„ ํ•ด๋ณด๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

  • ์ด๋ฒˆ์—๋Š” ํ”ผํƒ€๊ณ ๋ผ์Šค์˜ ์ •๋ฆฌ๋ฅผ ์ด์šฉํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹Œ ์†๊ฐ€๋ฝ์˜ X์ถ• ์ด๋™ ๋ฒ”์œ„์™€ Y์ถ• ์ด๋™ ๋ฒ”์œ„์ค‘ ํฐ ๋ถ€๋ถ„์ด ์ค‘์‹ฌ์ด ๋˜์–ด ์ด๋™ํ•˜๋ฉด ๋˜๊ฒ ๋‹ค ๋ผ๊ณ  ์ƒ๊ฐํ•˜์˜€์Šต๋‹ˆ๋‹ค.
  onPanResponderGrant: ({ nativeEvent }) => {
    const { touches } = nativeEvent;
    if (selectedIndexRef.current === null || touches.length !== 2) return;

    if (touches.length === 2) {
      const [touch1, touch2] = touches;
      const distance = getDistance(touch1, touch2);
      const distanceX = Math.abs(touch1.pageX - touch2.pageX);
      const distanceY = Math.abs(touch1.pageY - touch2.pageY);

      sizePositionRef.current = {
        xy: distance,
        x: distanceX,
        y: distanceY,
      };

      scaleRef._startingDistance = {
        x: distanceX,
        y: distanceY,
      };

      scaleRefXY._startingDistance = distance;
    }
  },
  onPanResponderMove: ({ nativeEvent }) => {
    const { touches } = nativeEvent;
    if (selectedIndexRef.current === null || touches.length !== 2) return;

    if (touches.length === 2) {
      const [touch1, touch2] = touches;
      const distance = getDistance(touch1, touch2);
      const distanceX = Math.abs(touch1.pageX - touch2.pageX);
      const distanceY = Math.abs(touch1.pageY - touch2.pageY);

      const scaleFactorAll = distance / scaleRefXY._startingDistance;
      const scaleFactorX = distanceX / scaleRef._startingDistance.x;
      const scaleFactorY = distanceY / scaleRef._startingDistance.y;

      scaleRefXY.setValue(scaleFactorAll);

      if (scaleFactorX > scaleFactorY && !sizeProportionMode) {
        scaleRef.setValue({
          x: scaleFactorX,
          y: 1,
        });
      }

      if (scaleFactorX < scaleFactorY && !sizeProportionMode) {
        scaleRef.setValue({
          x: 1,
          y: scaleFactorY,
        });
      }
    }
  },
if (sizeProportionMode) {
  transform.push({ scale: scaleRefXY });
} else {
  transform.push({ scaleX: scaleRef.x });
  transform.push({ scaleY: scaleRef.y });
}

return (
  <Animated.View
    key={element[index]?.id}
    onPress={() => handleSelect(index)}
    style={[{ position: "absolute" }, positionStyle, { transform }]}
    {...resizeResponder.panHandlers}
  >
    <TouchableOpacity>{shapeElements}</TouchableOpacity>
  </Animated.View>
);
  • sizeProportionMode๋ฅผ ์„ค์ •ํ•œ ๋’ค ๋น„๋ก€ ๋ชจ๋“œ๊ฐ€ ์•„๋‹ ๋•Œ๋Š” X์™€ Y์˜ scaleFactor๋ฅผ ๊ฑฐ๋ฆฌ์˜ ์ฐจ๋ฅผ ์ด์šฉํ•ด ๊ตฌํ•ด์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค.


์ด๋ ‡๊ฒŒ ์œ„์˜ ๊ณ ๋ฏผ๋“ค์„ ์ง€๋‚˜์ณ ์˜ค๋ฉฐ ์ด๋ฏธ์ง€์™€ ์š”์†Œ๋“ค์„ ํ„ฐ์น˜๋ฅผ ์ด์šฉํ•ด ์กฐ์ ˆํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ๊ฐ•๊ตฌํ•˜์˜€์œผ๋‹ˆ, ์ด์ œ ๊ฐ๊ฐ์˜ ๊ฐ’์„ ๊ฐœ๋ณ„์ ์œผ๋กœ ์ ์šฉ์‹œ์ผœ์ฃผ๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.


์ƒํƒœ ๊ด€๋ฆฌ



์—ฌ๋Ÿฌ ์š”์†Œ์˜ ์ƒํƒœ ๊ด€๋ฆฌ๋ฅผ ์šฉ์ดํ•˜๊ฒŒ ํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ๋ฌด์—‡์ผ๊นŒ?


์ถ”๊ฐ€์ ์ธ ๋ฌธ์ œ๋Š” ๋งŽ์€ ์š”์†Œ์˜ ์ƒํƒœ๋ฅผ ์ผ๋ จ์ ์œผ๋กœ ๊ด€๋ฆฌํ•˜๋Š” ์ž‘์—…์ด ๋ฌธ์ œ์˜€์Šต๋‹ˆ๋‹ค.

์˜ˆ์ธก์ด ๊ฐ€๋Šฅํ•˜๊ฒŒํ•˜๊ณ , ๋””๋ฒ„๊น…์— ์šฉ์ดํ•˜๊ฒŒ ๊ด€๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด Redux๋ฅผ ์ด์šฉํ•œ ์ „์—ญ ์ƒํƒœ ๊ด€๋ฆฌ๋ฅผ ์„ ํƒํ•˜์˜€์Šต๋‹ˆ๋‹ค.


์š”์†Œ๋งˆ๋‹ค ๋ณ€๊ฒฝ๋˜๋Š” ์†์„ฑ์„ ์–ด๋–ป๊ฒŒ ๋ฐ”๋กœ๋ฐ”๋กœ ๊ด€๋ฆฌํ•ด์ค„ ์ˆ˜ ์žˆ์„๊นŒ?


ํ•˜๋‚˜์˜ ํ™”๋ฉด์— ์—ฌ๋Ÿฌ ์š”์†Œ๋ฅผ ๋ณด์—ฌ์ค„ ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์„ ๊ณ ๋ฏผํ•ด ๋ณธ ๋’ค, ๊ฐ ์š”์†Œ๋งˆ๋‹ค Elements๋ฅผ ๋ถ„๋ฆฌํ•ด ๋ Œ๋”๋งํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

  1. ๊ฐ๊ฐ์˜ ์š”์†Œ์˜ ๊ด€๋ฆฌ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋‚˜๋ˆ•๋‹ˆ๋‹ค. ex) TextEditor
  2. ๊ฐ๊ฐ์˜ Editor ์ปดํฌ๋„ŒํŠธ์—์„œ ํ•ด๋‹น ํ”„๋กœํผํ‹ฐ์— ๋งž๋Š” action์„ dispatch ํ•ฉ๋‹ˆ๋‹ค.
  3. ์ƒ์„ฑ๋œ ๊ฐ์ฒด๋Š” ๊ฐ๊ฐ์˜ slice์— ํฌํ•จ๋˜๊ณ  ๋ Œ๋”๋ง ๋ฉ๋‹ˆ๋‹ค. ex) TextElements
  4. ์ด์ œ ๊ฐ๊ฐ์˜ ๊ฐ์ฒด๋Š” EditorRenderer ์ปดํฌ๋„ŒํŠธ์—์„œ updateNewElements action์„ ํ†ตํ•ด editorSlice์˜ allElements๋กœ zIndex์˜ ์ˆœ์„œ๋Œ€๋กœ ๊ด€๋ฆฌํ•ด์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค.

Untitled

<ContentBox>
  <View>
    <TextElements />
    <ImageElements />
    <GifElements />
    <ShapeElements />
  </View>
  <LayerModal />
  <ImageModal />
  <GifModal />
  <FontModal />
  <ColorModal />
  <IconModal />
</ContentBox>
useEffect(() => {
  dispatch(updateNewElements(textElements));
}, [textElements]);
updateNewElements: (state, action) => {
      const elements = action.payload;

      Object.keys(elements).forEach((key) => {
        const element = elements[key];
        state.allElements[element.zIndex] = element;
      });
    },

๋ณ€๊ฒฝ๋œ ๋ ˆ์ด์–ด(zIndex)๋ฅผ ์–ด๋–ป๊ฒŒ ๋ชจ๋“  ์š”์†Œ์— ์ ์šฉํ•ด ์ค„ ์ˆ˜ ์žˆ์„๊นŒ?

  • ์ด๋ฏธ์ง€ ํˆด์˜ ๊ฒฝ์šฐ์—๋Š” ํ•„์ˆ˜์ ์œผ๋กœ ๋ ˆ์ด์–ด๋ผ๋Š” ๊ฐœ๋…์ด ๋„์ž…์ด ๋ฉ๋‹ˆ๋‹ค.
  • ๊ทธ๋ ‡๋‹ค๋ฉด layer๋ฅผ ๋ณ€ํ™”์‹œํ‚ฌ ํ•„์š”๊ฐ€ ์žˆ์„ ๋•Œ๋Š” ์–ด๋–ป๊ฒŒ ์š”์†Œ๋“ค์„ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์„๊นŒ? ๊ฐ€ ๋‘๋ฒˆ์žฌ ํ™”๋‘๋กœ ๋– ์˜ฌ๋ž์Šต๋‹ˆ๋‹ค.
  • ์ƒ๊ฐํ•œ ๋ฐฉ๋ฒ•์€ ์ด๊ฑธ ๋‹จ๋ฐฉํ–ฅ์œผ๋กœ ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•˜๋ฉด ๋˜์ง€ ์•Š์„๊นŒ? ๋ผ๋Š” ์ƒ๊ฐ์„ ํ•˜์˜€์Šต๋‹ˆ๋‹ค.
  • ๊ฐ Elements => allElements => layerElements
  • ๋‹ค์‹œ ๋ณ€๊ฒฝ๋œ layerElements๋ฅผ ํ†ตํ•ด ๊ฐ ์š”์†Œ์— ๋ฟŒ๋ ค์ฃผ๋Š” ์ž‘์—…์„ ์ง„ํ–‰ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

Untitled (1)

useEffect(() => {
  const updatedElements = Object.keys(layerElements)
    .sort((a, b) => layerElements[a].zIndex - layerElements[b].zIndex)
    .map((key) => layerElements[key]);

  const shapes = [];
  const texts = [];
  const gifs = [];
  const images = [];

  updatedElements.forEach((element) => {
    switch (element.type) {
      case SHAPE:
        shapes.push(element);
        break;
      case TEXT:
        texts.push(element);
        break;
      case GIF:
        gifs.push(element);
        break;
      case IMAGE:
        images.push(element);
        break;
      default:
        break;
    }
  });

  if (texts.length > 0) {
    dispatch(updateAllTexts(texts));
  }
}, [layerElements]);

์ €์žฅํ•˜๊ธฐ



SVG, Text, Image, GIF๋ฅผ ํ•˜๋‚˜์˜ ๋ฐ์ดํ„ฐ๋กœ ์ €์žฅํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์ด ๋ฌด์—‡์ผ๊นŒ?


์ด๋ ‡๊ฒŒ ๋ณ€๊ฒฝ๋œ ๊ฐ์ฒด๋“ค์„ ๋‹ค์‹œ ๋ Œ๋”๋ง ์‹œ์ผœ์ฃผ๋Š” ์ž‘์—…๊นŒ์ง€ ์™„๋ฃŒํ•˜์˜€์œผ๋‚˜, ์ €์žฅ์ด๋ผ๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

์ด๋ฅผ ์œ„ํ•ด ์กฐ์‚ฌ๋ฅผ ํ•ด๋ณธ ๋’ค ๋‘๊ฐ€์ง€์˜ ๋ฐฉ๋ฒ•์„ ํ™•์ธํ•˜๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

  1. ๊ฐ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณ€ํ™˜ ์‹œ์ผœ ํ•˜๋‚˜์˜ ์ด๋ฏธ์ง€๋กœ ๋งŒ๋“œ๋Š” ๋ฐฉ๋ฒ•
  2. ํ•˜๋‚˜์˜ ์˜์—ญ์„ ์บก์ณํ•˜๋Š” ๋ฐฉ๋ฒ•

์ด ์ค‘์—์„œ 2๋ฒˆ์˜ ๋ฐฉ๋ฒ•์„ ์„ ํƒํ•˜๋Š” ๊ฒƒ์ด ์—๋Ÿฌ๋ฅผ ํ•ธ๋“ค๋งํ•˜๊ธฐ์— ์šฉ์ดํ•  ๊ฒƒ์ด๋ผ ์ƒ๊ฐํ•ด ์„ ํƒํ•˜์˜€์Šต๋‹ˆ๋‹ค.


์บก์ณํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ์–ด๋–ป๊ฒŒํ•˜๋ฉด GIF๋กœ ๋งŒ๋“ค ์ˆ˜ ์žˆ์„๊นŒ?


์ด์ œ ๋‹ค์Œ ๋ฌธ์ œ๋Š” Image์˜ ๊ฒฝ์šฐ์—๋Š” ์‰ฝ๊ฒŒ ์บก์ณ๋ฅผ ํ•˜๋ฉด ๋์ง€๋งŒ GIF์˜ ๊ฒฝ์šฐ ์–ด๋–ป๊ฒŒ ์ €์žฅํ•  ์ˆ˜ ์žˆ์„๊นŒ?์— ๋Œ€ํ•œ ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

GIF๋กœ ๋ณ€ํ™˜ ํ•ด์ฃผ๋Š” ๊ฒƒ์„ PNG๋กœ ์˜์—ญ ๋‚ด์˜ ๊ฐ€์žฅ ๊ธด GIF์˜ ๊ธธ์ด ๋งŒํผ ํ”„๋ ˆ์ž„๋งˆ๋‹ค ์บก์ณํ•ด์ค€ ๋’ค ๊ทธ๊ฒƒ์„ GIF๋กœ ๋งŒ๋“ค์–ด์ฃผ๋ฉด ๋˜์ง€ ์•Š์„๊นŒ? ๋ผ๋Š” ์ ‘๊ทผ์œผ๋กœ ์‹œ์ž‘ํ•ด ๋ณด์•˜์Šต๋‹ˆ๋‹ค.

PNG๋ฅผ GIF๋กœ ๋ฐ”๊พธ๋Š” ๊ฒƒ์€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ด์šฉํ•˜์ง€ ์•Š๊ณ  ์‹œ๋„ํ•ด ๋ณด์•˜์œผ๋‚˜, PNG๋ฅผ GIF๋กœ ๋ฐ”๊พธ๋Š” ๊ฒƒ์— ์ƒ๋‹นํ•œ ๋ฌด๋ฆฌ๊ฐ€ ์žˆ์—ˆ๊ธฐ์— ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ด์šฉํ•˜๊ธฐ๋กœ ํŒ๋‹จํ•˜์˜€์Šต๋‹ˆ๋‹ค.

์ด์—๋„ ๋ฌธ์ œ๋Š” ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

  • RN์ด ๊ฐ€์ง€๋Š” ํ•œ๊ณ„์ : RN์ด๊ธฐ ๋•Œ๋ฌธ์— PNG๋ฅผ GIF๋กœ ๋ฐ”๊พธ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ NODE๊ธฐ๋ฐ˜์ด๊ธฐ์— ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์–ด ์„œ๋ฒ„๋กœ ๋ณด๋‚ด์ค€ ๋’ค ๊ทธ๊ฒƒ๋“ค์„ ์„œ๋ฒ„์—์„œ GIF๋กœ ๋งŒ๋“ค์–ด ๋‹ค์‹œ RN์œผ๋กœ ๋ณด๋‚ด์ฃผ๋Š” ๋ฐฉ์‹์„ ์ฑ„ํƒํ•˜์˜€์Šต๋‹ˆ๋‹ค.
  • ๋ฒˆ๊ฑฐ๋กœ์šด ์ผ๋ จ์˜ ์ž‘์—…: PNG๋ฅผ GIF๋กœ ๋ฐ”๊พธ๋Š” ์ž‘์—…์€ ์ƒ๋‹นํžˆ ๋งŽ์€ ์ž‘์—…์ด ํ•„์š”๋กœ ์—ฌ๊ฒจ์ง‘๋‹ˆ๋‹ค.
  1. PNG๋ฅผ ํ”„๋ ˆ์ž„๋งˆ๋‹ค ์บก์ณํ•˜์—ฌ ๋ฐฐ์—ด๋กœ ๋งŒ๋“ค๊ธฐ
  2. ์บก์ณํ•œ PNG๋ฅผ base64๋กœ ์ธ์ฝ”๋”ฉ
  3. base64๋กœ ์ธ์ฝ”๋”ฉํ•œ PNG๋“ค์˜ ๋ฐฐ์—ด์„ ์„œ๋ฒ„๋กœ ๋ณด๋‚ด์ฃผ๊ธฐ
  4. ์„œ๋ฒ„์—์„œ๋Š” base64์„ ๋””์ฝ”๋”ฉํ•˜์—ฌ GIF๋กœ ๋ณ€ํ™˜ํ•œ ๋’ค ๋‹ค์‹œ base64๋กœ ์ธ์ฝ”๋”ฉ ํ•œ ํ›„ RN์œผ๋กœ ๋ฐ˜ํ™˜
  5. RN์—์„œ๋Š” base64๋ฅผ ๋‹ค์‹œ ๋””์ฝ”๋”ฉํ•œ ํ›„ GIF๋กœ ์ €์žฅํ•˜๊ธฐ.

Untitled (1)

  • ์—ฌ๊ธฐ์—์„œ base64๋ž€ ์ด๋ฏธ์ง€๋“ฑ์„ ์‰ฝ๊ฒŒ ๋ณ€ํ™˜ํ•  ์ˆ˜ ์žˆ๋Š” ์•„์Šคํ‚ค์ฝ”๋“œ์˜ ๋ฌธ์ž๋“ค๋กœ ์ด๋ฃจ์–ด์ง„ 64์ง„๋ฒ•์œผ๋กœ ์ธ์ฝ”๋”ฉํ•œ ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค.
const frames = [];
const encodedFrames = [];
const frameCount = Math.round(duration * 60);
const frameInterval = 1000 / (fps / 2);

for (let i = 0; i < frameCount; i++) {
  setTimeout(async () => {
    const frame = await captureRef(imageRef, {
      format: "png",
      quality: 1.0,
    });

    frames.push(frame);
  }, i * frameInterval);
}
  • frame์ด ๋ฐ˜๋ณต๋˜๋Š” ์ˆ˜ ๋งŒํผ capture๋ฅผ ํ•ด์ค€ ๋’ค
await new Promise((resolve) => setTimeout(resolve, frameCount * frameInterval));

for (const frame of frames) {
  const base64Data = await FileSystem.readAsStringAsync(frame, {
    encoding: FileSystem.EncodingType.Base64,
  });
  encodedFrames.push(`data:image/png;base64,${base64Data}`);
}
  • frames์— capture๊ฐ€ ๋œ ๊ฒƒ์„ ํ”„๋กœ๋ฏธ์Šค๋ฅผ ํ†ตํ•ด ์ฒดํฌํ•œ ๋’ค ์ด๊ฒƒ๋“ค์„ base64๋กœ ๋ณ€ํ™˜ํ•œ ๋’ค ๋‹ค์‹œ ๋ฐฐ์—ด์— ๋‹ด์•„์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค.

๊ทธ๊ฒƒ๋“ค์„ ์„œ๋ฒ„์—์„œ ๋ฐ›์€ ๋’ค

const encoder = new GIFEncoder(Math.round(width), Math.round(height));
encoder.start();
encoder.setRepeat(0);
encoder.setDelay(1000 / fps);
encoder.setQuality(5);

for (const frame of encodedFrames) {
  const buffer = Buffer.from(frame.split(",")[1], "base64");
  const { data, info } = await sharp(buffer) // sharp๋Š” PNG ์‚ฌ์ด์ฆˆ ์กฐ์ ˆ ๋ฐ ์ƒ์„ฑ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ž…๋‹ˆ๋‹ค.
    .resize(Math.round(width), Math.round(height), {
      fit: "contain",
      background: { r: 0, g: 0, b: 0, alpha: 0 },
    })
    .raw()
    .ensureAlpha()
    .toBuffer({ resolveWithObject: true });

  if (info.width !== Math.round(width) || info.height !== Math.round(height)) {
    console.error(
      `Skipping frame with invalid dimensions: ${info.width}x${info.height}`,
    );
    continue;
  }

  encoder.addFrame(data);
}

encoder.finish();
const gifBuffer = encoder.out.getData();

res.set("Content-Type", "image/gif");
res.send({ base64Gif: gifBuffer.toString("base64") });
  • Buffer๋ฅผ ์ด์šฉํ•ด ํ•œ ํ”„๋ ˆ์ž„์˜ base64๊ฐ€ ๋‹ค ๋“ค์–ด์˜ค๋ฉด ํ•˜๋‚˜ํ•˜๋‚˜๋งˆ๋‹ค sharp ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ํ†ตํ•ด PNG๋กœ ๋‹ค์‹œ ๋งŒ๋“ค์–ด ์ค€ ๋’ค GIFEncoder๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ ํ”„๋ ˆ์ž„์— ํ•˜๋‚˜ํ•˜๋‚˜ ๋„ฃ์–ด์ค€ ๋’ค GIF๋กœ ๋งŒ๋“ค์–ด ์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค.

ํ•˜๋‚˜์˜ ์„ฑ๊ณต์ ์ธ GIF๊ฐ€ ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!


UX


์–ด๋–ป๊ฒŒ ํ•˜๋ฉด ์œ ์ € ์นœํ™”์ ์œผ๋กœ ๋งŒ๋“ค ์ˆ˜ ์žˆ์„ ๊นŒ?

๋ชจ๋ฐ”์ผ ์ด๋ผ๋Š” ํŠน์„ฑ์ƒ ์†๊ฐ€๋ฝ์œผ๋กœ ์ ‘์ด‰ํ•ด ์ปจํŠธ๋กค ํ•˜๋Š” ๊ฒƒ๋“ค์˜ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์ด ์›น๋ณด๋‹ค ๋” ํฌ๊ฒŒ ๋‹ค๊ฐ€์˜ฌ ์ˆ˜ ์žˆ๋Š” ์š”์†Œ๋ผ๊ณ  ํŒ๋‹จํ•˜์˜€์Šต๋‹ˆ๋‹ค.

์ด๋ฅผ ์œ„ํ•ด ์–ด๋–ป๊ฒŒ ํ•˜๋ฉด ์ข€ ๋” ์œ ์ €๋“ค์ด ๋А๋ผ๊ธฐ์— ํŽธํ•˜๊ฒŒ ํ•  ์ˆ˜ ์žˆ์„๊นŒ? ๋ผ๋Š” ๊ณ ๋ฏผ์ด ์ƒ๊ฒผ์Šต๋‹ˆ๋‹ค.


๋“œ๋ž˜๊ทธ ์•ค ๋“œ๋ž์„ ํ™œ์šฉํ•ด ๋ณด์ž!


  • Layer์˜ ์ˆœ์„œ๋ฅผ ๋ณ€๊ฒฝํ•  ๋•Œ ๋“œ๋ž˜๊ทธ ์•ค ๋“œ๋ž์„ ํ™œ์šฉํ•˜๋Š” ๊ฒƒ์ด ์‚ฌ์šฉ์ž ๊ฒฝํ—˜ ๋ฉด์—์„œ ์ข€ ๋” ์นœํ™”์ ์ผ ๊ฒƒ์ด๋ผ ํŒ๋‹จํ•˜์˜€์Šต๋‹ˆ๋‹ค.
  • ์ด๋ฅผ ์œ„ํ•ด PanResponder์˜ ์ด๋ฒคํŠธ ์†์—์„œ ๋“œ๋ž˜๊ทธํ•œ ๋ ˆ์ด์–ด๋ฅผ ์˜ฎ๊ธฐ๋Š” ํ–‰์œ„๊ฐ€ ์ผ์ • ์ˆ˜์น˜ ๋ฒ”์œ„ ์ด์ƒ์„ ๋„˜์–ด๊ฐˆ ๋•Œ ์ž๋ฃŒ์˜ ์ˆœ์„œ๊ฐ€ ๋ณ€๊ฒฝ๋˜๊ฒŒ ํ•˜์˜€์Šต๋‹ˆ๋‹ค.
  • newIndex๋ฅผ ์ƒ์„ฑํ•œ ๋’ค ๊ตฌ์กฐ ๋ถ„ํ•ด ํ• ๋‹น์„ ์ด์šฉํ•ด ๋ฐฐ์—ด์˜ ์ˆœ์„œ๋ฅผ ๋ณ€๊ฒฝํ•ด ์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค.
  • ๊ทธ ๋’ค zIndex๋ฅผ ์ด์šฉํ•ด ๊ฐ์ฒด๋“ค์„ ์žฌ์ •๋ ฌ ํ•ด์ค€ ๋’ค ์—…๋ฐ์ดํŠธ ์‹œ์ผœ์ฃผ๋Š” ์ž‘์—…์„ ์ง„ํ–‰ํ•˜์˜€์Šต๋‹ˆ๋‹ค.
  onPanResponderRelease: (_, gestureState) => {
    const positionY = positionRef.current.y;
    const indexDiffByPosition = Math.min(
      Math.max(
        Math.round(Math.abs(positionY) / (SCREEN_HEIGHT * 0.125 * 0.9)),
        0,
      ),
      currentElements.length - 1,
    );

    let newIndex;
    if (indexDiffByPosition !== 0 && positionY > 0) {
      newIndex = currentIndex + indexDiffByPosition;
    }

    if (indexDiffByPosition !== 0 && positionY < 0) {
      newIndex = currentIndex - indexDiffByPosition;
    }

    if (newIndex === undefined || newIndex >= currentElements.length) {
      return movePan.setValue({ x: 0, y: 0 });
    }

    [currentElements[currentIndex], currentElements[newIndex]] = [
      currentElements[newIndex],
      currentElements[currentIndex],
    ];

    const newElements = currentElements.map((element, index) => {
      return { ...element, zIndex: currentElements.length - 1 - index };
    });

    selectedLayerIndex.current = newIndex;
    dispatch(updateAllElements(newElements));
  },


Schedule

ํ”„๋กœ์ ํŠธ ๊ธฐ๊ฐ„ : 2023.04.3 ~ 2023.04.24 / [๊ธฐํš 7์ผ, ๊ฐœ๋ฐœ 14์ผ]

1 ์ฃผ์ฐจ : ๊ธฐํš ๋ฐ ์„ค๊ณ„
  • ์•„์ด๋””์–ด ์ˆ˜์ง‘
  • ๊ธฐ์ˆ  ์Šคํƒ ์„ ์ •
  • Git ์ž‘์—… ํ”Œ๋กœ์šฐ ๊ฒฐ์ •
  • Figma๋ฅผ ์‚ฌ์šฉํ•œ Mockup ์ œ์ž‘
  • MongoDb๋ฅผ ์ด์šฉํ•œ DB Schema ์„ค๊ณ„
  • Notion์„ ์ด์šฉํ•œ ์นธ๋ฐ˜ ์ž‘์„ฑ
2 ~ 3 ์ฃผ์ฐจ : ๊ธฐ๋Šฅ ๊ฐœ๋ฐœ ๋ฐ ๋ฐœํ‘œ
  • RN ๊ตฌํ˜„

  • ๋ฐฑ์—”๋“œ ์„œ๋ฒ„ ๊ตฌํ˜„

  • ๋ฆฌ๋“œ๋ฏธ ์ž‘์„ฑ



Repository Link



Tech Stacks

Frontend

Backend



Memoir

React Native๋ฅผ ์ฒ˜์Œ ๋‹ค๋ค„๋ณด๋ฉฐ ๋А๊ผˆ๋˜ ์ ์€ "์†๊ฐ€๋ฝ์ด๋ผ๋Š” ์ ‘์ ์œผ๋กœ ๋ฒŒ์–ด์ง€๋Š”, ์ˆ˜ ๋งŽ์€ ์ผ๋“ค์ด ์žˆ๊ตฌ๋‚˜" ์˜€์Šต๋‹ˆ๋‹ค. ์›น๋งŒ์„ ๊ณต๋ถ€ํ–ˆ๋‹ค๋ฉด ์ง‘์ค‘ํ•ด๋ณด์ง€ ๋ชปํ–ˆ์„ ์ œ์Šค์ณ๋ผ๋Š” ๋ถ€๋ถ„์ด ์žฌ๋ฏธ์žˆ๋Š” ์š”์†Œ๋กœ์จ ์ž‘์šฉํ–ˆ๋˜ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

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

์ €๋Š” ์•ž์œผ๋กœ๋„ ์ด์™€ ๊ฐ™์ด ํ•ด๊ฒฐํ•ด์•ผ ํ•  ์š”์†Œ๋“ค์ด ๊ฐ€๋“ํ•œ ํ”„๋กœ์ ํŠธ๋“ค๊ณผ ํ•จ๊ป˜ํ•  ๊ฒƒ์ธ๋ฐ ์ด ์ƒํ™ฉ์ด ๊ฐœ๋ฐœ์„ ์ง€์†์ ์œผ๋กœ ํฅ๋ฏธ์žˆ๊ฒŒ ๋งŒ๋“ค์–ด ์ค€๋‹ค ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค.

์ด๋Ÿฌํ•œ ์ง€์†์ ์ธ ํฅ๋ฏธ์™€ ํƒ๊ตฌ๋ฅผ ํ†ตํ•ด ์—ฌ๋Ÿฌ ํ”„๋กœ์ ํŠธ๋“ค์„ ํ’€์–ด ๋‚˜๊ฐ€๋ณด๊ณ  ์‹ถ๋‹ค๋Š” ์ƒ๊ฐ์„ ๊ฐ€์ง€๊ฒŒ ๋˜์–ด ์ข‹์€ ๊ฒฝํ—˜์ด์—ˆ์Šต๋‹ˆ๋‹ค.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors