Pular para o conteúdo
charmotion

Splash screen animada no Expo e React Native

Por equipe Charmotion · Atualizado em

Ilustração de capa do guia: Splash screen animada no Expo e no React Native, com personagens de demonstração do Charmotion

No Expo, mantenha a splash screen nativa estática, chame SplashScreen.preventAutoHideAsync() na inicialização, renderize a sua intro com a mesma cor de fundo e chame hideAsync() quando a intro estiver pronta para ser desenhada. A intro pode ser um arquivo Rive, um vídeo ou Lottie. O Rive precisa de um development build em vez do Expo Go, e o Expo recomenda conferir a splash em um build de release.

Principais pontos

  • A splash do Expo é configurada no momento do build (cor, imagem, largura, modo escuro) e só tem um fade embutido.
  • Chame preventAutoHideAsync() no escopo global e depois oculte a splash quando o primeiro frame da sua intro estiver pronto.
  • Use uma única cor hex no backgroundColor da splash, na view raiz e na tela de intro.
  • Rive e outras bibliotecas nativas de animação precisam de um development build; o Expo Go mostra o seu ícone no lugar da splash.
  • Um arquivo de abertura do Charmotion não dispara evento de fim: espere 2,6 s e siga em frente.

Já tem um personagem ou logo? Veja seu personagem em telas reais de app: telas de splash, onboarding, carregamento e estado vazio. É grátis, roda no navegador e não envia nada.

Dá para animar a própria splash screen do Expo?

Não. A splash nativa é definida pelo config plugin expo-splash-screen com backgroundColor, image, imageWidth, resizeMode e uma variante dark, e essas configurações são compiladas no binário (Expo). Por baixo, ela é a launch screen do iOS e a splash screen do Android, com os mesmos limites.

{
  "expo": {
    "backgroundColor": "#0E1116",
    "plugins": [
      ["expo-splash-screen", {
        "backgroundColor": "#0E1116",
        "image": "./assets/icon-rest.png",
        "imageWidth": 160,
        "dark": { "backgroundColor": "#0E1116", "image": "./assets/icon-rest.png" }
      }]
    ]
  }
}

O único movimento que o Expo oferece é um fade de saída: SplashScreen.setOptions({ duration, fade }), em que fade funciona só no iOS e a duração padrão é 400 ms. Qualquer coisa além disso, como o seu ícone surgindo na tela, precisa ser um componente React renderizado logo depois da splash, que é o que fazem os próprios exemplos de splash animada do Expo (Expo Blog).

Como funcionam preventAutoHideAsync e hideAsync?

preventAutoHideAsync() mantém a splash nativa visível até você chamar hideAsync() (ou hide()). A documentação do Expo recomenda chamá-la no escopo global, sem await, para que rode antes da primeira renderização. Depois, esconda a splash no momento em que a intro consegue desenhar o primeiro frame, nem antes, nem depois de um atraso arbitrário.

// app/_layout.tsx (Expo Router)
import * as SplashScreen from "expo-splash-screen";
import { useState } from "react";
import { Stack } from "expo-router";
import { LaunchIntro } from "../components/LaunchIntro";

SplashScreen.preventAutoHideAsync();
SplashScreen.setOptions({ duration: 200, fade: true });

export default function RootLayout() {
  const [showApp, setShowApp] = useState(false);
  if (!showApp) return <LaunchIntro onDone={() => setShowApp(true)} />;
  return <Stack />;
}

Aqui, showApp é um state React seu, não um evento do arquivo de animação. É o componente de intro que decide quando esconder a splash e quando chamar onDone.

Como evitar o flash branco entre a splash e a intro?

Um flash branco significa que algo de outra cor foi desenhado entre a splash nativa e a sua intro. Elimine todas as lacunas:

  • Uma única cor hex em tudo. Use o mesmo valor no backgroundColor do plugin da splash, no backgroundColor raiz do app e no container da intro. A configuração do app no Expo descreve a cor raiz como o fundo "atrás de qualquer uma das suas views React" e observa que ela exige expo-system-ui no iOS (Expo).
  • Modo escuro também. Defina as opções dark da splash e escolha o fundo da intro a partir do mesmo esquema de cores.
  • Esconda no momento certo. Chame hideAsync() depois que o arquivo de animação tiver carregado, não na montagem. Se esconder antes, você vê por um instante uma árvore React vazia.
  • Mesma posição. Renderize o ícone da intro centralizado e no imageWidth da splash, para que a transição não dê salto.

O exemplo de splash em vídeo do Evan Bacon aplica a mesma regra com preto: fundos pretos no app, na splash e na splash do iOS, a splash nativa mantida até o vídeo carregar, e então o vídeo toca e a splash some (GitHub).

A intro deve ser em Rive, vídeo ou Lottie?

OpçãoIdeal paraPontos de atenção
Rive (@rive-app/react-native)Um arquivo vetorial pequeno cuja intro passa para um mascote interativo (loading, success, error)Módulo nativo: exige development build; o guia de Expo do Rive indica SDK 53+ e iOS 15.1+
Vídeo (expo-video)Intros detalhadas, 3D ou raster; revelações em tela cheiaArquivo mais pesado; sem interação. Mais simples com um clipe opaco cujo fundo tenha a mesma cor da splash
Lottie (lottie-react-native)Uma animação vetorial linear que um designer já exportouToca do início ao fim; mudar para outros estados exige trocar arquivos ou segmentos

Se o mesmo personagem deve continuar vivo no app depois da abertura, o Rive evita um segundo asset. Se a intro é uma cena cinematográfica única, vídeo exige menos código. Em qualquer caso, o padrão de transição não muda. Veja Lottie vs Rive vs vídeo para splash screen para o comparativo completo e Rive no Expo para configurar o development build.

Como tocar uma intro de abertura em Rive no Expo?

Este componente mantém a splash nativa até o arquivo carregar, toca a state machine de abertura, respeita o Reduzir Movimento e chama onDone após a duração da intro. Ele usa o runtime atual do Rive para React Native (Rive) e o AccessibilityInfo do React Native (React Native).

import { useEffect, useState } from "react";
import { AccessibilityInfo, Image, View } from "react-native";
import * as SplashScreen from "expo-splash-screen";
import { RiveView, useRiveFile } from "@rive-app/react-native";

const BG = "#0E1116";      // same hex as the splash backgroundColor
const INTRO_MS = 2600;     // LaunchStateMachine fires no end event

export function LaunchIntro({ onDone }: { onDone: () => void }) {
  const { riveFile } = useRiveFile(require("../assets/launch.riv"));
  const [reduce, setReduce] = useState<boolean | null>(null);

  useEffect(() => {
    AccessibilityInfo.isReduceMotionEnabled().then(setReduce);
    // Safety net: never trap the user if the file fails to load.
    const t = setTimeout(() => { SplashScreen.hideAsync(); onDone(); }, 4000);
    return () => clearTimeout(t);
  }, []);

  useEffect(() => {
    if (reduce === null) return;
    if (reduce) { SplashScreen.hideAsync(); onDone(); return; }
    if (!riveFile) return;
    SplashScreen.hideAsync();
    const t = setTimeout(onDone, INTRO_MS);
    return () => clearTimeout(t);
  }, [reduce, riveFile]);

  return (
    <View style={{ flex: 1, backgroundColor: BG, alignItems: "center", justifyContent: "center" }}>
      {riveFile && !reduce ? (
        <RiveView file={riveFile} stateMachineName="LaunchStateMachine"
          autoPlay={true} style={{ width: 160, height: 160 }} />
      ) : (
        <Image source={require("../assets/icon-rest.png")} style={{ width: 160, height: 160 }} />
      )}
    </View>
  );
}

Registre riv como extensão de asset no metro.config.js para que o require funcione, como descrito em Rive no Expo. Para uma intro em vídeo, troque a view do Rive por um player expo-video (Expo) e esconda a splash quando o player estiver pronto para tocar.

Um conjunto do Charmotion inclui este arquivo de abertura: LaunchStateMachine toca uma vez uma intro de 2,6 segundos do seu ícone ou mascote e termina na pose de repouso. O Charmotion exporta Rive e vídeo, não Lottie.

Por que minha splash não fica certa no Expo Go ou no dev build?

Porque nenhum dos dois é o resultado real. A documentação do Expo diz que o Expo Go mostra o ícone do app no lugar da splash screen e que as development builds também não refletem totalmente as configurações do config plugin, por isso recomenda testar a splash em uma release build.

  • Expo Go: sem splash personalizada e sem Rive, porque o pacote do Rive contém código nativo (Expo).
  • Development build: boa para construir o componente de intro; não é uma prévia fiel da splash.
  • Release build ou preview build: o único lugar para avaliar a correspondência de cor e o tempo da transição. Teste uma inicialização a frio, modo claro e escuro, e o Reduzir Movimento ativado.

Depois de mudar as configurações da splash no app.json, recompile o app nativo (npx expo prebuild --clean para builds locais); a splash fica embutida no binário e não é atualizada com um reload de JavaScript.

Como adicionar uma splash screen animada no Expo

  1. Configure uma splash estática. Defina backgroundColor, image, imageWidth e dark no plugin expo-splash-screen, e o mesmo backgroundColor raiz.
  2. Mantenha a splash. Chame SplashScreen.preventAutoHideAsync() no escopo global do seu layout raiz.
  3. Renderize a intro. Mostre um componente de intro sobre o mesmo fundo, com o ícone no tamanho da splash, usando Rive, expo-video ou Lottie.
  4. Esconda e espere. Chame hideAsync() assim que a intro puder ser desenhada e continue depois da duração dela (2,6 s para um arquivo de abertura do Charmotion), com um timeout de segurança.
  5. Teste uma release build. Verifique a inicialização a frio, o modo escuro e o Reduzir Movimento em uma release build ou preview build, não no Expo Go.

Perguntas frequentes

Posso usar um GIF como imagem da splash no Expo?

A imagem da splash é exibida pela launch screen nativa, que mostra uma imagem estática. Use um PNG estático na splash e toque qualquer animação, seja GIF, vídeo ou arquivo Rive, na primeira tela React com a mesma cor de fundo.

O preventAutoHideAsync funciona no Expo Go?

A função existe, mas o Expo Go mostra o ícone do app no lugar da splash configurada, então não dá para avaliar a transição ali. Use uma release build para ver como a splash e a intro ficam juntas.

Como saber quando uma intro de abertura em Rive terminou no React Native?

Se o arquivo dispara um evento próprio, escute esse evento. A LaunchStateMachine do Charmotion não dispara nenhum: ela toca a intro de 2,6 segundos uma vez e termina na pose de repouso, então inicie um timer de 2.600 ms quando o arquivo carregar e continue quando ele acabar.

Fontes

Guias relacionados