В веб-разработке есть класс багов, которые сложно объяснить, но легко увидеть:
- transition не срабатывает
- размеры элементов считаются неправильно
- layout “скачет” при первом появлении
- логика, завязанная на DOM, ведёт себя нестабильно
Во многих случаях причина одна —
👉 код выполняется раньше, чем браузер завершил первый рендер
В этой статье разберём простой паттерн, который решает эту проблему — DeferRender.
🚧 Проблема: неправильный момент выполнения
Когда Vue монтирует компонент:
- создаётся DOM
- применяются стили
- браузер делает layout
- происходит paint
Но твой код может выполниться между этими этапами.
Например:
- transition включается до первого paint → не срабатывает
scrollHeightчитается до layout → возвращает 0- вычисления делаются до того, как элемент реально появился на экране
👉 В итоге поведение становится непредсказуемым
💡 Решение: отложить рендер
Вот минимальная реализация:
<script setup lang="ts">
import { ref, onMounted } from 'vue'const ready = ref(false)onMounted(() => {
requestAnimationFrame(() => {
requestAnimationFrame(() => {
ready.value = true
})
})
})
</script>
<template>
<template v-if="ready">
<slot />
</template>
<template v-else>
<slot name="placeholder" />
</template>
</template>
🔍 Как это работает
Ключевая часть:
requestAnimationFrame(() => {
requestAnimationFrame(() => {
ready.value = true
})
})
Что происходит:
- первый
requestAnimationFrame— ждём следующий кадр - второй — ещё один кадр
👉 За это время браузер успевает:
- применить стили
- посчитать layout
- выполнить первый paint
И только после этого отображается основной контент.
🎯 Что это даёт
1. Корректный старт для transition
Проблема не в самих анимациях, а в том, когда они запускаются.
Без defer:
элемент появился → сразу финальное состояние → transition не запускается
С defer:
элемент уже отрисован → состояние меняется → transition может выполниться
2. Корректные размеры элементов
Если ты работаешь с:
getBoundingClientRectscrollHeightuseElementSize
👉 значения будут корректными, потому что layout уже посчитан
3. Предсказуемое поведение UI
Особенно полезно для:
- dropdown / accordion
- sticky-блоков
- компонентов с вычисляемыми размерами
- сложных layout-комбинаций
4. Контроль placeholder → контент
<DeferRender>
<template #placeholder>
<Skeleton />
</template> <RealContent />
</DeferRender>
👉 здесь важно не “плавное появление”, а контролируемый момент переключения
⚠️ Почему не nextTick
nextTick ждёт обновление Vue, но:
- не гарантирует завершение layout
- не гарантирует paint
👉 requestAnimationFrame работает на уровне браузера, а не фреймворка
🧠 Когда использовать
Используй DeferRender, если:
- transition ведёт себя нестабильно
- размеры элементов вычисляются неправильно
- UI зависит от layout сразу после mount
- есть визуальные глитчи при первом рендере
Не используй, если:
- компонент простой и статичный
- нет зависимости от времени рендера
🧩 Итог
DeferRender — это:
⏳ способ сдвинуть рендер на момент, когда браузер уже готов
Он не добавляет анимации и не меняет внешний вид напрямую,
но устраняет проблемы, вызванные неправильным таймингом.
И это один из тех случаев, когда
пара вызовов requestAnimationFrame даёт больше пользы, чем сложная логика.


Добавить комментарий