Как с помощью HTML, CSS и JavaScript воспроизвести эффект motion blur при переключении слайдов на сайте?

Всем привет! Нужна помощь в реализации сложного эффекта перехода между слайдами — не хватает понимания физики движения. <br/> <br/> Вот сам эффект (видео): <a href="https://www.youtube.com/watch?v=FXlsixi7wa8" rel="nofollow">https://www.youtube.com/watch?v=FXlsixi7wa8</a> <br/> <br/> Что уже пробовал:
  • 18 марта 2026 г. 15:03
Ответы на вопрос 2
Коротко — «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-шейдер) под ваш кейс.
В вашем примере происходит неравномерное растягивание картинок. Это не совсем классический motion blur, где мы что-то размыливаем. Тут нужны более хитрые трансформации. <br/> <br/> CSS не умеет делать такие эффекты (ну то есть можно для каждого пикселя на экране новый div создавать, но это безумие с точки зрения производительности). <br/> <br/> Стандартный способ решения подобных задач - WebGL. В вашем конкретном случае никакая сложная математика не нужна. Можно обойтись самыми <a href="https://habr.com/ru/articles/420847/" rel="nofollow">простыми шейдерами</a> .
Похожие вопросы