ํจ์คํธ์บ ํผ์ค ํ๊ธ์ฑ๋ฆฐ์ง 20์ผ์ฐจ ๋ฏธ์ (2์ 20์ผ) : ์ด๊ฒฉ์ฐจ ํจํค์ง : 21๊ฐ ํ๋ก์ ํธ๋ก ์์ฑํ๋ ์ธํฐ๋ํฐ๋ธ ์น ๊ฐ๋ฐ with Three.js & Canvas ๊ฐ์ ํ๊ธฐ
์ค๋์ ์ด์ด์ 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