Codrops вместе с командой GSAP разобрали, как собрать эффект mouse trail с физикой гравитации. Суть: при движении курсора спавнятся изображения, которые падают к нижней границе вьюпорта, отскакивают и исчезают — при этом сохраняя направление движения мыши в момент появления.
HTML и CSS: хитрость с предзагрузкой
Структура минималистичная — просто набор <img> внутри контейнера:
<section class="codrops_mwg">
<div class="medias">
<img src="./assets/medias/01.png" alt="">
<img src="./assets/medias/02.png" alt="">
<img src="./assets/medias/03.png" alt="">
<img src="./assets/medias/04.png" alt="">
</div>
</section>
Картинки сразу скрываются через CSS — но не через display: none, а хитрее:
.codrops_mwg .medias img {
width: 1px;
height: 1px;
top: 0;
left: 0;
position: absolute;
visibility: hidden;
pointer-events: none;
}
Зачем так? display: none не загружает изображения в кэш, а этот трюк заставляет браузер загрузить их сразу — без задержки при первом спавне. URL’ы потом собираются в массив через JS:
const root = document.querySelector('.codrops_mwg')
const images = []
root.querySelectorAll('.medias img').forEach(image => {
images.push(image.getAttribute('src'))
})
Трекинг курсора и порог спавна
Картинка создаётся не на каждый mousemove, а только когда курсор прошёл достаточное расстояние. Для этого накапливается дельта по обеим осям:
let incr = 0, oldIncrX = 0, oldIncrY = 0
const resetDist = window.innerWidth / 8
root.addEventListener('mousemove', e => {
const valX = e.clientX
const valY = e.clientY
incr += Math.abs(valX - oldIncrX) + Math.abs(valY - oldIncrY)
if (incr > resetDist) {
incr = 0
createMedia(
valX,
valY - root.getBoundingClientRect().top,
valX - oldIncrX,
valY - oldIncrY
)
}
oldIncrX = valX
oldIncrY = valY
})
Порог window.innerWidth / 8 адаптирует частоту спавна под размер экрана. deltaX и deltaY передаются в createMedia() — они задают направление дрейфа при падении. Y-позиция корректируется на root.getBoundingClientRect().top, чтобы эффект корректно работал при проскролленной странице.
Внутри createMedia()
Функция принимает четыре параметра: x, y, deltaX, deltaY.
Сначала — гард: если курсор слишком близко к нижнему краю вьюпорта, создание пропускается:
const H = window.innerHeight
if (y > H - 200) return
Далее создаётся элемент <img> и добавляется в DOM, затем запускается GSAP Timeline. По завершении таймлайна картинка удаляется из DOM:
const tl = gsap.timeline({
onComplete: () => {
root.removeChild(image)
tl && tl.kill()
}
})
Анимация: появление, дрейф и падение
Первый твин — появление с эластичным bounce. Позиция x/y здесь не задаётся, она управляется отдельными твинами, которые стартуют параллельно:
tl.fromTo(image, {
xPercent: -50 + (Math.random() - 0.5) * 80,
yPercent: -50 + (Math.random() - 0.5) * 10,
scaleX: 1.3,
scaleY: 1.3,
rotation: (Math.random() - 0.5) * 20
}, {
scaleX: 1,
scaleY: 1,
ease: 'elastic.out(2, 0.6)',
duration: 0.4
})
Горизонтальный дрейф — картинка смещается в сторону, куда двигался курсор в момент спавна, используя deltaX. Запускается одновременно с предыдущим твином через позицию '<':
tl.fromTo(image, {
x,
}, {
x: '+=' + deltaX,
// ...
}, '<')
Вертикальная часть — падение вниз, отскок и исчезновение — строится аналогично через отдельный твин с deltaY и соответствующим easing.
Итог
Ключевые идеи, которые можно утащить в свои проекты:
- Предзагрузка через 1×1 px вместо
display: none— картинки готовы мгновенно - Накопительный инкремент дельты вместо таймера — спавн привязан к реальному движению
- Порог
innerWidth / 8делает частоту появления адаптивной - Параллельные твины в одном Timeline разделяют логику появления, дрейфа и падения
- Гард по Y-позиции спасает от визуально сломанных ситуаций у края экрана
Демо и исходники доступны на странице туториала — там же можно скопировать компонент прямо в Webflow-проект. 🦆

