Animação de Quadro Branco
O visual do VideoScribe / Doodly: uma mão desenha ilustrações e textos à mão em um quadro, traço por traço, enquanto uma narração explica. Construa a partir de caminhos SVG animados que se desenham, mais uma mão marcadora que rastreia a ponta do desenho, e sequencie os elementos para que o quadro seja preenchido na ordem da narração.
Quando usar
- Um explicador "scribe" / doodle onde uma mão desenha arte e texto em um quadro branco.
- Animação de um esboço ou logotipo sendo desenhado ao vivo, linha por linha.
- Escrita à mão que se escreve sozinha, sincronizada com uma narração.
- Transformar uma ilustração estática em uma revelação guiada, traço por traço.
Mecanismo central: desenho de traço SVG
Um caminho "se desenha" animando stroke-dashoffset do seu comprimento total até 0 — o espaço do traço encolhe, expondo o traço do início ao fim.
const path = document.querySelector("#stroke");
const len = path.getTotalLength();
path.style.strokeDasharray = len; // um traço tão longo quanto o caminho inteiro
path.style.strokeDashoffset = len; // empurrado completamente para fora = invisível
path.getBoundingClientRect(); // força reflow antes da transição
path.style.transition = "stroke-dashoffset 1.2s linear";
path.style.strokeDashoffset = "0"; // desenha do início ao fim
Use pathLength="1" no <path> para normalizar: então stroke-dasharray:1; stroke-dashoffset:1 desenha qualquer caminho independentemente do comprimento real, e um único valor suavizado (0→1) é o progresso do desenho. linear (ou uma suavização muito sutil) parece uma mão firme; evite ease-in-out em traços longos — faz a mão dar trancos.
A mão marcadora segue a ponta
A ilusão depende de a mão estar exatamente no ponto sendo desenhado. Amostre o caminho no progresso atual do desenho e coloque a imagem da mão ali a cada quadro; a ponta da caneta do asset da mão deve se alinhar ao ponto do caminho (desloque a imagem para que sua ponta, não seu canto, caia sobre o ponto).
const hand = document.querySelector("#hand"); // <img>/<g>, ponta no canto superior esquerdo "nib"
const NIB = { x: 6, y: 4 }; // deslocamento da ponta dentro da arte da mão
function drawOn(path, hand, dur = 1200) {
const len = path.getTotalLength(), t0 = performance.now();
path.style.strokeDasharray = len;
(function frame(now) {
const k = Math.min(1, (now - t0) / dur); // progresso 0→1
path.style.strokeDashoffset = len * (1 - k); // expõe início→ponta
const p = path.getPointAtLength(len * k); // ponta atual nas coordenadas SVG
hand.setAttribute("transform", `translate(${p.x - NIB.x} ${p.y - NIB.y})`);
if (k < 1) requestAnimationFrame(frame);
})(t0);
}
getPointAtLength(len * k) é o carro-chefe: retorna a coordenada no caminho no mesmo progresso que dirige strokeDashoffset, então a ponta permanece grudada ao traço em crescimento. Levante a mão (desvanecer, pular para o próximo início) entre traços separados para que ela não deslize pelo quadro em branco.
Ordem de revelação — o quadro é preenchido
Sequencie os elementos da mesma forma que uma pessoa desenharia a cena, na ordem da narração: cada ilustração e cada bloco de escrita à mão desenha-se, segura, então o próximo começa. Estaque os traços dentro de um desenho; segure uma batida após cada elemento ser concluído antes de mover a mão. Um desenho = uma batida da narração. Não desenhe duas coisas ao mesmo tempo — o olho (e a única mão) só pode seguir uma ponta.
| Passo | Mecanismo |
|---|---|
| Desenhar um traço | stroke-dashoffset len→0, mão em getPointAtLength(len*k) |
| Desenho com múltiplos traços | estaque os traços; mão salta para o início de cada novo traço |
| Escrita à mão | traços por letra/palavra desenhados em ordem de leitura, mão seguindo |
| Segurar | pausa a mão fora do quadro após o elemento ser concluído (0,4–1,0s) |
| Próximo elemento | mover/desvanecer mão para o próximo início; começar seu desenho |
| Apagar / limpar | limpar ou desenhar reversamente para limpar o quadro para a próxima seção |
Convertendo arte em caminhos desenháveis
O desenho precisa de caminhos abertos de traço único em ordem de desenho — não formas compostas preenchidas.
- Crie ou trace arte linear como traços abertos (Illustrator/Inkscape: contorno, não preenchimento). Uma mancha preenchida não tem "traço" para animar; converta preenchimentos em um traço de linha central ou desenhe um caminho de contorno e preencha-o após o contorno ser concluído.
- A ordem importa: os elementos
<path>devem aparecer no SVG na ordem em que uma mão os desenharia — anime-os na ordem do documento. - Linhas longas e contínuas parecem mais naturais; quebre apenas onde uma caneta real levantaria.
- Para regiões de cor sólida: desenhe o traço do contorno primeiro, depois desvaneça/cresça o preenchimento atrás dele (
opacityou uma revelação com clip) para que pareça "colorido depois". - Escrita à mão: use uma fonte de traço/escrita à mão, converta glifos em caminhos, depois desenhe cada caminho do glifo na ordem do traço. Ou simule: mascare a palavra e varra uma revelação da esquerda para a direita com a mão acompanhando a borda da máscara.
Asset da mão marcadora
Um PNG/SVG de uma mão segurando um marcador, com a ponta em um ponto conhecido. Meça o deslocamento da ponta uma vez (NIB acima) e reutilize-o. Mantenha um único asset de mão para toda a peça para consistência. Espelhe-o (scaleX(-1)) apenas se precisar de uma mão esquerda. Adicione uma sombra sutil sob a mão para profundidade; remova-a durante as pausas quando a mão está "fora".
Transições de apagar / limpar
Entre seções, limpe o quadro para que não fique bagunçado:
- Limpar (wipe): anime um
clip-path(ou um retângulo branco) sobre o grupo preenchido para limpá-lo, mão opcionalmente "apagando". - Desenho reverso: execute
strokeDashoffset0→len para desfazer o desenho, mão seguindo para trás. - Desvanecer + redesenhar: faça cross-fade do grupo antigo e inicie o desenho do próximo grupo.
Mantenha uma gramática clara (sempre limpar da esquerda para a direita, por exemplo) para que as transições pareçam intencionais.
Ritmo para a narração
A duração do desenho deve acompanhar a fala, não um temporizador fixo. Estime a partir da narração a ~2,3 palavras/segundo; defina o tempo total de desenho de cada desenho aproximadamente igual ao comprimento da linha que ele ilustra, mais uma pausa curta. Um rótulo que a narração nomeia deve terminar de desenhar exatamente quando a palavra é dita. Desacelere a mão para linhas de ênfase; acelere traços conectivos menores. Nunca deixe a mão parada em um quadro concluído enquanto a narração continua — mova-a para fora ou comece o próximo elemento.
Lista de verificação de saída
- Cada traço é um único caminho aberto em ordem de desenho; nada aparece totalmente formado.
- A ponta da mão está exatamente na ponta do desenho durante cada traço (sem flutuação, sem atraso).
- A mão levanta-se limpa entre traços/elementos separados; sem deslizar pelo quadro em branco.
- Elementos desenham na ordem da narração, um de cada vez; cada um segura antes do próximo.
- Escrita à mão desenha na ordem de leitura; preenchimentos aparecem após seu contorno.
- A gramática de apagar/limpar é consistente; o quadro nunca fica excessivamente bagunçado.
- O tempo do desenho segue a linha da narração que ilustra.
prefers-reduced-motion: mostre o quadro concluído (todos os traços com offset 0, mão oculta) sem a animação de desenho.
Entregar e verificar (HTML autônomo)
Ajudante empacotado (
scripts/):scripts/seek-shot.sh anim.html 0 1.5 3congela o harness?t=Ne captura cada momento;scripts/contact-sheet.sh sheet.png frame-*.pngorganiza em mosaico para revisão rápida. Vejascripts/README.md.
Para uma cena de quadro branco autocontida (ilustração desenhada, escrita à mão, uma batida curta de scribe), o entregável é um arquivo HTML que abre diretamente no navegador — sem etapa de build, sem pipeline de renderização. Um único arquivo é o nível certo para animação web; não recorra a um bundler quando um arquivo faz o trabalho. (Para um vídeo scribe narrado completo com narração incorporada, construa-o como uma composição Remotion e verifique com remotion still — veja explainer-video.)
Contrato de saída:
- Um arquivo
.html: SVG inline (caminhos em ordem de desenho,pathLength="1"ou comprimento medido), o asset da mão inline ou como data-URI, e o driver de desenho em um único<script>inline. - Todo desenho em uma timeline mestre (GSAP
tl, ou um relógio rAF) para que toda a sequência tenha um único playhead que pode ser buscado —strokeDashoffsetE a posição da mão ambos dirigidos pelo mesmo valor de progresso. - Inclua o harness de busca abaixo para que qualquer momento possa ser congelado para capturas de tela. Congelar em
tmostra exatamente quanto do caminho está desenhado — a verificação depende disso.
Harness de busca — congele um momento exato para capturas de tela. ?t=N busca a timeline mestre para N segundos e pausa, para que uma captura de tela caia em um quadro fixo e determinístico no meio do desenho.
<script>
// ... construa sua timeline mestre como `tl` (chamadas drawOn colocadas nela) ...
const t = new URLSearchParams(location.search).get("t");
if (t !== null) { tl.pause(); tl.seek(parseFloat(t)); } // congelado em t segundos
// sem ?t → reproduz normalmente
window.__ready = true; // sinal de pronto para espera headless
console.log("duration", tl.duration());
</script>
Loop de verificação — renderizar → congelar → capturar → verificar:
- Abra o arquivo em momentos ao longo da timeline — início, um quadro no meio do desenho, fim:
…/scribe.html?t=0,?t=<dur/2>,?t=<dur>. Leiatl.duration()do console para o tempo final. Escolha pelo menos umtno meio de um traço — é onde a ilusão da mão na ponta é provada ou quebrada. - Capture em headless cada quadro congelado:
npx playwright screenshot --wait-for-timeout=500 "file://$PWD/scribe.html?t=1.2" frame-mid.png - INSPECIONE cada quadro — verifique fidelidade: em um
tno meio do traço, a ponta da mão está exatamente na ponta principal da porção desenhada (nem à frente, nem atrás, nem flutuando); o caminho está desenhado apenas até a ponta; em?t=<dur>cada traço completa-se limpo sem lacunas, sem overshoot, sem traço residual. Verifique artefatos: mão deslizando pelo quadro em branco entre traços, deslocamento da ponta errado (ponta flutua fora da linha), traços desenhando fora de ordem, preenchimentos aparecendo antes de seu contorno, desenhos cortados/fora do canvas, FOUC antes de fontes carregarem. - Itere: ajuste
NIB, ordem dos traços, durações e recapture até que cada quadro congelado pareça uma mão no meio do desenho.
Antes de finalizar:
- Abra autonomamente em um navegador — sem erros no console, sem CDN/assets faltando.
- Uma timeline mestre;
?t=Ncongela corretamente estrokeDashoffset+ posição da mão compartilham um valor de progresso. - Capturado em início / meio do traço / fim — ponta da mão na ponta durante todo o traço, traços completam limpos no fim, ordem de desenho correta.
- Mão levanta entre traços; sem deslizar pelo quadro em branco.
prefers-reduced-motionmostra o quadro concluído (todos os traços desenhados, mão oculta) sem a animação de desenho.
Para uma exportação narrada e determinística, porte o mesmo desenho (dirija strokeDashoffset e a transformação da mão a partir de useCurrentFrame()/fps) para uma composição Remotion e renderize para MP4/GIF — veja a skill explainer-video para o loop de renderizar-quadros → codificar.
Arquivos de referência
references/draw-on-recipes.md— mecânicas de desenho executáveis:getTotalLength/getPointAtLengthmão-segue-caminho, variantes GSAPDrawSVGPlugineMotionPathPlugin, escalonamento de múltiplos traços, escrita à mão por glifo, preenchimento-após-contorno, transições de apagar/limpar e desenho reverso, e a porta para Remotion orientada a quadro.references/whiteboard-pipeline.md— transformar arte em SVGs de traço único desenháveis, criação de ordem de desenho, fontes de escrita à mão/traço, o asset da mão marcadora e calibração da ponta, ritmo do tempo de desenho para a linha da narração e a sequência de construção do storyboard ao quadro.