Staggered-анимация: задержка через функцию от индекса

Задержка переходов как функция индекса

Визуальные переходы в D3.js строятся поверх механизма transition, где каждый элемент набора может иметь собственные параметры времени. Ключевым инструментом для создания каскадных (staggered) анимаций выступает функция delay, принимающая вычисление на основе данных и индекса элемента. Именно индекс i становится основой синхронизации последовательностей.


При работе с коллекциями DOM-элементов D3 передаёт в функции доступа два аргумента:

  • d — связанный с элементом datum
  • i — индекс элемента в текущей выборке

Индекс не зависит от исходного набора данных напрямую, а формируется в момент связывания и обхода selection. Это делает его устойчивым инструментом для построения временных последовательностей.

d3.selectAll("rect")
  .transition()
  .delay((d, i) => i * 50)
  .duration(600)
  .attr("y", d => 100 - d);

В данном случае каждый следующий прямоугольник начинает анимацию на 50 мс позже предыдущего, формируя линейный каскад.


Линейная задержка и её свойства

Функция вида i * k создаёт равномерную задержку между элементами:

  • постоянный шаг времени
  • предсказуемая структура анимации
  • визуальная «волна» появления

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

.delay((d, i) => i * 80)

При большом количестве элементов линейная модель может приводить к заметному растяжению общей длительности анимации, поскольку последний элемент получает максимальную задержку.


Обратный порядок появления

Изменение направления каскада достигается инвертированием индекса относительно длины выборки:

const nodes = d3.selectAll("circle");

nodes.transition()
  .delay((d, i) => (nodes.size() - i - 1) * 40)
  .attr("r", 6);

Такой подход формирует эффект «сборки» с конца массива к началу, часто используемый при обратных анимациях удаления или скрытия элементов.


Нелинейные функции задержки

Линейное распределение не всегда соответствует требуемой динамике. Использование нелинейных функций позволяет управлять плотностью появления элементов.

Квадратичная зависимость

.delay((d, i) => i * i * 10)

Эффект: первые элементы появляются почти одновременно, дальнейшие — с резко увеличивающимися интервалами.

Корневая зависимость

.delay((d, i) => Math.sqrt(i) * 120)

Эффект: быстрый старт и постепенное сглаживание интервалов.


Масштабирование задержки через шкалы D3

Более контролируемый способ — использование scaleLinear, позволяющего нормализовать диапазон индексов в диапазон времени.

const scaleDelay = d3.scaleLinear()
  .domain([0, 50])
  .range([0, 1000]);

d3.selectAll("rect")
  .transition()
  .delay((d, i) => scaleDelay(i));

Такой подход особенно полезен при неизвестном заранее размере выборки или при необходимости строгого ограничения общей длительности анимации.


Синусоидальная и волновая задержка

Использование тригонометрических функций создаёт волнообразные эффекты появления:

.delay((d, i) => Math.sin(i * 0.3) * 200 + 300)

В результате элементы не появляются строго последовательно, а формируют визуальные волны. Это применяется в декоративных визуализациях и динамических интерфейсах.


Групповая задержка

При наличии структурированных данных часто требуется синхронизация по группам. Индекс внутри группы может комбинироваться с внешним индексом.

d3.selectAll(".group")
  .selectAll("rect")
  .transition()
  .delay((d, i, nodes) => i * 30);

При работе с вложенными selection важно учитывать, что i относится к текущей группе, а не к глобальному набору.

Для глобальной координации используется внешний контекст:

let groupIndex = 0;

d3.selectAll(".group")
  .each(function() {
    d3.select(this)
      .selectAll("rect")
      .transition()
      .delay((d, i) => i * 20 + groupIndex * 200);
    groupIndex++;
  });

Связь с enter-update-exit

Staggered-анимации особенно часто применяются в фазе enter, где новые элементы вводятся в DOM:

const bars = d3.select("svg")
  .selectAll("rect")
  .data(data);

bars.enter()
  .append("rect")
  .attr("y", 100)
  .attr("height", 0)
  .transition()
  .delay((d, i) => i * 40)
  .attr("height", d => d);

Здесь задержка формирует ощущение постепенного «вырастания» диаграммы.


Комбинация delay и duration

Эффект каскада определяется не только задержкой, но и соотношением с длительностью анимации:

.transition()
  .delay((d, i) => i * 30)
  .duration(400)

Если delay существенно меньше duration, элементы перекрывают друг друга по времени, создавая плотную динамику. При большом delay возникает строго последовательная анимация.


Случайные вариации задержки

Добавление стохастики снижает механистичность анимации:

.delay((d, i) => i * 20 + Math.random() * 100)

Такая схема используется для имитации естественных процессов появления, особенно в плотных визуальных полях.


Оптимизация больших наборов данных

При тысячах элементов линейное масштабирование задержки становится критичным фактором производительности восприятия. Практика включает:

  • ограничение максимальной задержки
  • нормализацию индексов через scale
  • группировку элементов
const maxDelay = 800;

.delay((d, i) => Math.min(i * 10, maxDelay))

Индекс и порядок DOM-элементов

Индекс в delay зависит от порядка selection, а не от визуального положения. Перестановка элементов в DOM меняет поведение анимации без изменения данных.

d3.selectAll("rect")
  .sort((a, b) => a - b)
  .transition()
  .delay((d, i) => i * 25)

Сортировка позволяет управлять направлением каскада без изменения формулы задержки.


Синхронизация нескольких переходов

При наличии нескольких свойств, анимируемых одновременно, задержка применяется ко всему transition-объекту:

d3.selectAll("circle")
  .transition()
  .delay((d, i) => i * 50)
  .attr("cx", d => d.x)
  .attr("cy", d => d.y)
  .attr("r", 8)

Все атрибуты начинают изменяться одновременно внутри каждого элемента, но сами элементы стартуют по очереди.


Поведение delay в цепочках transitions

При использовании последовательных переходов каждый следующий transition может наследовать временную структуру:

d3.selectAll("rect")
  .transition()
  .delay((d, i) => i * 30)
  .attr("height", d => d)
  .transition()
  .delay(0)
  .attr("opacity", 0.5)

Первый переход формирует каскад, второй запускается после завершения первого для каждого элемента.


Итоговая структура управления индексной задержкой

Функция delay((d, i) => ...) выступает универсальным механизмом управления временной логикой визуализаций. Индекс становится абстрактной осью времени, на которую проецируются различные функции:

  • линейные
  • обратные
  • нелинейные
  • стохастические
  • масштабированные через шкалы

Комбинация этих подходов формирует гибкую систему управления последовательностью появления элементов в пространстве SVG и DOM, где временная структура становится частью визуальной композиции.