
Meu nome é Vladimir e desenvolvo front-end móvel para o Yandex Mail. Nossos aplicativos têm um tema sombrio há algum tempo, mas estava incompleto: apenas a interface e os e-mails comuns estavam sombrios. As mensagens com formatação personalizada permaneciam leves e se destacavam na interface escura, ferindo os olhos dos usuários à noite.
Hoje vou contar como solucionamos esse problema. Você aprenderá sobre duas técnicas simples que não funcionaram para nós e o método que finalmente fez o truque - a recoloração adaptativa da página. Também vou compartilhar algumas idéias sobre como adaptar imagens a um tema sombrio. Para ser justo, escurecer as páginas com CSS personalizado é uma tarefa bastante peculiar, mas acredito que alguns de vocês podem achar útil a nossa experiência.
Métodos simples
Antes de decidirmos nossa técnica mágica de flexão de cores, testamos duas opções realmente básicas: aplicar estilo escuro adicional ou um filtro CSS aos elementos. Nenhuma opção funcionou para nós, mas elas podem ser mais adequadas em outros casos (simples é legal, certo?).
Substituindo estilos
Essa é uma opção simples, que estende logicamente o próprio tema escuro do CSS do aplicativo. Você acabou de colocar estilos escuros extras em um contêiner de email (ou, em geral, em um contêiner para o conteúdo gerado pelo usuário escurecer):
.message--dark { background-color: black; color: white; }
No entanto, qualquer estilo nos elementos contidos no email substituirá nosso estilo raiz. E não !important
não ajuda aqui. A idéia pode dar um passo adiante, impedindo a herança:
.message--dark * { background-color: black !important; color: white !important; border-color: #333 !important; }
Nesse caso, você não pode prescindir !important
, pois o seletor em si não é muito específico. Isso também substitui a maioria dos estilos inline (aqueles com inline !important
ainda encontrarão seu caminho, e não há muito o que fazer sobre isso).
Nosso estilo pinta avidamente tudo da mesma cor, introduzindo outro problema. Há uma chance de o designer querer dizer algo com a maneira como organizam as cores (você sabe, prioridades, combinações, todo esse material de designer), e apenas pegamos a ideia e jogamos pela janela. Não é uma coisa agradável de se fazer.

Se você não respeita tanto os designers quanto eu e decide seguir esse método, não se esqueça de lidar com as coisas não tão óbvias:
box-shadow
. No entanto, você não poderá substituir apenas a cor. Ou livre-se das sombras ou faça as pazes com as claras.- As cores dos elementos semânticos, como links ou entradas.
- SVG embutido. Use
fill
vez de background
de background
e stroke
vez de color
, mas você nunca pode ter certeza, pode ser o contrário.
Do ponto de vista técnico, esse é um método sólido: são necessárias três linhas de código (OK, trinta para uma versão pronta para produção com todos os casos de borda resolvidos), é compatível com todos os navegadores do mundo, funciona em páginas dinâmicas prontas para uso, e a maneira como o CSS é anexado ao documento é completamente irrelevante. Há também um bônus interessante: você pode facilmente ajustar as cores no estilo para corresponder às cores principais do aplicativo (por exemplo, torne o plano de fundo #bbbbb8
vez de preto).
A propósito, é assim que costumávamos escurecer os e-mails antes. Se um e-mail tivesse estilos próprios, desistiríamos e deixaríamos claro, apenas por precaução.
Usando um filtro CSS
Este é um método inteligente e elegante. Você pode escurecer uma página usando um filtro CSS:
.message--dark { filter: invert(100) hue-rotate(180deg); }
As imagens ficam assustadoras, mas podemos facilmente corrigir isso:
.message-dark img { filter: invert(100) hue-rotate(180deg); }

No entanto, isso não resolve o problema das imagens de conteúdo anexadas por meio da propriedade background (claro, é útil para ajustar a proporção, mas e a semântica?). OK, vamos supor que podemos encontrar todos esses elementos, marcá-los explicitamente e mudar suas cores também.
O bom desse método é que ele preserva o brilho original e o equilíbrio do contraste. Por outro lado, está cheio de problemas que superam as vantagens:

- Páginas escuras tornam-se brilhantes.
- Você não pode controlar as cores finais. Que filtro você aplica ao plano de fundo para que ele corresponda à sua marca
#bbbbb8
? Isso é um arranhão de cabeça real. - A inversão dupla torna as imagens desbotadas.
- O desempenho é lento, especialmente em dispositivos móveis - faz sentido, em vez de simplesmente renderizar a página, o navegador executa transformações de Fourier a cada empate.
Esse método funcionaria para emails com texto em cores neutras, mas nunca conheci alguém com uma caixa de entrada cheia desse conteúdo. Por outro lado, os filtros são a única maneira de escurecer os elementos sem acessar seu conteúdo - quadros, componentes da Web ou imagens.
Tema escuro adaptável
E agora é hora de mágica! Com base nas desvantagens das duas primeiras abordagens, fizemos uma lista de verificação do que devemos cuidar:
- Torne o fundo escuro, o texto claro e as bordas em algum ponto intermediário.
- Detecte páginas escuras e preserve suas cores.
- Mantenha o equilíbrio original de brilho e contraste.
- Controle as cores resultantes.
- Deixe os tons inalterados.
Portanto, você precisa alterar as cores dos estilos para tornar o fundo escuro. Por que não fazer isso literalmente? Pegue todos os estilos, extraia as regras relacionadas à color
( color
, background
, border
, box-shadow
e suas subpropriedades) e substitua-as por suas versões "escuras": escureça o fundo e as bordas (mas não tanto quanto as fundo), ilumine o texto etc.
Este método tem uma vantagem incrível que aquecerá o coração de qualquer desenvolvedor. Você pode configurar regras de conversão de cores para cada propriedade - sim, usando o código! Com um pouco de imaginação, você pode ajustar as cores a qualquer tema externo, executar qualquer correção de cor desejada (por exemplo, fazer um tema claro em vez de escuro, ou um tema de qualquer cor que você quiser) e até adicionar um um pouco de contextualidade - por exemplo, trate as margens amplas e estreitas de maneira diferente.
As desvantagens são o que você esperaria de uma abordagem "tudo em js". Executamos scripts, bagunçamos o encapsulamento de estilo e analisamos CSS com regex. O último, no entanto, não é tão humilhante quanto o HTML, porque a gramática CSS é regular (pelo menos no nível em que estamos trabalhando).
Aqui está o nosso plano sombrio:
- Normalize as propriedades do estilo herdado (
bgcolor
e outros) e mova-as para style="..."
. - Encontre todos os estilos em linha.
- Encontre todas as regras relacionadas a cores em cada estilo (
background-color
, color
, box-shadow
e outras). - Pegue as cores das propriedades relacionadas à cor e combine-as com o conversor correto (escurecer o fundo, clarear o texto).
- Ligue para o conversor.
- Coloque as propriedades convertidas novamente no CSS.
O código do wrapper - normalização, localização de estilos, análise - é bastante trivial. Vamos ver como exatamente nosso conversor mágico funciona.
Conversões HSL
Escurecer uma cor não é tão simples quanto parece, especialmente se você deseja preservar a tonalidade (por exemplo, transformar azul em azul escuro, não laranja). Você pode fazer isso em RGB comum, mas é problemático. Se você gosta de design algorítmico, sabe que em RGB, mesmo os gradientes estão quebrados. Enquanto isso, trabalhar com cores no HSL é um verdadeiro prazer: em vez de vermelho, verde e azul, com os quais você não tem idéia do que fazer, você tem os três canais a seguir:
- Matiz - o que estamos procurando preservar.
- Saturação, que não é muito importante para nós no momento.
- Leveza, que estaremos mudando.
Você pode pensar nisso como um cilindro. Precisamos virar esse cilindro de cabeça para baixo. A correção de cores faz algo assim: (h, s, l) => [h, s, 1-l]
.
Cores que já estão OK
Às vezes, temos sorte e o design personalizado do email (ou parte dele) já está escuro. Isso significa que não precisamos mudar nada - o designer provavelmente fez um trabalho melhor ao escolher as cores do que o nosso algoritmo jamais poderia sonhar. No HSL, você só precisa considerar o L - Lightness. Se o seu valor for maior ou menor (para o texto e o plano de fundo, respectivamente) que o limite (que você pode definir), deixe-o em paz.
Circo dinâmico
Mesmo que não precisássemos disso (muito obrigado, desinfetante, você salvou meu dia aqui), vou listar os recursos extras que um tema adaptável precisa para escurecer as páginas inteiras e não apenas os e-mails estáticos engraçados dos anos 90. Aviso justo: esta tarefa não é para todos.
Estilos inline dinâmicos
Alterar estilos embutidos é o caso mais simples que atrapalha nossas páginas escuras. É comum, mas há uma correção simples: adicione MutationObserver
e corrija estilos embutidos assim que as alterações ocorrerem.
Estilos externos
Trabalhar com estilos referenciados por um elemento <link>
de dentro da página pode ser angustiante devido à assíncronia e às instruções @import
. E o CORS não melhora as coisas. Parece que esse problema pode ser resolvido com bastante elegância com um trabalhador da Web (proxying all *.css
).
Estilos dinâmicos
Para piorar as coisas, os scripts podem adicionar, remover ou reorganizar os elementos <style>
e <link>
conforme desejarem, e até alterar as regras em <style>
. Aqui você também precisará usar o MutationObserver
, mas todas as alterações terão mais processamento.
Variáveis CSS
As coisas ficam realmente loucas quando variáveis CSS entram em jogo. Você não pode escurecer as variáveis: mesmo que você ache que uma variável contém uma cor com base em seu formato (embora eu recomendo isso), você ainda não sabe qual é o seu papel - plano de fundo, texto, borda ou tudo de uma vez? Além disso, os valores das variáveis CSS são herdadas; portanto, é necessário rastrear não apenas os estilos, mas os elementos aos quais são aplicados, e isso rapidamente sai de controle.
Depois que as variáveis CSS chegam ao mainstream, estamos com problemas. Por outro lado, color()
será suportado até então, permitindo substituir todas as nossas conversões JS por uma color(var(--bg) lightness(-50%))
.
Sumário

No nosso caso, quando o desinfetante deixa apenas estilos embutidos, o escurecimento adaptável no nível CSS funciona como um encanto: oferece um escurecimento de melhor qualidade, não atrapalha a estrutura original e é relativamente simples e rápido. Mas estendê-lo para lidar com páginas dinâmicas provavelmente não vale a pena. Felizmente, se você estiver trabalhando com conteúdo gerado pelo usuário e não estiver desenvolvendo um navegador, provavelmente o seu desinfetante funcionará da mesma maneira.
Na prática, o modo adaptável deve ser pareado com substituições de estilo, pois elementos padrão como <input>
ou <a>
costumam usar os estilos de luz padrão.
Como escurecer imagens
O escurecimento da imagem é uma questão separada que me incomoda pessoalmente. É desafiador e me dá uma desculpa para finalmente usar a frase "análise espectral". Existem vários problemas típicos com imagens em um tema sombrio.
Algumas imagens são muito claras. Isso nos dá o mesmo problema que os e-mails brilhantes com os quais começamos nossa busca. Esse problema geralmente surge em fotos comuns, mas não é exclusivo delas. Como escrever CSS para boletins informativos não é muito divertido, algumas pessoas gostam de pular cantos, inserindo um layout complexo como imagem, o que evita o escurecimento. Quando vejo isso, meu perfeccionista interior geme de frustração. Essas imagens devem estar esmaecidas, mas não invertidas, a menos que você queira assustar seus usuários.

Depois, há imagens escuras com transparência real. Esse é um problema típico dos logotipos - eles são projetados com um plano de fundo claro em mente e, quando o substituímos por um plano de fundo escuro, o logotipo desaparece nele. Essas imagens precisam ser invertidas.

Em algum lugar no meio há imagens que usam um fundo branco que deve representar transparência. No tema sombrio, eles simplesmente terminam em um estranho triângulo branco. Em um mundo perfeito, poderíamos mudar o fundo branco para um transparente, mas se você já usou uma ferramenta de varinha mágica, sabe como é difícil fazer isso automaticamente.

Curiosamente, algumas imagens não têm significado algum. Geralmente, eles estão rastreando pixels ou "suportes de formato" em layouts particularmente bizarros. Você pode torná-los invisíveis com segurança (digamos, com opacity: 0
).

Analisando o que está dentro
Para descobrir como ajustar uma imagem para um tema escuro, precisamos olhar para dentro e analisar seu conteúdo, de preferência com um método rápido e simples. Aqui está a primeira versão de um algoritmo que resolve isso.
Primeiro conte os pixels escuros, claros e transparentes da imagem. Como uma otimização óbvia, apenas um pequeno subconjunto de pixels pode ser considerado. Em seguida, determine o brilho geral da imagem (claro, escuro ou médio) e se há alguma transparência. Inverta imagens escuras com transparência, ofusque imagens brilhantes e opacas e deixe o restante inalterado.
Fiquei muito feliz com essa descoberta, até encontrar um boletim de caridade com uma foto de uma lição em uma escola africana. O designer centralizou-o adicionando pixels transparentes nas laterais. Como não queríamos nos envolver em uma história sobre reconhecimento ofensivo de imagens, decidimos deixar a manipulação de imagens fora de nossa primeira iteração.
No futuro, podemos evitar esses problemas empregando a heurística que chamo de "análise espectral". Ou seja, contando o número de matizes diferentes na imagem e apenas invertendo se não houver muitos deles. Você pode usar o mesmo método para reconhecer imagens esboçadas brilhantes e invertê-las também.

Conclusão
Para criar um tema escuro completo, tivemos que encontrar uma maneira de escurecer os emails que contêm formatação personalizada - e foi o que fizemos. Primeiro, tentamos duas soluções CSS simples e puras - substituindo estilos e usando um filtro CSS. Nenhum deles atingiu a marca: o primeiro foi muito duro com o design original e o outro simplesmente não funcionou bem. No final, decidimos usar o escurecimento adaptável. Separamos os estilos, substituímos as cores e as juntamos novamente. No momento, estamos trabalhando para aplicar o tema sombrio às imagens. Para fazer isso, precisamos analisar o conteúdo deles e depois ajustar apenas alguns deles.
Se você precisar alterar a cor dos snipets HTML personalizados para ajustá-los a um tema sombrio, lembre-se de três métodos:
- Substituindo estilos. É um método sem confusão, sem confusão, e você precisará dele para a sua aplicação de qualquer maneira. A má notícia é que destrói todas as cores originais.
- Filtro CSS. É divertido, mas deixa muito a desejar. Reserve-o para elementos que não são fáceis de acessar, como quadros ou componentes da web.
- Reescrita de estilo. Esse método faz um ótimo trabalho no escurecimento, mas é mais complexo.
Mesmo se você nunca tentar isso, espero que você tenha se divertido ao ler este artigo!