Модуль d3-ease предназначен для управления скоростью
анимации во времени. Вместо линейного изменения значений библиотека
позволяет задавать сложные кривые ускорения и замедления, благодаря
которым анимация выглядит естественно, плавно и визуально
выразительно.
Функции easing используются внутри механизма анимации
d3-transition и определяют, как параметр времени
t изменяется в диапазоне от 0 до
1.
Линейная анимация:
selection.transition()
.duration(1000)
.ease(d3.easeLinear)
В данном случае изменение происходит равномерно.
Без easing движение объектов часто выглядит механическим:
Функции смягчения позволяют:
Каждая easing-функция принимает число:
t ∈ [0, 1]
и возвращает преобразованное значение:
ease(t)
Например:
d3.easeQuad(0.5)
вернёт не 0.5, а значение, вычисленное по квадратичной
кривой.
Графически easing-функция представляет собой кривую изменения скорости:
CDN:
<script src="https://d3js.org/d3.v7.min.js"></script>
ESM:
import * as d3 from "d3"
Отдельный импорт:
import {
easeLinear,
easeQuad,
easeBounce
} from "d3-ease"
easeLinear не изменяет скорость анимации.
d3.easeLinear(t) === t
Анимация движется равномерно.
d3.select("circle")
.transition()
.duration(2000)
.ease(d3.easeLinear)
.attr("cx", 500)
Подходит для:
Редко применяется в интерфейсах напрямую, поскольку выглядит неестественно.
Плавное ускорение.
В начале движение медленное, затем скорость возрастает.
.ease(d3.easeQuadIn)
Плавное замедление.
.ease(d3.easeQuadOut)
Анимация стартует быстро и постепенно тормозит.
Комбинация ускорения и замедления.
.ease(d3.easeQuadInOut)
Наиболее универсальный вариант.
svg.select("rect")
.transition()
.duration(1500)
.ease(d3.easeQuadInOut)
.attr("x", 400)
Кубические функции создают более выраженное ускорение.
Семейство:
easeCubicIneaseCubicOuteaseCubicInOut.transition()
.ease(d3.easeCubicOut)
Кривая ускорения становится более агрессивной:
Позволяет задавать степень полинома.
const ease = d3.easePolyIn.exponent(4)
const customEase = d3.easePolyIn.exponent(5)
selection.transition()
.ease(customEase)
Подходит для:
Использует синусоидальную кривую.
.ease(d3.easeSinIn)
Создаёт очень мягкое ускорение.
Движение выглядит плавным и «органичным».
easeSinIneaseSinOuteaseSinInOutcircle.transition()
.duration(1200)
.ease(d3.easeSinInOut)
.attr("r", 80)
Экспоненциальные функции создают резкие изменения скорости.
Семейство:
easeExpIneaseExpOuteaseExpInOut.transition()
.ease(d3.easeExpOut)
Анимация:
Подходит для:
Основана на окружности.
.ease(d3.easeCircleInOut)
Движение выглядит естественным и физически правдоподобным.
rect.transition()
.duration(1000)
.ease(d3.easeCircleOut)
.attr("width", 300)
Создаёт эффект подпрыгивания.
Семейство:
easeBounceIneaseBounceOuteaseBounceInOutball.transition()
.duration(2000)
.ease(d3.easeBounceOut)
.attr("cy", 400)
Объект:
Часто применяется в:
Создаёт эффект резинки или пружины.
Семейство:
easeElasticIneaseElasticOuteaseElasticInOut.transition()
.ease(d3.easeElasticOut)
Объект:
const elastic = d3.easeElastic
.amplitude(1.5)
.period(0.4)
Управляет силой колебаний.
.amplitude(2)
Чем больше значение, тем сильнее раскачивание.
Управляет частотой колебаний.
.period(0.2)
Меньший период создаёт более частые колебания.
Создаёт движение назад перед стартом.
.transition()
.ease(d3.easeBackOut)
Перед движением объект слегка отклоняется в противоположную сторону.
Полезно для:
const back = d3.easeBack.overshoot(3)
Определяет силу отклонения.
.overshoot(5)
Большие значения делают эффект драматичнее.
Практически все easing-функции имеют три варианта.
Ускорение в начале.
.ease(d3.easeCubicIn)
Замедление в конце.
.ease(d3.easeCubicOut)
Комбинация двух режимов.
.ease(d3.easeCubicInOut)
| Функция | Поведение |
|---|---|
| Linear | Равномерное движение |
| Quad | Мягкое ускорение |
| Cubic | Более сильное ускорение |
| Sin | Очень плавное движение |
| Exp | Резкие изменения скорости |
| Circle | Естественная физика |
| Bounce | Подпрыгивание |
| Elastic | Пружина |
| Back | Откат назад |
selection.transition()
.duration(1000)
.ease(d3.easeCubicOut)
.attr("x", 300)
const svg = d3.select("svg")
svg.append("circle")
.attr("cx", 50)
.attr("cy", 100)
.attr("r", 30)
.attr("fill", "steelblue")
.transition()
.duration(2000)
.ease(d3.easeBounceOut)
.attr("cx", 500)
function customEase(t) {
return t * t * t
}
.transition()
.ease(customEase)
function waveEase(t) {
return Math.sin(t * Math.PI / 2)
}
function pulseEase(t) {
return Math.abs(Math.sin(6 * Math.PI * t))
}
bars.transition()
.delay((d, i) => i * 100)
.duration(800)
.ease(d3.easeBackOut)
.attr("height", d => d.value)
Столбцы:
path.transition()
.duration(2000)
.ease(d3.easeLinear)
.attrTween("stroke-dasharray", tweenDash)
bars.transition()
.ease(d3.easeBounceOut)
.attr("y", d => y(d.value))
arcs.transition()
.ease(d3.easeElasticOut)
Плохо:
.duration(10000)
Оптимально:
.duration(300)
.duration(800)
.duration(1500)
Bounce и Elastic:
Тип easing должен соответствовать задаче:
| Сценарий | Подходящий easing |
|---|---|
| UI | CubicOut |
| Графики | Linear |
| Модальные окна | BackOut |
| Игровые эффекты | Bounce |
| Декоративные анимации | Elastic |
console.time("animation")
.transition()
.on("end", () => {
console.log("done")
})
Неправильно:
selection.ease(d3.easeBounce)
Правильно:
selection.transition()
.ease(d3.easeBounce)
.ease(d3.easeExpIn)
Может выглядеть агрессивно для интерфейса.
Плохо:
одновременно в одном интерфейсе.
const DEFAULT_EASE = d3.easeCubicOut
function animate(selection) {
return selection.transition()
.duration(800)
.ease(d3.easeCubicOut)
}
const motion = {
fast: 200,
normal: 600,
slow: 1200,
ease: d3.easeCubicOut
}
Модуль d3-ease тесно интегрирован с:
d3-transitiond3-selectiond3-interpolateselection.transition()
.duration(1500)
.ease(d3.easeCircleInOut)
.attrTween("transform", interpolateTransform)
Во время анимации D3:
[0,1];d3.easeCubicOut
d3.easeSinInOut
d3.easeLinear
d3.easeBackOut
d3.easeBounceOut
const bars = svg.selectAll("rect")
.data(data)
bars.enter()
.append("rect")
.attr("x", (d, i) => i * 60)
.attr("y", 300)
.attr("width", 40)
.attr("height", 0)
.transition()
.duration(1200)
.delay((d, i) => i * 100)
.ease(d3.easeCubicOut)
.attr("y", d => 300 - d.value)
.attr("height", d => d.value)
svg.append("circle")
.attr("cx", 200)
.attr("cy", 50)
.attr("r", 30)
.transition()
.duration(2500)
.ease(d3.easeBounceOut)
.attr("cy", 400)
card.transition()
.duration(1000)
.ease(d3.easeElasticOut)
.style("opacity", 1)
.style("transform", "scale(1)")