Условный рендеринг

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

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


Связь состояния и анимации

В классическом JavaScript условный рендеринг реализуется через:

  • if / else
  • тернарные операторы
  • управление классами или стилями

При использовании Popmotion добавляется промежуточный слой — анимация, которая реагирует на изменение состояния.

Пример базовой зависимости:

let isVisible = false;

function toggle() {
  isVisible = !isVisible;
  render();
}

Без анимации элемент просто появляется или исчезает. С Popmotion добавляется плавный переход:

import { tween, styler } from "popmotion";

const box = document.querySelector(".box");
const style = styler(box);

function show() {
  tween({
    from: { opacity: 0, y: -50 },
    to: { opacity: 1, y: 0 },
    duration: 300
  }).start(style.set);
}

function hide() {
  tween({
    from: { opacity: 1, y: 0 },
    to: { opacity: 0, y: -50 },
    duration: 300
  }).start(style.set);
}

Управление жизненным циклом элемента

Ключевая проблема условного рендеринга с анимациями — момент удаления элемента из DOM.

Если элемент удаляется сразу, анимация исчезновения не успевает выполниться.

Подход с отложенным удалением

function hideElement(element, onComplete) {
  const style = styler(element);

  tween({
    from: { opacity: 1 },
    to: { opacity: 0 },
    duration: 300
  }).start({
    update: style.set,
    complete: () => {
      element.remove();
      if (onComplete) onComplete();
    }
  });
}

Здесь элемент:

  1. Сначала анимируется
  2. Затем удаляется

Условный рендеринг через состояние и фабрики элементов

Создание элементов можно обернуть в функцию:

function createBox() {
  const el = document.createElement("div");
  el.classList.add("box");
  document.body.appendChild(el);
  return el;
}

Связка с условием:

let currentBox = null;

function render(isVisible) {
  if (isVisible && !currentBox) {
    currentBox = createBox();
    animateIn(currentBox);
  }

  if (!isVisible && currentBox) {
    animateOut(currentBox, () => {
      currentBox = null;
    });
  }
}

Так обеспечивается:

  • отсутствие дублирования
  • корректное управление состоянием DOM
  • контроль над анимациями

Использование Popmotion Actions для условных переходов

Popmotion предоставляет Actions — абстракцию над анимациями.

Пример с использованием tween как Action:

import { tween } from "popmotion";

function fade(element, from, to) {
  return tween({ from, to, duration: 300 }).start(v => {
    element.style.opacity = v;
  });
}

Условное использование:

if (isVisible) {
  fade(box, 0, 1);
} else {
  fade(box, 1, 0);
}

Композиция анимаций при условном рендеринге

Popmotion позволяет комбинировать анимации:

Параллельное выполнение

import { parallel, tween } from "popmotion";

function animateIn(element) {
  const style = styler(element);

  parallel(
    tween({ from: 0, to: 1 }),
    tween({ from: -100, to: 0 })
  ).start(([opacity, y]) => {
    style.set({ opacity, y });
  });
}

Последовательное выполнение

import { chain } from "popmotion";

chain(
  tween({ from: 0, to: 1, duration: 200 }),
  tween({ from: 1, to: 0.8, duration: 100 })
).start(v => {
  element.style.opacity = v;
});

Такая композиция особенно полезна при:

  • сложных появлениях
  • staged-анимациях
  • последовательных состояниях UI

Управление состоянием через observable-подход

Popmotion поддерживает реактивный стиль через value.

import { value, tween } from "popmotion";

const visibility = value(0, v => {
  box.style.opacity = v;
});

Изменение состояния:

function show() {
  tween({ to: 1 }).start(visibility);
}

function hide() {
  tween({ to: 0 }).start(visibility);
}

Здесь:

  • состояние (visibility) хранится отдельно
  • рендеринг происходит автоматически через подписку

Динамический условный рендеринг списков

При работе со списками возникает задача:

  • добавление элементов
  • удаление элементов
  • анимация каждого изменения

Пример добавления

function addItem(container) {
  const item = document.createElement("div");
  item.classList.add("item");
  container.appendChild(item);

  tween({
    from: { opacity: 0, scale: 0.8 },
    to: { opacity: 1, scale: 1 }
  }).start(styler(item).set);
}

Пример удаления

function removeItem(item) {
  const style = styler(item);

  tween({
    to: { opacity: 0, scale: 0.8 }
  }).start({
    update: style.set,
    complete: () => item.remove()
  });
}

Контроль прерывания анимаций

При быстром переключении состояния важно:

  • отменять предыдущие анимации
  • избегать конфликтов
let animation;

function animate(to) {
  if (animation) animation.stop();

  animation = tween({ to }).start(v => {
    box.style.opacity = v;
  });
}

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

animate(isVisible ? 1 : 0);

Условный рендеринг и взаимодействие с событиями

Popmotion удобно комбинируется с обработчиками событий:

button.addEventListener("click", () => {
  isVisible = !isVisible;
  render(isVisible);
});

С добавлением анимации:

  • пользователь получает визуальную обратную связь
  • интерфейс ощущается «живым»

Разделение логики и анимации

Рекомендуется разделять:

  1. Логику состояния
  2. Логику рендеринга
  3. Логику анимации

Пример структуры:

// state
let isVisible = false;

// animation
function animateIn(el) { /* ... */ }
function animateOut(el, done) { /* ... */ }

// render
function render() { /* ... */ }

Это позволяет:

  • переиспользовать анимации
  • упрощать поддержку
  • масштабировать приложение

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

1. Мгновенное удаление элемента

  • приводит к обрыву анимации

2. Отсутствие контроля над состоянием

  • дублирование элементов
  • несинхронизированный UI

3. Перезапуск анимаций без остановки

  • визуальные артефакты

4. Смешивание логики

  • сложный для поддержки код

Паттерн «visibility вместо display»

Вместо удаления элемента можно:

  • управлять opacity
  • управлять pointer-events
function hide(element) {
  tween({ to: 0 }).start(v => {
    element.style.opacity = v;
    element.style.pointerEvents = "none";
  });
}

Преимущества:

  • элемент остаётся в DOM
  • проще управлять анимациями
  • меньше операций с DOM

Использование easing-функций

Popmotion поддерживает различные easing-функции:

import { easing, tween } from "popmotion";

tween({
  to: 1,
  ease: easing.easeInOut
}).start(v => {
  box.style.opacity = v;
});

Это позволяет:

  • делать анимации более естественными
  • улучшать UX при условном рендеринге

Итоговая архитектура условного рендеринга с Popmotion

Эффективная схема:

  1. Состояние управляет видимостью
  2. Рендер решает, создавать или удалять элемент
  3. Анимация отвечает за переходы
  4. Управление жизненным циклом синхронизировано с анимацией

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