Библиотека ScrollMagic предоставляет механизм синхронизации прокрутки страницы с анимациями. В контексте WebGL это позволяет управлять состоянием 3D-сцены, параметрами шейдеров, положением камеры и пост-эффектами на основе позиции скролла.
Основная идея — связать прогресс сцены ScrollMagic
(progress от 0 до 1) с параметрами WebGL.
Типичная структура:
onUpdateПример базовой связки:
const controller = new ScrollMagic.Controller();
const scene = new ScrollMagic.Scene({
triggerElement: "#trigger",
duration: 1000
})
.on("progress", (event) => {
const progress = event.progress;
updateWebGL(progress);
})
.addTo(controller);
Функция updateWebGL изменяет состояние сцены.
Один из базовых эффектов — перемещение камеры.
function updateWebGL(progress) {
camera.position.z = 5 - progress * 3;
camera.position.y = progress * 2;
camera.lookAt(scene.position);
}
Результат:
ScrollMagic удобно использовать как драйвер для интерполяции значений.
function lerp(start, end, t) {
return start + (end - start) * t;
}
function updateWebGL(progress) {
mesh.rotation.y = lerp(0, Math.PI * 2, progress);
}
Использование easing-функций:
function easeInOut(t) {
return t < 0.5
? 2 * t * t
: 1 - Math.pow(-2 * t + 2, 2) / 2;
}
function updateWebGL(progress) {
const eased = easeInOut(progress);
mesh.scale.set(eased, eased, eased);
}
ScrollMagic особенно мощен при управлении uniform-переменными в шейдерах.
uniform float uProgress;
void main() {
vec3 pos = position;
pos.z += sin(pos.x * 5.0 + uProgress * 10.0) * 0.2;
gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
}
JS:
function updateWebGL(progress) {
material.uniforms.uProgress.value = progress;
}
Эффект:
Создание глубины достигается за счёт разной скорости движения объектов.
function updateWebGL(progress) {
background.position.z = -10 + progress * 2;
midground.position.z = -5 + progress * 4;
foreground.position.z = progress * 6;
}
Это создаёт:
При использовании EffectComposer (например, в Three.js)
можно управлять пост-эффектами:
function updateWebGL(progress) {
bloomPass.strength = progress * 2;
}
function updateWebGL(progress) {
colorPass.uniforms.saturation.value = 1 - progress;
}
Часто используется закрепление canvas на экране:
new ScrollMagic.Scene({
triggerElement: "#webgl-section",
duration: 2000,
triggerHook: 0
})
.setPin("#canvas-container")
.addTo(controller);
Это позволяет:
Для сложных эффектов используется несколько сцен:
// Первая сцена
new ScrollMagic.Scene({ duration: 500 })
.on("progress", (e) => animateIntro(e.progress))
.addTo(controller);
// Вторая сцена
new ScrollMagic.Scene({ offset: 500, duration: 500 })
.on("progress", (e) => animateMiddle(e.progress))
.addTo(controller);
Так достигается:
ScrollMagic не заменяет рендер-цикл WebGL.
Правильная структура:
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
animate();
ScrollMagic только изменяет параметры, но не управляет отрисовкой.
Не стоит пересчитывать сложные операции внутри
onUpdate:
let lastProgress = 0;
function updateWebGL(progress) {
if (Math.abs(progress - lastProgress) < 0.001) return;
lastProgress = progress;
// обновления
}
Canvas должен быть изолирован:
При работе с большим количеством объектов:
InstancedMeshВместо прямого изменения параметров лучше использовать state-модель:
const state = {
progress: 0,
rotation: 0,
scale: 1
};
function updateWebGL(progress) {
state.progress = progress;
state.rotation = progress * Math.PI;
}
В рендере:
mesh.rotation.y = state.rotation;
Это упрощает:
ScrollMagic часто используется вместе с GSAP:
const tween = gsap.to(mesh.rotation, {
y: Math.PI * 2
});
new ScrollMagic.Scene({
duration: 1000
})
.setTween(tween)
.addTo(controller);
GSAP:
Пример: переход между сценами WebGL
function updateWebGL(progress) {
if (progress < 0.5) {
scene1.visible = true;
scene2.visible = false;
scene1.opacity = 1 - progress * 2;
} else {
scene1.visible = false;
scene2.visible = true;
scene2.opacity = (progress - 0.5) * 2;
}
}
Скролл может управлять:
function updateWebGL(progress) {
material.uniforms.uMix.value = progress;
}
В шейдере:
vec4 color = mix(texture1, texture2, uMix);
Комбинация всех техник:
Каждый участок скролла — отдельная “глава”.
ScrollMagic предоставляет индикаторы:
scene.addIndicators();
Это показывает:
Причины:
Решение:
Если WebGL отстаёт:
Рекомендации:
Создание полноценной “линейки времени” внутри шейдера:
if (uProgress < 0.3) {
// первая фаза
} else if (uProgress < 0.7) {
// вторая
} else {
// третья
}
function updateWebGL(progress) {
geometry.morphTargetInfluences[0] = progress;
}
let last = 0;
function updateWebGL(progress) {
const velocity = progress - last;
last = progress;
material.uniforms.uVelocity.value = velocity;
}
Используется для:
Рекомендуемая структура:
scroll.js — управление ScrollMagicscene.js — WebGL сценаanimations.js — логика анимацийshaders/ — GLSL-кодТакое разделение позволяет строить сложные, управляемые скроллом WebGL-интерфейсы с высокой степенью контроля и предсказуемости поведения.