<div class="canvas-container"></div>
<div class="page">
<section class="section">
<p>Scroll Down</p>
</section>
<section class="section">
<h1>Bear vs. Witch</h1>
</section>
<section class="section bear-stats">
Bear Stats
</section>
<section class="section witch-stats">
Witch Stats
</section>
<section class="section winner">
Winner
</section>
</div>
html, body {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
.canvas-container {
z-index: 1;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.page {
position: relative;
z-index: 2;
}
.section {
width: 100vw;
height: 100vh;
// outline: 1px solid red;
display: flex;
}
View Compiled
console.clear();
import * as THREE from "https://cdn.jsdelivr.net/npm/three@0.121.1/build/three.module.js";
import { gsap } from "https://cdn.skypack.dev/gsap";
import { ScrollTrigger } from "https://cdn.skypack.dev/gsap/ScrollTrigger";
import { GLTFLoader } from "https://cdn.jsdelivr.net/npm/three@0.121.1/examples/jsm/loaders/GLTFLoader.js";
// import threeGLTFLoader from "https://cdn.skypack.dev/three-gltf-loader"
gsap.registerPlugin(ScrollTrigger);
// --- CONSTS
const COLORS = {
background: "white",
light: "#ffffff",
sky: "#aaaaff",
ground: "#88ff88",
blue: "steelblue"
};
const PI = Math.PI;
const wireframeMaterial = new THREE.MeshBasicMaterial({
color: "white",
wireframe: true
});
// --- SCENE
const scenes = {
real: new THREE.Scene(),
wire: new THREE.Scene()
};
scenes.wire.overrideMaterial = wireframeMaterial;
let size = { width: 0, height: 0 };
// const scene = new THREE.Scene();
scenes.real.background = new THREE.Color(COLORS.background);
scenes.real.fog = new THREE.Fog(COLORS.background, 15, 20);
scenes.wire.background = new THREE.Color(COLORS.blue);
const views = [
{ height: 1, bottom: 0, scene: scenes.real, camera: null },
{ height: 0, bottom: 0, scene: scenes.wire, camera: null }
];
// --- RENDERER
const renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.physicallyCorrectLights = true;
renderer.outputEncoding = THREE.sRGBEncoding;
renderer.toneMapping = THREE.ReinhardToneMapping;
renderer.toneMappingExposure = 5;
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
const container = document.querySelector(".canvas-container");
container.appendChild(renderer.domElement);
// --- CAMERA
let cameraTarget = new THREE.Vector3(0, 3, 0);
views.forEach((view) => {
view.camera = new THREE.PerspectiveCamera(
40,
size.width / size.height,
0.1,
100
);
view.camera.position.set(0, 1, 0);
view.scene.add(view.camera);
});
// --- LIGHTS
const directionalLight = new THREE.DirectionalLight(COLORS.light, 2);
directionalLight.castShadow = true;
directionalLight.shadow.camera.far = 10;
directionalLight.shadow.mapSize.set(1024, 1024);
directionalLight.shadow.normalBias = 0.05;
directionalLight.position.set(2, 5, 3);
scenes.real.add(directionalLight);
const hemisphereLight = new THREE.HemisphereLight(
COLORS.sky,
COLORS.ground,
0.5
);
scenes.real.add(hemisphereLight);
// --- FLOOR
const plane = new THREE.PlaneGeometry(100, 100);
const floorMaterial = new THREE.MeshStandardMaterial({ color: COLORS.ground });
const floor = new THREE.Mesh(plane, floorMaterial);
floor.receiveShadow = true;
floor.rotateX(-Math.PI * 0.5);
scenes.real.add(floor);
// --- ON RESIZE
const onResize = () => {
size.width = container.clientWidth;
size.height = container.clientHeight;
views.forEach((view) => {
view.camera.aspect = size.width / size.height;
view.camera.updateProjectionMatrix();
});
renderer.setSize(size.width, size.height);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
};
window.addEventListener("resize", onResize);
onResize();
// --- TICK
const tick = () => {
views.forEach((view) => {
view.camera.lookAt(cameraTarget);
let bottom = size.height * view.bottom;
let height = size.height * view.height;
renderer.setViewport(0, 0, size.width, size.height);
renderer.setScissor(0, bottom, size.width, height);
renderer.setScissorTest(true);
renderer.render(view.scene, view.camera);
});
window.requestAnimationFrame(() => tick());
};
tick();
const toLoad = [
{
name: "witch",
file:
"https://vazxmixjsiawhamofees.supabase.co/storage/v1/object/public/models/witch/model.gltf",
group: new THREE.Group()
},
{
name: "bear",
file:
"https://vazxmixjsiawhamofees.supabase.co/storage/v1/object/public/models/bear/model.gltf",
group: new THREE.Group()
}
];
const models = {};
const clones = {};
let cameras = null;
let witches = null;
let bears = null;
const setupAnimation = () => {
cameras = { positions: [views[0].camera.position, views[1].camera.position] };
witches = {
position: [models.witch.position, clones.witch.position],
rotation: [models.witch.rotation, clones.witch.rotation]
};
bears = {
position: [models.bear.position, clones.bear.position],
rotation: [models.bear.rotation, clones.bear.rotation]
};
gsap.set(witches.position, {x: 5});
gsap.set(bears.position, {x: -5});
ScrollTrigger.matchMedia({
"(prefers-reduced-motion: no-preference)": desktopAnimation
});
};
const desktopAnimation = () => {
let section = 0;
const tl = gsap.timeline({
default: {
duration: 1,
ease: "power2.inOut"
},
scrollTrigger: {
trigger: ".page",
start: "top top",
end: "bottom bottom",
scrub: 0.1
}
});
tl.to(cameraTarget, {y: 1}, section)
tl.to(witches.position, { x: 1 }, section);
tl.to(bears.position, { x: -1 }, section);
tl.to(cameras.positions, {z: 5, ease: "power2.out"}, section)
section += 1;
tl.to(witches.position, { x: 5, ease: "power4.in" }, section);
tl.to(bears.position, { z: 2 }, section);
tl.to(views[1], {height: 1, ease: "none"}, section)
section += 1;
tl.to(witches.position, { x: 1, z: 2, ease: "power4.out" }, section);
tl.to(bears.position, { z: 0, x: -5, ease: "power4.in" }, section);
section += 1;
tl.to(witches.position, { x: 1, z: 0 }, section);
tl.to(bears.position, { z: 0, x: -1 }, section);
tl.to(views[1], {height: 0, bottom: 1, ease: "none"}, section)
};
const LoadingManager = new THREE.LoadingManager(() => {
setupAnimation();
});
const gltfLoader = new GLTFLoader(LoadingManager);
toLoad.forEach((item) => {
gltfLoader.load(item.file, (model) => {
model.scene.traverse((child) => {
if (child instanceof THREE.Mesh) {
child.receiveShadow = true;
child.castShadow = true;
}
});
item.group.add(model.scene);
scenes.real.add(item.group);
models[item.name] = item.group;
const clone = item.group.clone();
clones[item.name] = clone;
scenes.wire.add(clone);
});
});
View Compiled
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.