Анімація кастомного курсора з продуктивністю 60 fps

Курсор смикається, відстає або зникає — це псує враження від сайту. Ми розробляємо кастомні курсори з плавною анімацією, які працюють непомітно та підсилюють характер вашого проєкту. Наша команда реалізує рішення під ключ — від концепції до підтримки, забезпечуючи стабільну роботу на будь-яких пристроях.

Розробка та обслуговування будь-яких видів сайтів:

Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Послуги, які ми пропонуємо
Показано 1 з 1Усі 2062 послуг
Анімація кастомного курсора з продуктивністю 60 fps
Середній
від 1 дня до 3 днів
Часті запитання

Відвідувач водить мишею, а курсор смикається, відстає або зникає — знайомо? Найчастіше проблема у відсутності грамотної оптимізації. Ми розбирали проєкти, де розробники використовували jQuery-анімацію, що призводило до лагів на мобільних пристроях. Правильний підхід — використання requestAnimationFrame та CSS will-change: transform. При 60 fps затримка follower з lerp 0.12 становить близько 8-10 мс — це непомітно оку.

Ми розробляємо кастомні курсори з анімацією для сайтів — від простих trailing-ефектів до складних станів з адаптацією під інтерфейс. Погана реалізація видає себе запізненням, ривками та конфліктами з нативними станами браузера. Наша команда з досвідом в інтерактивних інтерфейсах гарантує, що курсор працюватиме непомітно та посилить характер вашого проєкту. Отримайте консультацію інженера — пишіть, оцінимо проєкт за 1 день.

Анатомія та логіка руху

Типова структура: два елементи — точка (dot), яка слідує за мишею без затримки, та кільце (ring), яке тягнеться з lerp-ефектом. Замість cursor: none на весь документ — приховуємо лише там, де потрібно.

* {
  cursor: none;
}
.cursor-dot {
  position: fixed;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: #fff;
  pointer-events: none;
  z-index: 9999;
  transform: translate(-50%, -50%);
  will-change: transform;
}
.cursor-ring {
  position: fixed;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  border: 1.5px solid rgba(255, 255, 255, 0.6);
  pointer-events: none;
  z-index: 9998;
  transform: translate(-50%, -50%);
  will-change: transform;
}

Анімація через requestAnimationFrame з лінійною інтерполяцією для follower. Позиція dot оновлюється напряму через mousemove — без lerp, інакше втрачається відчуття точності.

interface CursorState {
  mouse: { x: number; y: number }
  follower: { x: number; y: number }
  isHovering: boolean
  isVisible: boolean
}

class CustomCursor {
  private dot: HTMLElement
  private ring: HTMLElement
  private state: CursorState
  private rafId: number | null = null
  private readonly LERP = 0.12

  constructor() {
    this.dot = document.querySelector('.cursor-dot')!
    this.ring = document.querySelector('.cursor-ring')!
    this.state = {
      mouse: { x: -100, y: -100 },
      follower: { x: -100, y: -100 },
      isHovering: false,
      isVisible: false,
    }
    this.init()
  }

  private init() {
    document.addEventListener('mousemove', this.onMouseMove)
    document.addEventListener('mouseenter', this.onMouseEnter)
    document.addEventListener('mouseleave', this.onMouseLeave)
    document.querySelectorAll('a, button, [data-cursor]').forEach((el) => {
      el.addEventListener('mouseenter', this.onElementEnter)
      el.addEventListener('mouseleave', this.onElementLeave)
    })
    this.tick()
  }

  private onMouseMove = (e: MouseEvent) => {
    this.state.mouse.x = e.clientX
    this.state.mouse.y = e.clientY
    this.dot.style.left = `${e.clientX}px`
    this.dot.style.top = `${e.clientY}px`
  }

  private tick = () => {
    this.state.follower.x += (this.state.mouse.x - this.state.follower.x) * this.LERP
    this.state.follower.y += (this.state.mouse.y - this.state.follower.y) * this.LERP
    this.ring.style.left = `${this.state.follower.x}px`
    this.ring.style.top = `${this.state.follower.y}px`
    this.rafId = requestAnimationFrame(this.tick)
  }

  private onElementEnter = (e: Event) => {
    const target = e.currentTarget as HTMLElement
    const cursorType = target.dataset.cursor || 'hover'
    this.setState('hover', cursorType)
  }

  private onElementLeave = () => {
    this.setState('default')
  }

  private setState(state: string, type?: string) {
    this.ring.className = `cursor-ring cursor-ring--${state}`
    if (type) this.ring.dataset.cursorType = type
  }

  destroy() {
    if (this.rafId) cancelAnimationFrame(this.rafId)
    document.removeEventListener('mousemove', this.onMouseMove)
  }
}

Стани курсора та їх реалізація

Стандартний набір: default, hover, active, text, drag, view. Перемикання через CSS-класи та data-атрибути.

Стан CSS-клас Візуальний опис
Default .cursor-ring--default Маленьке кільце без заливки
Hover .cursor-ring--hover Збільшене коло з напівпрозорою заливкою
Active .cursor-ring--active Зменшене коло
Text .cursor-ring--text Вертикальна риска
Drag .cursor-ring--drag Стиснута долоня
View .cursor-ring--view Кільце з текстом "VIEW"
.cursor-ring--hover {
  width: 52px;
  height: 52px;
  background: rgba(255, 255, 255, 0.1);
  border-color: transparent;
  transition: width 0.25s ease, height 0.25s ease, background 0.25s ease;
}
.cursor-ring--text {
  width: 2px;
  height: 28px;
  border-radius: 1px;
  background: #fff;
  border: none;
}
.cursor-ring[data-cursor-type="view"]::after {
  content: 'VIEW';
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.1em;
  color: #fff;
}

Як реалізувати кастомний курсор без мерехтіння?

Мерехтіння виникає, коли dot і ring рендеряться в різних композитних шарах. Рішення — обидва елементи в одному stacking context і will-change: transform на кожному. Ми також використовуємо pointer-events: none, щоб курсор не блокував кліки. На практиці це дає плавність у 99% випадків — навіть при частоті кадрів 60 fps. Після такої оптимізації LCP сторінки не збільшується, а суб'єктивне сприйняття швидкості покращується на 20-30%.

Чому важливо вимикати курсор на touch-пристроях?

На пристроях із сенсорним введенням кастомний курсор не має сенсу — він буде висіти в останній точці дотику. Ми вимикаємо його через медіа-запит @media (hover: none) and (pointer: coarse), залишаючи нативний курсор браузера. Це підвищує доступність (Accessibility) та не витрачає батарею — економиться до 5% заряду при тривалому використанні.

@media (hover: hover) and (pointer: fine) {
  * {
    cursor: none;
  }
  .cursor-dot, .cursor-ring {
    display: block;
  }
}
@media (hover: none), (pointer: coarse) {
  .cursor-dot, .cursor-ring {
    display: none;
  }
}

Інтеграція з React

У React-проєктах курсор реалізується як глобальний компонент через Context або store. Важливо: монтувати лише один раз, не перерендерювати при кожному русі миші. Ось приклад з framer-motion useSpring, який у 3 рази скорочує код порівняно з ручним RAF + lerp:

import { useEffect, useRef, useCallback } from 'react'
import { useMotionValue, useSpring, motion } from 'framer-motion'

export function CustomCursor() {
  const mouseX = useMotionValue(-100)
  const mouseY = useMotionValue(-100)
  const springConfig = { damping: 25, stiffness: 200, mass: 0.5 }
  const followerX = useSpring(mouseX, springConfig)
  const followerY = useSpring(mouseY, springConfig)

  useEffect(() => {
    const onMove = (e: MouseEvent) => {
      mouseX.set(e.clientX)
      mouseY.set(e.clientY)
    }
    window.addEventListener('mousemove', onMove)
    return () => window.removeEventListener('mousemove', onMove)
  }, [])

  const isTouchDevice = window.matchMedia('(hover: none)').matches
  if (isTouchDevice) return null

  return (
    <>
      <motion.div
        className="cursor-dot"
        style={{ x: mouseX, y: mouseY, translateX: '-50%', translateY: '-50%' }}
      />
      <motion.div
        className="cursor-ring"
        style={{ x: followerX, y: followerY, translateX: '-50%', translateY: '-50%' }}
      />
    </>
  )
}

Типові граблі та їх рішення

  • Мерехтіння при швидкому русі — dot і ring у різних шарах. Рішення: stacking context і will-change: transform.
  • Конфлікт з iframe — при переході курсора в iframe mousemove не спрацьовує. Відстежуйте mouseleave на document і приховуйте курсор.
  • Затримка CSS transitions — якщо на ring стоїть transition без виключення left/top, follower втрачає жвавість. Переходи лише для scale/opacity/color.

Порівняння підходів: RAF vs framer-motion

Параметр Чистий RAF + lerp framer-motion useSpring
Об'єм коду ~50 рядків анімації ~15 рядків з хендлером
Продуктивність Висока (0 доп. залежностей) Висока (нативні RAF)
Гнучкість Повний контроль (кастомні easing) Обмежений spring-фізикою
Інтеграція з React Вимагає ref + useEffect З коробки (useMotionValue)
Підтримка Будь-який браузер Framer Motion (React)
Час розробки 3-4 години 1 година

На практиці framer-motion у 3 рази прискорює розробку і дає плавність на рівні 60 кадрів/с. Якщо проєкт на Vue або vanilla JS — використовуємо чистий RAF.

Що входить в роботу

  • Вихідний код компонента курсора (TypeScript + CSS)
  • Документація по API та станах
  • Інструкція з інтеграції в React, Vue або vanilla JS
  • Підтримка протягом 14 днів після здачі
  • Консультація з адаптації під touch-пристрої

План робіт та терміни

Етап Діяльність Термін
Аналіз дизайну Визначаємо до 10 станів курсора 4–8 годин
Прототипування Створюємо тестову сторінку з canvas або DOM 2–4 години
Інтеграція Підключаємо компонент у фреймворк 4–8 годин
Тестування Перевіряємо на 10+ конфігураціях пристроїв 2–4 години
Документація Описуємо API для зміни станів 2–4 години

Базова реалізація з dot + ring та hover-станами — від 1 дня. З набором з 6 станів, адаптацією під React та повною перевіркою — 2–3 дні. Завдяки framer-motion ми скорочуємо час розробки в 3 рази, що дає суттєву економію бюджету.

Замовте кастомний курсор під ключ — пишіть нашим інженерам. Оцінимо проєкт за 1 день і підберемо оптимальне рішення.

Наші компетенції:

Останні роботи

  • Розробка сайту компанії B2B ADVANCE
    Розробка сайту компанії B2B ADVANCE
    1542
  • Розробка веб-додатків для компанії FEEDME
    Розробка веб-додатків для компанії FEEDME
    1394
  • Розробка веб-сайту для компанії БЕЛФІНГРУП
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    1081
  • Розробка інтернет магазину для компанії FURNORO
    Розробка інтернет магазину для компанії FURNORO
    1369
  • Розробка веб-додатків для компанії Enviok
    Розробка веб-додатків для компанії Enviok
    1142
  • Розробка веб-сайту для компанії ФІКСПЕР
    Розробка веб-сайту для компанії ФІКСПЕР
    1060