Отменяемые View Transitions в SPA
Отменяемые View Transitions в SPA document.startViewTransition() прекрасен в демках и разваливается, когда пользователь кликает по ссылкам быстрее, чем заканчивается анимация. Наложение переходов. Браузер сам скипнет предыдущий переход, начиная новый, но ваш updateCallback уже мог стартовать fetch и записать состояние роутера - получаете гонку, в которой побеждает не последний клик. Лечится явным AbortController на загрузку данных и skipTransition() на активный переход. Scroll restoration. Браузер делает два снапшота: до и после updateCallback. Если восстанавливать скролл после finished, новый снапшот уже зафиксирован - будет видимый прыжок. Позицию нужно восстанавливать внутри updateCallback, а scroll-behavior: smooth на время перехода отключать, иначе кадр снапшота ловится в середине анимации скролла. Утечки. view-transition-name должен быть уникален в документе. Имя, оставленное на элементе, который вы «закрыли» (display: none, но узел в DOM), уронит следующий переход ошибкой о дубликате. Плюс до finished живут псевдо-оверлеи ::view-transition-old/new: если держите на них блокировку скролла (overflow: hidden, position: fixed), снимайте в finally, иначе после отмены страница останется «залипшей». Каркас: let activeVT: ViewTransition | null = null; let ac: AbortController | null = null; async function navigate(to: string) { activeVT?.skipTransition(); // гасим висящую анимацию ac?.abort(); // и незавершённый запрос ac = new AbortController(); const data = await loadPage(to, { signal: ac.signal }) .catch(() => null); if (!data) return; // aborted const vt = document.startViewTransition(async () => { history.replaceState({ y: scrollY }, '', location.pathname); render(data); restoreScroll(to); // скролл - ДО снапшота нового состояния }); activeVT = vt; document.documentElement.classList.add('vt-lock'); vt.ready.catch(() => {}); // скип реджектит ready, без unhandled try { await vt.finished; } catch { // переход отменён - это норма } finally { document.documentElement.classList.remove('vt-lock'); if (activeVT === vt) activeVT = null; } } Ещё пара мелочей: восстанавливайте фокус (document.activeElement) после перехода, иначе скринридер останется на удалённом узле; в updateCallback не запускайте лишних await - браузер ждёт промис, а старый снапшот всё это время висит мёртвым кадром. Источники: - MDN: View Transition API, ViewTransition.skipTransition() - W3C CSS View Trans