Blog

Entre um trabalho e outro, às vezes conseguimos escrever alguma coisa...

Criando um relógio no estilo iOS usando o canvas

Parte 2: Desenhando os elementos do relógio


Como todo projeto organizado, precisamos definir os objetivos, levantar o que será necessário, preparar as ferramentas. Com nosso relógio não será diferente. Primeiro, vamos começar desenhando e definindo os primeiros elementos de nosso relógio como o círculo que irá compor o background, os números e os ponteiros de horas, minutos e segundos.

O código fonte mostrado neste post e nas próximas 2 partes são trechos do projeto completo, que será disponibilizado para download ao publicarmos a 4ª parte. Então alguns trechos poderão não fazer sentido inicialmente, mas basta acompanhar e aos poucos o relógio ganhará movimento e será finalizado.

Alguns métodos e propriedades nativas do canvas que serão utilizados, estão abaixo classificados quanto a sua função :

Definir cores, estilos para cores e sombras para elementos

fillStyle Defini a cor, gradiente ou padrão(imagem) usado para preencher o desenho
strokeStyle Defini a cor, gradiente ou padrão(imagem) usado em bordas
shadowColor Defini a cor usada para sombra
shadowBlur Defini o nível de "blur" ou "desfoque" da sombra para tornar mais real
shadowOffsetX Defini a distância horizontal da sombra em relação ao elemento
shadowOffsetY Defini a distância vertical da sombra em relação ao elemento
createLinearGradient() Cria um gradiente linear usado em um contexto canvas
createRadialGradient() Cria um gradiente radial ou circular usado em um contexto canvas
addColorStop() Método complementar dos gradientes linear e radial para adicionar e misturar diferentes cores


Definir estilo para linha simples

lineWidth Defini a largura de uma linha


Criar Retângulos e limpar pixel em um determinado retângulo

rect() Criar retângulos definindo largura, altura, posição horizontal e vertical
fillRect() Semelhante ao método "rect()", com a diferença que este desenha um retângulo preenchido(cheio).
clearRect() Limpa pixels de um determinado retângulo


Definir caminhos para criação de formas e elementos

fill() Preenche uma forma no caminho em contexto
stroke() Desenha uma borda no atual elemento em contexto
beginPath() Defini ou reinicia o início do caminho
closePath() Fecha o caminho atual retornando para o anterior
moveTo() Move todos os elementos do caminho horizontalmente e verticalmente em relação ao eixo das abscisas e ordenadas do canvas
lineTo() Adiciona pontos criando uma reta(linha) a partir do último ponto definido na tela
arc() Cria arcos, círculos ou ate mesmo parte de círculos.


Transformações em elementos, para tamanhos e posições

scale() Defini escalas para um desenho, aumentando ou diminuindo sua proporção
rotate() Defini em quantos graus um desenho irá rotacionar, em graus radianos
translate() Defini em quantos pixels será deslocado horizontalmente ou verticalmente um desenho


Escrever textos, definir fontes e estilos de cores

font Defini a família da fonte e outras propriedades como tamanho para o texto em contexto
textAlign Defini o alinhamento do texto em contexto, exemplo, a esquerda, a direita ou centralizado
textBaseline Defini a base do texto em contexto, exemplo, meio.
fillText() Desenha um texto preenchido na tela do canvas, definindo sua posição horizontal e vertical em relação ao eixo zero das abscisas e ordenadas do canvas


Composição de opacidade ou transparência em elementos

globalAlpha Defini o alfa da transparência para elementos em contexto


Métodos para definição de contextos e armazenamento de informações

save() Salva o contexto atual do canvas
restore() Restaura o contexto anterior ao atual salvo


Desenhando o círculo drawBackgroundClock()


Algumas das variáveis utilizadas no escopo desta função são:

ang Armazena o valor em graus radianos de 180º
pi Armazena o valor de 2 ? ( onde PI vale aproximadamente 3,14...)
bg Armazena uma referência ao objeto background que contém as cores do relógio


Para desenhar nosso relógio, iremos montar 4 círculos sendo eles dois completos e dois meia lua, para formar efeitos de gradiente e deixar com um realismo mais próximo do iOS versão 6.

Nas linhas 91, 92 e 93 acima, eu defino três variáveis para uso interno da função, que desenhará o círculo. Sendo uma delas a "ang" para guardar o valor de um ângulo de 180 graus que será usado em duas circunferências do background. E outro valor guardado, será a variável "bg", um objeto já definido no início do projeto que contém as cores desse background, onde há a possibilidade de alteração dessas cores através de valores já definidos. Se você não conhece objetos em javascript, confira no link Objeto e funções construtoras mais informações sobre o assunto.

Para efeito de reusabilidade, definimos uma função genérica para criação de um círculo. Através dela, definiremos cor, borda, início do ângulo, fim do ângulo da circunferência, posição x do círculo, posição y do círculo, se vai possuir escala em x ou em y, e também se irá possuir efeito radial em sua cor. Definidas desta forma a função circle() assume os seguintes valores:

color Se definido, preenche o círculo com a cor escolhida em hexadecimal ou RGB
ang Início do ângulo(radianos) para formar o círculo
end_ang Fim do ângulo(radianos) para formar o círculo
x Distância horizontal em pixels do círculo em contexto
y

Distância vertical em pixels do círculo em contexto

scX Valor da escala horizontal para o círculo em contexto
scY Valor da escala vertical para o círculo em contexto
radial Definir se o círculo conterá efeito gradiente radial


Nas linhas 95 a 123, está escrita a função que desenha um círculo no canvas contendo todas as informações descritas no parágrafo anterior. A importância de possuir uma função genérica para criar um círculo é justamente evitar repetição de código milhares de vezes para a mesma finalidade, desta forma o código fica limpo e mais prático. Claro, que esta função não está perfeita, mais detalhes poderiam ser informados via parâmetro, mas este não é nosso objetivo, então para exercício de casa fica a dica para tornar aberta a escolha de tal tipo de radial, tamanho da borda e entre outras informações.

As quatro últimas linhas 125, 126, 127 e 128 são nossos círculos desenhados que compõem o background do relógio.

A primeira linha 125, é a borda do nosso relógio definida na cor cinza(#CCC) por padrão dentro da função, funcionando assim, se o a cor for informada, a circunferência assumirá a cor desejada, caso omitido a cor, cria a borda por padrão.

A segunda linha 126, é o cinza mais claro (#F3F2F2), que preencherá nosso relógio todo ficando desta forma:

Se tudo ocorreu certinho, retornando para as linhas de códigos, 127 e 128 finalizam nosso background, desenhando duas circunferências em forma de meia lua, de forma, que uma terá uma meia lua um pouco maior que a outra para formar um efeito de radial personalizado, uma espécie de curva no nosso relógio. Veja o resultado.

Depois de executado, este será nosso background para o relógio durante o dia. Agora vamos para os números.

Desenhando e posicionando os números drawHours()


Algumas das variáveis utilizadas no escopo desta função são :

i Controladora da iteração dos números 1 ao 12
x Posição horizontal em pixels do número em contexto
y Posição vertical em pixels do número em contexto
ang Ângulo em radianos do número em contexto
dif Diferença que será utilizada no cálculo do ângulo em relação ao total
total Total de números corrente que serão trabalhados
percentScreen Valor de 10% porcentos da tela armazenado
distance Distância correspondente de uma extremidade a outra da circunferência dos números
radiusHours Raio da circunferência dos números

font

Fonte definida para os números(Trebuchet MS)


Para desenhar os números e posicioná-los corretamente em nosso relógio, vamos lembrar um pouco da Trigonometria estudada no ensino médio. A figura abaixo mostra um círculo com as posições em graus, para os locais dos números do relógio (lembre que o círculo possui 360 graus, divididos em 12, nos dão intervalos de 30 graus):

Nosso problema é encontrar a posição do canvas onde os números do relógio devem ser colocados. Vamos ver, por exemplo, o número 2. No desenho abaixo, vemos que o número 2 está a 30º de distância do número 3 (que fica exatamente sobre o eixo X):


O local onde o número 2 está é exatamente uma posição x,y sobre o círculo. Por definição, o tamanho x é o cosseno do ângulo formado entre o eixo horizontal e a seta que indica o número 2, ou seja, cos(30º). A altura do número 2 é o valor y que, por definição, é o seno do ângulo indicado, ou seja, sen(30º). Veja isso na imagem abaixo:


Assim, temos que é fácil encontrar as posições x,y de qualquer um dos números do relógio, sabendo o ângulo de distância entre ele e o número 3 (posição 0º, segundo os padrões adotados).

Temos então que, para qualquer número do relógio, os valores de x e y serão, respectivamente, o coseno e o seno do ângulo alfa formado em relação ao eixo horizontal, conforme mostrado na figura:

x,y = cos(?),sen(?)

Seguindo, esta ideia, a posição do número 1, a 60º do eixo x, será

x = cos(60º) e y = sen(60º)


Implementando o cálculo trigonométrico em JavaScript

O cálculo trigonométrico em JavaScript trabalha com ângulos na forma de radianos. Precisamos então transformar os graus em radianos. Isso é feito sabendo que a circunferência mede 2 ? r, onde r é o raio do círculo e ? é a constante (aprox.) 3,14.

Com o ângulo em radianos, encontramos a posição dos ponteiros, aplicando na fórmula :

x = DISTÂNCIA x RAIO x COS(ÂNGULO)

y = DISTÂNCIA x RAIO x SEN(ÂNGULO)

Onde, x e y correspondem respectivamente a distância exata em pixel em relação ao centro do círculo.

DISTÂNCIA = distância de uma ponta a outra do círculo

RAIO = distância em relação centro do círculo

ÂNGULO = ângulo que será calculado em radianos

Aplicando esta fórmula, todos os 12 números serão desenhados corretamente em nosso relógio. Para melhorar a apresentação aplicamos uma fonte já definida no início do nosso projeto (Trebuchet MS), guardada na variável font. O tamanho da fonte foi definido em 10% da largura da tela, guardada na variável percentScreen. Desta forma, qualquer tamanho do relógio terá números proporcionalmente adaptados.

Enfim, nosso relógio ganha uma cara nova ficando desta maneira :


Desenhando os ponteiros de horas, minutos e segundos drawHoursLine(), drawMinutesLine() e drawSecondLine()

Umas das fases mais simples do projeto é desenhar os ponteiros, pois o canvas permite desenhar livremente circunferências, linhas, retângulos e entre outros elementos já definidos. Utilizaremos o recurso de criar uma forma própria, neste caso, um triângulo. E para isso, definimos uma função genérica para criar os triângulos que serão nossos ponteiros da hora e do minuto. O ponteiro dos segundos terá uma largura mais fina do que os outros, ficando em forma de uma reta.

Veja o código:

A função para desenhar nossos triângulos é drawPonteiro() nas linhas 167 a 187 do projeto correspondem ao escopo da função, ela recebe algumas informações como o "pointer" ou ponteiro no instante, assim como, quantos ângulos este ponteiro irá se deslocar, a diferença entre o instante do ângulo e o total, o total, altura do triângulo, largura do triângulo, cores 1 e 2 para fazer gradiente, posicionar tantos x em relação ao centro e informar o tipo de gradiente. Assim Definida ela assumirá os seguintes valores:

pointer Um número que correspondente o determinado instante do ponteiro
dif Diferença entre o número atual e o total utilizado no cálculo do ângulo
total Total de números que serão convertidos em graus radianos para formar os ângulos
heightLine Altura do ponteiro em pixels
widthLine Largura do ponteiro em pixels
color1 Cor usada no efeito linear gradiente
color2

Cor usada no efeito linear gradiente

x Distância horizontal em pixels em relação ao centro da tela
g Tipo de gradiente e configurações de posicionamento


Assumindo estes valores, podemos criar nossos ponteiros, é só conferir no projeto que existe três funções para desenhar o ponteiro da hora, minuto e segundo, respectivamente nomeados por, drawHoursLine(), drawMinutesLine(), drawSecondsLine(). Que irão utilizar a função drawPonteiro(), estas funções estão dividas justamente em cada módulo para um ponteiro diferente, justamente para alterar as configurações de algum ponteiro isoladamente.

As funções drawHoursLine() e drawMinutesLine() desenham um triângulo na tela a partir do centro sendo que o ponteiro da hora tem uma altura menor que do minuto. Todas informadas através de porcentagem em relação a tela, para que os ponteiros se adaptem ao tamanho que for selecionado para o relógio.


1. drawHoursLine() e drawMinutesLine()

A função do ponteiro das horas, precisa respectivamente do atributo hours do objeto clock, que será sempre atualizado gerando diferentes ângulos para nosso ponteiro. E em seguida, passamos a diferença entre o total de números(três), e o total de números(doze), para cálculo dos ângulos. Com estas informações nosso ponteiro de horas recebe todos os dados necessários para calcular os ângulos do ponteiro.

Desenhar os ponteiros agora e fácil só precisa informar o resto das propriedades de altura, largura, cor e distância horizontal. Para nosso relógio ganhar uma cara nova. Mas para matar dois leões de uma vez só, implementar o ponteiro do minuto, seguirá o mesmo procedimento do ponteiro da hora, com a diferença que o total de números agora será de sessenta e a diferença de quinze, sem falar, que este ponteiro terá uma altura um pouco maior que o ponteiro das horas. Com isso, nosso relógio fica desta maneira:


2. drawSecondsLine()

Feito os ponteiros das horas e minutos, agora adicione a função para o ponteiro do segundo as mesmas configurações do ponteiro dos minutos, com a diferença que agora a largura é 0.75 justamente para não ficar com aparência de um triângulo. A cor que foi definida para este ponteiro foi uma vermelha(#DB3E3E), onde mais tarde a mesma será alterada quando anoitecer.

Adicionada esta última função, veja como nosso relógio ficou :

Perceba que nosso relógio já esta quase pronto, no código completo você poderá conferir alguns recursos a mais implementados no relógio como marcadores central e entre outros recursos, afim de melhorar a visualização dos ponteiros e fazer um efeito de arredondamento para transmitir um pouco de realismo. Estes marcadores, utilizam a mesma ideia que aplicamos no background do relógio. Pois, eles são dois círculos desenhados em cima dos ponteiros.

No próximo post, onde iremos implementar os movimentos dos ponteiros.



PARTE 1: INTRODUÇÃO AO CANVAS

PARTE 2: DESENHANDO OS ELEMENTOS DO RELÓGIO

PARTE 3: ADICIONANDO MOVIMENTO AOS PONTEIROS

PARTE 4: FINALIZANDO O PROJETO


Para ser informado sobre a publicação da continuação, siga-nos no Twitter