Как анимировать SVG в React, чтобы анимация обводки начиналась с середины?
Имеется SVG файл с прямоугольником, который нужно анимировать так, чтобы зелёная линия начинала движение не с начала обводки, а с её середины. Текущий код использует requestAnimationFrame для анимации stroke-dashoffset, но не достигает желаемого эффекта.
useEffect(() => {
if (!svgRef.current) return;
const svgElement = svgRef.current;
const animatedElement = svgElement.querySelector('#svg_5');
// Инициализация stroke dasharray
const length = animatedElement.getTotalLength ? animatedElement.getTotalLength() : 1000;
animatedElement.style.strokeDasharray = length;
animatedElement.style.strokeDashoffset = length;
animatedElement.style.strokeWidth = 2;
animatedElement.style.stroke = "#00ff7f";
if (!isAnimated) return;
let startTime = null;
const duration = 6000;
const animate = (timestamp) => {
if (!startTime) startTime = timestamp;
const elapsed = timestamp - startTime;
const progress = Math.min(elapsed / duration, 1);
setIsProgress(progress);
const totalLength = animatedElement.getTotalLength ? animatedElement.getTotalLength() : 1000;
const startOffset = (startStoke / 100) * totalLength;
const endOffset = 0.6 * totalLength;
const currentOffset = endOffset + (startOffset-endOffset) * progress;
animatedElement.style.strokeDashoffset = currentOffset;
animatedElement.style.stroke = "#00ff7f";
if (progress < 1 && isAnimated) {
animationRef.current = requestAnimationFrame(animate);
} else if (progress >= 1) {
setIsAnimated(false);
}
};
animationRef.current = requestAnimationFrame(animate);
return () => {
if (animationRef.current) {
cancelAnimationFrame(animationRef.current);
}
};
}, [isAnimated, startStoke, endStoke]);
Как правильно настроить параметры анимации, чтобы линия начинала движение с середины обводки SVG элемента?
Чтобы анимировать SVG обводку в React так, чтобы анимация начиналась с середины, нужно правильно настроить параметры stroke-dasharray и stroke-dashoffset. Текущий код имеет несколько проблем в расчетах и настройке параметров.
Содержание
- Проблема текущего подхода
- Правильный метод анимации с середины
- Полный пример компонента React
- Альтернативные методы CSS анимации
- Расчет параметров для разных типов SVG
- Оптимизация производительности
Проблема текущего подхода
В текущем коде есть несколько ключевых проблем:
- Неверные параметры
startStokeиendStoke- переменные используются, но не определены - Неправильный расчет смещения - логика анимации смещения не учитывает середину пути
- Отсутствие правильной настройки
stroke-dasharray- для анимации с середины нужна другая конфигурация
Как отмечают эксперты из CSS-Tricks, ключевая идея состоит в том, чтобы “настроить смещение так, чтобы занимало весь путь” и использовать правильные соотношения dash и gap.
Правильный метод анимации с середины
Для анимации с середины нужно использовать следующий подход:
useEffect(() => {
if (!svgRef.current) return;
const svgElement = svgRef.current;
const animatedElement = svgElement.querySelector('#svg_5');
// Получаем полную длину пути
const length = animatedElement.getTotalLength();
// Настраиваем параметры для анимации с середины
animatedElement.style.strokeDasharray = length / 2; // Половина длины для dash
animatedElement.style.strokeDashoffset = length / 4; // Начинаем с четверти пути (середина)
animatedElement.style.strokeWidth = 2;
animatedElement.style.stroke = "#00ff7f";
if (!isAnimated) return;
let startTime = null;
const duration = 6000;
const animate = (timestamp) => {
if (!startTime) startTime = timestamp;
const elapsed = timestamp - startTime;
const progress = Math.min(elapsed / duration, 1);
setIsProgress(progress);
const totalLength = animatedElement.getTotalLength();
// Анимация от середины к концу
const startOffset = length / 4; // Начинаем с середины
const endOffset = length; // Двигаемся до конца
const currentOffset = startOffset + (endOffset - startOffset) * (1 - progress);
animatedElement.style.strokeDashoffset = currentOffset;
if (progress < 1 && isAnimated) {
animationRef.current = requestAnimationFrame(animate);
} else if (progress >= 1) {
setIsAnimated(false);
}
};
animationRef.current = requestAnimationFrame(animate);
return () => {
if (animationRef.current) {
cancelAnimationFrame(animationRef.current);
}
};
}, [isAnimated]);
Полный пример компонента React
Вот полный компонент с анимацией обводки, начинающейся с середины:
import React, { useRef, useEffect, useState } from 'react';
const SVGStrokeAnimation = () => {
const svgRef = useRef(null);
const animationRef = useRef(null);
const [isAnimated, setIsAnimated] = useState(false);
const [isProgress, setIsProgress] = useState(0);
const [isAnimatingFromMiddle, setIsAnimatingFromMiddle] = useState(false);
const startAnimation = () => {
setIsAnimated(true);
setIsAnimatingFromMiddle(true);
};
const resetAnimation = () => {
setIsAnimated(false);
setIsAnimatingFromMiddle(false);
};
useEffect(() => {
if (!svgRef.current) return;
const svgElement = svgRef.current;
const animatedElement = svgElement.querySelector('#svg_5');
if (!animatedElement) return;
// Получаем полную длину пути
const length = animatedElement.getTotalLength();
// Настраиваем параметры для анимации
animatedElement.style.strokeDasharray = length;
animatedElement.style.strokeDashoffset = length;
animatedElement.style.strokeWidth = 2;
animatedElement.style.stroke = "#00ff7f";
animatedElement.style.transition = 'none';
if (!isAnimated) return;
let startTime = null;
const duration = 6000;
const animate = (timestamp) => {
if (!startTime) startTime = timestamp;
const elapsed = timestamp - startTime;
const progress = Math.min(elapsed / duration, 1);
setIsProgress(progress);
const totalLength = animatedElement.getTotalLength();
if (isAnimatingFromMiddle) {
// Анимация с середины
const middleOffset = length / 2; // Середина пути
const endOffset = 0; // Конец пути
// Разделяем анимацию: сначала рисуем половину, затем вторую половину
let currentOffset;
if (progress < 0.5) {
// Первая половина: рисуем от середины к началу
const firstHalfProgress = progress * 2;
currentOffset = middleOffset - (middleOffset - 0) * firstHalfProgress;
} else {
// Вторая половина: рисуем от середины к концу
const secondHalfProgress = (progress - 0.5) * 2;
currentOffset = middleOffset + (length - middleOffset) * secondHalfProgress;
}
animatedElement.style.strokeDashoffset = currentOffset;
} else {
// Обычная анимация с начала
const startOffset = length;
const endOffset = 0;
const currentOffset = startOffset + (endOffset - startOffset) * progress;
animatedElement.style.strokeDashoffset = currentOffset;
}
if (progress < 1 && isAnimated) {
animationRef.current = requestAnimationFrame(animate);
} else if (progress >= 1) {
setIsAnimated(false);
}
};
animationRef.current = requestAnimationFrame(animate);
return () => {
if (animationRef.current) {
cancelAnimationFrame(animationRef.current);
}
};
}, [isAnimated, isAnimatingFromMiddle]);
return (
<div>
<div style={{ marginBottom: '20px' }}>
<button onClick={startAnimation} disabled={isAnimated}>
{isAnimatingFromMiddle ? 'Анимация с середины' : 'Начать анимацию'}
</button>
<button onClick={resetAnimation} style={{ marginLeft: '10px' }}>
Сбросить
</button>
<div style={{ marginTop: '10px' }}>
Прогресс: {(isProgress * 100).toFixed(1)}%
</div>
</div>
<svg
ref={svgRef}
width="400"
height="300"
viewBox="0 0 400 300"
style={{ border: '1px solid #ccc' }}
>
<rect
id="svg_5"
x="50"
y="50"
width="300"
height="200"
fill="none"
style={{ strokeDasharray: '0', strokeDashoffset: '0' }}
/>
</svg>
</div>
);
};
export default SVGStrokeAnimation;
Альтернативные методы CSS анимации
Для более простых случаев можно использовать чистую CSS анимацию, как предлагают эксперты из DEV Community:
.svg-stroke-middle {
stroke-dasharray: 100%;
stroke-dashoffset: 50%;
animation: drawFromMiddle 2s ease-in-out forwards;
}
@keyframes drawFromMiddle {
0% {
stroke-dashoffset: 50%;
}
100% {
stroke-dashoffset: 0%;
}
}
В React компоненте это можно использовать так:
const SVGStrokeAnimationCSS = () => {
const [isAnimated, setIsAnimated] = useState(false);
const startAnimation = () => {
setIsAnimated(true);
};
return (
<div>
<button onClick={startAnimation} disabled={isAnimated}>
Начать CSS анимацию
</button>
<svg width="400" height="300" viewBox="0 0 400 300">
<rect
x="50"
y="50"
width="300"
height="200"
fill="none"
className={`svg-stroke-middle ${isAnimated ? 'animated' : ''}`}
style={{
strokeWidth: 2,
stroke: "#00ff7f",
strokeDasharray: '100%',
strokeDashoffset: '50%',
transition: stroke-dashoffset 2s ease-in-out
}}
/>
</svg>
</div>
);
};
Расчет параметров для разных типов SVG
Для разных типов SVG элементов расчеты могут отличаться:
-
Для прямоугольников:
javascriptconst length = 2 * (width + height); // Периметр прямоугольника const middleOffset = length / 2; // Середина периметра -
Для окружностей:
javascriptconst radius = circleElement.getAttribute('r'); const length = 2 * Math.PI * radius; // Длина окружности const middleOffset = length / 2; // Середина окружности -
Для сложных путей:
javascriptconst length = pathElement.getTotalLength(); const middleOffset = length / 2;
Как объясняют эксперты из Team Treehouse, stroke-dasharray и stroke-dashoffset работают вместе для создания эффектов анимации обводки.
Оптимизация производительности
Для лучшей производительности:
-
Используйте
will-change:css.animated-element { will-change: stroke-dashoffset; } -
Оптимизируйте анимацию:
javascript// Используйте requestAnimationFrame с правильной частотой const animate = (timestamp) => { // ... логика анимации animationRef.current = requestAnimationFrame(animate); }; -
Кэшируйте значения:
javascriptconst cachedLength = animatedElement.getTotalLength(); // Используйте cachedLength вместо повторных вызовов -
Используйте CSS transitions для простых анимаций:
javascriptanimatedElement.style.transition = 'stroke-dashoffset 2s ease-in-out'; animatedElement.style.strokeDashoffset = '0';
Таким образом, чтобы анимация обводки SVG начиналась с середины, нужно правильно рассчитать stroke-dashoffset, начиная с половины длины пути, и настроить анимацию для движения к началу и концу одновременно или поэтапно.
Источники
- Animate SVG Lines and Closed Paths with Dash Attributes - Let’s WP
- How SVG Line Animation Works | CSS-Tricks
- Animating stroke-dasharray and stroke-dashoffset (How To) | Team Treehouse
- Simple SVG drawing effect with stroke-dasharray & stroke-dashoffset - DEV Community
- stroke-dashoffset | CSS-Tricks Almanac
- Animating a complex SVG icon with dash-array and dash-offset in CSS | Justin Marsan