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:

  1. Gera um identificador alfanumérico hash exclusivo para o componente (ex: data-v-7ba5bd90).
  2. Injeta esse atributo HTML em todos os nós gerados pelo template do componente.
  3. Reescreve os seletores CSS para incluir o atributo (ex: .card torna-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:

  1. v-enter-from: Ponto inicial de entrada (removido no frame seguinte).
  2. v-enter-active: Presente durante toda a fase de entrada. Define transition: all 0.3s ease.
  3. v-enter-to: Ponto de chegada da entrada.
  4. v-leave-from: Ponto inicial da saída.
  5. v-leave-active: Presente durante toda a saída.
  6. 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); }