์ฑŒ๋ฆฐ์ง€

ํŒจ์ŠคํŠธ์บ ํผ์Šค ํ™˜๊ธ‰์ฑŒ๋ฆฐ์ง€ 20์ผ์ฐจ ๋ฏธ์…˜ (2์›” 20์ผ) : ์ดˆ๊ฒฉ์ฐจ ํŒจํ‚ค์ง€ : 21๊ฐœ ํ”„๋กœ์ ํŠธ๋กœ ์™„์„ฑํ•˜๋Š” ์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒ ์›น ๊ฐœ๋ฐœ with Three.js & Canvas ๊ฐ•์˜ ํ›„๊ธฐ

์–‘๋‚˜๋‹ˆ 2024. 2. 20. 22:37

์˜ค๋Š˜์€ ์ด์–ด์„œ Part3 [Three.js]์˜ Chapter04 ์ธํ„ฐ๋ ‰ํ‹ฐ๋ธŒ ์นด๋“œ ๊ฐ•์˜๋ฅผ ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค.(09~10๊ฐ•)

 

๊ทธ๋Ÿผ ์˜ค๋Š˜ ๊ฐ•์˜์—์„œ ๋ฐฐ์šด ๋‚ด์šฉ๋“ค๊ณผ ๋А๋‚€์ ์„ ๊ฐ„๋žตํ•˜๊ฒŒ ์ž‘์„ฑํ•ด ๋ณผ๊ฒŒ์š”!

 

๐Ÿ”Ž01. ์นด๋“œ ์ƒ‰์ƒ ๋ณ€๊ฒฝ ๋ฒ„ํŠผ ์ถ”๊ฐ€ํ•˜๊ธฐ

์˜ค๋Š˜์€ ์œ„ ์ด๋ฏธ์ง€์ฒ˜๋Ÿผ ์ƒ‰์ƒ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๋ฉด ์• ๋‹ˆ๋ฉ”์ด์…˜์ด ์‹คํ–‰๋˜๋ฉด์„œ ์นด๋“œ ์ƒ‰์ƒ์ด ๋ณ€๊ฒฝ๋˜๋Š” ํšจ๊ณผ๋ฅผ ์ถ”๊ฐ€ํ•ด๋ณด์•˜์Šต๋‹ˆ๋‹ค.

์šฐ์„  main.js์—์„œ ์ปฌ๋Ÿฌ ๋ฐฐ์—ด์„ ์ถ”๊ฐ€ํ•ด์ฃผ์‹œ๊ณ , ์นด๋“œ ๊ฐ์ฒด์˜ ๊ธฐ๋ณธ์ƒ‰๋„ ์ปฌ๋Ÿฌ๋ฐฐ์—ด์˜ ์ฒซ๋ฒˆ์งธ ๊ฐ’์œผ๋กœ ๋ณ€๊ฒฝํ•ด์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค.

  const COLORS = ["#ff6e6e", "#31e0c1", "#006fff", "#ffd732"];
  const card = new Card({
    width: 10,
    height: 15.8,
    radius: 0.5,
    color: COLORS[0],
  });

 

์ปฌ๋Ÿฌ ๋ฆฌ์ŠคํŠธ UI๋ฅผ ๋งŒ๋“ค๊ธฐ์œ„ํ•ด index.html์— ํ•˜๋‹จ ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ 

    <div class="container"></div>

container ์š”์†Œ์— ์ƒ‰์ƒ๋ณ„ ๋ฒ„ํŠผ์„ ์ถ”๊ฐ€ํ•˜๊ธฐ ์œ„ํ•ด main.js์—์„œ ์•„๋ž˜ ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•ด์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค.

  const container = document.querySelector(".container");

  COLORS.forEach((color) => {
    const button = document.createElement("button");
    button.style.backgroundColor = color;
    container.append(button);
  });

์ด๋ ‡๊ฒŒํ•˜๋ฉด ์•„๋ž˜ ํ™”๋ฉด์ฒ˜๋Ÿผ ์ขŒ์ธก ์ƒ๋‹จ์— 4๊ฐœ์˜ ์ƒ‰์ƒ๋ฒ„ํŠผ์ด ์ถ”๊ฐ€๋œ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ์š”, css ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•ด์„œ ์ข€๋” ์˜ˆ์˜๊ฒŒ ๋ณด์ด๋„๋ก ์ˆ˜์ •ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. button์— ์ถ”๊ฐ€๋œ ๋ฆฌ์…‹ css๋Š” ์•„๋ž˜ ์‚ฌ์ดํŠธ์—์„œ ๋ณต๋ถ™ํ•ด์™”์Šต๋‹ˆ๋‹ค.

https://gist.github.com/MoOx/9137295

 

reset-button.css

GitHub Gist: instantly share code, notes, and snippets.

gist.github.com

.container {
  position: absolute;
  bottom: 64px;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  justify-content: center;
  width: 100%;
  padding: 16px;
}
.container button {
  width: 32px;
  height: 32px;
  border-radius: 50%;
}
.container button:not(:first-child) {
  margin-left: 16px;
}
button {
  border: none;
  margin: 0;
  padding: 0;
  width: auto;
  overflow: visible;

  background: transparent;

  /* inherit font & color from ancestor */
  color: inherit;
  font: inherit;

  /* Normalize `line-height`. Cannot be changed from `normal` in Firefox 4+. */
  line-height: normal;

  /* Corrects font smoothing for webkit */
  -webkit-font-smoothing: inherit;
  -moz-osx-font-smoothing: inherit;

  /* Corrects inability to style clickable `input` types in iOS */
  -webkit-appearance: none;
}

 

๊ทธ๋ฆฌ๊ณ  ๋ฒ„ํŠผ์ด ํด๋ฆญ๋˜๋ฉด ์ƒ‰์ƒ์ด ๋ณ€๊ฒฝ๋  ์ˆ˜ ์žˆ๊ฒŒ ํด๋ฆญ ์ด๋ฒคํŠธ๋ฅผ ์ถ”๊ฐ€ํ•ด์ค๋‹ˆ๋‹ค. ์ด๋•Œ๋„ ์ €๋ฒˆ๊ฐ•์˜ ์ฒ˜๋Ÿผ 'mesh'์˜ material ์†์„ฑ์—์„œ color๊ฐ’์„ ๋ณ€๊ฒฝํ•ด์ฃผ์‹œ๋ฉด ๋ฉ๋‹ˆ๋‹ค.

 button.addEventListener("click", () => {
      card.mesh.material.color = new THREE.Color(color);
    });

 

์ด๋ ‡๊ฒŒ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๊ณ  ๋‚˜๋ฉด ์•„๋ž˜์™€ ๊ฐ™์€ ๊ฒฐ๊ณผ๋ฌผ์„ ํ™•์ธํ•˜์‹ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

๐Ÿ”Ž02. GSAP์„ ์ด์šฉํ•œ ์• ๋‹ˆ๋ฉ”์ด์…˜ ๊ตฌํ˜„

์นด๋“œ๊ฐ€ ๋Œ์•„๊ฐ€๋Š” ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ์ถ”๊ฐ€ํ•˜๊ธฐ์œ„ํ•ด ๊ฐ•์˜์—์„œ๋Š” GSAP ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค.

์•„๋ž˜์ฒ˜๋Ÿผ ํŒจํ‚ค์ง€๋ฅผ ์„ค์น˜ํ•ด ์ฃผ์‹œ๊ณ  main.js์—์„œ gsap์„ import ํ•ด์ฃผ์„ธ์š”

npm install gsap

import { gsap } from 'gsap';

gsap์—์„œ๋Š” to ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์• ๋‹ˆ๋ฉ”์ด์…˜ ํšจ๊ณผ๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ฒซ๋ฒˆ์งธ ์ธ์ž๋กœ ๋Œ€์ƒ๊ฐ์ฒด๋ฅผ ๋‘๋ฒˆ์งธ ์ธ์ž๋กœ ์• ๋‹ˆ๋ฉ”์ด์…˜ ์˜ต์…˜์„ ์ถ”๊ฐ€ํ•ด์ฃผ์„ธ์š”. ๊ฐ•์˜์—์„œ๋Š” y์ถ• ๊ธฐ์ค€์œผ๋กœ ์‹œ๊ณ„๋ฐฉํ–ฅ์œผ๋กœ 2๋ฐ”ํ€ด ํšŒ์ „ํ•  ์ˆ˜ ์žˆ๋„๋ก ์•„๋ž˜ ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

*์œ„ ๋‚ด์šฉ์— ๋Œ€ํ•œ ์ž์„ธํ•œ ์„ค๋ช…์€ ํ•˜๋‹จ์— ์ถ”๊ฐ€ํ•ด๋‘์—ˆ์Šต๋‹ˆ๋‹ค!

  gsap.to(card.mesh.rotation, {y:-Math.PI * 4})

์ž์„ธํ•œ ๋‚ด์šฉ์€ ํ•˜๋‹จ ๋ฌธ์„œ๋ฅผ ์ฐธ๊ณ ํ•ด์ฃผ์„ธ์š”

https://gsap.com/docs/v3/GSAP/gsap.to()

 

gsap.to() | GSAP | Docs & Learning

Returns : Tween

gsap.com

๊ทธ๋ฆฌ๊ณ  ๋ถ€๋“œ๋Ÿฌ์šด ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ์œ„ํ•ด duration๊ณผ easeํ•จ์ˆ˜ ์†์„ฑ์„ ์ถ”๊ฐ€ํ•ด์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค.

  gsap.to(card.mesh.rotation, {
    y: -Math.PI * 4,
    duration: 2.5,
    ease: "back.out(2.5)",
  });

Ease ๊ด€๋ จ ์ž์„ธํ•œ ๋‚ด์šฉ์€ ์•„๋ž˜ ๋ฌธ์„œ๋ฅผ ํ™•์ธํ•ด์ฃผ์„ธ์š”!
https://gsap.com/resources/getting-started/Easing

 

Easing | GSAP | Docs & Learning

Easing is possibly the most important part of motion design. A well-chosen ease will add personality and breathe life into your animation.

gsap.com

๋งˆ์ง€๋ง‰์œผ๋กœ ์นด๋“œ๋ฅผ ํด๋ฆญํ•  ๋•Œ์—๋„ ์• ๋‹ˆ๋ฉ”์ด์…˜์ด ์ ์šฉ๋˜๊ฒŒ ์ฝ”๋“œ๋ฅผ ์ˆ˜์ •ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

    button.addEventListener("click", () => {
      card.mesh.material.color = new THREE.Color(color);
      gsap.to(card.mesh.rotation, {
        y: card.mesh.rotation.y - Math.PI / 2, // ํ˜„์žฌ ํšŒ์ „๊ฐ๋„๋ฅผ ๊ธฐ์ค€์œผ๋กœ 90๋„ ๋งŒํผ ๋Œ์•„๊ฐ€๊ฒŒ
        duration: 1,
        ease: "back.out(2.5)",
      });
    });

 

โœจ์˜ค๋Š˜์˜ ๊ฒฐ๊ณผ๋ฌผ

 

gsap์„ ๋“ค์–ด๋ณด๊ธฐ๋Š” ํ–ˆ์œผ๋‚˜,,์‚ฌ์šฉํ•ด๋ณธ์ ์ด ์—†์–ด์„œ ์• ๋‹ˆ๋ฉ”์ด์…˜ ์ฃผ๋Š” ๋ถ€๋ถ„์ด ์ข€ ํ—ท๊ฐˆ๋ ธ๋˜ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค. gsap ๋ฌธ์„œ์—์„œ๋Š” to ๋ฉ”์„œ๋“œ์˜ ์ฒซ๋ฒˆ์งธ ๊ฐ’์œผ๋กœ ํด๋ž˜์Šค๋ช…์„ ๋„˜๊ธฐ๋Š” ๊ฒƒ ๊ฐ™์€๋ฐ ๊ฐ•์˜์—์„œ๋Š” mesh์˜ rotation ์†์„ฑ์„ ๋„˜๊ธฐ๊ณ  ์žˆ์–ด์„œ ์ฒซ๋ฒˆ์งธ ์ธ์ž์˜ ํƒ€์ž…์ด ๊ถ๊ธˆํ•ด์ ธ ์ฐพ์•„๋ณด์•˜์Šต๋‹ˆ๋‹ค.

target ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ํด๋ž˜์Šค๋ช…์ด๋‚˜ ์•„์ด๋””๊ฐ’์„ ๋„˜๊ธฐ๋ฉด GSAP ๋‚ด๋ถ€์—์„œ ์…€๋ ‰ํ„ฐ์— ๋งž๋Š” ์š”์†Œ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๊ณ , ๊ทธ์™ธ๋กœ ์ง์ ‘ ์š”์†Œ๋‚˜ ๊ฐ์ฒด, ๊ฐ์ฒด ๋ฐฐ์—ด์„ ๋„˜๊ธธ ์ˆ˜ ์žˆ๋‹ค๊ณ ํ•ฉ๋‹ˆ๋‹ค. card.mesh.rotation์€ Three.js์—์„œ ์‚ฌ์šฉ๋˜๋Š” Euler ๊ฐ์ฒด์ด๋ฉฐ, ์ด ๊ฐ์ฒด๋Š” x, y, z ์„ธ ๊ฐœ์˜ ์†์„ฑ์„ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ gsap.to์˜ ์ฒซ ๋ฒˆ์งธ ์ธ์ž๋กœ ์ „๋‹ฌ๋œ ๊ฐ์ฒด๋Š” x, y, z ์†์„ฑ์„ ๊ฐ€์ง„ ๊ฐ์ฒด์ด๊ณ , gsap์ด ์ด ๊ฐ’์„ ๋‘๋ฒˆ์งธ ํŒŒ๋ผ๋ฏธํ„ฐ์— ๋งž๊ฒŒ ๋ณ€๊ฒฝํ•จ์— ๋”ฐ๋ผ ๋ฌผ์ฒด์— ์• ๋‹ˆ๋ฉ”์ด์…˜์ด ์ ์šฉ๋  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค!

https://threejs.org/docs/index.html?q=mesh#api/en/math/Euler

 

three.js docs

 

threejs.org

์• ๋‹ˆ๋ฉ”์ด์…˜์„ ์‚ฌ์šฉํ•  ์ผ์ด ์žˆ์„ ๋•Œ ๊ธฐ๋ณธ css(transition, keyframe) ๊ฐ€ ์•„๋‹Œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ ๋ณดํ†ต framer-motion์ด๋‚˜ react transition group์„ ์‚ฌ์šฉํ–ˆ๊ณ  react-spring, animated ๋ฅผ ํ•œ ๋‘๋ฒˆ ์‚ฌ์šฉํ•ด๋ณธ ๊ฒฝํ—˜์ด ์žˆ๋Š”๋ฐ ์ด๋ฒˆ ๊ฐ•์˜๋ฅผ ํ†ตํ•ด ์ƒˆ๋กœ์šด ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ํ•˜๋‚˜ ์•Œ์•„๊ฐ„ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.  ์ฐพ์•„๋ณด๋‹ˆ ์š”์†Œ๋“ค์ด ์—ฐ๋‹ฌ์•„ ์›€์ง์ด๋Š” ๋™์ž‘๋“ค์˜ ์†๋„, ๊ฐ€์†, ๊ฐ์†, ๊ฒฝ๋กœ๋“ฑ์„ ๋ชจ๋‘ ์„ธ์„ธํ•˜๊ฒŒ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์–ด์„œ ๋งŽ์€ ๊ฐœ๋ฐœ์ž๋“ค์—๊ฒŒ ๊ฐ๊ด‘๋ฐ›๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ผ๊ณ ํ•˜๋„ค์š”..! ์ œ๊ฐ€ ์ž์ฃผ ์‚ฌ์šฉํ•˜๋Š” react์—์„œ๋„ useGSAP์ด๋ผ๋Š” ์ด๋ฆ„์˜ Hook์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์„ ๊ฒƒ ๊ฐ™๊ตฌ์š”.

 

(์ฐพ์•„๋ณด๋‹ˆ js ํ”„๋ ˆ์ž„์›Œํฌ์— ๊ตฌ์• ๋ฐ›์ง€ ์•Š๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ผ๊ณ  ํ•˜๋„ค์š” ํ˜ธ์˜ค..)

https://gsap.com/resources/React/

 

React & GSAP | GSAP | Docs & Learning

Animate with ease in React - our useGSAP() hook handles all animation cleanup for you.

gsap.com

๋‹ค์Œ ํ”„๋กœ์ ํŠธ์—์„œ๋Š” Gsap์„ ์‚ฌ์šฉํ•ด์„œ ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ์ ์šฉํ•ด๋ด์•ผ๊ฒ ์Šต๋‹ˆ๋‹ค ใ…Žใ…Ž

(p.s GSAP์ด 'GreenSock Animation Platform'์˜ ์•ฝ์ž๋ผ๋Š”๋ฐ, ์ดˆ๋ก์–‘๋ง์ด ๋„๋Œ€์ฒด ๋ฌด์Šจ์˜๋ฏธ์ผ๊นŒ์š”..?)

โœจ์˜ค๋Š˜์˜ ํ•™์Šต ์ธ์ฆ ์ƒท

 

 

๋ณธ ํฌ์ŠคํŒ…์€ ํŒจ์ŠคํŠธ์บ ํผ์Šค ํ™˜๊ธ‰ ์ฑŒ๋ฆฐ์ง€ ์ฐธ์—ฌ๋ฅผ ์œ„ํ•ด ์ž‘์„ฑํ•˜์˜€์Šต๋‹ˆ๋‹ค.

https://fastcampus.co.kr/event_online_challenge_2401_mission

 

์ปค๋ฆฌ์–ด ์„ฑ์žฅ์„ ์œ„ํ•œ ์ตœ๊ณ ์˜ ์‹ค๋ฌด๊ต์œก ์•„์นด๋ฐ๋ฏธ | ํŒจ์ŠคํŠธ์บ ํผ์Šค

์„ฑ์ธ ๊ต์œก ์„œ๋น„์Šค ๊ธฐ์—…, ํŒจ์ŠคํŠธ์บ ํผ์Šค๋Š” ๊ฐœ์ธ๊ณผ ์กฐ์ง์˜ ์‹ค์งˆ์ ์ธ '์—…(ๆฅญ)'์˜ ์„ฑ์žฅ์„ ๋•๊ณ ์ž ๋ชจ๋“  ์ข…๋ฅ˜์˜ ๊ต์œก ์ฝ˜ํ…์ธ  ์„œ๋น„์Šค๋ฅผ ์ œ๊ณตํ•˜๋Š” ๋Œ€ํ•œ๋ฏผ๊ตญ No. 1 ๊ต์œก ์„œ๋น„์Šค ํšŒ์‚ฌ์ž…๋‹ˆ๋‹ค.

fastcampus.co.kr

https://bit.ly/48sS29N