Original article: SVG Toggle Button Tutorial – How to Handle Dark Mode with CSS and JavaScript

¿Cómo se puede detectar el modo oscuro en CSS y JavaScript? ¿Cómo puedes anularlo manualmente con un botón de alternancia? ¿Y cómo se puede crear un ícono de sol y luna con SVG?

En este tutorial, aprenderá cómo detectar el modo oscuro en CSS y JavaScript, y creará un botón de alternancia con SVG para anular el comportamiento predeterminado. Utilizará HTML, CSS y JavaScript sin formato, por lo que no necesita ningún requisito preliminar antes de comenzar.

También puedes ver este artículo como vídeo en YouTube.

Tabla de contenido

Cómo manejar el modo oscuro con CSS

Supongamos que tiene un sitio web sencillo con algo de texto. De forma predeterminada, configura el color del texto en negro y el color de fondo en blanco. Implementar el modo oscuro para este sitio con CSS es muy sencillo:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>Dark Mode</title>
    <link rel="stylesheet" href="index.css" />
    <script src="index.js" defer></script>
  </head>

  <body>
    <p>
      How to detect dark mode in CSS and in JavaScript? How can we override it
      manually with a toggle button? In this quick tutorial, we look into
      detecting dark mode in CSS and JavaScript, and then we create a toggle
      button with SVG to override the default behavior.
    </p>
  </body>
</html>
Captura de pantalla-2024-03-07-at-11.33.28
Un sitio web sencillo con algo de texto en modo oscuro.

Todo lo que necesitas hacer es agregar una consulta de medios y establecer una condición. Con esta condición, configura las siguientes declaraciones CSS para que solo sean válidas si el esquema de color preferido es oscuro.

Dentro de esta consulta de medios, puede definir los colores para el modo oscuro. En este caso, inviertes los colores y estableces el color del texto en blanco y el color de fondo en negro:

body {
  font-family: Montserrat;
  margin: 50px;
  max-width: 500px;
}

@media (prefers-color-scheme: dark) {
  body {
    background-color: black;
    color: white;
  }
}

Esto tomará la configuración de su sistema operativo o configuración del navegador. De forma predeterminada, proviene del sistema operativo, pero el navegador puede decidir anularlo. En Google Chrome, puede encontrar esta configuración en "Apariencia". De forma predeterminada, sigue la configuración del dispositivo.

Lo bueno de la solución CSS es que si cambia esta configuración mientras visita el sitio web, el estilo se actualizará automáticamente.

De esta manera, puede establecer un estilo personalizado para el elemento del cuerpo y también para cualquier otro elemento.

Sin embargo, en un caso no funciona. No puedes diseñar lo que hay dentro de un elemento HTML Canvas con CSS. Si creaste un juego completamente desde JavaScript usando Canvas API o Three.js, también debes configurar los colores para el modo oscuro en JavaScript.

En los próximos pasos, cubriremos esto y veremos cómo crear un botón de alternancia SVG para cambiar entre los modos claro y oscuro.

Cómo codificar un icono de sol con SVG

Antes de aprender a manejar el modo oscuro en JavaScript, hagamos un desvío rápido y veamos cómo codificar un botón de alternancia del modo oscuro con SVG. Detectar el modo oscuro es una cosa, pero debes permitir al usuario alternar manualmente entre los modos claro y oscuro.

Diapositivas-1.022
Icono del sol

Consulte mi tutorial anterior si necesita una introducción rápida a la codificación de iconos SVG . Contiene muchos ejemplos excelentes, desde niveles principiantes hasta avanzados. Y si eres nuevo en los SVG, no te preocupes. Estos son ejemplos muy simples.

Entonces, comencemos con el svgelemento. Esto servirá como contenedor para todos los elementos de la imagen. Establezca su tamaño en 30 por 30:

Diapositivas-1.004
El svgelemento

Luego, agrega un círculo. Para un circleelemento, debes establecer las coordenadas centrales del círculo y su radio. Las coordenadas centrales son 15 y el radio es 6. Luego, establece un color con la fillpropiedad:

<svg width="30" height="30">
  <circle cx="15" cy="15" r="6" fill="currentColor" />
</svg>
Diapositivas-1.006
Agregamos un circlecomo núcleo del sol.

Para establecer el color, puede utilizar la currentColorpropiedad que asume la colorconfiguración actual de CSS. Esto será útil más adelante cuando alterne los modos oscuro y claro. El icono cambiará de color automáticamente.

Luego, agrega los rayos del sol. Debe usar el lineelemento para esto, donde debe establecer las coordenadas inicial y final. También puede establecer el color del trazo con la strokepropiedad stroke-widthpara agregar grosor y la stroke-linecappropiedad para redondear los extremos de las líneas:

<svg width="30" height="30">
  <circle cx="15" cy="15" r="6" fill="currentColor" />

  <line
    id="ray"
    stroke="currentColor"
    stroke-width="2"
    stroke-linecap="round"
    x1="15"
    y1="1"
    x2="15"
    y2="4"
  ></line>
</svg>
Diapositivas-1.010
Agregamos un lineelemento como un rayo de sol.

Ahora, una vez que tengas un rayo, puedes reutilizar el mismo rayo para dibujar los demás.

Puedes darle a este rayo idy reutilizarlo con el useelemento. Para los elementos reutilizados, puede establecer una rotación. Establezca el ángulo de rotación y el centro de rotación. Quieres rotar los rayos alrededor del centro del sol, así que configúralo en 15,15. Luego, incrementa la rotación en 45 grados para cada rayo:

<svg width="30" height="30">
  <circle cx="15" cy="15" r="6" fill="currentColor" />

  <line
    id="ray"
    stroke="currentColor"
    stroke-width="2"
    stroke-linecap="round"
    x1="15"
    y1="1"
    x2="15"
    y2="4"
  ></line>

  <use href="#ray" transform="rotate(45 15 15)" />
  <use href="#ray" transform="rotate(90 15 15)" />
  <use href="#ray" transform="rotate(135 15 15)" />
  <use href="#ray" transform="rotate(180 15 15)" />
  <use href="#ray" transform="rotate(225 15 15)" />
  <use href="#ray" transform="rotate(270 15 15)" />
  <use href="#ray" transform="rotate(315 15 15)" />
</svg>
Diapositivas-1.016
El icono del sol terminado.

Cómo detectar el modo oscuro en JavaScript

Antes de llegar al ícono de la luna, veamos cómo detectar el modo oscuro en JavaScript. Esto puede ser útil al crear un juego, como lo hicimos hace un par de semanas en el tutorial del juego Gorilas JavaScript .

En ese juego, estábamos dibujando en un elemento HTML Canvas con JavaScript. Configuramos todos los colores con JavaScript. Si queremos admitir el modo oscuro, podemos configurar los colores en función de una darkModevariable. Pero ¿cómo detectamos si estamos en modo oscuro? ¿Cómo establecemos el valor de esta variable?

El siguiente código es un fragmento de ejemplo del tutorial del juego anterior. Aquí configuramos el color de relleno antes de dibujar un rectángulo en el lienzo.

Diapositivas-1.018
Al dibujar sobre un canvaselemento configuramos los colores desde JavaScript. ¿Pero cómo detectamos el modo oscuro?

Detectar el modo oscuro en JavaScript también es muy sencillo. Curiosamente, esta solución también depende de los selectores de consultas CSS que usaste antes.

Puedes crear un matchMediaobjeto con la misma condición que usamos en CSS. Este método puede comprobar si el documento coincide con una consulta de medios. Pasar prefers-color-scheme: darkcomo argumento:

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

let darkMode = darkModeMediaQuery.matches;

. . .

function drawBuildings() {
  state.buildings.forEach((building) => {
    ctx.fillStyle = darkMode ? "#254D7E" : "#947285";
    ctx.fillRect(building.x, 0, building.width, building.height);
  });
}

Luego, puede verificar la matchespropiedad de este objeto. Si es cierto, entonces estás en modo oscuro. Puede guardar esto en una variable y, luego, puede usar esta variable para decidir qué colores debe usar al pintar en el elemento del lienzo.

Esta variable, sin embargo, no se actualiza automáticamente cuando cambia entre el modo claro y el modo oscuro. Debe agregar un detector de eventos que detecte si la configuración cambia.

Aquí, definimos una función que verifica la matchespropiedad del objeto entrante para decidir si acaba de cambiar al modo brillante u oscuro:

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

let darkMode = darkModeMediaQuery.matches;

darkModeMediaQuery.addEventListener("change", (e) => {
  if (e.matches) {
    darkMode = true;
  } else {
    darkMode = false;
  }
});

. . .

function drawBuildings() {
  state.buildings.forEach((building) => {
    ctx.fillStyle = darkMode ? "#254D7E" : "#947285";
    ctx.fillRect(building.x, 0, building.width, building.height);
  });
}

Ahora, si configuras los colores según esta darkModevariable, deberías ver que la apariencia del juego cambia una vez que cambias entre el modo claro y oscuro en la configuración del sistema operativo. Mira esta demostración para verlo en acción.

Cómo codificar un icono de luna con SVG

Antes de discutir la anulación de la configuración predeterminada del sistema operativo con un botón de alternancia, examinemos la otra mitad de nuestro ícono de alternancia: Dibujemos una luna.

Diapositivas-1.023
Los iconos del Sol y la Luna

Comience con un svgelemento del mismo tamaño y defina una ruta dentro de él. Puede definir un pathelemento estableciendo su datributo. En este atributo, construyes una ruta a partir de una serie de comandos:

Diapositivas-1.026
Definimos a pathcon una serie de comandos

Comienza con el comando mover a para ir a la posición inicial. Este comando consta de la letra My la coordenada inicial:

Diapositivas-1.027
Usando el comando mover a dentro de una ruta

Luego, usa un comando de arco para dibujar el arco exterior de la luna. Este comando puede dar un poco de miedo porque tiene varias propiedades. Veamos que tenemos:

Diapositivas-1.028
El comando arco y sus varias propiedades.

Un comando siempre continúa el comando anterior, por lo que este arco dibujará el arco a partir de las coordenadas del comando de desplazamiento. Los comandos también terminan con las coordenadas del punto final.

Aquí estableces dónde termina el arco. El resto de las propiedades tratan sobre cómo dibujar un arco desde el punto inicial hasta el punto final:

Diapositivas-1.029
Las dos últimas propiedades del comando arco muestran el punto final del arco.

Las dos primeras propiedades son el radio horizontal y vertical de nuestro arco. En nuestro caso, queremos tener el arco de un círculo, por lo que establecemos el mismo valor para ambos. Con el tercer argumento, puedes establecer una rotación. Cuando ambos radios son iguales, esta propiedad no hace diferencia. Puedes dejarlo en cero:

Diapositivas-1.030
Radio horizontal y vertical del arco.

Luego, tenemos la propiedad de bandera de arco grande. Con esto, podrás decidir si ir por el camino largo o corto hasta nuestra coordenada final. Puedes ver que puedes llegar al punto final de múltiples maneras, incluso con los mismos radios.

Hay dos arcos: en el caso del primero, irás por el camino largo y en el caso del segundo, irás por el camino corto. Esta es una bandera, por lo que el valor aquí puede ser 0 o 1:

Diapositivas-1.032
La bandera de arco grande decide si debemos llegar al punto final por el camino corto o por el largo.

Finalmente, está la bandera de barrido. Básicamente, esto establece si debes dibujar el arco en el sentido de las agujas del reloj o en el sentido contrario a las agujas del reloj. Las dos opciones se reflejan entre sí. En el primer caso, lo configuras en cero; en el segundo, lo configuras en uno:

Diapositivas-1.035
La bandera de barrido decide si debemos ir en el sentido de las agujas del reloj o en el sentido contrario a las agujas del reloj.

Ahora que tienes un arco, configuras el otro. Aquí, establece el punto final al principio. A las mismas coordenadas que utilizó para el comando de desplazamiento.

Luego puedes usar los mismos radios, pero tienes que cambiar la bandera de arco grande y la bandera de barrido para terminar con una luna:

<svg width="30" height="30">
  <path
    fill="currentColor"
    d="
      M 23, 5
      A 12 12 0 1 0 23, 25
      A 12 12 0 0 1 23, 5"
  />
</svg>
Diapositivas-1.039
El ícono de la luna terminado.

¿Cómo puedes usar estos dos íconos en un botón para alternar el modo claro y el modo oscuro en JavaScript?

Cómo alternar el modo oscuro con JavaScript

Si desea anular la configuración del sistema o del navegador para el modo oscuro con un cambio manual, ya no puede confiar en la consulta de medios CSS. Esto funciona para representar la interfaz de usuario según la configuración, pero no puede anularla desde JavaScript.

Diapositivas-1.041
No puedes anular una consulta de medios CSS

En su lugar, puede definir una dark-modeclase y alternarla desde JavaScript.

En CSS, defina una clase que cambiará la misma configuración que hizo antes la consulta de medios. Luego, en JavaScript, puedes usar la misma lógica que tenías antes para obtener la configuración predeterminada y luego agregar o eliminar esta clase.

Puede configurar esta clase en la carga de nuestra página inicial y alternarla si hace clic en un botón:

body {
  font-family: Montserrat;
  margin: 50px;
  max-width: 500px;
}

.dark-mode {
  background-color: black;
  color: white;
}

Ahora, ¿cómo se alterna esto con un botón? En su archivo HTML, agregue un elemento de botón con un controlador de eventos. Luego, mueva ambos SVG dentro de este elemento de botón y asígneles ID. Alternará la visibilidad de estos íconos desde JavaScript:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>Dark Mode</title>
    <link rel="stylesheet" href="index.css" />
    <script src="index.js" defer></script>
  </head>

  <body>
    <p>
      How to detect dark mode in CSS and in JavaScript? How can we override it
      manually with a toggle button? In this quick tutorial, we look into
      detecting dark mode in CSS and JavaScript, and then we create a toggle
      button with SVG to override the default behavior.
    </p>

    <button onclick="toggleDarkMode()">
      <svg width="30" height="30" id="light-icon">
        <circle cx="15" cy="15" r="6" fill="currentColor" />

        <line
          id="ray"
          stroke="currentColor"
          stroke-width="2"
          stroke-linecap="round"
          x1="15"
          y1="1"
          x2="15"
          y2="4"
        ></line>

        <use href="#ray" transform="rotate(45 15 15)" />
        <use href="#ray" transform="rotate(90 15 15)" />
        <use href="#ray" transform="rotate(135 15 15)" />
        <use href="#ray" transform="rotate(180 15 15)" />
        <use href="#ray" transform="rotate(225 15 15)" />
        <use href="#ray" transform="rotate(270 15 15)" />
        <use href="#ray" transform="rotate(315 15 15)" />
      </svg>
      <svg width="30" height="30" id="dark-icon">
        <path
          fill="currentColor"
          d="
          M 23, 5
          A 12 12 0 1 0 23, 25
          A 12 12 0 0 1 23, 5"
        />
      </svg>
    </button>
  </body>
</html>
Mueva ambos íconos SVG a un buttonelemento y establezca uno único idpara cada uno

También puede desarmar la apariencia predeterminada del elemento del botón en CSS, excepto la propiedad del cursor. Deberías tener eso como puntero:

. . .

button {
  all: unset;
  cursor: pointer;
}

. . .

Ahora, implementemos el controlador de eventos en JavaScript. Primero necesitas acceder a los íconos SVG por ID:

const lightIcon = document.getElementById("light-icon");
const darkIcon = document.getElementById("dark-icon");

. . .

Luego, agregue la dark-modeclase al bodyelemento en caso de que esté en modo oscuro y oculte uno de los íconos SVG según la darkModevariable. Detectas el modo oscuro como lo hiciste antes:

. . .

// Check if dark mode is preferred
const darkModeMediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
let darkMode = darkModeMediaQuery.matches;

// Set dark-mode class on body if darkMode is true and pick icon
if (darkMode) {
  document.body.classList.add("dark-mode");
  darkIcon.setAttribute("display", "none");
} else {
  lightIcon.setAttribute("display", "none");
}

. . .

Y finalmente, podemos implementar la función que invierte la darkModepropiedad. Esta función alterna la dark-modeclase en el elemento del cuerpo y alterna los íconos SVG:

. . .

// Toggle dark mode on button click
function toggleDarkMode() {
  // Toggle darkMode variable
  darkMode = !darkMode;

  // Toggle dark-mode class on body
  document.body.classList.toggle("dark-mode");

  // Toggle light and dark icons
  if (darkMode) {
    lightIcon.setAttribute("display", "block");
    darkIcon.setAttribute("display", "none");
  } else {
    lightIcon.setAttribute("display", "none");
    darkIcon.setAttribute("display", "block");
  }
}

Ahora, esto funciona: de forma predeterminada, todavía tienes la configuración del sistema operativo o del navegador. Pero una vez que haga clic en este botón, lo anulará manualmente.

Captura de pantalla-2024-03-07-at-12.31.42
Aspecto final en modo claro.
Captura de pantalla-2024-03-07-at-12.31.27
Aspecto final en modo oscuro

Próximos pasos

Con todo esto en su lugar, tiene una funcionalidad que toma la configuración del modo oscuro del navegador o del sistema operativo de forma predeterminada, y puede anularla con un atractivo botón de alternancia. En la versión de YouTube de este tutorial , también puede aprender cómo localStorageguardar esta configuración para la próxima sesión.

Si desea obtener más información sobre los SVG, consulte SVG-tutorial.com , donde puede obtener más información sobre los SVG desde niveles principiantes hasta avanzados con muchos ejemplos excelentes.

Si desea utilizar este comportamiento en un juego, consulta el tutorial del juego Gorillas JavaScript , donde creamos el juego completo desde cero. Es un enorme tutorial de dos horas que cubre el dibujo en un elemento Canvas con JavaScript y toda la lógica del juego con JavaScript simple.