Анимация кастомного курсора с производительностью 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
    1368
  • Разработка веб-приложения для компании Enviok
    Разработка веб-приложения для компании Enviok
    1140
  • Разработка веб-сайта для компании ФИКСПЕР
    Разработка веб-сайта для компании ФИКСПЕР
    1060