Criando aplicativos HTML5

Usando telas do HTML5 para a visualização de dados

Brandon Satrom

Baixar o código de exemplo

Nos seus primeiros dias online, quando a Web era um pouco mais do que uma coleção de textos e links estáticos, havia um interesse crescente em oferecer suporte a outros tipos de conteúdo. Em 1993, Marc Andreessen, criador do navegador Mosaic, que evoluiria para o Netscape Navigator, propôs a marca IMG como padrão para a inserção de imagens embutidas no texto de uma página. Logo depois, a marca IMG se tornou realmente o padrão para adicionar recursos gráficos às páginas da Web, padrão esse que ainda está em uso atualmente. Você até poderia afirmar que, à medida que passamos da Web dos documentos para a Web dos aplicativos, a marca IMG tornou-se mais importante do que nunca.

A mídia, em geral, é certamente mais importante do que nunca e, embora a necessidade da mídia na Web tenha evoluído nos últimos 18 anos, a imagem permaneceu estática. Os autores da Web têm buscado usar mídias dinâmicas tais como áudio, vídeo e animações interativas em seus sites e aplicativos e, até recentemente, a principal solução era usar um plug-in como o Flash ou o Silverlight.

Atualmente, com o HTML5, os elementos de mídia no navegador ganharam reprovação. Você provavelmente já ouviu falar das novas marcas de Áudio e Vídeo, que permitem o funcionamento desses tipos de conteúdo como cidadãos de primeira classe no navegador, sem demandar o uso de plug-ins. O artigo do próximo mês cobrirá esses dois elementos e as suas APIs em detalhes. Você provavelmente também já ouviu falar do elemento tela, uma superfície de desenho com um conjunto rico de APIs de JavaScript, que possibilitam a criação e a manipulação imediata de imagens e animações. O que a marca IMG fez pelo conteúdo gráfico estático, a tela pode fazer pelo conteúdo dinâmico e programável por script.

Apesar de ser tão interessante, o elemento tela sofre de um pequeno problema de percepção. Devido ao seu potencial, a tela costuma ser demonstrada por meio de animações complexas ou jogos e, embora esses representem o que se pode fazer com esta, eles também podem levar o usuário a crer que trabalhar com a tela é complicado e difícil, algo que só deve ser usado para casos complexos como animações e jogos.

No artigo desse mês, eu gostaria de retroceder um pouco da sofisticação e complexidade da tela e mostrar algumas utilizações simples e básicas desta, com o objetivo de posicionar a tela como uma opção poderosa para a visualização de dados nos seus aplicativos da Web. Com isso em mente, eu focarei no modo como você pode iniciar na tela e desenhar linhas, formas e textos simples. Em seguida, falarei sobre como você pode trabalhar com gradientes nas formas, além de como adicionar imagens externas a uma tela. Finalmente, como fiz em todo essa série, eu concluirei com uma breve discussão sobre o suporte retroativo de tela para os navegadores mais antigos.

Introdução à Tela HTML5

De acordo com a especificação W3C HTML5 (w3.org/TR/html5/the-canvas-element.html), o elemento tela "fornece scripts com uma tela de bitmap dependente da resolução, que pode ser usada para a renderização de gráficos, jogos gráficos ou outras imagens visuais imediatamente.” A tela é realmente definida nas duas especificações W3C. A primeira faz parte da especificação principal do HTML5, onde o próprio elemento é definido em detalhes. Essa especificação abrange o modo de utilização do elemento tela, a obtenção do contexto de desenho, as APIs para exportação do conteúdo da tela e as considerações de segurança dos fornecedores do navegador. A segunda consiste no Contexto 2D da Tela do HTML (w3.org/TR/2dcontext), o qual abordarei daqui a pouco.

A introdução à tela é tão simples quanto adicionar um elemento <canvas> à marcação HTML5, como a seguir:

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="utf-8" />
        <title>My Canvas Demo </title>               
        <link rel="stylesheet" href="style.css" />
      </head>
      <body>
        <canvas id="chart" width="600" height="450"></canvas>       
      </body>
    </html>

Apesar de possuir um elemento tela no DOM agora, instalar essa marcação na página não resulta em nada, já que o elemento tela não possui um conteúdo até que você o adicione. É aí que entra o contexto de desenho. Para mostrá-lo onde a minha tela em branco se localiza, posso usar o CSS para estilizá-lo, então adicionarei uma linha pontilhada azul em torno do elemento em branco.

    canvas {
        border-width: 5px;
        border-style: dashed;
        border-color: rgba(20, 126, 239, 0.50)
    }

Quando a página for aberta no Internet Explorer 9+, Chrome, Firefox, Opera ou Safari, o resultado será ilustrado na Figura 1.

A Blank, Styled Canvas Element
Figura 1 Elemento tela em branco com estilo

Ao usar a tela, você executará a maior parte do trabalho no JavaScript, onde as APIs expostas de um contexto de desenho da tela podem ser aproveitadas para manipular cada pixel da superfície. Para obter o contexto de desenho da tela, você precisa obter o elemento de tela do DOM e, em seguida, chamar o método getContext desse elemento.

var _canvas = document.getElementById('chart');
var _ctx = _canvas.getContext("2d");

O GetContext retorna um objeto com uma API que pode ser usada para desenhar na tela em questão. O primeiro argumento desse método (nesse caso, o “2d”) especifica a API de desenho que se deseja utilizar para a tela. “2d” se refere ao Contexto 2D da Tela do HTML que mencionei anteriormente. Como você pode imaginar, 2D significa que esse é um contexto de desenho de duas dimensões. No momento da elaboração deste artigo, o Contexto 2D é o único contexto de desenho com suporte amplo e é o que iremos usar para esse artigo. Há um trabalho contínuo e de experimentação em torno do contexto de desenho 3D, de modo que a tela deve fornecer mais potência para os nossos aplicativos no futuro.

Desenhando linhas, formas e texto

Agora que temos o elemento de tela em nossa página e obtivemos o contexto de desenho no JavaScript, nós podemos começar a adicionar o conteúdo. Já que desejo focar na visualização de dados, usarei a tela para desenhar um gráfico de barras para representar os dados de vendas do mês atual de uma loja de artigos esportivos fictícia. Esse exercício demandará o desenho das linhas dos eixos; de formas e do preenchimento das barras, além do texto das etiquetas em cada eixo e barra.

Vamos iniciar com as linhas dos eixos x- e y-. Desenhar linhas (ou trajetórias) com o contexto de tela é um processo de duas etapas. Primeiramente, você "marca" as linhas na superfície usando uma série de chamadas lineTo(x, y) e moveTo(x, y). Cada método aproveita as coordenadas x- e y- no objeto da tela (começando do canto superior esquerdo) para usá-las ao executar a operação (e não as coordenadas da própria tela). O método moveTo o deslocará para as coordenadas especificadas e o método lineTo marcará uma linha das coordenadas atuais às coordenadas especificadas. Por exemplo, o código a seguir marcará o eixo y- na superfície:

// Draw y axis.
_ctx.moveTo(110, 5);
_ctx.lineTo(110, 375);

Se adicionar esse código a seu script e executá-lo no navegador, você observará que nada acontece. Uma vez que essa primeira etapa consiste apenas numa marcação, nada é desenhado na tela. A marcação simplesmente instrui o navegador a anotar a trajetória que será descarregada para a tela em algum momento no futuro. Quando estiver pronto para desenhar as trajetórias na tela, eu posso configurar a propriedade strokeStyle do meu contexto e, em seguida, chamar o método de traço, que preencherá as linhas invisíveis. O resultado é ilustrado na Figura 2.

 

// Define Style and stroke lines.
_ctx.strokeStyle = "#000";
_ctx.stroke();

A Single Line on the Canvas
Figura 2 Linha única na tela

Já que a definição de linhas (lineTo, moveTo) e o desenho de linhas (stroke) são dissociados, você pode agrupar várias operações lineTo e moveTo em lote e, em seguida, enviar todas elas para a tela de uma vez só. Eu farei isso para os eixos x- e y- e também para as operações que desenham setas ao final de cada eixo. A função completa para o desenho dos eixos é exibida na Figura 3 e o resultado na Figura 4.

Figura 3 Função drawAxes

function drawAxes(baseX, baseY, chartWidth) {
   var leftY, rightX;
   leftY = 5;
   rightX = baseX + chartWidth;
   // Draw y axis.
   _ctx.moveTo(baseX, leftY);
   _ctx.lineTo(baseX, baseY);
   // Draw arrow for y axis.
   _ctx.moveTo(baseX, leftY);
   _ctx.lineTo(baseX + 5, leftY + 5);
   _ctx.moveTo(baseX, leftY);
   _ctx.lineTo(baseX - 5, leftY + 5);
   // Draw x axis.
   _ctx.moveTo(baseX, baseY);
   _ctx.lineTo(rightX, baseY);
   // Draw arrow for x axis.
   _ctx.moveTo(rightX, baseY);
   _ctx.lineTo(rightX - 5, baseY + 5);
   _ctx.moveTo(rightX, baseY);
   _ctx.lineTo(rightX - 5, baseY - 5);
   // Define style and stroke lines.
   _ctx.strokeStyle = "#000";
   _ctx.stroke();
}

Completed X- and Y-Axes
Figura 4 Eixos X- e Y- concluídos

Já temos os eixos, mas é preciso rotulá-los para que se tornem mais úteis. O contexto 2D da tela especifica as APIs para adicionar texto aos elementos de tela, para que você não precise emendar criações confusas como textos flutuantes sobre o elemento de tela. Dito isso, o texto da tela não fornece um modelo de caixa ou não aceita estilos CSS definidos para um texto com a largura de uma página, e assim por diante. A API fornece uma atributo de fonte que funciona da mesma forma que uma regra de fonte CSS, como também as propriedades textAlign e textBaseline, para oferecer a você algum controle sobre a posição relativa às coordenadas fornecidas, mas fora isso, desenhar o texto na tela é uma questão de selecionar o ponto exato da tela para posicionar o texto fornecido.

O eixo x- representa produtos de nossa loja de artigos esportivos fictícia, então devemos rotular o eixo de acordo:

var height, widthOffset;
height = _ctx.canvas.height;
widthOffset = _ctx.canvas.width/2;
_ctx.font = "bold 18px sans-serif";
_ctx.fillText("Product", widthOffset, height - 20);

No trecho desse código, eu estou configurando a propriedade de fonte opcional e fornecendo uma cadeia de caracteres para desenhar na superfície, juntamente com as coordenadas x- e y- para usar como a posição inicial da cadeia de caracteres. Nesse exemplo, eu desenharei a palavra “Produto” no meio da minha tela, 20 pixels da parte inferior a superior, que deixa espaço para os rótulos de cada produto no meu gráfico de barras. Eu farei algo semelhante para o rótulo do eixo y-, que contém os dados de vendas de cada produto. O resultado é ilustrado na Figura 5.

Canvas with Text
Figura 5 Tela com texto

Agora que temos uma estrutura para o nosso gráfico, nós podemos adicionar as barras. Vamos criar dados de vendas fictícios para o gráfico de barras, que definiremos como uma matriz JavaScript de literais de objetos.

var salesData = [{
   category: "Basketballs",
   sales: 150
}, {
   category: "Baseballs",
   sales: 125
}, {
   category: "Footballs",
   sales: 300
}];

Com esses dados disponíveis, nós podemos utilizar o fillRect e o fillStyle para desenhar nossas barras no gráfico.

O fillRect(x, y, largura, altura) desenhará um retângulo na tela e as coordenadas x- e y-, com a largura e a altura especificadas. É importante observar que o fillRect desenha formas, começando pelo canto superior esquerdo e irradiando para fora, a não ser que você defina valores de largura e altura negativos; nesse caso, o preenchimento será irradiado na direção oposta. Para desenhar tarefas como gráficos, precisaremos desenhar as barras de cima para baixo, e não da baixo para cima.

Para desenhar as barras, nós podemos fazer um loop na matriz de dados de vendas e chamar o fillRect com as coordenadas adequadas:

var i, length, category, sales;
var barWidth = 80;
var xPos = baseX + 30;
var baseY = 375;       
for (i = 0, length = salesData.length; i < length; i++) {
   category = salesData[i].category;
   sales = salesData[i].sales;
   _ctx.fillRect(xPos, baseY - sales-1, barWidth, sales);
   xPos += 125;
}

Nesse código, a largura de cada barra é padrão, enquanto a altura é retirada da propriedade de vendas de cada produto na matriz. O resultado desse código é visualizado na Figura 6.

Rectangles as Bar Chart Data
Figura 6 Retângulos como dados do gráfico de barras

Agora, nós temos um gráfico tecnicamente seguro, mas as barras pretas sólidas deixam algo a desejar. Vamos aprimorá-las com cor e, em seguida, adicionar um efeito gradiente.

Usando cores e gradientes

Quando o método fillRect de um contexto de desenho for chamado, o contexto usará a propriedade fillStyle atual para estilizar o retângulo conforme ele vai sendo desenhado. O estilo padrão é preto sólido e é por isso que nosso gráfico tem a aparência exibida na Figura 6. O fillStyle aceita cores nomeadas, hexadecimais e RGB, então vamos adicionar uma funcionalidade para estilizar cada barra antes dela ser desenhada:

// Colors can be named hex or RGB.
colors = ["orange", "#0092bf", "rgba(240, 101, 41, 0.90)"];       
...
_ctx.fillStyle = colors[i % length];
_ctx.fillRect(xPos, baseY - sales-1, barWidth, sales);

Em primeiro lugar, nós criamos uma matriz de cores. Em seguida, ao fazermos um loop em cada produto, nós usaremos uma dessas cores como estilo de preenchimento desse elemento. O resultado é ilustrado na Figura 7.

Using fillStyle to Style Shapes
Figura 7 Usando o fillStyle para estilizar formas

A aparência melhora, porém o fillStyle é muito flexível e permite que você use gradientes lineares e radiais, em vez de cores sólidas apenas. O contexto de desenho de 2D determina duas funções de gradiente, createLinerGradient e createRadialGradient, sendo que as duas podem melhorar o estilo de suas formas através de transições de cores suaves.

Para esse exemplo, vou definir uma função createGradient que aceitará as coordenadas x- e y- para o gradiente, a largura e a cor primária a ser usada:

function createGradient(x, y, width, color) {
   var gradient;
   gradient = _ctx.createLinearGradient(x, y, x+width, y);
   gradient.addColorStop(0, color);
   gradient.addColorStop(1, "#efe3e3");
   return gradient;
}

Depois de chamar a função createLinearGradient com as coordenadas de início e fim, eu adicionarei dois limitadores de cor ao objeto do gradiente retornado pelo contexto de desenho. O método addColorStop adicionará transições de cor ao gradiente; ele pode ser chamado inúmeras vezes com os primeiros valores de parâmetros entre 0 e 1. Depois de configurar o gradiente, eu o retornarei para a função.

O objeto do gradiente pode ser configurado como a propriedade fillStyle no contexto, no lugar das cadeias de caracteres hexadecimais e RGB definidas no exemplo anterior. Eu usarei as mesmas cores do início e, em seguida, elas serão esmaecidas para um cinza claro.

colors = ["orange", "#0092bf", "rgba(240, 101, 41, 0.90)"];
_ctx.fillStyle = createGradient(xPos, baseY - sales-1, barWidth, colors[i % length]);
_ctx.fillRect(xPos, baseY - sales-1, barWidth, sales);

O resultado da opção de gradiente pode ser visualizado na Figura 8.

Using Gradients in a Canvas
Figura 8 Usando gradientes numa tela

Como utilizar imagens

Nesse momento, teremos um gráfico de ótima aparência, que conseguimos renderizar no navegador usando algumas dúzias de linhas de JavaScript. Eu poderia parar aqui, mas ainda há uma API de tela básica relacionada com a utilização de imagens que desejo abordar. A tela não permite apenas que você substitua imagens estáticas por um conteúdo interativo e baseado em script, mas você também pode usar imagens estáticas para aprimorar as visualizações da tela.

Para essa demonstração, eu gostaria de usar imagens como as barras de um gráfico de barras. E não apenas qualquer imagem, mas as imagens dos próprios itens. Com essa meta em mente, o site da Web possui uma pasta que contém imagens JPG para cada produto, nesse caso, basketballs.jpg, baseballs.jpg e footballs.jpg. Tudo o que eu preciso é posicionar e dimensionar cada imagem adequadamente.

O contexto de desenho 2D define um método drawImage com três sobrecargas, aceitando três, cinco ou nove parâmetros. O primeiro parâmetro é sempre a imagem do elemento DOM a ser desenhada. A versão mais simples de drawImage também aceita as coordenadas x- e y- na tela e desenha a imagem como ela é naquele local. Você também pode fornecer valores de largura e altura como os dois últimos parâmetros, que dimensionarão a imagem no tamanho anterior ao desenho na superfície. Finalmente, o uso mais complexo de drawImage permite cortar uma imagem para um retângulo definido, dimensioná-la para um conjunto de dimensões predefinido e, finalmente, desenhá-la na tela, nas coordenadas especificadas.

Uma vez que as imagens originais que possuo são imagens em grande escala usadas em qualquer outro local no site, eu usarei a última abordagem. Nesse exemplo, em vez de chamar o fillRect para cada item à medida que faço o loop na matriz salesData, eu criarei um elemento DOM da imagem, configurarei o seu original como uma das imagens de produto e vou renderizar uma versão cortada dessa imagem para meu gráfico, como mostrado na Figura 9.

Figura 9 Desenhando imagens numa tela

// Set outside of my loop.
xPos = 110 + 30;     
// Create an image DOM element.
img = new Image();
img.onload = (function(height, base, currentImage, currentCategory) {
  return function() {
    var yPos, barWidth, xPos;
    barWidth = 80;
      yPos = base - height - 1;
    _ctx.drawImage(currentImage, 30, 30, barWidth, height, xPos, yPos,
      barWidth, height);
      xPos += 125;           
  }
})(salesData[i].sales, baseY, img, salesData[i].category);
img.src = "images/" + salesData[i].category + ".jpg";

Uma vez que estou criando essas imagens dinamicamente, em vez de adicioná-las manualmente à marcação na ocasião do desenho, eu não devo presumir que posso configurar a fonte da imagem e, em seguida, desenhar essa imagem na tela. Para garantir que eu desenhe cada imagem somente quando ela estiver totalmente carregada, eu adicionarei uma lógica ao evento onload para a imagem e, posteriormente, vou encapsular esse código numa função de auto-invocação, que cria um fechamento com variáveis que apontam para a categoria de produto e variáveis de vendas e de posicionamento corretas. Você pode ver o resultado na Figura 10.

Using Images on a Canvas
Figura 10 Usando as imagens numa tela

Usando um suporte retroativo de tela

Como você deve saber, as versões do Internet Explorer anteriores a versão 9, como também as versões mais antigas de outros navegadores, não oferecem suporte ao elemento de tela. Você mesmo pode verificar isso abrindo o a versão demo do projeto no Internet Explorer e pressionando F12 para abrir as ferramentas do desenvolvedor. A partir das ferramentas F12, você pode alterar o modo do navegador para o Internet Explorer 8 ou o Internet Explorer 7 e atualizar a página. Você provavelmente visualizará uma exceção de JavaScript com a mensagem “O objeto não oferece suporte à propriedade do método getContext.” O contexto de desenho 2D não está disponível e nem o próprio elemento de tela. Também é importante saber que, mesmo no Internet Explorer 9, a tela não está disponível, a não ser que você defina um DOCTYPE. Conforme mencionei no primeiro artigo dessa série (msdn.microsoft.com/magazine/hh335062), é sempre uma boa ideia usar o <!DOCTYPE html> no topo de todas as páginas HTML para garantir que os recursos mais recentes no navegador estejam disponíveis.

O curso de ação mais simples a ser tomado para os usuários cujos navegadores não oferecem suporte à tela é usar um elemento fallback, tal como uma imagem ou texto. Por exemplo, para exibir uma imagem fallback para os usuários, você pode utilizar uma marcação como a seguir:

    <canvas id=”chart”>
      <img id=”chartIMG” src=”images/fallback.png”/>
    </canvas>

Qualquer conteúdo que você posicionar na marca <canvas> será renderizado apenas se o navegador do usuário não oferecer suporte à tela. Isso significa que você pode posicionar imagens ou texto dentro da tela como um simples fallback sem verificações para os usuários.

Se você desejar prolongar o suporte de fallback, a boa notícia é que há uma variedade de soluções de suporte retroativo para tela, então, você poderá ficar à vontade para usá-las nos navegadores mais antigos, desde que você verifique as soluções potenciais e tome ciência das limitações de determinado suporte retroativo. Como já afirmei em outros artigos dessa série, o ponto inicial para encontrar um suporte retroativo para uma tecnologia HTML5 deve ser a página Suportes retroativos para vários navegadores HTML5 no Modernizr wiki, no GitHub (bit.ly/nZW85d). No momento da elaboração deste artigo, há vários suportes retroativos de tela, inclusive dois deles que retornam ao Flash e ao Silverlight.

 No projeto de demonstração para download desse artigo, eu uso explorercanvas (code.google.com/p/explorercanvas), que utiliza a linguagem VML (Vector Markup Language) com suporte do Internet Explorer para criar aproximações da funcionalidade de tela e texto de tela (code.google.com/p/canvas-text), que incorpora o suporte adicional à renderização de texto nos navegadores antigos.

Conforme ilustrado nos artigos anteriores, você pode usar o Modernizr para o suporte à detecção de recursos para tela (e canvastext) num navegador, chamando o Modernizr.canvas e, em seguida, usar o Modernizr.load para carregar de forma assíncrona o explorercanvas, quando necessário. Para obter mais informações, consulte modernizr.com.

Se você não quer usar o Modenrizr, há outra maneira de adicionar o explorercanvas condicionalmente para as versões antigas do IE: Comentários condicionais:

    <!--[if lt IE 9]>
      <script src="js/excanvas.js"></script>
      <script src="js/canvas.text.js"></script>
    <![endif]-->

Quando o Internet Explorer 8 ou as versões mais antigas encontram um comentário formatado como tal, eles executarão o bloco como uma instrução If e incluirão o explorercanvas e os arquivos de script de canvas-text. Outros navegadores, inclusive o Internet Explorer 10, tratarão o bloco todo como um comentário e o ignorarão completamente.

Ao avaliar o suporte retroativo potencial para o seu aplicativo, certifique-se de observar o montante do contexto de desenho 2D que recebe apoio de determinado suporte retroativo. Apenas alguns deles oferecem suporte total para todos os usos, embora quase todos possam tratar dos casos básicos observados nesse artigo.

Embora eu não tenha abordado tudo aqui, há muito mais a ser feito com a tela, desde responder a eventos de clique e a outros eventos e alterar dados da tela à animação da superfície de desenho, renderização e manipulação de imagens pixel-a-pixel, gravação do estado e exportação de toda a superfície como a sua própria imagem. De fato, há livros inteiros que dissertam sobre telas. Você não precisa ser um desenvolvedor de jogos para experimentar o poder da tela e eu espero ter convencido você disso à medida que discuti sobre os seus conceitos básicos nesse artigo. Eu o encorajo a ler as especificações e a mergulhar de cabeça nessa nova tecnologia de gráficos super interessante.

Se você estiver procurando mais informações sobre o suporte à tela no Internet Explorer 9, verifique o Guia do Desenvolvedor do IE9 online (msdn.microsoft.com/ie/ff468705). Além disso, certifique-se de verificar as demonstrações do Canvas Pad disponíveis no site de test drive do IE (bit.ly/9v2zv5). Para obter uma lista de outros suportes retroativos a vários navegadores para tela, verifique a lista completa de suportes retroativos em (bit.ly/eBMoLW).

Finalmente, todas as demonstrações deste artigo, que se encontram disponíveis online, foram criadas usando o WebMatrix, uma ferramenta de desenvolvimento para a Web grátis e simples, da Microsoft. Você mesmo pode experimentar o WebMatrix em aka.ms/webm.

Brandon Satrom trabalha como desenvolvedor e divulgador da Microsoft nos arredores de Austin, Texas. Ele mantém um blog em userinexperience.com e pode ser seguido no Twitter em twitter.com/BrandonSatrom.

Agradecemos aos seguintes especialistas técnicos pela revisão deste artigo: Jatinder Mann e Clark Sell