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');
this.textLines
, λ€μ΄κ° λ¬Έμκ° λ΄κΈ΄ this.letters
λ₯Ό λ³μλ‘ λ§λ€κΈ°this.textLines
λ°°μ΄μ map
ν¨μλ‘ μννκ³ , κ° λΌμΈμ κΈΈμ΄λ₯Ό μ§μ νλ length
λ₯Ό 맀κ°λ³μλ‘ λ§λ€κΈ°Array.from
ν¨μλ‘ λ°°μ΄μ μμ±νκ³ , length
κ°μ²΄λ₯Ό ν΅ν΄ λ°°μ΄μ κΈΈμ΄λ₯Ό μ§μ this.letters
μ μ΄ κΈΈμ΄λ₯Ό length
λ₯Ό μ¬μ©νμ¬ κ΅¬νκ³ , Math.random()
μ κ³±νμ¬ μ΄ κΈΈμ΄ λ΄μμ λλ€ν μ«μ λ§λ€κΈ°Math.floor
λ₯Ό μ¬μ©νκ³ , this.letters[]
λ°°μ΄ μμ λλ€ μ«μλ₯Ό λ£μ΄μ, λλ€ν λ¬Έμμ΄λ€μ λ°°μ΄λ‘ μ±μ°κΈ°join('')
μ μ¬μ©νμ¬ κ° λ°°μ΄μ μλ λλ€ν λ¬Έμμ΄λ€μ ν©μΉκ³ , join('\n')
μ ν΅ν΄ κ° λ¬Έμμ΄μ μ€λ°κΏ λ¬Έμ \n
μΌλ‘ κ²°ν©νμ¬ κ° μ€λ‘ λλκΈ°
Three.js
λ μΉ λΈλΌμ°μ μμ 3D κ·Έλν½μ ꡬννκΈ° μν μλ°μ€ν¬λ¦½νΈ λΌμ΄λΈλ¬λ¦¬μ΄λ©°,WebGL
μ κΈ°λ°μΌλ‘ 3D μ λλ©μ΄μ λ° κ·Έλν½μ λ³΄λ€ μ½κ² λ§λ€ μ μκ² ν©λλ€.
Scene
- 3D μ₯λ©΄μ μ μνλ κΈ°λ³Έμ μΈ μ»¨ν
μ΄λGeometry
- 3D κ°μ²΄μ λͺ¨μ λ° κ΅¬μ‘° μ μTexture
- 3D κ°μ²΄μ νλ©΄ μ΄λ―Έμ§ μ
ν λ μ¬μ©Material
- 3D κ°μ²΄μ νλ©΄ νΉμ±(μκ°μ μμ±)μ μ μνλ μμLight
- 3D μ¬μμ μ‘°λͺ
ν¨κ³Όλ₯Ό ꡬννλ λ° μ¬μ©Camera
- 3D μ₯λ©΄μ λ λλ§νκΈ° μν΄ μ¬μ©λλ μμ μ μMesh
- 3D 곡κ°μμ μ€μ λ‘ λ³΄μ΄λ κ°μ²΄λ₯Ό λνλ΄λ μμ (Geometry
+ Material
)Renderer
- 3D μ₯λ©΄μ μ€μ νλ©΄μ λ λλ§νλ μν μ λ΄λΉ (WebGL
κΈ°λ°)this.scene = new THREE.Scene();
Three.js
μ₯λ©΄(Scene
) μμ±νκΈ°this.geometry = new THREE.SphereGeometry(1, 64, 32);
1
μΈ κ΅¬ λͺ¨μμ 3D κ°μ²΄ μμ±, 64
μ 32
λ ꡬμ μν λ° μμ§ μΈκ·Έλ¨ΌνΈ μμ΄λ©°, μΈκ·Έλ¨ΌνΈκ° λ§μμλ‘ κ΅¬κ° λ 맀λλ½κ² 보μ΄κ² λ¨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;
Three.js
μ TextureLoader
λ₯Ό μ¬μ©νμ¬ 3D κ°μ²΄μ μ μ©ν μ΄λ―Έμ§ λΆλ¬μ€κΈ°sRGBColorSpace
λ‘ μ€μ νμ¬ μ νν μμμ΄ λμ¬ μ μλλ‘ νκΈ°x
μΆ κΈ°μ€ .25 λ§νΌ λ³κ²½ (μνλ λλ‘ ν
μ€μ²μ μμΉ λ³κ²½ κ°λ₯)generateMipmaps
λ₯Ό false
λ‘ μ€μ (Mipmaps
μ¬μ©μ λ‘κ³ μ μ λͺ
λκ° λ¨μ΄μ§ μ μμ)Mipmaps λ?
Mipmaps
λ μλ³Έ ν μ€μ²μ μ¬λ¬ ν΄μλλ₯Ό ν¬ν¨νλ μΌλ ¨μ ν μ€μ² μ΄λ―Έμ§λ‘, κ° λ 벨μ ν΄μλλ μ΄μ λ 벨μ μ λ° ν¬κΈ°μ λλ€. μλ₯Ό λ€μ΄, 256x256 ν¬κΈ°μ μλ³Έ ν μ€μ²κ° μλ€λ©΄, λ€μ λ 벨μ 128x128, κ·Έ λ€μμ 64x64, 32x32, 16x16, 8x8, 4x4, 2x2, κ·Έλ¦¬κ³ 1x1 ν¬κΈ°λ‘ μ€μ΄λ€κ² λλ©°, μΉ΄λ©λΌκ° ν΄λΉ κ°μ²΄μμ λ©μ΄μ§ λ GPUλ μμ ν¬κΈ°μ ν μ€μ²λ₯Ό μ¬μ©νλ―λ‘ λ λλ§μ΄ λ λΉ λ₯΄κ² μ΄λ£¨μ΄μ§λλ€.
(2μ κ±°λμ κ³± ν΄μλλ₯Ό κ°μ§ μ΄λ―Έμ§λ₯ΌThree.js
μ ν μ€μ²λ‘ μ¬μ©ν΄μΌ νλ μ΄μ )
this.material = new THREE.MeshBasicMaterial({map: this.texture});
this.texture
λ₯Ό μ¬μ©νμ¬ Material
μ 맡ννκΈ°MeshBasicMaterial
μ μ‘°λͺ
μ λ°μνμ§ μμΌλ©°, λ¨μν ν
μ€μ²λ§ 보μ¬μ€)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);
Perspective Camera
λ§λ€κΈ°z
μΆ λ°©ν₯μΌλ‘ 1.66λ§νΌ μ΄λ (μΉ΄λ©λΌμ μμΉ μνλ λλ‘ μ€μ κ°λ₯)Scene
μ μΆκ°this.mesh = new THREE.Mesh(this.geometry, this.material); this.scene.add(this.mesh);
Geometry
μ Material
μ μ¬μ©νμ¬ Mesh
λ§λ€κΈ°Mesh
λ₯Ό Scene
μ μΆκ°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));
WebGLRenderer
λ₯Ό μμ±νκ³ , λ λλ§μ μ¬μ©ν μΊλ²μ€ μ€μ νκΈ°antialias: true, alpha: true
λ₯Ό μμ±νμ¬ μ€ν°μ¨λ¦¬μ΄μ±μ νμ±ννκ³ , Renderer
μ λ°°κ²½μ ν¬λͺ
νκ² λ§λ€κΈ° (κ²μμμ΄ κΈ°λ³Έ λ°°κ²½μμΌλ‘ μ€μ λμ΄ μμ)Renderer
μ ν¬κΈ° μ€μ (μΊλ²μ€μ λλΉ / λμ΄)Renderer
μ ν½μ
λΉμ¨μ μ€μ , Math.min
μ μ¬μ©νμ¬ window.devicePixelRatio
, 2
μ€ μμ κ°μ μ¬μ© (κ³ ν΄μλ λμ€νλ μ΄μμλ μ±λ₯κ³Ό μ λͺ
λλ₯Ό μ΅μ ννκΈ° μν΄μ)WebGLRendererλ?
Three.js
μμ μ¬μ©λλWebGL
κΈ°λ°μRenderer
λ‘, μΉ λΈλΌμ°μ μμ 3D κ·Έλν½μ ν¨μ¨μ μΌλ‘ λ λλ§ν μ μλλ‘WebGL API
λ₯Ό νμ©ν©λλ€.
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();
Three.js
μ λ΄μ₯λμ΄ μλ Clock
ν¨μλ₯Ό μ¬μ©νμ¬ μκ° μΈ‘μ μμtick
ν¨μ λ§λ€κΈ°clock.getElaspedTime();
μ μ¬μ©νμ¬ κ²½κ³Όλ μκ° μΈ‘μ (Clock
ν¨μκ° μμλ μμ λΆν°)Mesh
λ₯Ό y
μΆ λ°©ν₯μΌλ‘ νμ μν€κΈ° (κ²½κ³Όλ μκ°μ μνλ μ«μλ₯Ό κ³±νμ¬ μ λλ©μ΄μ
μ μ€νΌλ μ‘°μ )Scene
κ³Ό Camera
λ₯Ό μ¬μ©νμ¬ λ λλ§ νκΈ°window.requestAnimationFrame(tick);
μ νΈμΆνμ¬ λ§€ νλ μλ§λ€ tick
ν¨μλ₯Ό νΈμΆνκΈ°tick
ν¨μλ₯Ό νΈμΆνμ¬ μ λλ©μ΄μ
μμrequestAnimationFrameμ΄λ?
λΈλΌμ°μ μμ μ λλ©μ΄μ μ ν¨μ¨μ μΌλ‘ μνν μ μλλ‘ μ§μνλ
JavaScript
ν¨μμ λλ€.
μ΄ ν¨μλ λΈλΌμ°μ μ μ¬νμΈνΈ μ£ΌκΈ°μ λ§μΆ° μ½λ°± ν¨μλ₯Ό μ€νν¨μΌλ‘μ¨,CPU
μGPU
μ μ¬μ©μ μ΅μ ννκ³ , μ λλ©μ΄μ μ λΆλλ¬μμ 보μ₯ν©λλ€.
<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>
.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); } ... } } }
const teamTl = gsap.timeline({ scrollTrigger: { trigger: '.group-team', start: '+=0', end: '+=2500', scrub: 1, pin: !0, } }); teamTl.to('.team-list',{ rotateY: 240 }, 'a');
.team-area
μ λλΉ λ° λμ΄λ₯Ό μ€μ νκ³ , perspective
κ° μ€μ νκΈ°
CSS
μperspective
μμ±μ 3D 곡κ°μμ μμμ 보λ μμ μ κΉμ΄λ₯Ό μ μνλ λ° μ¬μ©λ©λλ€.
(λΆλͺ¨ μμμ μ μ©λλ©°, μ΄ λΆλͺ¨ μμμ μμ μμλ€μ 3D ν¨κ³Όκ° μ μ©)
.team-list
μ transform-style: preserve-3d;
μ μ© ν, zμΆ λ°©ν₯μΌλ‘ -400px
λ§νΌ μ΄λ (μμμ κ° μ€μ κ°λ₯)
CSS
μμ 3D λ³νμ μ²λ¦¬νλ λ°©λ²μ μ§μ , μμκ° 3D 곡κ°μμ μμ μμλ€μ μ΄λ»κ² λ λλ§ν μ§λ₯Ό κ²°μ
(.team-list
μ μμ μμμΈ.team-item
μ΄ 3D 곡κ°μμ λ λλ§ λ¨)
.team-item
μ position: absolute;
λ₯Ό μ¬μ©νμ¬ κ²ΉμΉκ² λ§λ€κ³ , κ°κ° rotateY
μμ±μ μ¬μ©νμ¬ μ λͺ¨μμΌλ‘ λ°°μΉμν€κΈ° (μ΄ 3κ°μ μ¬λΌμ΄λκ° μκ³ , μμ κ°λλ 360
λμ΄κΈ° λλ¬Έμ, κ° μμμ 0
λ, 120
λ, 240
λμ κ°μ μμ±)
.team-list
μμ z
μΆ λ°©ν₯μΌλ‘ -400px
λ§νΌ μ΄λνμΌλ―λ‘, .team-item
μλ z
μΆ λ°©ν₯μΌλ‘ 400px
λ§νΌ μ΄λ (κ°μ μμλ‘ μ€μ κ°λ₯, .team-area
μ λλΉ 720px
λ₯Ό .team-item
μ κ·Έλλ‘ μ μ©νκΈ° μν΄μ)
λ³μ teamTl
μ gsap
νμλΌμΈ μ§μ (νμλΌμΈ λ΄μμ scrollTrigger
μ€μ μ μ¬μ©νμ¬ μ€ν¬λ‘€μ λ°λΌ μ λλ©μ΄μ
μ΄ λμν μ μλλ‘ λ§λ€κΈ°)
.team-area
μ 컨ν
μ΄λμΈ .group-team
μ νΈλ¦¬κ±° μμλ‘ μ€μ
start
μ end
κ° μ€μ (0px
νΈλ¦¬κ±° μμκ° μ²μ λνλ λ μμνκ³ 2500px
λ§νΌ μ€ν¬λ‘€ λ ν μ λλ©μ΄μ
μ’
λ£)
pin
μ μ¬μ©νμ¬ μ λλ©μ΄μ
μ΄ λλ λκΉμ§ νΈλ¦¬κ±° μμ κ³ μ μν€κΈ°
teamTl.to
λ₯Ό μ¬μ©νμ¬ .team-list
μ μ λλ©μ΄μ
μ μ μνκ³ νμλΌμΈμ μΆκ°
.team-list
μ rotateY
κ°μ μ μ©νμ¬ μ λλ©μ΄μ
μμ±νκΈ° (ν μμκ° νμ νμ¬ νλ©΄ μ€μμ μμΉμν€λ €λ©΄ 120
λ λ§νΌ νμ, λͺ¨λ μμλ€μ΄ μ€μμ νλ²μ© μμΉνλ €λ©΄ μ΄ 240
λ νμ)