Skip to content

Instantly share code, notes, and snippets.

@Bendzae
Last active December 30, 2023 13:05
Show Gist options
  • Save Bendzae/47d7be8762d8418e9f990c6cf30c2225 to your computer and use it in GitHub Desktop.
Save Bendzae/47d7be8762d8418e9f990c6cf30c2225 to your computer and use it in GitHub Desktop.
Three.js camera movement on scroll
// Three JS scene component
const Scene = () => {
const camPositionKeyframes = new Map([
[0, new Vector3(0,1000,0)],
[0.5, new Vector3(500,1000,-200)],
[1.0, new Vector3(1000,1000,400)],
[1.5, new Vector3(1500,1000,-800)],
];
const lookTargetKeyframes = new Map([
[0, new Vector3(0,0,0)],
[0.5, new Vector3(500,0,-200)],
[1.0, new Vector3(1000,0,400)],
[1.5, new Vector3(1500,0,-800)],
];
let lookTarget = lookTargetKeyframes[0].clone();
const zoomKeyFrames = new Map([
[0, 0.25],
[0.5, 0.6],
[1.0, 0.2],
[1.5, 0.6],
]
]);
useFrame((state, delta) => {
const scroll = window.scrollY / document.documentElement.clientHeight;
updateCameraZoom(scroll, state, delta, zoomKeyFrames);
updateCameraMovement(
scroll,
camPositionKeyframes,
lookTargetKeyframes,
state,
lookTarget,
delta
);
return (...)
}
function updateCameraZoom(
scroll: number,
state: RootState,
delta: number,
zoomKeyFrames: Map<number, number>
) {
const zoom = getInterpolatedValue<number>(
scroll,
zoomKeyFrames,
(v0, v1, t) => lerp(v0, v1, t)
);
easing.damp(state.camera, 'zoom', zoom, 0.3, delta);
}
function updateCameraMovement(
scroll: number,
camPositionKeys: Map<number, Vector3>,
camTargetKeys: Map<number, Vector3>,
state: RootState,
lookTarget: Vector3,
delta: number
) {
let p = getInterpolatedValue<Vector3>(scroll, camPositionKeys, (v0, v1, t) =>
v0.clone().lerp(v1, t)
);
let look = getInterpolatedValue<Vector3>(scroll, camTargetKeys, (v0, v1, t) =>
v0.clone().lerp(v1, t)
);
if (state.camera.position.distanceTo(p) > 1) {
easing.damp3(state.camera.position, p, 0.3, delta);
} else {
state.camera.position.set(p.x, p.y, p.z);
}
if (look.distanceTo(lookTarget) > 1) {
easing.damp3(lookTarget, look, 0.3, delta);
} else {
lookTarget = look;
}
state.camera.lookAt(lookTarget);
state.camera.updateProjectionMatrix();
}
function getInterpolatedValue<T>(
time: number,
keyFrames: Map<number, T>,
interpolate: (v0: T, v1: T, t: number) => T
): T {
const keys = Array.from(keyFrames.keys());
let k1 = keys.findIndex((k) => k > time);
k1 = k1 < 0 ? keys.length - 1 : k1;
const k0 = Math.max(k1 - 1, 0);
const k0_val = keys[k0];
const k1_val = keys[k1];
const distance = k1_val - k0_val;
const t = clamp((time - k0_val) / distance, 0, 1);
return interpolate(keyFrames.get(k0_val)!, keyFrames.get(k1_val)!, t);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment