Функции смягчения (easing): d3-ease

Модуль d3-ease предназначен для управления скоростью анимации во времени. Вместо линейного изменения значений библиотека позволяет задавать сложные кривые ускорения и замедления, благодаря которым анимация выглядит естественно, плавно и визуально выразительно.

Функции easing используются внутри механизма анимации d3-transition и определяют, как параметр времени t изменяется в диапазоне от 0 до 1.

Линейная анимация:

selection.transition()
    .duration(1000)
    .ease(d3.easeLinear)

В данном случае изменение происходит равномерно.

Без easing движение объектов часто выглядит механическим:

  • начало слишком резкое;
  • конец слишком внезапный;
  • отсутствует инерция;
  • нет ощущения физики движения.

Функции смягчения позволяют:

  • плавно ускорять анимацию;
  • постепенно замедлять движение;
  • имитировать отскок;
  • создавать эффект упругости;
  • формировать кинематографические переходы.

Принцип работы easing-функций

Каждая easing-функция принимает число:

t ∈ [0, 1]

и возвращает преобразованное значение:

ease(t)

Например:

d3.easeQuad(0.5)

вернёт не 0.5, а значение, вычисленное по квадратичной кривой.

Графически easing-функция представляет собой кривую изменения скорости:

  • по оси X — время;
  • по оси Y — прогресс анимации.

Подключение модуля

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

Особенности

easeLinear не изменяет скорость анимации.

d3.easeLinear(t) === t

Анимация движется равномерно.


Пример

d3.select("circle")
    .transition()
    .duration(2000)
    .ease(d3.easeLinear)
    .attr("cx", 500)

Когда используется

Подходит для:

  • технических анимаций;
  • таймеров;
  • индикаторов загрузки;
  • равномерного движения.

Редко применяется в интерфейсах напрямую, поскольку выглядит неестественно.


Квадратичные easing-функции

easeQuadIn

Плавное ускорение.

В начале движение медленное, затем скорость возрастает.

.ease(d3.easeQuadIn)

easeQuadOut

Плавное замедление.

.ease(d3.easeQuadOut)

Анимация стартует быстро и постепенно тормозит.


easeQuadInOut

Комбинация ускорения и замедления.

.ease(d3.easeQuadInOut)

Наиболее универсальный вариант.


Пример

svg.select("rect")
    .transition()
    .duration(1500)
    .ease(d3.easeQuadInOut)
    .attr("x", 400)

Кубические easing-функции

Особенности

Кубические функции создают более выраженное ускорение.

Семейство:

  • easeCubicIn
  • easeCubicOut
  • easeCubicInOut

Пример

.transition()
.ease(d3.easeCubicOut)

Отличия от Quad

Кривая ускорения становится более агрессивной:

  • старт медленнее;
  • разгон сильнее;
  • торможение мягче.

Полиномиальные easing-функции

easePolyIn

Позволяет задавать степень полинома.

const ease = d3.easePolyIn.exponent(4)

Пример

const customEase = d3.easePolyIn.exponent(5)

selection.transition()
    .ease(customEase)

Где применяется

Подходит для:

  • сложных motion-интерфейсов;
  • кинематографических переходов;
  • динамических визуализаций.

Синусоидальные easing-функции

easeSinIn

Использует синусоидальную кривую.

.ease(d3.easeSinIn)

Особенности

Создаёт очень мягкое ускорение.

Движение выглядит плавным и «органичным».


Семейство

  • easeSinIn
  • easeSinOut
  • easeSinInOut

Пример

circle.transition()
    .duration(1200)
    .ease(d3.easeSinInOut)
    .attr("r", 80)

Экспоненциальные easing-функции

Поведение

Экспоненциальные функции создают резкие изменения скорости.

Семейство:

  • easeExpIn
  • easeExpOut
  • easeExpInOut

Пример

.transition()
.ease(d3.easeExpOut)

Визуальный эффект

Анимация:

  • стартует крайне быстро;
  • либо очень резко ускоряется;
  • создаёт ощущение энергии.

Когда полезно

Подходит для:

  • появления всплывающих окон;
  • UI-анимаций;
  • динамических дашбордов;
  • игровых эффектов.

Круговые easing-функции

easeCircleInOut

Основана на окружности.

.ease(d3.easeCircleInOut)

Особенности

Движение выглядит естественным и физически правдоподобным.


Пример

rect.transition()
    .duration(1000)
    .ease(d3.easeCircleOut)
    .attr("width", 300)

Bounce easing

Назначение

Создаёт эффект подпрыгивания.

Семейство:

  • easeBounceIn
  • easeBounceOut
  • easeBounceInOut

Пример

ball.transition()
    .duration(2000)
    .ease(d3.easeBounceOut)
    .attr("cy", 400)

Визуальный эффект

Объект:

  • падает;
  • ударяется;
  • несколько раз подпрыгивает.

Использование

Часто применяется в:

  • игровых интерфейсах;
  • onboarding-анимациях;
  • декоративных эффектах;
  • интерактивных элементах.

Elastic easing

Особенности

Создаёт эффект резинки или пружины.

Семейство:

  • easeElasticIn
  • easeElasticOut
  • easeElasticInOut

Пример

.transition()
.ease(d3.easeElasticOut)

Визуальный эффект

Объект:

  • перелетает целевую точку;
  • возвращается;
  • колеблется;
  • стабилизируется.

Настройка амплитуды

const elastic = d3.easeElastic
    .amplitude(1.5)
    .period(0.4)

Параметры

amplitude()

Управляет силой колебаний.

.amplitude(2)

Чем больше значение, тем сильнее раскачивание.


period()

Управляет частотой колебаний.

.period(0.2)

Меньший период создаёт более частые колебания.


Back easing

Назначение

Создаёт движение назад перед стартом.


Пример

.transition()
.ease(d3.easeBackOut)

Визуальный эффект

Перед движением объект слегка отклоняется в противоположную сторону.


Использование

Полезно для:

  • кнопок;
  • карточек;
  • всплывающих панелей;
  • drag-and-drop интерфейсов.

Настройка overshoot

const back = d3.easeBack.overshoot(3)

overshoot()

Определяет силу отклонения.

.overshoot(5)

Большие значения делают эффект драматичнее.


In, Out и InOut

Практически все easing-функции имеют три варианта.


In

Ускорение в начале.

.ease(d3.easeCubicIn)

Out

Замедление в конце.

.ease(d3.easeCubicOut)

InOut

Комбинация двух режимов.

.ease(d3.easeCubicInOut)

Сравнение поведения easing-функций

Функция Поведение
Linear Равномерное движение
Quad Мягкое ускорение
Cubic Более сильное ускорение
Sin Очень плавное движение
Exp Резкие изменения скорости
Circle Естественная физика
Bounce Подпрыгивание
Elastic Пружина
Back Откат назад

Использование easing внутри transition

Базовый синтаксис

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)

Создание собственной easing-функции

Пользовательская функция

function customEase(t) {
    return t * t * t
}

Использование

.transition()
.ease(customEase)

Нестандартные easing-функции

Синусоида

function waveEase(t) {
    return Math.sin(t * Math.PI / 2)
}

Пульсация

function pulseEase(t) {
    return Math.abs(Math.sin(6 * Math.PI * t))
}

Комбинирование easing и delay

Пример

bars.transition()
    .delay((d, i) => i * 100)
    .duration(800)
    .ease(d3.easeBackOut)
    .attr("height", d => d.value)

Результат

Столбцы:

  • появляются по очереди;
  • имеют эффект отката;
  • формируют каскадную анимацию.

easing в анимации графиков

Плавное появление линий

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)

Производительность easing-анимаций

Основные рекомендации

Избегать чрезмерно длинных анимаций

Плохо:

.duration(10000)

Оптимально:

.duration(300)
.duration(800)
.duration(1500)

Не использовать сложные easing повсюду

Bounce и Elastic:

  • нагружают восприятие;
  • быстро утомляют;
  • подходят только для акцентов.

Использовать easing осмысленно

Тип easing должен соответствовать задаче:

Сценарий Подходящий easing
UI CubicOut
Графики Linear
Модальные окна BackOut
Игровые эффекты Bounce
Декоративные анимации Elastic

Отладка easing-анимаций

Проверка времени

console.time("animation")

Событие завершения

.transition()
.on("end", () => {
    console.log("done")
})

Частые ошибки

Отсутствие transition()

Неправильно:

selection.ease(d3.easeBounce)

Правильно:

selection.transition()
    .ease(d3.easeBounce)

Слишком резкие easing-функции

.ease(d3.easeExpIn)

Может выглядеть агрессивно для интерфейса.


Чрезмерное количество эффектов

Плохо:

  • Bounce;
  • Elastic;
  • Back;

одновременно в одном интерфейсе.


Архитектурные рекомендации

Использование констант

const DEFAULT_EASE = d3.easeCubicOut

Повторное использование

function animate(selection) {
    return selection.transition()
        .duration(800)
        .ease(d3.easeCubicOut)
}

Централизация motion-системы

const motion = {
    fast: 200,
    normal: 600,
    slow: 1200,
    ease: d3.easeCubicOut
}

Совместимость с d3-transition

Модуль d3-ease тесно интегрирован с:

  • d3-transition
  • d3-selection
  • d3-interpolate

Пример совместной работы

selection.transition()
    .duration(1500)
    .ease(d3.easeCircleInOut)
    .attrTween("transform", interpolateTransform)

Внутренний механизм easing

Во время анимации D3:

  1. вычисляет время;
  2. нормализует его в диапазон [0,1];
  3. передаёт значение easing-функции;
  4. получает скорректированный прогресс;
  5. вычисляет итоговое состояние объекта.

Наиболее популярные easing-функции

Для интерфейсов

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)

Практический пример: bounce-мяч

svg.append("circle")
    .attr("cx", 200)
    .attr("cy", 50)
    .attr("r", 30)
    .transition()
    .duration(2500)
    .ease(d3.easeBounceOut)
    .attr("cy", 400)

Практический пример: elastic-появление

card.transition()
    .duration(1000)
    .ease(d3.easeElasticOut)
    .style("opacity", 1)
    .style("transform", "scale(1)")