Last active
October 10, 2024 18:13
-
-
Save aldoyh/0aee92e9e91cb40551693fe71f661ffb to your computer and use it in GitHub Desktop.
GSAP ScrollSmoother + Three.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<link rel="preconnect" href="https://fonts.googleapis.com"> | |
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> | |
<link href="https://fonts.googleapis.com/css2?family=Work+Sans:wght@800" rel="stylesheet"> | |
<div id="content" class='content'> | |
<p> | |
Blackbird singing in the dead of <span>night</span><br /> | |
Take these broken <span>wings</span> and learn to fly<br /> | |
All your life<br /> | |
You were only <span>waiting</span> for this moment to arise<br /> | |
Blackbird <span>singing</span> in the dead of night<br /> | |
Take these sunken eyes and <span>learn</span> to see<br /> | |
All your life<br /> | |
You were only waiting for this <span>moment</span> to be free<br /> | |
Blackbird fly, blackbird fly<br /> | |
Into the light of a <span>dark</span> black night<br /> | |
Blackbird fly, blackbird fly<br /> | |
Into the light of a dark <span>black</span> night<br /> | |
Blackbird <span>singing</span> in the dead of night<br /> | |
Take these broken wings and <span>learn</span> to fly<br /> | |
All your life<br /> | |
You were only <span>waiting</span> for this moment to arise<br /> | |
You were only waiting for this <span>moment</span> to arise<br /> | |
You were only waiting for this moment to <span>arise</span><br /> | |
</p> | |
<a href='https://malven.co' target="_blank" class='logo'> | |
<svg width="63" height="52" viewBox="0 0 63 52" fill="none" xmlns="http://www.w3.org/2000/svg"> | |
<path d="M31.4 26.3L1.2 51.2V1.3l30.2 25z" stroke="#fff" stroke-width=".9" stroke-miterlimit="10" stroke-linejoin="round"></path> | |
<path d="M61.6 26.3L31.4 51.2 1.2 26.3l15.1-12.5 15.1 12.5 15.1-12.5 15.1 12.5z" stroke="#fff" stroke-width=".9" stroke-miterlimit="10" stroke-linejoin="round"></path> | |
<path d="M61.6 26.3v24.9L31.4 26.3l30.2-25v25z" stroke="#fff" stroke-width=".9" stroke-miterlimit="10" stroke-linejoin="round"></path> | |
</svg> | |
</a> | |
</div> | |
<!-- Three Container --> | |
<div data-app-container></div> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Experience { | |
constructor( | |
options = { | |
containerSelector: "[data-app-container]" | |
} | |
) { | |
this.options = options; | |
this.container = document.querySelector(this.options.containerSelector); | |
// GSAP Plugins | |
gsap.registerPlugin(ScrollTrigger, ScrollSmoother); | |
// Time | |
this.clock = new THREE.Clock(); | |
this.time = 0; | |
// THREE items | |
this.renderer; | |
this.camera; | |
this.scene; | |
this.controls; | |
this.meshes = []; | |
// Rotation | |
this.targetRotation = 0; | |
// Settings | |
this.settings = { | |
cameraDistance: 5, | |
scalePeriod: 8 | |
}; | |
this.init(); | |
} | |
init = () => { | |
this.createApp(); | |
this.createItems(); | |
this.initScroll(); | |
this.update(); | |
this.container.classList.add("is-ready"); | |
}; | |
initScroll = () => { | |
this.scrollSmoother = ScrollSmoother.create({ | |
content: "#content", | |
smooth: 1.2 | |
}); | |
// Add scroll triggers for each span item | |
document.querySelectorAll("span").forEach((span) => { | |
ScrollTrigger.create({ | |
trigger: span, | |
start: "top 90%", | |
end: "bottom 10%", | |
onUpdate: (self) => { | |
const dist = Math.abs(self.progress - 0.5); | |
const lightness = this.mapToRange(dist, 0, 0.5, 80, 100); | |
span.style.setProperty("--l", lightness + "%"); | |
} | |
}); | |
}); | |
}; | |
createApp = () => { | |
// Renderer | |
this.renderer = new THREE.WebGLRenderer({ | |
antialias: false, | |
alpha: true | |
}); | |
this.renderer.setPixelRatio(1.5); | |
this.renderer.setSize( | |
this.container.offsetWidth, | |
this.container.offsetHeight | |
); | |
this.container.appendChild(this.renderer.domElement); | |
// Camera | |
this.camera = new THREE.PerspectiveCamera( | |
45, | |
this.container.offsetWidth / this.container.offsetHeight, | |
1, | |
10000 | |
); | |
this.camera.position.set(0, 0, this.settings.cameraDistance); | |
this.scene = new THREE.Scene(); | |
// Orbit Controls | |
this.controls = new THREE.OrbitControls( | |
this.camera, | |
this.renderer.domElement | |
); | |
this.controls.enableKeys = false; | |
this.controls.enableZoom = false; | |
this.controls.enableDamping = false; | |
// Resize the renderer on window resize | |
window.addEventListener( | |
"resize", | |
() => { | |
this.camera.aspect = | |
this.container.offsetWidth / this.container.offsetHeight; | |
this.camera.updateProjectionMatrix(); | |
this.renderer.setSize( | |
this.container.offsetWidth, | |
this.container.offsetHeight | |
); | |
}, | |
true | |
); | |
// Ambient Light | |
let ambientLight = new THREE.AmbientLight(0xffffff, 0.1); | |
this.scene.add(ambientLight); | |
// Directional Light | |
let directionalLight = new THREE.DirectionalLight(0xffffff, 0.4); | |
directionalLight.position.set(5, 3, 2); | |
directionalLight.target.position.set(0, 0, 0); | |
this.scene.add(directionalLight); | |
}; | |
createItems = () => { | |
// Box | |
let boxGeom = new THREE.BoxBufferGeometry(2, 2, 2); | |
let material = new THREE.MeshLambertMaterial({ | |
color: 0xffffff | |
}); | |
const itemCount = 40; | |
for (let i = 0; i < itemCount; i++) { | |
const mesh = new THREE.Mesh(boxGeom, material); | |
mesh.position.y = 13 * (Math.random() * 2 - 1); | |
mesh.position.x = 3 * (Math.random() * 2 - 1); | |
mesh.position.z = 4 * (Math.random() * 2 - 1); | |
mesh.rotation.y = Math.PI * Math.random(); | |
mesh.rotationSpeed = Math.random() * 0.01 + 0.005; | |
this.scene.add(mesh); | |
this.meshes.push(mesh); | |
} | |
}; | |
updateItems = () => { | |
const time = this.time; | |
const amplitude = 0.05; | |
const period = this.settings.scalePeriod; | |
const baseScale = 0.2; | |
const scaleEffect = | |
baseScale + amplitude * Math.sin(Math.PI * 2 * (time / period)); | |
this.meshes.forEach((mesh) => { | |
mesh.scale.set(scaleEffect, scaleEffect, scaleEffect); | |
// Update rotation | |
mesh.rotation.x += mesh.rotationSpeed; | |
}); | |
// Update camera | |
const cameraRange = 10; | |
this.camera.position.y = this.mapToRange( | |
this.scrollSmoother.progress, | |
0, | |
1, | |
cameraRange, | |
-cameraRange | |
); | |
}; | |
mapToRange = (value, inMin, inMax, outMin, outMax) => { | |
return ((value - inMin) * (outMax - outMin)) / (inMax - inMin) + outMin; | |
}; | |
update = () => { | |
this.time = this.clock.getElapsedTime(); | |
this.updateItems(); | |
this.renderer.render(this.scene, this.camera); | |
window.requestAnimationFrame(this.update); | |
}; | |
} | |
new Experience(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/ScrollTrigger.min.js"></script> | |
<script src="https://assets.codepen.io/16327/ScrollSmoother.min.js"></script> | |
<script src="https://unpkg.com/[email protected]/build/three.min.js"></script> | |
<script src="https://unpkg.com/[email protected]/examples/js/controls/OrbitControls.js"></script> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// --------------------------------------------------------------- | |
// Base | |
// --------------------------------------------------------------- | |
html, | |
body { | |
min-height: 100vh; | |
background-color: #111; | |
} | |
html { | |
font-size: 62.5%; | |
box-sizing: border-box; | |
} | |
body { | |
} | |
*, | |
*:before, | |
*:after { | |
box-sizing: inherit; | |
} | |
// --------------------------------------------------------------- | |
// Content | |
// --------------------------------------------------------------- | |
.content { | |
color: white; | |
font-family: "Work Sans", sans-serif; | |
padding: 3vw; | |
padding-top: 9vh; | |
$min-value: 28; | |
$max-value: 124; | |
$min-size: 300; | |
$max-size: 1500; | |
font-size: calc( | |
#{$min-value}px + #{$max-value - $min-value} * (100vw - #{$min-size}px) / #{$max-size - | |
$min-size} | |
); | |
// Gradients | |
span { | |
--h: 0; | |
--s: 100%; | |
--l: 80%; | |
@for $idx from 1 through 30 { | |
&:nth-child(#{$idx}) { | |
--h: #{$idx * 5 + 160}; | |
} | |
} | |
--color-light: hsl(var(--h), var(--s), var(--l)); | |
--color-dark: hsl(calc(var(--h) + 30), var(--s), var(--l)); | |
position: relative; | |
color: var(--color-light); | |
display: inline; | |
background: linear-gradient(to right, var(--color-light), var(--color-dark)), | |
no-repeat; | |
-webkit-background-clip: text; | |
-webkit-text-fill-color: transparent; | |
-webkit-box-decoration-break: clone; | |
} | |
} | |
.logo { | |
display: block; | |
padding-bottom: 7rem; | |
margin-top: 20vh; | |
} | |
// --------------------------------------------------------------- | |
// Three Container | |
// --------------------------------------------------------------- | |
[data-app-container] { | |
position: fixed; | |
top: 0; | |
left: 0; | |
width: 100%; | |
height: 100%; | |
pointer-events: none; | |
transition: opacity 1s; | |
opacity: 0; | |
&.is-ready { | |
opacity: 1; | |
} | |
> canvas { | |
position: absolute; | |
top: 0; | |
left: 0; | |
width: 100%; | |
height: 100%; | |
&:focus { | |
outline: none; | |
} | |
} | |
&:before, | |
&:after { | |
content: ""; | |
display: block; | |
position: absolute; | |
left: 0; | |
right: 0; | |
height: 15vh; | |
} | |
&:before { | |
top: 0; | |
background: linear-gradient(to bottom, black, rgba(black, 0)); | |
} | |
&:after { | |
background: linear-gradient(to top, black, rgba(black, 0)); | |
bottom: 0; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment