Skip to content

Instantly share code, notes, and snippets.

@aldoyh
Last active October 10, 2024 18:13
Show Gist options
  • Save aldoyh/0aee92e9e91cb40551693fe71f661ffb to your computer and use it in GitHub Desktop.
Save aldoyh/0aee92e9e91cb40551693fe71f661ffb to your computer and use it in GitHub Desktop.
GSAP ScrollSmoother + Three.js
<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>
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();
<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>
// ---------------------------------------------------------------
// 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