Creación de un reloj con las nuevas funciones trigonométricas sin() y cos() de CSS

Creación de un reloj con las nuevas funciones trigonométricas sin() y cos() de CSS

Nodo de origen: 1999799

¡Las funciones de trigonometría CSS están aquí! Bueno, lo son si estás usando las últimas versiones de Firefox y Safari, eso es. Tener este tipo de poder matemático en CSS abre un montón de posibilidades. En este tutorial, pensé que sumergiríamos los dedos de los pies en el agua para tener una idea de algunas de las funciones más nuevas: sin() y cos().

Hay otras funciones trigonométricas en camino, incluyendo tan() — Entonces, ¿por qué centrarse solo en sin() y cos()? Son perfectos para la idea que tengo en mente, que es colocar texto a lo largo del borde de un círculo. Eso ha sido cubierto aquí en CSS-Tricks cuando Chris compartió un enfoque que usa una mezcla de Sass. Eso fue hace seis años, así que démosle un tratamiento vanguardista.

Esto es lo que tengo en mente. Nuevamente, solo es compatible con Firefox y Safari en este momento:

Entonces, no es exactamente como si las palabras formaran una forma circular, pero estamos colocando caracteres de texto a lo largo del círculo para formar la esfera de un reloj. Aquí hay algunas marcas que podemos usar para comenzar:

<div class="clock"> <div class="clock-face"> <time datetime="12:00">12</time> <time datetime="1:00">1</time> <time datetime="2:00">2</time> <time datetime="3:00">3</time> <time datetime="4:00">4</time> <time datetime="5:00">5</time> <time datetime="6:00">6</time> <time datetime="7:00">7</time> <time datetime="8:00">8</time> <time datetime="9:00">9</time> <time datetime="10:00">10</time> <time datetime="11:00">11</time> </div>
</div>

A continuación, aquí hay algunos estilos súper básicos para el .clock-face envase. Decidí usar el <time> etiqueta con un datetime atributo. 

.clock { --_ow: clamp(5rem, 60vw, 40rem); --_w: 88cqi; aspect-ratio: 1; background-color: tomato; border-radius: 50%; container-type: inline; display: grid; height: var(--_ow); place-content: center; position: relative; width var(--_ow);
}

Decoré un poco las cosas allí, pero solo para obtener la forma básica y el color de fondo para ayudarnos a ver lo que estamos haciendo. Observe cómo guardamos el width valor en un Variable CSS. Usaremos eso más tarde. No hay mucho que ver hasta ahora:

Círculo grande de color tomate con una lista vertical de números del 1 al 12 a la izquierda.

Parece una especie de experimento de arte moderno, ¿verdad? Introduzcamos una nueva variable, --_r, para almacenar el círculo radius, que es igual a la mitad del ancho del círculo. De esta manera, si el ancho (--_w) cambia, el valor del radio (--_r) también se actualizará, gracias a otra función matemática de CSS, calc():

.clock { --_w: 300px; --_r: calc(var(--_w) / 2); /* rest of styles */
}

Ahora, un poco de matemáticas. Un círculo es de 360 ​​grados. Tenemos 12 etiquetas en nuestro reloj, así que queremos colocar los números cada 30 grados (360 / 12). En math-land, un círculo comienza a las 3 en punto, por lo que el mediodía es en realidad menos 90 grados de eso, que son 270 grados (360 - 90).

Agreguemos otra variable, --_d, que podemos usar para establecer un la licenciatura valor de cada número en la esfera del reloj. Vamos a incrementar los valores en 30 grados para completar nuestro círculo:

.clock time:nth-child(1) { --_d: 270deg; }
.clock time:nth-child(2) { --_d: 300deg; }
.clock time:nth-child(3) { --_d: 330deg; }
.clock time:nth-child(4) { --_d: 0deg; }
.clock time:nth-child(5) { --_d: 30deg; }
.clock time:nth-child(6) { --_d: 60deg; }
.clock time:nth-child(7) { --_d: 90deg; }
.clock time:nth-child(8) { --_d: 120deg; }
.clock time:nth-child(9) { --_d: 150deg; }
.clock time:nth-child(10) { --_d: 180deg; }
.clock time:nth-child(11) { --_d: 210deg; }
.clock time:nth-child(12) { --_d: 240deg; }

OK, ahora es el momento de ensuciarnos las manos con el sin() y cos() funciones! Lo que queremos hacer es usarlos para obtener las coordenadas X e Y de cada número para que podamos colocarlos correctamente alrededor de la esfera del reloj.

La fórmula para la coordenada X es radius + (radius * cos(degree)). Conectemos eso a nuestro nuevo --_x variable:

--_x: calc(var(--_r) + (var(--_r) * cos(var(--_d))));

La fórmula para la coordenada Y es radius + (radius * sin(degree)). Tenemos lo que necesitamos para calcular que:

--_y: calc(var(--_r) + (var(--_r) * sin(var(--_d))));

Hay algunas cosas de limpieza que debemos hacer para configurar los números, así que vamos a ponerles un estilo básico para asegurarnos de que estén absolutamente posicionados y colocados con nuestras coordenadas:

.clock-face time { --_x: calc(var(--_r) + (var(--_r) * cos(var(--_d)))); --_y: calc(var(--_r) + (var(--_r) * sin(var(--_d)))); --_sz: 12cqi; display: grid; height: var(--_sz); left: var(--_x); place-content: center; position: absolute; top: var(--_y); width: var(--_sz);
}

Aviso --_sz, que usaremos para el width y height de los números en un momento. Veamos lo que tenemos hasta ahora.

Gran círculo de color tomate con etiquetas de número de hora descentradas a lo largo de su borde.

¡Esto definitivamente se parece más a un reloj! ¿Ves cómo la esquina superior izquierda de cada número se coloca en el lugar correcto alrededor del círculo? Necesitamos "reducir" el radio al calcular las posiciones para cada número. Podemos deducir el tamaño de un número (--_sz) del tamaño del círculo (--_w), antes de calcular el radio:

--_r: calc((var(--_w) - var(--_sz)) / 2);
Gran círculo de color tomate con etiquetas de números de hora a lo largo de su borde redondeado.

¡Mucho mejor! Cambiemos los colores, para que se vea más elegante:

Un reloj blanco con números sobre un fondo gris oscuro. El reloj no tiene brazos.

¡Podríamos parar aquí mismo! Logramos el objetivo de colocar texto alrededor de un círculo, ¿verdad? Pero, ¿qué es un reloj sin brazos para mostrar horas, minutos y segundos?

Usemos una sola animación CSS para eso. Primero, agreguemos tres elementos más a nuestro marcado,

<div class="clock"> <!-- after <time>-tags --> <span class="arm seconds"></span> <span class="arm minutes"></span> <span class="arm hours"></span> <span class="arm center"></span>
</div>

Luego, un marcado común para los tres brazos. Nuevamente, la mayor parte de esto es solo asegurarse de que los brazos estén absolutamente posicionados y colocados en consecuencia:

.arm { background-color: var(--_abg); border-radius: calc(var(--_aw) * 2); display: block; height: var(--_ah); left: calc((var(--_w) - var(--_aw)) / 2); position: absolute; top: calc((var(--_w) / 2) - var(--_ah)); transform: rotate(0deg); transform-origin: bottom; width: var(--_aw);
}

Usaremos el misma animación para los tres brazos:

@keyframes turn { to { transform: rotate(1turn); }
}

La única diferencia es el tiempo que tardan los brazos individuales en dar una vuelta completa. Para el brazo de horas, se necesita 12 horas para dar una vuelta completa. El animation-duration La propiedad solo acepta valores en milisegundos y segundos. Sigamos con los segundos, que son 43,200 segundos (60 seconds * 60 minutes * 12 hours).

animation: turn 43200s infinite;

Se necesita 1 hora para brazo de minutos para dar una vuelta completa. Pero queremos que esto sea un animación de varios pasos por lo que el movimiento entre los brazos es escalonado en lugar de lineal. Necesitaremos 60 pasos, uno por cada minuto:

animation: turn 3600s steps(60, end) infinite;

El brazo de segundos is casi lo mismo como el brazo de minutos, pero la duración es de 60 segundos en lugar de 60 minutos:

animation: turn 60s steps(60, end) infinite;

Actualicemos las propiedades que creamos en los estilos comunes:

.seconds { --_abg: hsl(0, 5%, 40%); --_ah: 145px; --_aw: 2px; animation: turn 60s steps(60, end) infinite;
}
.minutes { --_abg: #333; --_ah: 145px; --_aw: 6px; animation: turn 3600s steps(60, end) infinite;
}
.hours { --_abg: #333; --_ah: 110px; --_aw: 6px; animation: turn 43200s linear infinite;
}

¿Qué pasa si queremos empezar en el momento actual? Necesitamos un poco de JavaScript:

const time = new Date();
const hour = -3600 * (time.getHours() % 12);
const mins = -60 * time.getMinutes();
app.style.setProperty('--_dm', `${mins}s`);
app.style.setProperty('--_dh', `${(hour+mins)}s`);

he añadido id="app" a la esfera del reloj y establezca dos nuevas propiedades personalizadas que establezcan un valor negativo animation-delay, como lo hizo Mate Marschalko cuando compartió un reloj de solo CSSgetHours() método de JavaScipt Date objeto está usando el formato de 24 horas, por lo que usamos el remainder operador para convertirlo al formato de 12 horas.

En el CSS, necesitamos agregar el animation-delay también:

.minutes { animation-delay: var(--_dm, 0s); /* other styles */
} .hours { animation-delay: var(--_dh, 0s); /* other styles */
}

Solo una cosa más. Usando CSS @supports y las propiedades que ya hemos creado, podemos proporcionar una alternativa a los navegadores que no son compatibles sin() y cos(). (¡Gracias, Temani Afif!):

@supports not (left: calc(1px * cos(45deg))) {
  time {
    left: 50% !important;
    top: 50% !important;
    transform: translate(-50%,-50%) rotate(var(--_d)) translate(var(--_r)) rotate(calc(-1*var(--_d)))
  }
}

¡Y voilá! ¡Nuestro reloj está listo! Aquí está la demostración final una vez más. Nuevamente, solo es compatible con Firefox y Safari en este momento.

qué más podemos hacer?

Solo estoy jugando aquí, pero podemos convertir rápidamente nuestro reloj en una galería de imágenes circular reemplazando el <time> etiquetas con <img> luego actualizando el ancho (--_w) y radio (--_r) valores:

Probemos uno más. Mencioné anteriormente que el reloj se parecía a un experimento de arte moderno. Podemos apoyarnos en eso y recrear un patrón que vi en un póster (que desafortunadamente no compré) en una galería de arte el otro día. Según recuerdo, se llamaba “Luna” y consistía en un montón de puntos formando un círculo.

Un círculo grande se formó a partir de un grupo de círculos rellenos más pequeños de varios colores tierra.

Usaremos una lista desordenada esta vez ya que los círculos no siguen un orden en particular. Ni siquiera vamos a poner todos los elementos de la lista en el marcado. En cambio, inyectémoslos con JavaScript y agreguemos algunos controles que podemos usar para manipular el resultado final.

Los controles son entradas de rango (<input type="range">) que envolveremos en un <form> y escucha el input evento.

<form id="controls"> <fieldset> <label>Number of rings <input type="range" min="2" max="12" value="10" id="rings" /> </label> <label>Dots per ring <input type="range" min="5" max="12" value="7" id="dots" /> </label> <label>Spread <input type="range" min="10" max="40" value="40" id="spread" /> </label> </fieldset>
</form>

Ejecutaremos este método en "entrada", lo que creará un montón de <li> elementos con el grado (--_d) variable que usamos anteriormente aplicada a cada uno. También podemos reutilizar nuestra variable de radio (--_r).

También quiero que los puntos sean de diferentes colores. Entonces, aleatoricemos (bueno, no completamente randomizado) el valor de color HSL para cada elemento de la lista y almacenarlo como una nueva variable CSS, --_bgc:

const update = () => { let s = ""; for (let i = 1; i <= rings.valueAsNumber; i++) { const r = spread.valueAsNumber * i; const theta = coords(dots.valueAsNumber * i); for (let j = 0; j < theta.length; j++) { s += `<li style="--_d:${theta[j]};--_r:${r}px;--_bgc:hsl(${random( 50, 25 )},${random(90, 50)}%,${random(90, 60)}%)"></li>`; } } app.innerHTML = s;
}

El random() El método elige un valor dentro de un rango definido de números:

const random = (max, min = 0, f = true) => f ? Math.floor(Math.random() * (max - min) + min) : Math.random() * max;

Y eso es. Usamos JavaScript para renderizar el marcado, pero tan pronto como se renderiza, realmente no lo necesitamos. El sin() y cos() funciones nos ayudan a colocar todos los puntos en los lugares correctos.

Reflexiones finales

Colocar cosas alrededor de un círculo es un ejemplo bastante básico para demostrar los poderes de las funciones trigonométricas como sin() y cos(). Pero es realmente Genial que obtengamos funciones modernas de CSS que brindan nuevas soluciones para soluciones antiguas. Estoy seguro de que veremos casos de uso mucho más interesantes, complejos y creativos, especialmente a medida que la compatibilidad con navegadores llegue a Chrome y Edge.

Sello de tiempo:

Mas de Trucos CSS