Переходы которые не тошнят: паттерны смены экранов для мобайла и веба
Основной чат
Чат для вайбкодеров: новости, гайды, поиск исполнителей, маркетплейс и разбор реальных кейсов.
Переход между экранами — самый часто встречающийся motion-паттерн и самый часто ломающий UX. Плохой переход дезориентирует: пользователь не понимает где он был, где он теперь, что произошло.
Хороший переход невидим. Ты замечаешь только что стало лучше понятно.
Почему переходы важны для ориентации
Без анимации переход — телепортация. Пользователь был на списке, нажал, оказался на детальной странице. Мозг не успел построить пространственную связь между двумя состояниями.
С правильной анимацией — движение. Пользователь видит что «пошёл вглубь» (slide left), что «вернулся назад» (slide right), что «что-то всплыло поверх» (slide up). Ментальная карта продукта строится через эти переходы.
Неправильный переход ломает ментальную карту. Slide влево когда ожидался вправо. Fade там где нужен scale. Пользователь чувствует «что-то не так» — даже если не может назвать причину.
Семь паттернов с логикой выбора
Fade (crossfade)
Текущий экран растворяется, новый появляется. Самый нейтральный — не передаёт направление.
Когда: экраны не иерархически связаны (смена разделов через боковое меню), модальные диалоги, смена вкладок таба.
Не когда: drill-down навигация где нужно показать «я пошёл глубже».
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.page-enter { animation: fadeIn 0.25s ease-out; }
Slide horizontal
Следующий экран приходит справа, текущий уходит влево. Назад — обратное.
Когда: drill-down навигация (список → деталь), iOS navigation stack, любая иерархическая структура.
Почему работает: имитирует физическое перелистывание. Пользователь понимает что «пошёл вглубь» и знает в какую сторону возвращаться.
.page-enter {
transform: translateX(100%);
animation: slideIn 0.3s ease-out forwards;
}
.page-exit {
animation: slideOut 0.3s ease-in forwards;
}
@keyframes slideIn { to { transform: translateX(0); } }
@keyframes slideOut { to { transform: translateX(-30%); opacity: 0.3; } }
Slide vertical (bottom sheet)
Появление снизу — стандарт для modal bottom sheets на мобайле. Исчезновение вниз при закрытии.
Когда: bottom sheets, action sheets, drawer-панели на мобайле. Создаёт чёткое разделение между «основным контентом» и «временным контентом».
Scale (zoom)
Элемент увеличивается до нового экрана. Самый сильный способ показать что новый экран «вышел» из конкретного элемента.
Когда: нажатие на карточку открывает детальный экран, thumbnail → full screen, аватар → профиль пользователя.
Реализация: transform: scale(0.9) → transform: scale(1) с transform-origin в точке нажатия.
Shared Element Transition (morph)
Элемент на одном экране плавно трансформируется в элемент на другом. Карточка в списке становится хедером детального экрана.
Когда: список → деталь, thumbnail → полноэкранное фото.
В CSS: View Transitions API.
В React: layoutId в Framer Motion.
// Framer Motion — один layoutId на обоих экранах
// На экране списка:
<motion.div layoutId={`card-${item.id}`}>
// На экране детали:
<motion.div layoutId={`card-${selectedId}`}>
Reveal
Новый экран появляется из-под старого. Для sidebar-навигации: контент «сдвигается» открывая панель.
Cross-fade с blur
Текущий экран размывается и исчезает, новый появляется чётким. Создаёт ощущение «другого уровня» — более глубокого погружения.
Правило консистентности
Один продукт — одна система переходов. Mixing slide и scale в зависимости от настроения ломает ментальную модель.
Минимальная система:
- Drill-down (вглубь) → slide left
- Назад → slide right
- Modal / overlay → fade или slide up
- Смена разделов → fade
Запиши это в DESIGN.md и дай AI в контекст — иначе каждый экран будет с другим переходом.
View Transitions API — переходы без JavaScript
Для многостраничных сайтов (MPA) — автоматические переходы без роутер-библиотек:
/* Включить для всего сайта */
@view-transition {
navigation: auto;
}
/* Кастомизация */
::view-transition-old(root) {
animation: slide-out 0.3s ease-in;
}
::view-transition-new(root) {
animation: slide-in 0.3s ease-out;
}
@keyframes slide-out { to { transform: translateX(-100%); } }
@keyframes slide-in { from { transform: translateX(100%); } }
Поддержка: Chrome, Edge, Safari 18+. Для Firefox — пока полифилл.
Промпт для Codex / Claude Code
Добавь систему переходов между страницами в Next.js проект.
Правила:
- Навигация вглубь (список → деталь): slide left
- Навигация назад: slide right
- Модальные окна: fade + scale(0.95→1)
- Смена разделов через nav: fade
Реализация: View Transitions API для нативных переходов.
Fallback для неподдерживаемых браузеров — мгновенный переход без анимации.
Duration: 300ms, easing: cubic-bezier(0.16, 1, 0.3, 1).
prefers-reduced-motion: мгновенные переходы.
Не использовать Framer Motion только для переходов — View Transitions API достаточно.
Почему переходы между экранами — самое сложное в motion
Hover-анимация затрагивает один элемент. Переход между экранами затрагивает всё — старый экран исчезает, новый появляется, иногда отдельные элементы «переезжают» между ними. Ошибка здесь видна сразу и на весь экран.
Три самые частые ошибки:
Слишком длинный transition. 600мс для перехода между страницами — это почти секунда ожидания при каждом клике. На сайте с 20 страницами пользователь потратит минуты только на анимации.
Неправильное направление. Пользователь нажимает кнопку «назад» — экран уходит вправо (правильно). Нажимает ссылку вглубь — экран тоже уходит вправо (неправильно, должен влево). Ментальная модель нарушается.
Transition прерывается. Пользователь нажимает быстро, следующий переход начинается до завершения предыдущего. Если не обработано правильно — визуальный мусор.
Shared Element Transition: когда делать, когда не делать
Shared Element Transition (карточка превращается в детальный экран) — один из самых впечатляющих паттернов. Но у него есть цена.
Работает хорошо когда: один элемент «трансформируется» в другой, пользователь видит визуальную связь, экраны физически близки по контенту (список товаров → страница товара).
Не работает когда: элементов много и непонятно какой «трансформируется», контент экранов сильно отличается, переход работает медленно на слабых устройствах.
Техническая реализация через View Transitions API или Framer Motion layoutId — относительно простая. Дизайнерская реализация — сложная: нужно точно спроектировать начальное и конечное состояние обоих экранов чтобы переход выглядел естественно.
Мобайл vs десктоп: разные паттерны
На мобайле пользователь взаимодействует пальцем и ожидает физическое ощущение. Slide transition который следует за свайпом — естественно. Fade который появляется независимо от жеста — диссонанс.
На десктопе мышь не оставляет «следа движения». Fade или subtle slide — нормально. Агрессивный slide как на мобайле — обычно избыточно.
При адаптивном дизайне: определи разные motion-системы для мобайла и десктопа. @media (pointer: coarse) для устройств с тач — более физические переходы. @media (pointer: fine) для мыши — более сдержанные.
Когда переход вообще не нужен
Не каждая навигация требует анимированного перехода. Три случая когда переход лучше убрать:
Быстрые повторяющиеся навигации. Пользователь листает список нажимая «назад→вперёд» быстро. Каждый переход 300мс — за 10 нажатий он потратил 3 секунды только на анимации. Решение: после третьего быстрого нажатия за секунду — отключать переход.
Тяжёлый контент. Страница с большим количеством изображений и данных — переход начинается до того как новая страница загружена. Пользователь видит пустой экран с анимацией. Решение: показывать переход только после того как базовый контент загружен.
Действия в той же области. Фильтр изменился — карточки перестроились. Нужен ли переход на уровне страницы? Скорее всего нет — достаточно анимировать сами карточки.
☐ Определена система: какой паттерн для какой навигации
☐ Drill-down: slide направление совпадает с Back
☐ Нет mixing разных паттернов без логики
☐ Система записана в DESIGN.md
☐ View Transitions API или Framer Motion AnimatePresence реализованы
☐ prefers-reduced-motion: мгновенные переходы
☐ Duration 250–350мс (не 500мс+ для базовых переходов)