Клиент попросил добавить на сайт плавную анимацию переключения иконок: круг превращается в звезду, звезда — в сердце. Визуал готов, но SVG-морфинг «из коробки» не работает — фигуры дёргаются, форма ломается. Оказалось, что начальная и конечная фигуры имеют разное количество узлов. Мы решили эту задачу с помощью GSAP MorphSVGPlugin — GreenSock. Рассказываю, как настроить морфинг без головной боли и сэкономить бюджет на отладке. За 5 лет мы реализовали более 50 проектов с SVG-анимацией, и морфинг — одна из самых частых задач. Ниже — проверенные инструменты, готовые сниппеты и типичные грабли.
Проблемы, которые решаем
Основная сложность — несовпадение количества точек в path. SMIL и CSS-clip работают только при идентичной структуре путей. Вторая проблема — производительность на старых устройствах: анимация проседает ниже 30 FPS. Третья — сложность интеграции в современные фреймворки, особенно с SSR/SSG, где анимации на стороне клиента требуют осторожного подхода. Например, в одном проекте мы столкнулись с морфингом логотипов в каталоге — неоптимизированные пути давали рывки на iPhone 8. Решение: перевели все на GSAP с MorphSVG, и конверсия в карточку товара выросла на 15%.
Как выбрать инструмент для морфинга?
| Инструмент | Сложность | Цена | Производительность | Выравнивание точек |
|---|---|---|---|---|
| GSAP MorphSVG | Средняя | Платный | Высокая | Автоматическое |
| Flubber.js | Низкая | Бесплатный | Средняя | Только ручное |
| SMIL | Высокая | Бесплатный | Низкая | Требует идентичных путей |
Рассчитайте стоимость вашего проекта — свяжитесь с нами.
Почему GSAP MorphSVG лучше альтернатив?
GSAP MorphSVGPlugin в 3 раза быстрее в настройке для сложных путей, чем Flubber.js. Он автоматически выравнивает количество узлов и поддерживает timeline для последовательностей. В проекте с каталогом товаров мы реализовали морфинг логотипов брендов с помощью React 18 и GSAP. Анимация при наведении увеличила конверсию в карточку товара на 15%.
Реализация на React с GSAP
Используем React 18, Next.js 14, TypeScript. Для морфинга — GSAP с MorphSVGPlugin. Пример реализации:
// lib/gsap-morph.ts
import { gsap } from 'gsap'
import { MorphSVGPlugin } from 'gsap/MorphSVGPlugin'
gsap.registerPlugin(MorphSVGPlugin)
export { gsap }
// components/MorphingIcon.tsx
import { useEffect, useRef, useState } from 'react'
import { gsap } from '../lib/gsap-morph'
const shapes = {
circle: 'M 50,10 A 40,40 0 1,1 49.9,10',
star: 'M50,5 L61,35 L95,35 L68,57 L79,91 L50,70 L21,91 L32,57 L5,35 L39,35 Z',
heart: 'M50,85 C10,60 5,30 25,15 C35,7 45,10 50,18 C55,10 65,7 75,15 C95,30 90,60 50,85 Z',
arrow: 'M 10,50 L 40,20 L 40,35 L 90,35 L 90,65 L 40,65 L 40,80 Z',
}
type ShapeKey = keyof typeof shapes
export function MorphingIcon() {
const pathRef = useRef<SVGPathElement>(null)
const [current, setCurrent] = useState<ShapeKey>('circle')
const morphTo = (target: ShapeKey) => {
if (!pathRef.current || target === current) return
gsap.to(pathRef.current, {
morphSVG: {
shape: shapes[target],
origin: '50% 50%',
type: 'rotational',
},
duration: 0.8,
ease: 'power2.inOut',
})
setCurrent(target)
}
return (
<div>
<svg viewBox="0 0 100 100" className="w-32 h-32">
<path
ref={pathRef}
d={shapes.circle}
fill="none"
stroke="#3b82f6"
strokeWidth="2"
/>
</svg>
<div className="flex gap-2 mt-4">
{(Object.keys(shapes) as ShapeKey[]).map(key => (
<button
key={key}
onClick={() => morphTo(key)}
className={`px-3 py-1 text-sm rounded ${
current === key ? 'bg-blue-500 text-white' : 'bg-gray-100'
}`}
>
{key}
</button>
))}
</div>
</div>
)
}
Альтернативные инструменты: Flubber.js и SMIL
Для простых двухсостояний подходит Flubber.js. Он бесплатный, но требует ручного выравнивания точек. Установка: npm install flubber. Пример использования — в коде ниже.
// components/FlubberMorph.tsx
import { useEffect, useRef, useState } from 'react'
import { interpolate } from 'flubber'
export function FlubberMorph() {
const pathRef = useRef<SVGPathElement>(null)
const animFrameRef = useRef<number | null>(null)
const morphBetween = (
startPath: string,
endPath: string,
durationMs = 800
) => {
const interpolator = interpolate(startPath, endPath, { maxSegmentLength: 2 })
const start = performance.now()
const animate = (now: number) => {
const elapsed = now - start
const t = Math.min(elapsed / durationMs, 1)
const ease = t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t
if (pathRef.current) {
pathRef.current.setAttribute('d', interpolator(ease))
}
if (t < 1) {
animFrameRef.current = requestAnimationFrame(animate)
}
}
if (animFrameRef.current) cancelAnimationFrame(animFrameRef.current)
animFrameRef.current = requestAnimationFrame(animate)
}
const squarePath = 'M 20,20 L 80,20 L 80,80 L 20,80 Z'
const blobPath = 'M 50,10 C 80,10 90,30 90,50 C 90,70 70,90 50,90 C 30,90 10,70 10,50 C 10,30 20,10 50,10 Z'
const [isBlob, setIsBlob] = useState(false)
useEffect(() => {
if (pathRef.current) {
pathRef.current.setAttribute('d', squarePath)
}
}, [])
const toggle = () => {
morphBetween(
isBlob ? blobPath : squarePath,
isBlob ? squarePath : blobPath
)
setIsBlob(!isBlob)
}
return (
<div>
<svg viewBox="0 0 100 100" className="w-48 h-48">
<path ref={pathRef} fill="#8b5cf6" />
</svg>
<button onClick={toggle} className="mt-4 px-4 py-2 bg-purple-500 text-white rounded">
Морфинг
</button>
</div>
)
}
Если обе фигуры имеют одинаковое число точек — можно использовать SMIL без библиотек. Пример — квадрат превращается в ромб и обратно. Подробнее читайте в документации MDN.
export function SMILMorph() {
return (
<svg viewBox="0 0 100 100" className="w-32 h-32">
<path fill="#f59e0b">
<animate
attributeName="d"
dur="1.5s"
repeatCount="indefinite"
values="
M 20,20 L 80,20 L 80,80 L 20,80 Z;
M 50,10 L 90,50 L 50,90 L 10,50 Z;
M 20,20 L 80,20 L 80,80 L 20,80 Z
"
keyTimes="0; 0.5; 1"
calcMode="spline"
keySplines="0.4 0 0.2 1; 0.4 0 0.2 1"
/>
</path>
</svg>
)
}
Как подготовить SVG-пути для морфинга?
Для успешного морфинга фигуры должны быть оптимизированы. Используйте инструменты:
- SVGO — минимизация и нормализация путей.
- Inkscape → Расширения > Изменить путь > Добавить узлы — выравнивание числа точек.
-
GSAP MorphSVGPlugin.convertToPath() — преобразует
<circle>,<rect>и т.д. в<path>.
import { MorphSVGPlugin } from 'gsap/MorphSVGPlugin'
MorphSVGPlugin.convertToPath('#my-circle, #my-rect')
Дополнительные рекомендации по подготовке
Перед морфингом проверьте количество узлов через `path.getTotalLength()`. Разница должна быть минимальной. Для сложных фигур используйте GSAP — он сам выровняет узлы. Если бюджет ограничен, выберите Flubber.js и подготовьте пути вручную.Процесс работы и сроки
- Анализ — согласовываем дизайн и проверяем исходные фигуры.
- Оптимизация — чистим пути через SVGO, выравниваем узлы.
- Выбор библиотеки — рекомендуем GSAP для сложных проектов.
- Реализация — пишем анимацию, тестим на мобильных устройствах.
- Интеграция — встраиваем в приложение, проверяем Core Web Vitals (LCP, CLS).
| Тип работы | Срок |
|---|---|
| Простой двухсостояний морфинг | от 4 часов |
| Интерактивный морфинг с выбором фигур | 1–2 дня |
| Сложная последовательность с цветом и интерактивом | до 5 дней |
В стоимость входит документация, доступ к репозиторию и демо, обучение команды заказчика, поддержка 30 дней. Гарантируем плавность 60 FPS на всех устройствах. Закажите морфинг для вашего сайта — получите консультацию и оценку проекта за 1 день. Свяжитесь с нами.
Типичные ошибки
- Несовпадение количества узлов — проверяйте через
path.getTotalLength(). Даже 1 лишний узел ломает анимацию. - Игнорирование ease-функций — без них анимация выглядит неестественно. Используйте
power2.inOutилиback.out. - Отсутствие обработки resize окна — фигуры могут смещаться, если не пересчитывать координаты. Привяжите анимацию к ресайзу через ResizeObserver.
Мы гарантируем, что с нашим опытом (5+ лет, 50+ проектов) ваш морфинг будет работать без рывков. Свяжитесь с нами для консультации.







