Criando um Relógio com as Novas Funções de Trigonometria CSS sin() e cos()

Criando um Relógio com as Novas Funções de Trigonometria CSS sin() e cos()

Nó Fonte: 1999799

As funções de trigonometria CSS estão aqui! Bem, eles são se você estiver usando as versões mais recentes do Firefox e do Safari. Ter esse tipo de poder matemático em CSS abre um monte de possibilidades. Neste tutorial, pensei em mergulhar os pés na água para ter uma ideia de algumas das funções mais recentes: sin() e cos().

Existem outras funções de trigonometria em andamento - incluindo tan() - então por que focar apenas em sin() e cos()? Eles são perfeitos para a ideia que tenho em mente, que é colocar o texto ao longo da borda de um círculo. Isso foi abordado aqui no CSS-Tricks quando Chris compartilhou uma abordagem que usa um mixin Sass. Isso foi há seis anos, então vamos dar um tratamento de última geração.

Aqui está o que eu tenho em mente. Novamente, só é suportado no Firefox e Safari no momento:

Portanto, não é exatamente como palavras formando uma forma circular, mas estamos colocando caracteres de texto ao longo do círculo para formar um mostrador de relógio. Aqui estão algumas marcações que podemos usar para dar o pontapé inicial:

<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>

Em seguida, aqui estão alguns estilos super básicos para o .clock-face recipiente. resolvi usar o <time> marcar com um 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);
}

Eu decorei um pouco as coisas lá, mas apenas para obter a forma básica e a cor de fundo para nos ajudar a ver o que estamos fazendo. Observe como salvamos o width valor num Variável CSS. Usaremos isso mais tarde. Não há muito o que ver até agora:

Grande círculo colorido de tomate com uma lista vertical de números de 1 a 12 à esquerda.

Parece algum tipo de experimento de arte moderna, certo? Vamos introduzir uma nova variável, --_r, para armazenar o círculo raio, que é igual à metade da largura do círculo. Desta forma, se a largura (--_w) muda, o valor do raio (--_r) também será atualizado — graças a outra função matemática do CSS, calc():

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

Agora, um pouco de matemática. Um círculo tem 360 graus. Temos 12 rótulos em nosso relógio, então queremos colocar os números a cada 30 graus (360 / 12). Na terra da matemática, um círculo começa às 3 horas, então meio-dia é na verdade menos 90 graus disso, que é 270 graus (360 - 90).

Vamos adicionar outra variável, --_d, que podemos usar para definir um grau valor para cada número no mostrador do relógio. Vamos incrementar os valores em 30 graus para completar nosso 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, agora é a hora de sujar as mãos com o sin() e cos() funções! O que queremos fazer é usá-los para obter as coordenadas X e Y para cada número, para que possamos colocá-los corretamente no mostrador do relógio.

A fórmula da coordenada X é radius + (radius * cos(degree)). Vamos conectar isso em nosso novo --_x variável:

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

A fórmula para a coordenada Y é radius + (radius * sin(degree)). Temos o que precisamos para calcular que:

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

Há algumas coisas de limpeza que precisamos fazer para configurar os números, então vamos colocar um estilo básico neles para garantir que eles estejam absolutamente posicionados e posicionados com nossas 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);
}

Perceber --_sz, que usaremos para o width e height dos números em um momento. Vamos ver o que temos até agora.

Grande círculo colorido de tomate com rótulos de número de horas descentrados ao longo de sua borda.

Isso definitivamente se parece mais com um relógio! Veja como o canto superior esquerdo de cada número está posicionado no lugar correto ao redor do círculo? Precisamos “encolher” o raio ao calcular as posições de cada número. Pudermos deduzir o tamanho de um número (--_sz) do tamanho do círculo (--_w), antes de calcularmos o raio:

--_r: calc((var(--_w) - var(--_sz)) / 2);
Grande círculo colorido de tomate com rótulos de número de hora ao longo de sua borda arredondada.

Muito melhor! Vamos mudar as cores, para ficar mais elegante:

Um mostrador de relógio branco com números sobre um fundo cinza escuro. O relógio não tem braços.

Podemos parar aqui mesmo! Atingimos o objetivo de colocar o texto ao redor de um círculo, certo? Mas o que é um relógio sem braços para mostrar horas, minutos e segundos?

Vamos usar uma única animação CSS para isso. Primeiro, vamos adicionar mais três elementos à nossa marcação,

<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>

Em seguida, algumas marcações comuns para todos os três braços. Novamente, a maior parte disso é apenas garantir que os braços estejam absolutamente posicionados e colocados de acordo:

.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);
}

Vamos usar o mesma animação para os três braços:

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

A única diferença é o tempo que os braços individuais levam para fazer uma volta completa. Para o braço de horas, leva 12 horas para fazer uma volta completa. O animation-duration propriedade aceita apenas valores em milissegundos e segundos. Vamos ficar com os segundos, que são 43,200 segundos (60 seconds * 60 minutes * 12 hours).

animation: turn 43200s infinite;

Leva 1 hora para o braço de minutos para fazer uma volta completa. Mas queremos que isso seja um animação em várias etapas então o movimento entre os braços é escalonado em vez de linear. Vamos precisar de 60 passos, um para cada minuto:

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

A braço de segundos is quase o mesmo como o braço de minutos, mas a duração é de 60 segundos em vez de 60 minutos:

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

Vamos atualizar as propriedades que criamos nos estilos comuns:

.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;
}

E se quisermos começar no horário atual? Precisamos de um pouco 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`);

eu já adicionei id="app" ao mostrador do relógio e defina duas novas propriedades personalizadas nele que definem um valor negativo animation-delay, como Mate Marschalko fez quando ele compartilhou um relógio somente CSS. O getHours() método de JavaScipt's Date objeto está usando o formato de 24 horas, então usamos o remainder operador para convertê-lo em formato de 12 horas.

No CSS, precisamos adicionar o animation-delay também:

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

Só mais uma coisa. Usando CSS @supports e as propriedades que já criamos, podemos fornecer um fallback para navegadores que não suportam sin() e cos(). (Obrigado, 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)))
  }
}

E, voilà! Nosso relógio acabou! Aqui está a demonstração final mais uma vez. Novamente, só é suportado no Firefox e Safari no momento.

O que mais podemos fazer?

Apenas brincando aqui, mas podemos rapidamente transformar nosso relógio em uma galeria de imagens circulares, substituindo o <time> tags com <img> em seguida, atualizando a largura (--_w) e raio (--_r) valores:

Vamos tentar mais um. Mencionei anteriormente como o relógio parecia um experimento de arte moderna. Podemos nos apoiar nisso e recriar um padrão que vi em um pôster (que infelizmente não comprei) em uma galeria de arte outro dia. Pelo que me lembro, chamava-se “Lua” e consistia em um monte de pontos formando um círculo.

Um grande círculo formado por um monte de círculos menores preenchidos de várias cores de tons de terra.

Usaremos uma lista não ordenada desta vez, pois os círculos não seguem uma ordem específica. Não vamos nem colocar todos os itens da lista na marcação. Em vez disso, vamos injetá-los com JavaScript e adicionar alguns controles que podemos usar para manipular o resultado final.

Os controles são entradas de faixa (<input type="range">) que vamos embrulhar em um <form> e escute o 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>

Vamos executar este método em “input”, que irá criar um monte de <li> elementos com o grau (--_d) variável que usamos anteriormente aplicada a cada um. Também podemos redirecionar nossa variável de raio (--_r).

Eu também quero que os pontos sejam de cores diferentes. Então, vamos randomizar (bem, não completamente randomizado) o valor de cor HSL para cada item da lista e armazená-lo como uma nova variável 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;
}

A random() método escolhe um valor dentro de um intervalo definido de números:

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

E é isso. Usamos JavaScript para renderizar a marcação, mas assim que ela é renderizada, não precisamos mais dela. O sin() e cos() funções nos ajudam a posicionar todos os pontos nos lugares certos.

Considerações finais

Colocar coisas ao redor de um círculo é um exemplo bastante básico para demonstrar os poderes das funções trigonométricas como sin() e cos(). Mas é clientes legal que estamos obtendo recursos CSS modernos que fornecem novas soluções para soluções alternativas antigas. Tenho certeza de que veremos casos de uso muito mais interessantes, complexos e criativos, especialmente quando o suporte do navegador chegar ao Chrome e ao Edge.

Carimbo de hora:

Mais de Truques CSS