DESIGN IDEA

seung weon, seoΒ·2024λ…„ 6μ›” 24일
0

project

λͺ©λ‘ 보기
8/8
post-thumbnail

🟠 DESIGN IDEA

  • ν”„λ‘œμ νŠΈ 이름: DESIGN IDEA
  • μ‚¬μš©ν•œ μ–Έμ–΄: HTML, SCSS, JavaScript
  • μ‚¬μš©ν•œ JavaScript 라이브러리: GSAP, Three.js

Random Text λ§Œλ“€κΈ°

JavaScript

this.textLines = [8, 17, 21, 23, 26, 28, 30, 31, 32, 31, 30, 28, 26, 23, 21, 17, 8];
this.letters = '1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()';
this.textLines.map((length) => 
	Array.from({length}, () => this.letters[Math.floor(Math.random() * this.letters.length)]).join('')).join('\n');
  1. 각 λ¬Έμžμ—΄μ˜ 길이λ₯Ό 담은 λ°°μ—΄ this.textLines, λ“€μ–΄κ°ˆ λ¬Έμžκ°€ λ‹΄κΈ΄ this.lettersλ₯Ό λ³€μˆ˜λ‘œ λ§Œλ“€κΈ°
  2. this.textLines 배열을 map ν•¨μˆ˜λ‘œ μˆœνšŒν•˜κ³ , 각 라인의 길이λ₯Ό μ§€μ •ν•˜λŠ” lengthλ₯Ό λ§€κ°œλ³€μˆ˜λ‘œ λ§Œλ“€κΈ°
  3. Array.from ν•¨μˆ˜λ‘œ 배열을 μƒμ„±ν•˜κ³ , length 객체λ₯Ό 톡해 λ°°μ—΄μ˜ 길이λ₯Ό 지정
  4. 랜덀 문자둜 λ“€μ–΄κ°ˆ this.letters의 총 길이λ₯Ό lengthλ₯Ό μ‚¬μš©ν•˜μ—¬ κ΅¬ν•˜κ³ , Math.random()을 κ³±ν•˜μ—¬ 총 길이 λ‚΄μ—μ„œ λžœλ€ν•œ 숫자 λ§Œλ“€κΈ°
  5. μ •μˆ˜λ‘œ λ§Œλ“€κΈ° μœ„ν•΄ Math.floorλ₯Ό μ‚¬μš©ν•˜κ³ , this.letters[] λ°°μ—΄ μ•ˆμ— 랜덀 숫자λ₯Ό λ„£μ–΄μ„œ, λžœλ€ν•œ λ¬Έμžμ—΄λ“€μ„ λ°°μ—΄λ‘œ μ±„μš°κΈ°
  6. join('')을 μ‚¬μš©ν•˜μ—¬ 각 배열에 μžˆλŠ” λžœλ€ν•œ λ¬Έμžμ—΄λ“€μ„ ν•©μΉ˜κ³ , join('\n')을 톡해 각 λ¬Έμžμ—΄μ„ μ€„λ°”κΏˆ 문자 \n으둜 κ²°ν•©ν•˜μ—¬ 각 μ€„λ‘œ λ‚˜λˆ„κΈ°

3D 둜고 μ• λ‹ˆλ©”μ΄μ…˜

Three.jsλž€?

Three.jsλŠ” μ›Ή λΈŒλΌμš°μ €μ—μ„œ 3D κ·Έλž˜ν”½μ„ κ΅¬ν˜„ν•˜κΈ° μœ„ν•œ μžλ°”μŠ€ν¬λ¦½νŠΈ 라이브러리이며, WebGL을 기반으둜 3D μ• λ‹ˆλ©”μ΄μ…˜ 및 κ·Έλž˜ν”½μ„ 보닀 μ‰½κ²Œ λ§Œλ“€ 수 있게 ν•©λ‹ˆλ‹€.

Three.jsλ₯Ό κ΅¬μ„±ν•˜λŠ” κΈ°λ³Έ μš”μ†Œ

  • Scene - 3D μž₯면을 μ •μ˜ν•˜λŠ” 기본적인 μ»¨ν…Œμ΄λ„ˆ
  • Geometry - 3D 객체의 λͺ¨μ–‘ 및 ꡬ쑰 μ •μ˜
  • Texture - 3D 객체의 ν‘œλ©΄ 이미지 μž…νž λ•Œ μ‚¬μš©
  • Material - 3D 객체의 ν‘œλ©΄ νŠΉμ„±(μ‹œκ°μ  속성)을 μ •μ˜ν•˜λŠ” μš”μ†Œ
  • Light - 3D μ”¬μ—μ„œ μ‘°λͺ… 효과λ₯Ό κ΅¬ν˜„ν•˜λŠ” 데 μ‚¬μš©
  • Camera - 3D μž₯면을 λ Œλ”λ§ν•˜κΈ° μœ„ν•΄ μ‚¬μš©λ˜λŠ” μ‹œμ  μ •μ˜
  • Mesh - 3D κ³΅κ°„μ—μ„œ μ‹€μ œλ‘œ λ³΄μ΄λŠ” 객체λ₯Ό λ‚˜νƒ€λ‚΄λŠ” μš”μ†Œ (Geometry + Material)
  • Renderer - 3D μž₯면을 μ‹€μ œ 화면에 λ Œλ”λ§ν•˜λŠ” 역할을 λ‹΄λ‹Ή (WebGL기반)

Javascript

Scene

 this.scene = new THREE.Scene();
  1. μƒˆλ‘œμš΄ Three.js μž₯λ©΄(Scene) μƒμ„±ν•˜κΈ°

Geometry

 this.geometry = new THREE.SphereGeometry(1, 64, 32);
  1. λ°˜μ§€λ¦„μ΄ 1인 ꡬ λͺ¨μ–‘μ˜ 3D 객체 생성, 64와 32λŠ” ꡬ의 μˆ˜ν‰ 및 수직 μ„Έκ·Έλ¨ΌνŠΈ 수이며, μ„Έκ·Έλ¨ΌνŠΈκ°€ λ§Žμ„μˆ˜λ‘ ꡬ가 더 λ§€λ„λŸ½κ²Œ 보이게 됨

Texture

this.textureLoader = new THREE.TextureLoader();
this.texture = this.textureLoader.load('textures/logo-texture.png');
this.texture.colorSpace = THREE.SRGBColorSpace;
this.texture.offset.x = .25;
this.texture.generateMipmaps = false;
  1. Three.js의 TextureLoaderλ₯Ό μ‚¬μš©ν•˜μ—¬ 3D 객체에 μ μš©ν•  이미지 뢈러였기
  2. ν…μŠ€μ²˜μ˜ 색상을 sRGBColorSpace둜 μ„€μ •ν•˜μ—¬ μ •ν™•ν•œ 색상이 λ‚˜μ˜¬ 수 μžˆλ„λ‘ ν•˜κΈ°
  3. ν…μŠ€μ²˜μ˜ μœ„μΉ˜λ₯Ό xμΆ• κΈ°μ€€ .25 만큼 λ³€κ²½ (μ›ν•˜λŠ” λŒ€λ‘œ ν…μŠ€μ²˜μ˜ μœ„μΉ˜ λ³€κ²½ κ°€λŠ₯)
  4. generateMipmapsλ₯Ό false둜 μ„€μ • (Mipmaps μ‚¬μš©μ‹œ 둜고의 μ„ λͺ…도가 λ–¨μ–΄μ§ˆ 수 있음)

Mipmaps λž€?

MipmapsλŠ” 원본 ν…μŠ€μ²˜μ˜ μ—¬λŸ¬ 해상도λ₯Ό ν¬ν•¨ν•˜λŠ” 일련의 ν…μŠ€μ²˜ μ΄λ―Έμ§€λ‘œ, 각 레벨의 ν•΄μƒλ„λŠ” 이전 레벨의 절반 ν¬κΈ°μž…λ‹ˆλ‹€. 예λ₯Ό λ“€μ–΄, 256x256 크기의 원본 ν…μŠ€μ²˜κ°€ μžˆλ‹€λ©΄, λ‹€μŒ λ ˆλ²¨μ€ 128x128, κ·Έ λ‹€μŒμ€ 64x64, 32x32, 16x16, 8x8, 4x4, 2x2, 그리고 1x1 크기둜 μ€„μ–΄λ“€κ²Œ 되며, 카메라가 ν•΄λ‹Ή κ°μ²΄μ—μ„œ λ©€μ–΄μ§ˆ λ•Œ GPUλŠ” μž‘μ€ 크기의 ν…μŠ€μ²˜λ₯Ό μ‚¬μš©ν•˜λ―€λ‘œ λ Œλ”λ§μ΄ 더 λΉ λ₯΄κ²Œ μ΄λ£¨μ–΄μ§‘λ‹ˆλ‹€.
(2의 κ±°λ“­μ œκ³± 해상도λ₯Ό 가진 이미지λ₯Ό Three.js의 ν…μŠ€μ²˜λ‘œ μ‚¬μš©ν•΄μ•Ό ν•˜λŠ” 이유)

Material

this.material = new THREE.MeshBasicMaterial({map: this.texture});
  1. 미리 λ‘œλ”©ν•΄λ‘” ν…μŠ€μ²˜ 즉, this.textureλ₯Ό μ‚¬μš©ν•˜μ—¬ Material에 λ§΅ν•‘ν•˜κΈ°
    (MeshBasicMaterial은 μ‘°λͺ…을 λ°˜μ˜ν•˜μ§€ μ•ŠμœΌλ©°, λ‹¨μˆœνžˆ ν…μŠ€μ²˜λ§Œ λ³΄μ—¬μ€Œ)

Camera

this.camera = new THREE.PerspectiveCamera(75, this.sizes.width / this.sizes.height, 0.1, 100);
this.camera.position.z = 1.66;
this.scene.add(this.camera);
  1. μ‹œμ•Όκ° 75도, ν™”λ©΄ λΉ„μœ¨ (μΊ”λ²„μŠ€μ˜ λ„ˆλΉ„ / 높이), κ°€μ‹œ λ²”μœ„ 0.1 ~ 100의 Perspective Camera λ§Œλ“€κΈ°
  2. 카메라λ₯Ό zμΆ• λ°©ν–₯으둜 1.66만큼 이동 (μΉ΄λ©”λΌμ˜ μœ„μΉ˜ μ›ν•˜λŠ” λŒ€λ‘œ μ„€μ • κ°€λŠ₯)
  3. 카메라λ₯Ό Scene에 μΆ”κ°€

Mesh

this.mesh = new THREE.Mesh(this.geometry, this.material);
this.scene.add(this.mesh);
  1. 미리 μƒμ„±ν•œ Geometry와 Material을 μ‚¬μš©ν•˜μ—¬ Mesh λ§Œλ“€κΈ°
  2. Meshλ₯Ό Scene에 μΆ”κ°€

Renderer

this.renderer = new THREE.WebGLRenderer({
	canvas: this.canvas,
    antialias: true,
    alpha: true
});
this.renderer.setSize(this.sizes.width, this.sizes.height);
this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
  1. WebGLRendererλ₯Ό μƒμ„±ν•˜κ³ , λ Œλ”λ§μ— μ‚¬μš©ν•  μΊ”λ²„μŠ€ μ„€μ •ν•˜κΈ°
  2. antialias: true, alpha: trueλ₯Ό μž‘μ„±ν•˜μ—¬ 앀티앨리어싱을 ν™œμ„±ν™”ν•˜κ³ , Renderer의 배경을 투λͺ…ν•˜κ²Œ λ§Œλ“€κΈ° (검은색이 κΈ°λ³Έ λ°°κ²½μƒ‰μœΌλ‘œ μ„€μ •λ˜μ–΄ 있음)
  3. Renderer의 크기 μ„€μ • (μΊ”λ²„μŠ€μ˜ λ„ˆλΉ„ / 높이)
  4. Renderer의 ν”½μ…€ λΉ„μœ¨μ„ μ„€μ •, Math.min을 μ‚¬μš©ν•˜μ—¬ window.devicePixelRatio, 2 쀑 μž‘μ€ 값을 μ‚¬μš© (고해상도 λ””μŠ€ν”Œλ ˆμ΄μ—μ„œλ„ μ„±λŠ₯κ³Ό μ„ λͺ…도λ₯Ό μ΅œμ ν™”ν•˜κΈ° μœ„ν•΄μ„œ)

WebGLRendererλž€?

Three.jsμ—μ„œ μ‚¬μš©λ˜λŠ” WebGL 기반의 Renderer둜, μ›Ή λΈŒλΌμš°μ €μ—μ„œ 3D κ·Έλž˜ν”½μ„ 효율적으둜 λ Œλ”λ§ν•  수 μžˆλ„λ‘ WebGL APIλ₯Ό ν™œμš©ν•©λ‹ˆλ‹€.

Animation

this.clock = new THREE.Clock();
const tick = () => {
	const elaspedTime = clock.getElapsedTime();
    this.mesh.rotation.y = elaspedTime * 1.2;
    this.renderer.render(this.scene, this.camera);
    window.requestAnimationFrame(tick);
};
tick();
  1. Three.js에 λ‚΄μž₯λ˜μ–΄ μžˆλŠ” Clock ν•¨μˆ˜λ₯Ό μ‚¬μš©ν•˜μ—¬ μ‹œκ°„ μΈ‘μ • μ‹œμž‘
  2. μ• λ‹ˆλ©”μ΄μ…˜ 루프λ₯Ό μ •μ˜ν•  tick ν•¨μˆ˜ λ§Œλ“€κΈ°
  3. clock.getElaspedTime();을 μ‚¬μš©ν•˜μ—¬ 경과된 μ‹œκ°„ μΈ‘μ • (Clock ν•¨μˆ˜κ°€ μ‹œμž‘λœ μ‹œμ λΆ€ν„°)
  4. 경과된 μ‹œκ°„μ— λ”°λΌμ„œ Meshλ₯Ό yμΆ• λ°©ν–₯으둜 νšŒμ „μ‹œν‚€κΈ° (경과된 μ‹œκ°„μ— μ›ν•˜λŠ” 숫자λ₯Ό κ³±ν•˜μ—¬ μ• λ‹ˆλ©”μ΄μ…˜μ˜ μŠ€ν”Όλ“œ μ‘°μ •)
  5. Sceneκ³Ό Cameraλ₯Ό μ‚¬μš©ν•˜μ—¬ λ Œλ”λ§ ν•˜κΈ°
  6. window.requestAnimationFrame(tick);을 ν˜ΈμΆœν•˜μ—¬ 맀 ν”„λ ˆμž„λ§ˆλ‹€ tick ν•¨μˆ˜λ₯Ό ν˜ΈμΆœν•˜κΈ°
  7. tick ν•¨μˆ˜λ₯Ό ν˜ΈμΆœν•˜μ—¬ μ• λ‹ˆλ©”μ΄μ…˜ μ‹œμž‘

requestAnimationFrameμ΄λž€?

λΈŒλΌμš°μ €μ—μ„œ μ• λ‹ˆλ©”μ΄μ…˜μ„ 효율적으둜 μˆ˜ν–‰ν•  수 μžˆλ„λ‘ μ§€μ›ν•˜λŠ” JavaScript ν•¨μˆ˜μž…λ‹ˆλ‹€.
이 ν•¨μˆ˜λŠ” λΈŒλΌμš°μ €μ˜ 재페인트 주기에 맞좰 콜백 ν•¨μˆ˜λ₯Ό μ‹€ν–‰ν•¨μœΌλ‘œμ¨, CPU와 GPU의 μ‚¬μš©μ„ μ΅œμ ν™”ν•˜κ³ , μ• λ‹ˆλ©”μ΄μ…˜μ˜ λΆ€λ“œλŸ¬μ›€μ„ 보μž₯ν•©λ‹ˆλ‹€.

3D Carousel(μŠ¬λΌμ΄λ”) λ§Œλ“€κΈ°

HTML

<div class="group-team">
...
  <div class="team-area">
    <ul class="team-list">
      <li class="team-item">
        <img src="./images/team-img.png" alt="team-img">
      	  <div class="desc-box">
            ...
          </div>
      </li>
      <li class="team-item">
    	...
      </li>
      <li class="team-item">
    	...
      </li>
    </ul>
  </div>
</div>

SCSS

.team-area{
  position: relative;
  width: 720px;
  aspect-ratio: 1/1;
  perspective: 1200px;
  margin: auto 0;
  .team-list{
    position: absolute;
    top: 0;left: 0;
    width: 100%;
    height: 100%;
    transform-style: preserve-3d;
    transform: translateZ(-400px);
    .team-item{
      position: absolute;
      top: 0;left: 0;
      width: 100%;height: 100%;
      &:first-child{
        transform: rotateY(0) translateZ(400px);
      }
      &:nth-child(2){
        transform: rotateY(120deg) translateZ(400px);
      }
      &:last-child{
        transform: rotateY(240deg) translateZ(400px);
      }
      ...
    }
  }
}

JavaScript

const teamTl = gsap.timeline({
  scrollTrigger: {
  	trigger: '.group-team',
    start: '+=0',
    end: '+=2500',
    scrub: 1,
    pin: !0,
  }
});
teamTl.to('.team-list',{
  rotateY: 240
  }, 'a');
  1. .team-area의 λ„ˆλΉ„ 및 높이λ₯Ό μ„€μ •ν•˜κ³ , perspective κ°’ μ„€μ •ν•˜κΈ°

perspective λž€?

CSS의 perspective 속성은 3D κ³΅κ°„μ—μ„œ μžμ‹μ„ λ³΄λŠ” μ‹œμ μ˜ 깊이λ₯Ό μ •μ˜ν•˜λŠ” 데 μ‚¬μš©λ©λ‹ˆλ‹€.
(λΆ€λͺ¨ μš”μ†Œμ— 적용되며, 이 λΆ€λͺ¨ μš”μ†Œμ˜ μžμ‹ μš”μ†Œλ“€μ— 3D νš¨κ³Όκ°€ 적용)

  1. .team-list에 transform-style: preserve-3d; 적용 ν›„, zμΆ• λ°©ν–₯으둜 -400px 만큼 이동 (μž„μ˜μ˜ κ°’ μ„€μ • κ°€λŠ₯)

preserve-3d λž€?

CSSμ—μ„œ 3D λ³€ν™˜μ„ μ²˜λ¦¬ν•˜λŠ” 방법을 지정, μš”μ†Œκ°€ 3D κ³΅κ°„μ—μ„œ μžμ‹ μš”μ†Œλ“€μ„ μ–΄λ–»κ²Œ λ Œλ”λ§ν• μ§€λ₯Ό κ²°μ •
(.team-list의 μžμ‹ μš”μ†ŒμΈ .team-item이 3D κ³΅κ°„μ—μ„œ λ Œλ”λ§ 됨)

  1. .team-item에 position: absolute;λ₯Ό μ‚¬μš©ν•˜μ—¬ 겹치게 λ§Œλ“€κ³ , 각각 rotateY 속성을 μ‚¬μš©ν•˜μ—¬ 원 λͺ¨μ–‘μœΌλ‘œ λ°°μΉ˜μ‹œν‚€κΈ° (총 3개의 μŠ¬λΌμ΄λ“œκ°€ 있고, μ›μ˜ κ°λ„λŠ” 360도이기 λ•Œλ¬Έμ—, 각 μš”μ†Œμ— 0도, 120도, 240λ„μ˜ 값을 μž‘μ„±)

  2. .team-list μ—μ„œ zμΆ• λ°©ν–₯으둜 -400px 만큼 μ΄λ™ν–ˆμœΌλ―€λ‘œ, .team-itemμ—λŠ” zμΆ• λ°©ν–₯으둜 400px 만큼 이동 (값은 μž„μ˜λ‘œ μ„€μ • κ°€λŠ₯, .team-area의 λ„ˆλΉ„ 720pxλ₯Ό .team-item에 κ·ΈλŒ€λ‘œ μ μš©ν•˜κΈ° μœ„ν•΄μ„œ)

  3. λ³€μˆ˜ teamTl에 gsap νƒ€μž„λΌμΈ 지정 (νƒ€μž„λΌμΈ λ‚΄μ—μ„œ scrollTrigger 섀정을 μ‚¬μš©ν•˜μ—¬ μŠ€ν¬λ‘€μ— 따라 μ• λ‹ˆλ©”μ΄μ…˜μ΄ λ™μž‘ν•  수 μžˆλ„λ‘ λ§Œλ“€κΈ°)

  4. .team-area의 μ»¨ν…Œμ΄λ„ˆμΈ .group-team을 트리거 μš”μ†Œλ‘œ μ„€μ •

  5. start와 end κ°’ μ„€μ • (0px 트리거 μš”μ†Œκ°€ 처음 λ‚˜νƒ€λ‚ λ•Œ μ‹œμž‘ν•˜κ³  2500px 만큼 슀크둀 된 ν›„ μ• λ‹ˆλ©”μ΄μ…˜ μ’…λ£Œ)

  6. pin을 μ‚¬μš©ν•˜μ—¬ μ• λ‹ˆλ©”μ΄μ…˜μ΄ 끝날 λ•ŒκΉŒμ§€ 트리거 μš”μ†Œ κ³ μ •μ‹œν‚€κΈ°

  7. teamTl.toλ₯Ό μ‚¬μš©ν•˜μ—¬ .team-list에 μ• λ‹ˆλ©”μ΄μ…˜μ„ μ •μ˜ν•˜κ³  νƒ€μž„λΌμΈμ— μΆ”κ°€

  8. .team-list에 rotateY 값을 μ μš©ν•˜μ—¬ μ• λ‹ˆλ©”μ΄μ…˜ μ™„μ„±ν•˜κΈ° (ν•œ μš”μ†Œκ°€ νšŒμ „ν•˜μ—¬ ν™”λ©΄ 쀑앙에 μœ„μΉ˜μ‹œν‚€λ €λ©΄ 120도 만큼 ν•„μš”, λͺ¨λ“  μš”μ†Œλ“€μ΄ 쀑앙에 ν•œλ²ˆμ”© μœ„μΉ˜ν•˜λ €λ©΄ 총 240도 ν•„μš”)

profile
to reach new possibilities

0개의 λŒ“κΈ€