<p>
Thanks to
<a href="https://sketchfab.com/3d-models/japanese-bridge-garden-d122e17593eb4012913cde927486d15a">kristenlee</a>
for this beatiful model.
</p>
<div class="canvas-wrapper">
<canvas></canvas>
</div>
<div class="start supersonic-driver">
I'm a driver, I start an animation when I appear
</div>
<div class="end supersonic-driver">
I'm a driver, I finish an animation when I appear
</div>
.start {
position: absolute;
top: 100vh;
z-index: 10;
}
.end {
position: absolute;
top: 300vh;
z-index: 10;
}
.canvas-wrapper {
height: 100vh;
position: fixed;
left: 0;
top: 0;
width: 100%;
}
canvas {
display: block;
width: 100%;
height: 100%;
}
import {
Scene,
WebGLRenderer,
AnimationMixer,
Color,
ACESFilmicToneMapping,
AmbientLight,
sRGBEncoding
} from "https://esm.sh/three@0.145.0";
import { GLTFLoader } from "https://esm.sh/three@0.145.0/addons/loaders/GLTFLoader.js";
import { TheSupersonicPlugin } from "https://esm.sh/the-supersonic-plugin-for-scroll-based-animation@2.1.0"
const plugin = new TheSupersonicPlugin([{
start: ".start",
end: ".end",
hooks: {
onBeforeInit(driver) {
const container = document.querySelector(".canvas-wrapper");
const scene = new Scene();
const renderer = new WebGLRenderer({
antialias: true,
canvas: container.querySelector("canvas")
});
const easedProgress = 0;
scene.background = new Color(0x79bde7);
scene.add(new AmbientLight(0xc0d5ff, 0.3));
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(container.clientWidth, container.clientHeight);
renderer.outputEncoding = sRGBEncoding;
renderer.toneMapping = ACESFilmicToneMapping;
const loader = new GLTFLoader();
loader
.loadAsync(
"https://the-supersonic-plugin-for-scroll-based-animation.pages.dev/example.glb"
)
.then((gltf) => {
const camera = gltf.cameras[0];
camera.aspect = container.clientWidth / container.clientHeight;
camera.fov = 70;
camera.updateProjectionMatrix();
const mixer = new AnimationMixer(gltf.scene);
const action = mixer.clipAction(gltf.animations[0]);
action.play();
driver.data = {
container,
scene,
renderer,
easedProgress,
camera,
mixer
};
scene.add(gltf.scene);
plugin.updateLimits();
plugin.observer.instance.observe(driver.helper.domElement);
});
},
onBeforeRender(driver) {
let delta = (driver.progress - driver.data.easedProgress) * 0.05;
if (Math.abs(delta) > 0.00001)
driver.progress = driver.data.easedProgress += delta;
},
onAfterRender(driver) {
if (driver.data.camera) {
const { renderer, scene, camera } = driver.data;
driver.data.mixer.setTime(1.95 * driver.progress);
renderer.render(scene, camera);
}
},
onUpdateLimits(driver) {
const top = driver.start.top + driver.plugin.screenHeight;
driver.helper.updateLimits({
top,
height: driver.end.top - top + driver.plugin.screenHeight
}); // We need to make it, because when 'end' appears on the screen, driver stops animating, but we want animation to play till the end, so we expand the bottom line of the helper
if (driver.data.camera) {
const { container, camera, renderer, scene } = driver.data;
camera.aspect = container.clientWidth / container.clientHeight;
camera.updateProjectionMatrix();
renderer.setSize(container.clientWidth, container.clientHeight);
renderer.render(scene, camera);
}
}
}
}])
This Pen doesn't use any external JavaScript resources.