Alguém Colocou DOOM para Rodar em CSS. Sim, CSS.
Se você é desenvolvedor front-end e acha que já viu de tudo, preciso te contar uma história. Niels Leenheer, um desenvolvedor holandês conhecido por empurrar os limites do que browsers conseguem fazer, decidiu que seu próximo projeto seria renderizar DOOM — aquele DOOM, de 1993 — inteiramente em CSS. Sem Canvas. Sem WebGL. Sem WebGPU. Cada parede, cada piso, cada barril explosivo e cada Imp enfurecido é uma <div> posicionada em espaço 3D usando CSS transforms.
O resultado está ao vivo em cssdoom.wtf e o código-fonte no GitHub. E antes que você pergunte: sim, é jogável.
Eu confesso que quando vi pela primeira vez no Hacker News — onde o projeto acumulou mais de 400 pontos e quase 100 comentários — minha reação foi uma mistura de “por que alguém faria isso?” e “como diabos isso funciona?”. Vamos destrinchar.
O Que Exatamente Significa “Renderizado em CSS”?
Preciso fazer uma distinção importante aqui, porque o título pode confundir. A lógica do jogo — colisão, IA dos inimigos, mecânica de tiro — roda em JavaScript. O que está em CSS puro é toda a renderização: a projeção 3D, as animações de sprites, portas abrindo, elevadores subindo, projéteis voando, iluminação por setor. O JavaScript só alimenta o CSS com novas coordenadas do jogador e deixa o CSS cuidar de atualizar o que aparece na tela.
Pense assim: o JavaScript é o cérebro do jogo, mas os olhos são 100% CSS. Cada frame que você vê na tela é calculado pelo motor de renderização do browser usando propriedades CSS. Nada de <canvas>, nada de getContext('2d'), nada de shaders WebGL. Só <div>s com transform e filter.
Leenheer explicou que idealmente teria feito tudo em CSS — e mencionou que Lyra Rebane já construiu uma CPU x86 inteira em CSS — mas a técnica simplesmente não é rápida o suficiente para o game loop. Rodar uma CPU emulada em CSS é possível, mas em velocidades medidas em frames por minuto, não por segundo. Então o JavaScript cuida da lógica, e o CSS cuida dos pixels.
E olha: a renderização é a parte genuinamente difícil. Qualquer um consegue escrever um game loop em JS. Fazer projeção 3D perspectiva usando folhas de estilo? Isso é outro nível.
A Arquitetura: Dados do WAD Direto Para Custom Properties
O DOOM original usa um formato de arquivo chamado WAD que contém toda a geometria dos mapas: vértices, linedefs, sidedefs e setores. O cssDOOM extrai exatamente esses mesmos dados e os transforma em CSS custom properties em elementos <div>.
Cada parede recebe suas coordenadas brutas do DOOM como variáveis CSS — dois pares de coordenadas x/y, alturas de piso e teto. A partir daí, o CSS calcula o resto. Não existe pré-processamento de geometria. O CSS faz a trigonometria em tempo real.
/* Cada parede recebe coordenadas do WAD como custom properties */
.wall {
--x1: 1024;
--y1: -3072;
--x2: 1088;
--y2: -3072;
--floor: 0;
--ceiling: 128;
}
As 7 Features de CSS Moderno Que Tornaram Isso Possível
Aqui é onde fica técnico — e fascinante. Leenheer usa sete funcionalidades de CSS moderno trabalhando juntas. Eu vou quebrar cada uma:
1. Funções Matemáticas: hypot() e atan2()
Essa é a base de tudo. Para calcular a largura de uma parede no espaço 3D, você precisa do teorema de Pitágoras. Em CSS:
width: calc(hypot(var(--delta-x), var(--delta-y)) * 1px);
A função hypot() faz exatamente o que faria em qualquer linguagem de programação: recebe dois catetos e retorna a hipotenusa. Já atan2() calcula o ângulo de rotação da parede. Essas funções foram adicionadas ao CSS com suporte em Chrome 120+, Firefox 118+ e Safari 15.4+.
Eu lembro quando a CSS Working Group anunciou a adição dessas funções matemáticas — sin(), cos(), tan(), atan2(), hypot(), sqrt(), pow(). Na época, muita gente questionou: “Para que isso serve em CSS?”. Agora a gente sabe.
2. CSS 3D Transforms
O DOOM original é um jogo “2.5D” — o mapa é 2D visto de cima, mas renderizado como se fosse 3D. O cssDOOM mapeia as coordenadas 2D do DOOM para o espaço 3D do CSS usando um truque de inversão de eixos:
transform: translate3d(var(--x), calc(-1 * var(--z)), calc(-1 * var(--y)));
Em vez do mapeamento padrão x/y/z, o projeto usa translate3d(x, -z, -y). A matemática simplesmente funciona quando você faz essa inversão, e as rotações das paredes caem naturalmente no lugar.
3. @property Para Animações de Custom Properties
O @property permite registrar custom properties com tipos específicos, o que habilita animações suaves. Sem isso, você não consegue interpolar valores numéricos em custom properties — o browser não sabe que --player-height é um número.
@property --player-height {
syntax: '<number>';
inherits: false;
initial-value: 41;
}
Isso é usado para animar a altura do jogador quando ele entra em um elevador ou cai de uma plataforma.
4. clip-path com polygon() e path()
Os setores do DOOM não são retangulares — são polígonos arbitrários. O clip-path com polygon() e path() usando a regra evenodd define a forma de cada setor, incluindo buracos (um setor dentro de outro).
5. Filtros SVG Para Efeitos Especiais
O efeito de invisibilidade do Spectre usa uma combinação de feColorMatrix, feTurbulence e feDisplacementMap como filtros SVG aplicados via CSS. É o tipo de coisa que você normalmente faria com um shader em WebGL.
6. Anchor Positioning
Funcionalidade relativamente nova que posiciona elementos relativamente a outros elementos dinâmicos. No cssDOOM, é usada para posicionar elementos da UI (HUD) relativamente à barra de status.
7. Animações CSS Para Mecânicas de Jogo
Portas, elevadores, projéteis e ciclos de sprites usam @keyframes com steps() para animação frame-a-frame. O background-position dos sprites muda via steps(), criando a ilusão clássica de animação por sprite sheet.
| Feature CSS | Uso no cssDOOM | Suporte em 2026 |
|---|---|---|
hypot(), atan2() | Cálculos de geometria 3D | Chrome 120+, Firefox 118+, Safari 15.4+ |
translate3d() | Posicionamento 3D de paredes e objetos | Universal (desde 2013) |
@property | Animação de custom properties numéricas | Chrome 85+, Firefox 128+, Safari 15.4+ |
clip-path: polygon() | Formas de setores não-retangulares | Universal |
| Filtros SVG | Efeito de invisibilidade do Spectre | Universal |
| Anchor Positioning | Posicionamento de UI | Chrome 125+, Firefox parcial |
@keyframes + steps() | Sprites e mecânicas (portas, elevadores) | Universal |
O Truque da Câmera: Mover o Mundo Inteiro
Uma das sacadas mais elegantes é como a câmera funciona. Em engines tradicionais, você move a câmera pelo mundo. No cssDOOM, o jogador fica parado e o mundo inteiro se move na direção oposta. Se o jogador anda para frente, todas as <div> do mapa se movem para trás.
Parece contraproducente — mover milhares de divs em vez de uma câmera — mas é assim que o CSS 3D funciona. O perspective fica no container pai, e os elementos filhos se movem dentro desse espaço. É a mesma abordagem que qualquer tutorial de CSS 3D ensina, só que levada ao extremo.
Sprites: Billboarding e Espelhamento
Os sprites (monstros, itens, decoração) precisam sempre encarar a câmera — uma técnica chamada billboarding. No cssDOOM, cada sprite recebe um rotateY() que anula a rotação do jogador, fazendo o sprite sempre ficar de frente.
Para variações direcionais (um Imp visto de frente versus de costas), o jogo usa scaleX(-1) como flag de espelhamento. E a animação frame-a-frame usa background-position com steps(), exatamente como sprite sheets em jogos 2D tradicionais.
/* Sprite sempre encarando a câmera */
.sprite {
transform: rotateY(calc(var(--player-angle) * -1deg));
}
/* Animação frame-a-frame */
.imp-walk {
animation: imp-walk 0.5s steps(4) infinite;
}
@keyframes imp-walk {
from { background-position: 0 0; }
to { background-position: -256px 0; }
}
Iluminação: Uma Cascata Brilhante
O DOOM original tem iluminação por setor — cada área do mapa tem seu próprio nível de luz. O cssDOOM implementa isso com filter: brightness(var(--light)), onde --light é uma custom property que cascata do setor para todos os elementos dentro dele.
Luzes piscantes? Uma animação CSS no --light do setor. Simples. Elegante.
Performance: Onde o Bicho Pega
Aqui é onde a realidade bate. Renderizar um jogo 3D com milhares de <div> no DOM não é exatamente o que os browsers foram otimizados para fazer. Estamos falando de centenas — às vezes milhares — de elementos com transform: translate3d() sendo recalculados a cada movimento do jogador. O compositor do browser precisa resolver a profundidade de cada elemento, aplicar perspectiva, e rasterizar tudo em tempo real.
Sem nenhuma otimização, o frame rate despencaria. O Leenheer implementou um sistema de culling (remoção de elementos fora da tela) usando um truque inteligente com animações pausadas.
A ideia é genial na sua gambiarra: uma animação com animation-play-state: paused e um animation-delay negativo calculado com base na distância do elemento até o jogador cria essencialmente uma condicional em CSS. Dependendo do valor calculado, o elemento fica visível ou invisível. Na prática, é um if disfarçado de animação.
/* Culling via animação pausada — hack brilhante */
.wall-segment {
animation: cull 1s paused;
animation-delay: calc(var(--distance) * -1ms);
}
@keyframes cull {
0%, 50% { visibility: visible; }
51%, 100% { visibility: hidden; }
}
É um hack? Totalmente. Mas funciona porque o CSS ainda não tem uma função if() nativa (está em desenvolvimento). Quando if() chegar, esse tipo de lógica condicional vai ficar muito mais limpo. Por enquanto, a comunidade CSS improvisa — e o cssDOOM é o exemplo mais extremo dessa improvisação.
Resultados Por Browser
Os benchmarks são reveladores:
| Browser | Performance | Observações |
|---|---|---|
| Firefox | Melhor | WebRender lida bem com CSS 3D, renderização GPU acelerada |
| Chrome | Boa, mas instável | Compositor tem problemas com milhares de transforms 3D |
| Safari (desktop) | Razoável | View Transitions achatam preserve-3d |
| Safari (iOS) | Precária | Mapas grandes crasham |
Firefox liderando em performance de CSS 3D é algo que eu não esperava, mas faz sentido — o WebRender foi projetado exatamente para esse tipo de workload pesado de renderização.
O Que Isso Significa Para o CSS em 2026
Vamos ser honestos: ninguém vai substituir Unity ou Unreal por CSS para fazer jogos. Esse não é o ponto. Se você quer fazer um jogo 3D de verdade, use WebGPU, Godot, ou qualquer engine de verdade.
O ponto é outro. O CSS evoluiu de uma linguagem de estilos para um motor de renderização legítimo. Se você não acompanhou CSS de perto desde 2024, vai se surpreender com o que mudou. Funções trigonométricas, variáveis tipadas, anchor positioning, container queries, cascade layers, nesting nativo, @scope, @layer — a linguagem que a gente usava para centralizar divs (e falhava nisso metade das vezes) agora faz trigonometria em tempo real.
Eu já vi projetos de CSS que implementaram relógios analógicos, visualizações de dados interativas, e animações complexas de UI — tudo sem uma linha de JavaScript. O cssDOOM é o caso mais extremo, mas está numa tendência clara de empurrar CSS cada vez mais longe.
O cssDOOM é um proof of concept extremo, mas as técnicas individuais são absolutamente aplicáveis no dia a dia:
hypot()eatan2()servem para layouts circulares, tooltips que apontam dinamicamente para elementos, cálculos de distância entre elementos, e posicionamento radial de menus@propertyhabilita animações que antes precisavam de JavaScript — como transições de cor em gradientes, contadores animados, e interpolação de valores numéricos- Anchor Positioning substitui bibliotecas inteiras de posicionamento como Popper.js e Floating UI, sem nenhuma dependência externa
- Filtros SVG via CSS são uma alternativa leve a efeitos que normalmente exigiriam Canvas — blur direcional, efeitos de distorção, color grading
A real é que a maioria dos devs front-end usa talvez 30% do CSS disponível em 2026. E não é por falta de funcionalidade — é por falta de exploração. Projetos como o cssDOOM servem como um catálogo vivo do que é possível.
Como Testar
Se ficou curioso, acesse cssdoom.wtf no Firefox para a melhor experiência. O código-fonte está em github.com/NielsLeenheer/cssDOOM. E o write-up técnico completo — que eu recomendo fortemente — está no blog do Niels Leenheer.
Ah, e se você está se perguntando qual vai ser o próximo passo… o Leenheer já fez DOOM rodar em um osciloscópio dos anos 80. CSS foi a evolução natural. A pergunta que fica é: o que mais a gente está subestimando no CSS?
Fonte de inspiração: CSS is DOOMed – Rendering DOOM in 3D with CSS por Niels Leenheer













