Capítulo 11: Scoped CSS, Tailwind CSS e Transições Nativas ()
Capítulo 11: Scoped CSS, Tailwind CSS e Transições Nativas ()
Especialização em Front-End com Vue • Vue 3 & Nuxt 3 • Composition API, Pinia, Vue Router, SSR e TypeScript
🗺️ Mapa Conceitual do Ciclo de Transições e Estilização
flowchart TD
A["Estado Reativo Alterna Visibilidade (v-if / v-show)"] --> B["Componente Nativo <Transition>"]
B --> C["1. *-enter-from (Opacidade 0 / Deslocamento Inicial)"]
C --> D["2. *-enter-active (Curva de Transição CSS / Duração)"]
D --> E["3. *-enter-to (Estado Visual Final Estável)"]
E -->|Remoção do Elemento| F["4. *-leave-from (Estado Inicial de Saída)"]
F --> G["5. *-leave-active (Transição de Desaparecimento)"]
G --> H["6. *-leave-to (Descarte e Remoção Física do DOM)"]
subgraph ESTILIZACAO["Arquitetura de Estilos Vue 3"]
I["<style scoped> -> data-v-[hash]"]
J[":deep() -> Seleciona Nós Filhos/Internos"]
K["Tailwind CSS -> Utilitários Atômicos"]
end
B --- ESTILIZACAO
style A fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
style B fill:#fff3e0,stroke:#ff9800,stroke-width:2px
style E fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
style H fill:#fce4ec,stroke:#e91e63,stroke-width:2px
🏛️ 1. Isolamento de CSS e Animações Declarativas no Vue 3
O ecossistema do Vue oferece um equilíbrio elegante entre estilização atômica (com Tailwind CSS), encapsulamento de escopo (<style scoped>) e orquestração de animações através dos componentes embutidos <Transition> e <TransitionGroup>.
1.1 O Mecanismo Interno do <style scoped>
Quando o compilador @vue/compiler-sfc encontra uma tag <style scoped>, ele executa uma transformação via PostCSS:
- Gera um identificador alfanumérico hash exclusivo para o componente (ex:
data-v-7ba5bd90). - Injeta esse atributo HTML em todos os nós gerados pelo template do componente.
- Reescreve os seletores CSS para incluir o atributo (ex:
.cardtorna-se.card[data-v-7ba5bd90]).
1.2 O Seletor Profundo :deep()
Por padrão, estilos scoped não vazam para dentro de componentes filhos ou nós gerados dinamicamente via v-html ou bibliotecas de UI externas (como PrimeVue ou Element Plus).
Para estilizar nós filhos a partir do pai sem quebrar o isolamento do escopo, utiliza-se a pseudo-classe :deep():
/* Transforma-se em: [data-v-hash] .child-content */
:deep(.child-content) {
background-color: #f1f5f9;
}
1.3 As 6 Classes de Transição do <Transition>
Ao envelopar qualquer elemento com <Transition name="fade">, o Vue gerencia automaticamente 6 classes CSS nos momentos precisos do ciclo de inserção e remoção:
v-enter-from: Ponto inicial de entrada (removido no frame seguinte).v-enter-active: Presente durante toda a fase de entrada. Definetransition: all 0.3s ease.v-enter-to: Ponto de chegada da entrada.v-leave-from: Ponto inicial da saída.v-leave-active: Presente durante toda a saída.v-leave-to: Ponto de conclusão da saída, logo antes da remoção física do nó do DOM.
1.4 Transições de Listas com <TransitionGroup> e Técnica FLIP
Ao animar listas renderizadas por v-for, o <TransitionGroup> adiciona a classe especial v-move. O Vue utiliza o algoritmo FLIP (First, Last, Invert, Play) para calcular as coordenadas geométricas de cada item e animar sua movimentação suave quando itens vizinhos são inseridos ou deletados.
⚖️ 2. Tabela Comparativa de Estratégias de Animação
| Abordagem | Controle de Performance | Complexidade de Código | Caso de Uso Primário |
|---|---|---|---|
<Transition> Nativo |
Máxima (Aceleração por GPU via CSS transforms). | Mínima (Declarativo no template). | Modais, tooltips, abas, banners de alerta. |
<TransitionGroup> FLIP |
Alta (Interpolação de coordenadas DOM). | Baixa (Exige classe .v-move). |
Listas dinâmicas, kanbans, tabelas ordenáveis. |
JavaScript Hooks (@enter) |
Média (Depende da biblioteca JS: GSAP/Motion). | Moderada (Imperativo no script). | Animações orquestradas complexas com timeline. |
⚠️ 3. Armadilhas Comuns em Produção (Gotchas & Anti-Patterns)
Anti-Pattern 1: Pulos Bruscos em Animações de Saída do <TransitionGroup>
Erro Comum: Itens restantes da lista dão um salto brusco instantâneo quando um item é deletado, ao invés de deslizarem suavemente.
Abordagem Correta: Adicione position: absolute; width: 100% na classe *-leave-active. Isso remove o elemento saindo do fluxo do layout, permitindo que os outros nós se reposicionem via *-move imediatamente.
Anti-Pattern 2: Esquecer :key Obrigatório em Filhos de <TransitionGroup>
Ao contrário de <Transition> simples, <TransitionGroup> exige que todo elemento filho possua uma chave :key exclusiva.
💻 4. Código Conceitual Arquitetural Comentado
<!-- src/components/AnimatedAlertStack.vue -->
<script setup lang="ts">
import { ref } from 'vue';
interface AlertMessage {
id: number;
text: string;
}
const alerts = ref<AlertMessage[]>([
{ id: 1, text: 'Instância de banco provisionada com sucesso.' },
{ id: 2, text: 'Certificado SSL renovado automaticamente.' }
]);
let counter = 3;
function pushAlert(): void {
alerts.value.unshift({ id: counter++, text: `Evento de telemetria #${counter}` });
}
function removeAlert(id: number): void {
alerts.value = alerts.value.filter((a) => a.id !== id);
}
</script>
<template>
<div class="max-w-md mx-auto p-4 space-y-4">
<div class="flex justify-between items-center">
<h3 class="font-bold text-slate-800 text-sm">Fila de Eventos</h3>
<button
@click="pushAlert"
class="px-3 py-1.5 bg-indigo-600 hover:bg-indigo-700 text-white rounded-lg text-xs font-semibold"
>
+ Novo Evento
</button>
</div>
<!-- Orquestração de Lista Animada com Suporte a FLIP -->
<TransitionGroup name="stack" tag="div" class="space-y-2 relative">
<div
v-for="item in alerts"
:key="item.id"
class="alert-box p-3 bg-white border border-slate-200 rounded-xl shadow-sm flex justify-between items-center text-xs"
>
<span class="text-slate-700">{{ item.text }}</span>
<button @click="removeAlert(item.id)" class="text-slate-400 hover:text-red-500 font-bold ml-2">✕</button>
</div>
</TransitionGroup>
</div>
</template>
<style scoped>
/* Transições de Entrada e Saída */
.stack-enter-from {
opacity: 0;
transform: translateY(-16px) scale(0.95);
}
.stack-enter-to, .stack-leave-from {
opacity: 1;
transform: translateY(0) scale(1);
}
.stack-leave-to {
opacity: 0;
transform: translateX(24px);
}
.stack-leave-active {
position: absolute;
width: 100%;
}
/* Transição suave de reposicionamento dos nós vizinhos */
.stack-move, .stack-enter-active, .stack-leave-active {
transition: all 0.35s cubic-bezier(0.4, 0, 0.2, 1);
}
</style>
🛠️ 5. Cheat-Sheet de Classes de Transição do Vue
/* Estrutura Canônica para <Transition name="minha-anim"> */
.minha-anim-enter-from { opacity: 0; transform: scale(0.95); }
.minha-anim-enter-active { transition: all 0.2s ease-out; }
.minha-anim-enter-to { opacity: 1; transform: scale(1); }
.minha-anim-leave-from { opacity: 1; }
.minha-anim-leave-active { transition: all 0.15s ease-in; }
.minha-anim-leave-to { opacity: 0; transform: scale(0.95); }