Коротко — «motion blur» — это не просто размытие по оси, а интеграл перемещающегося изображения за время «экспозиции». На практике длина и направление смазывания пропорциональны вектору скорости (speed × exposure_time). На вебе есть три рабочих подхода — от простого и дешёвого до высококачественного и требовательного к WebGL:
1) Визуальная имитация (ghosting) — простой, работает в CSS/DOM
2) Canvas — хорош для растровых слайдов, больше контроля (accumulation)
3) WebGL / GLSL — лучший по качеству и производительности для сложных эффектов (дирекционный blur с выборкой по направлению скорости)
Ниже — кратко про физику + примеры кода для каждого подхода.
Физика (очень просто)
- Объект движется со скоростью v (px/s), время экспозиции T (s) — тогда длина смазывания L = v * T (px).
- Направление смазывания = направление движения. Чем больше T или v, тем длиннее полоска.
Подход 1 — Ghosting (простая имитация)
Идея: при переходе создаём N копий слайдов, сдвигаем их по направлению движения и уменьшаем непрозрачность. Очень мало нагрузки, но выглядит «штучно».
Пример (HTML + CSS + JS):
HTML:
<div class="slider">
<img id="current" src="slide1.jpg">
<img id="next" src="slide2.jpg">
</div>
CSS (упростил):
.slider { position: relative; width: 800px; height: 500px; overflow: hidden; }
.slider img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
JS (ghosting):
const N = 8; // число «призраков»
function transition(fromImg, toImg, direction = 1, duration = 600) {
const container = fromImg.parentNode;
const rect = container.getBoundingClientRect();
const distance = rect.width * 0.3; // на сколько пикселей делаем смещение (можно вычислять по v*T)
const clones = [];
for (let i = 0; i < N; i++) {
const c = fromImg.cloneNode();
c.style.transition = 'none';
c.style.opacity = (1 - i / (N + 1)) * 0.9;
const frac = i / N;
const dx = direction * distance * frac;
c.style.transform = `translateX(${dx}px)`;
c.style.pointerEvents = 'none';
container.appendChild(c);
clones.push(c);
}
// запускаем анимацию основного слайда
requestAnimationFrame(() => {
clones.forEach((c, i) => {
c.style.transition = `transform ${duration}ms linear, opacity ${duration}ms linear`;
const frac = i / N;
// смещаем дальше, чтоб создать «тянущийся» след
const dx = direction * distance * (1 + frac);
c.style.transform = `translateX(${dx}px)`;
c.style.opacity = 0;
});
toImg.style.transition = `transform ${duration}ms cubic-bezier(.2,.9,.2,1)`;
toImg.style.transform = `translateX(${0}px)`; // приводим в нужное положение
setTimeout(() => clones.forEach(c => c.remove()), duration + 20);
});
}
Комментарий: подгоняйте distance и N; можно для вертикальных переходов менять translateY. Этот способ самый лёгкий.
Подход 2 — Canvas accumulation (trail / integration)
Идея: на canvas отрисовывать слайд несколько раз с малыми смещениями и alpha, или оставить частичный остаток предыдущих кадров (accumulation buffer).
Пример (упрощённый):
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
const img = new Image(); img.src = 'slide1.jpg';
const steps = 12;
function drawMotionBlur(image, dx) {
ctx.clearRect(0,0,canvas.width,canvas.height);
for (let i = 0; i < steps; i++) {
const t = i / (steps - 1);
ctx.globalAlpha = 1 / steps; // лучше использовать экспоненциальный профиль
ctx.drawImage(image, dx * t, 0, canvas.width, canvas.height);
}
ctx.globalAlpha = 1;
}
Во время анимации вычисляйте dx = v * (t/T), и перерисовывайте на requestAnimationFrame. Canvas даёт гибкость: можно использовать compositeOperation, разные альфа-кривые для более естественного вида, но может быть нагрузочно при больших размерах.
Подход 3 — WebGL (рекомендуется для плавного, качественного directional blur)
Идея: в фрагментном шейдере взять texel и усреднить выборки вдоль вектора скорости (motion vector). Это даёт правильную, направленную размытую «полоску».
Фрагментный шейдер (GLSL, упрощённо):
precision mediump float;
uniform sampler2D uTex;
uniform vec2 uVelocity; // в пикселях или нормализованных координатах
uniform int uSamples;
varying vec2 vUv;
void main() {
vec4 sum = vec4(0.0);
// velocity в нормализованных texcoords (например в координатах 0..1)
for (int i = 0; i < 64; i++) { // компилятор уберёт лишние если uSamples меньше
if (i >= uSamples) break;
float t = float(i) / float(uSamples - 1);
vec2 uv = vUv - uVelocity * (t - 0.5); // центрируем выборку по экспозиции
sum += texture2D(uTex, uv);
}
gl_FragColor = sum / float(uSamples);
}
Как использовать:
- Подайте uVelocity = motion_pixels / texture_size (например, dx / width).
- uSamples от 8 до 32 — качество/скорость tradeoff.
- В Three.js можно сделать ShaderMaterial, в котором текстура — рендер-таргет с текущим слайдом.
Пример на Three.js (схематично):
const material = new THREE.ShaderMaterial({
uniforms: {
uTex: { value: new THREE.TextureLoader().load('slide1.jpg') },
uVelocity: { value: new THREE.Vector2(0.05, 0.0) },
uSamples: { value: 16 }
},
vertexShader: /* ... */,
fragmentShader: /* shader выше */
});
Плюсы: аккуратный направленный blur, можно динамически менять длину. Минусы: нужно знакомство с WebGL / three.js, моб.оптимизация.
Ещё варианты
- SVG filter с feConvolveMatrix / feGaussianBlur + feOffset + feMerge — сложно сконфигурировать для направленного смазывания и не очень быстро.
- CSS filter: blur() даёт круговое/ isotropic blur, не то что нужно для motion blur.
Практические советы
- Для слайдов (изображений/фонов) проще: посчитайте длину смазывания в px: L = transition_distance / transition_duration × exposure_time. Для веба exposure_time можно выбрать равным доле длительности анимации (например 0.5 * duration).
- Для плавности используйте линейную временную функцию (linear) для «хвостов». Для самого объекта можно оставить easing.
- Тестируйте на мобильных — WebGL быстрее на больших сценах, но большое число samples ударит по FPS.
- Если у вас несколько слоёв (текст поверх изображения), лучше размазывать только фон/изображение, а текст оставлять чётким либо использовать маски.
Резюме
- Для простоты: ghosting с клонами DOM.
- Для лучшего качества: canvas accumulation.
- Для наилучшего качества при высокой производительности/контроле: WebGL shader sampling по вектору скорости.
Если нужно — пришлите HTML/CSS вашего слайдера (или укажите, это горизонтальный слайд/вертикальный/параллакс и тип контента: изображения/видео/DOM), и я подготовлю точный рабочий пример (DOM-ghosting или WebGL-шейдер) под ваш кейс.