Go to English Blog

Implementando dark mode em conteúdo web

Leia em 7 minutos

Bem-vindo a 2019, o ano que a tecnologia abraçou o dark mode. Tanto Android 10 quanto iOS 13 tiveram uma nova versão no mês de setembro, e uma das funcionalidades mais aclamadas foi dark mode. Apps nativos podem implementar temas específicos para cada modo (i.e. dark e light), mas como fazer isso na web? Esse artigo mostra como você pode tirar vantagem dessa nova moda e deixar todo mundo feliz.

Existem benefícios em se usar dark mode?

Quando você começa a ler sobre dark mode, logo encontra frases como “é melhor para os olhos” ou “mais eficiente no consumo de energia”. A verdade é que para que sua bateria se beneficie de dark mode, é preciso que você tenha telas OLED/AMOLED, que só estão disponíveis em aparelhos novos mais caros.

Se você usa LCD ou outro tipo de tela, mudar as cores não irá estender o tempo de uso de sua bateria. Isso significa que a maior parte das pessoas não irá se beneficiar desse aspecto do dark mode.

Mas e quanto a benefícios à saúde? Com certeza é melhor, certo? Não é bem assim. Infelizmente não temos muitos estudos científicos sobre o assunto para dizermos com precisão que o dark mode é melhor. O que sabemos é que o tempo que passamos olhando para telas é o fator mais importante para o cansaço dos olhos. Quanto menos você ficar olhando sua tela melhor.

Alguns médicos também mencionam que mais contraste (seja tema claro, seja escuro) é mais importante que mudar o tema. E alguns médicos inclusive mencionam que dark mode pode ser mais prejudicial para pessoas com astigmatismo.

Eu não gosto muito de temas escuros com textos muito claros (i.e. fundo preto com texto branco); meus olhos ficam com aquela sensação de imagem fixa que demora um tempo até sumir. E o oposto também é verdade para algumas pessoas. O fato é que sem pesquisas sérias sobre o assunto tudo o que podemos fazer é falar sobre nossas experiências pessoais.

Agora, eu realmente acredito que a maior vantagem de dark mode é quando você está usando seu telefone em um quarto com pouca iluminação, mas você não deveria estar usando seu telefone antes de dormir de qualquer modo, certo?

Suporte dos navegadores

Qual o suporte dos navegadores para dark mode? Para detectar se um navegador tem suporte a dark mode ou não, você precisará da media query (prefers-color-scheme: dark), disponível nos seguintes navegadores:

Pois é… o versionamento de navegadores se tornou estúpido, e eu não deveria nem ter listado aqui de qualquer modo. Enfim, o que você precisa saber é que o Safari 13 é muito, muito novo, assim como o suporte para esse tipo de media query em todos os outros navegadores. Do ponto de vista de negócios, não faz muito sentido adicionar suporte para dark mode, mas você vai fazer isso de qualquer modo que eu sei. Então, aqui vai… é assim que você define seu CSS:

body {
  color: #333;
  background: #fff;
}

@media screen and (prefers-color-scheme: dark) {
  body {
    background: #2d3239;
    color: #75715e;
  }

  h1 {
    color: #e9d970;
  }
}

Você pode alternar entre um modo e outro no Web Inspector do Safari. Eu acho que o Chrome ainda não tem nada parecido, mas não deve demorar muito para lançarem algo similar. Veja este exemplo aqui.

Eu sei o que você pensando. O CSS pode sair de controle rapidinho, e concordo totalmente! A menos que você use variáveis. Mais especificamente, variáveis de CSS.

Criando temas com variáveis de CSS

O truque para manter o CSS sobre controle é usar variáveis. Para fazer isso, você precisa usar o formato --variable-name: value.

:root {
  --page-background: #fff;
}

body {
  background: var(--page-background);
}

É simples assim. Agora, como definir variáveis para os temas claro e escuro? Primeiro, você precisa declarar todas as variáveis que serão usadas sem nenhuma media query.

:root {
  --page-background: #fff;
  --page-title: #333;
  --page-text: #333;
}

body {
  background: var(--page-background);
  color: var(--page-text);
}

h1 {
  color: var(--page-title);
}

Depois, você pode adicionar as mesmas variávies usando a media query que casa dark mode. O CSS ficará parecido com isso:

:root {
  --page-background: #fff;
  --page-title: #333;
  --page-text: #333;
}

@media screen and (prefers-color-scheme: dark) {
  :root {
    --page-background: #2d3239;
    --page-title: #e9d970;
    --page-text: #75715e;
  }
}

body {
  background: var(--page-background);
  color: var(--page-text);
}

h1 {
  color: var(--page-title);
}

Você pode ver este exemplo aqui. Você pode definir qualquer propriedade do seu tema incluindo imagens de fundo, bordas, sombras e filtros.

Dark mode em imagens e vídeos

Não é incomum termos imagens/vídeos com bastante brilho por toda a página. Um truque que pode ajudar a reduzir a luminosidade de imagens e vídeos é usar filtros; você pode tentar uma combinação de opacity e grayscale. Adicione as variáveis --image-grayscale e --image-opacity e modifique até atingir algo que funcione para sua página.

:root {
  --page-background: #fff;
  --page-title: #333;
  --page-text: #333;
  --image-grayscale: 0;
  --image-opacity: 100%;
}

@media screen and (prefers-color-scheme: dark) {
  :root {
    --page-background: #2d3239;
    --page-title: #e9d970;
    --page-text: #75715e;
    --image-grayscale: 50%;
    --image-opacity: 60%;
  }
}

img,
video {
  filter: grayscale(var(--image-grayscale)) opacity(var(--image-opacity));
}

Uso de imagens diferentes em temas claros e escuros com uso de filtros de CSS

Você pode ver este exemplo aqui. Este truque pode não funcionar em todos os lugares e pode precisar de um elemento englobando as imagens e/ou vídeos. Eventualmente você precisará renderizar uma imagem completamente diferente. É aí que entra o elemento <picture>.

O elemento <picture> tem suporte a media queries. Se você quer renderizar uma imagem diferente para dark mode, use um <source> diferente que tem media="(prefers-color-scheme: dark)". Se o navegador não tiver suporte para dark mode, ou se o usuário estiver usando um tema claro, então a imagem que será renderizada será a padrão.

<picture>
  <source srcset="beach.jpg" media="(prefers-color-scheme: dark)" />
  <img src="pool.jpg" />
</picture>

Agora, o CSS não precisa mais de nenhum código relacionado a filtros, mas deixo isso como exercício para você. O resultado final pode ser visto aqui.

<picture> element in action!

Em alguns casos, usar uma imagem SVG direto na página fará mais sentido. Basta mudar as cores com CSS e pronto! Essa técnica funciona muito bem para ícones, logos e coisas semelhantes. É importante mencionar que para poder estilizar elementos contidos em <svg> é preciso renderizar o SVG diretamente na página. Usar a tag <img> não irá funcionar. Dito isso, nosso código para SVG pode ser algo assim:

<svg
  id="logo"
  width="250px"
  height="55px"
  viewBox="0 0 250 55"
  version="1.1"
  xmlns="http://www.w3.org/2000/svg"
  xmlns:xlink="http://www.w3.org/1999/xlink"
>
  <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
    <g id="logo">
      <path d="..." id="background" fill="#0091FF"></path>
      <path d="..." id="letter" fill="#FFD700" fill-rule="nonzero"></path>
      <path d="..." id="words" fill="#45638B" fill-rule="nonzero"></path>
    </g>
  </g>
</svg>

As cores aplicadas no arquivo SVG são referentes ao tema padrão. Por isso, teremos que sobrescrever todas as cores para que funcionem em dark mode. Logo, esta tarefa será extremamente entediante se você tiver um SVG muito complexo.

@media screen and (prefers-color-scheme: dark) {
  :root {
    --page-background: #2d3239;
    --page-title: #e9d970;
    --page-text: #75715e;

    --logo-background: #4d5866;
    --logo-words: #fff;
  }

  #logo--words {
    fill: var(--logo-words);
  }

  #logo--background {
    fill: var(--logo-background);
  }
}

Veja este exemplo aqui.

Estilizando <svg> para dark mode

Alternativamente, você pode usar currentColor como o valor das propriedades fill e stroke. Assim, só será preciso mudar a propriedade color do elemento SVG ou de um de seus elementos-pai. Essa abordagem funciona extremamente bem com ícones de linha.

O exemplo acima gera uma nova cor toda vez que o botão é clicado, definindo a propriedade color do elemento <body>. Isso é feito com document.body.style.color = newColor.

Detectando dark mode com JavaScript

Você pode precisar detectar dark mode com JavaScript para atualizar gráficos, por exemplo. Para isso será necessário usar window.matchMedia. A detecção é bastante simples.

function isDarkMode() {
  return (
    window.matchMedia &&
    window.matchMedia("(prefers-color-scheme: dark)").matches
  );
}

function renderCanvas() {
  const theme = isDarkMode()
    ? { background: "#4d5866", border: "#7091ba" }
    : { background: "#ffffff", border: "#000" };

  const canvas = document.querySelector("canvas");
  const ctx = canvas.getContext("2d");
  const x = 15;
  const y = 15;
  const width = canvas.width - x * 2;
  const height = canvas.height - y * 2;

  ctx.clearRect(0, 0, canvas.width, canvas.height);
  ctx.lineWidth = 5;
  ctx.strokeStyle = theme.border;
  ctx.strokeRect(x, y, width, height);
  ctx.fillStyle = theme.background;
  ctx.fillRect(x, y, width, height);
}

renderCanvas();

Como você pode ver, basta definir o tema do código JavaScript condicionalmente quando você detectar o uso de dark mode. Este exemplo pode ser visto aqui.

Se você alternar entre um modo e outro (o seu computador pode estar configurado parar alternar entre os modos automaticamente), as cores do código renderizado pelo JavaScript ficariam erradas. Você pode usar MediaQueryList.addListener para detectar a mudança de temas e iniciar uma nova renderização do gráfico.

const darkModeMatcher =
  window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)");

function isDarkMode() {
  return darkModeMatcher && darkModeMatcher.matches;
}

function onDarkModeChange(callback) {
  if (!darkModeMatcher) {
    return;
  }

  darkModeMatcher.addListener(({ matches }) => callback(matches));
}

function renderCanvas(useDarkTheme) {
  const theme = useDarkTheme
    ? { background: "#4d5866", border: "#7091ba" }
    : { background: "#ffffff", border: "#000" };

  const canvas = document.querySelector("canvas");
  const ctx = canvas.getContext("2d");
  const x = 15;
  const y = 15;
  const width = canvas.width - x * 2;
  const height = canvas.height - y * 2;

  ctx.clearRect(0, 0, canvas.width, canvas.height);
  ctx.lineWidth = 5;
  ctx.strokeStyle = theme.border;
  ctx.strokeRect(x, y, width, height);
  ctx.fillStyle = theme.background;
  ctx.fillRect(x, y, width, height);
}

renderCanvas(isDarkMode());
onDarkModeChange(renderCanvas);

Esse código irá garantir que a função renderCanvas seja executada toda vez que o tema mudar. Você pode ver este exemplo aqui.

E com isso você tem praticamente todas as informações para implementar dark mode em conteúdos web.

Finalizando

Dark mode é a mais nova moda no mundo da tecnologia. E mesmo sem nenhum estudo sério comprovando os chamados “benefícios do dark mode”, você pode considerar implementar apenas pela felicidade de seus clientes. Os aspectos técnicos são bastante simples, mas não se engane: criar temas escuros é um processo desafiador, especialmente quando você precisa levar em consideração a direção de arte de todo o site, incluindo imagens e vídeos.

Outra coisa a se considerar é se você deve mudar o tema automaticamente ou se deve adicionar uma opção para que os usuários escolham o tema de preferência. Essa última opção também é simples de ser implementada se você adicionar uma classe ou atributo data em um elemento (e.g. <html data-theme="dark">), mas isto traz um problema que é ter que alternar imagens e vídeos manualmente quando existe a mudança de um tema para o outro. Ou isso, ou apenas ignore a mudança do tema, o que provalmente funcionará para a maioria dos casos.