Анимации и переходы

Mithril не содержит встроенного анимационного движка и не навязывает конкретный способ работы с анимациями. Архитектура фреймворка строится вокруг быстрого виртуального DOM и предсказуемых жизненных циклов компонентов, что позволяет реализовывать анимации через стандартные веб-технологии: CSS, JavaScript, Web Animations API и сторонние библиотеки. Ключевая особенность — точный контроль над моментами вставки, обновления и удаления DOM-узлов.

Основные точки интеграции анимаций:

  • CSS-переходы и анимации
  • Жизненные циклы компонентов
  • Хуки oncreate, onupdate, onremove
  • Ключ key для управления повторным использованием DOM-узлов

CSS-переходы как базовый инструмент

Наиболее простой и производительный способ анимации — использование CSS transition. Mithril не мешает этому подходу, так как не манипулирует стилями напрямую.

const Fade = {
    view() {
        return m("div.fade", "Контент")
    }
}
.fade {
    opacity: 0;
    transition: opacity 300ms ease;
}

.fade.active {
    opacity: 1;
}

Активация анимации происходит через изменение класса. В Mithril это обычно делается через состояние компонента.

const Fade = {
    visible: false,
    view() {
        return m("div.fade" + (this.visible ? ".active" : ""), "Контент")
    }
}

Изменение состояния приводит к перерисовке, CSS самостоятельно запускает переход.


Анимации при создании элемента (oncreate)

Хук oncreate вызывается после добавления DOM-узла в документ. Это идеальное место для инициации анимации появления.

const SlideIn = {
    view() {
        return m("div.panel", {
            oncreate(vnode) {
                vnode.dom.classList.add("enter")
                requestAnimationFrame(() => {
                    vnode.dom.classList.add("enter-active")
                })
            }
        }, "Панель")
    }
}
.panel {
    transform: translateX(-20px);
    opacity: 0;
}

.panel.enter-active {
    transform: translateX(0);
    opacity: 1;
    transition: transform 250ms ease, opacity 250ms ease;
}

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


Обновление и анимация изменений (onupdate)

Хук onupdate вызывается после обновления DOM-узла. Он применяется для анимации переходов между состояниями.

const Resizable = {
    view(vnode) {
        return m("div.box", {
            style: { width: vnode.attrs.width + "px" },
            onupdate(vnode) {
                vnode.dom.classList.add("resizing")
            }
        })
    }
}
.box {
    transition: width 300ms ease;
}

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


Анимации удаления (onremove)

По умолчанию Mithril мгновенно удаляет элементы из DOM. Для анимированного удаления используется onremove, который должен вернуть Promise.

const FadeOut = {
    view() {
        return m("div.item", {
            onremove(vnode) {
                return new Promise(resolve => {
                    vnode.dom.classList.add("leave")
                    vnode.dom.addEventListener("transitionend", resolve)
                })
            }
        }, "Удаляемый элемент")
    }
}
.item {
    opacity: 1;
    transition: opacity 200ms ease;
}

.item.leave {
    opacity: 0;
}

Пока Promise не завершён, Mithril удерживает элемент в DOM, позволяя корректно завершить анимацию.


Управление повторным использованием DOM через key

Mithril агрессивно переиспользует DOM-узлы для повышения производительности. Для анимаций списков это может мешать, поэтому используется атрибут key.

items.map(item =>
    m("li", { key: item.id }, item.text)
)

Наличие key гарантирует:

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

При анимации сортировок и фильтраций key является обязательным.


Анимации списков

Для списков часто комбинируются oncreate и onremove.

const ListItem = {
    view(vnode) {
        return m("li", {
            oncreate(v) {
                v.dom.classList.add("appear")
            },
            onremove(v) {
                return new Promise(resolve => {
                    v.dom.classList.add("disappear")
                    v.dom.addEventListener("transitionend", resolve)
                })
            }
        }, vnode.children)
    }
}
li {
    transition: transform 200ms ease, opacity 200ms ease;
}

li.appear {
    transform: translateY(0);
    opacity: 1;
}

li.disappear {
    transform: translateY(-10px);
    opacity: 0;
}

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


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

Для более сложных сценариев используется element.animate.

const Bounce = {
    view() {
        return m("div.box", {
            oncreate(vnode) {
                vnode.dom.animate([
                    { transform: "scale(0.9)" },
                    { transform: "scale(1)" }
                ], {
                    duration: 300,
                    easing: "ease-out"
                })
            }
        })
    }
}

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

  • полный контроль таймингов
  • отсутствие зависимости от CSS
  • возможность отмены и управления анимацией через объект Animation

Интеграция сторонних библиотек

Mithril не конфликтует с GSAP, anime.js и подобными решениями. Подключение осуществляется через хуки.

oncreate(vnode) {
    gsap.from(vnode.dom, { opacity: 0, y: 20, duration: 0.3 })
}

Критически важно:

  • не изменять DOM вне хуков
  • избегать мутаций элементов, которые Mithril может пересоздать
  • использовать key для стабильности

Производительность и практические ограничения

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

  • анимировать transform и opacity
  • избегать анимации height и top при больших DOM-деревьях
  • не запускать тяжёлые анимации в onupdate без проверки изменений
  • минимизировать количество принудительных reflow

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


Архитектурный подход

В сложных приложениях анимации выносятся в:

  • отдельные компоненты
  • специализированные директивы через фабрики компонентов
  • обёртки над DOM-узлами

Пример фабрики:

const Animated = animation => ({
    view(vnode) {
        return m(vnode.tag, {
            ...vnode.attrs,
            oncreate(v) {
                animation.enter(v.dom)
            },
            onremove(v) {
                return animation.leave(v.dom)
            }
        }, vnode.children)
    }
})

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


Итоговая модель

Анимации в Mithril строятся не вокруг магии, а вокруг точного знания жизненного цикла DOM. Фреймворк предоставляет минимальный, но достаточный набор хуков, позволяя использовать стандартные веб-инструменты максимально эффективно.