Crie com o Chrome

Trazendo peças de LEGO® para a web em vários dispositivos

O Construa com Chrome, um experimento divertido para usuários do Google Chrome para computadores lançado originalmente na Austrália, foi relançado em 2014 com disponibilidade global, compatibilidade com o The LEGO® MovieTM e compatibilidade recém-adicionada a dispositivos móveis. Neste artigo, vamos compartilhar alguns aprendizados do projeto, especialmente sobre a mudança de uma experiência só para computadores para uma solução multitelas compatível com entrada por mouse e por toque.

A história do Build with Chrome

A primeira versão do Build with Chrome foi lançada na Austrália em 2012. Queríamos demonstrar o poder da Web de uma forma totalmente nova e levar o Chrome para um público totalmente novo.

O site tinha duas partes principais: o modo "Build", em que os usuários podem construir criações usando peças de LEGO, e o modo "Explorar", para navegar pelas criações em uma versão do Google Maps desenvolvida para LEGO.

O 3D interativo foi essencial para oferecer aos usuários a melhor experiência de construção de LEGO. Em 2012, o WebGL estava disponível apenas em navegadores para computador. Por isso, o Build foi segmentado como uma experiência somente para computadores. O recurso Explorar usou o Google Maps para exibir as criações, mas, quando o zoom se aproximava o suficiente, trocou para uma implementação WebGL do mapa mostrando as criações em 3D, ainda usando o Google Maps como a textura da placa de base. Esperávamos construir um ambiente em que entusiastas de LEGO de todas as idades pudessem expressar sua criatividade de forma fácil e intuitiva e explorar as criações uns dos outros.

Em 2013, decidimos expandir o Construa com Chrome para novas tecnologias da Web. Entre essas tecnologias, estava o WebGL no Chrome para Android, que, naturalmente, permitiria que o Build with Chrome evoluísse para uma experiência móvel. Para começar, desenvolvemos protótipos de toque antes de questionar o hardware para a "Builder Tool" a fim de entender o comportamento de gestos e a capacidade de resposta tátil que podemos enfrentar em um navegador em comparação com um aplicativo móvel.

Um front-end responsivo

Precisávamos oferecer suporte a dispositivos com entrada por toque e mouse. No entanto, usar a mesma interface em telas sensíveis ao toque não acabou sendo uma solução ideal devido às restrições de espaço.

No Build, há muita interatividade: aumentar e diminuir o zoom, mudar as cores das peças e, claro, selecionar, girar e colocar peças. É uma ferramenta em que os usuários costumam passar muito tempo, então é importante que eles tenham acesso rápido a tudo o que usam com frequência e precisam se sentir à vontade para interagir com ela.

Ao projetar um aplicativo de toque altamente interativo, você vai descobrir que a tela parece rapidamente pequena e que os dedos do usuário tendem a cobrir grande parte da tela durante a interação. Isso ficou óbvio para nós ao trabalhar com o Builder. Ao fazer o design, você realmente precisa considerar o tamanho da tela física em vez dos pixels nos gráficos. É importante minimizar o número de botões e controles para ter o máximo possível de espaço na tela dedicado ao conteúdo real.

Nosso objetivo era fazer com que o Build pareça natural em dispositivos de toque, não apenas adicionando entrada por toque à implementação original para computador, mas fazendo com que fosse como se fosse realmente o toque. Acabamos com duas variações da interface, uma para computadores e tablets com telas grandes e outra para dispositivos móveis com telas menores. Quando possível, é melhor usar uma única implementação e uma transição fluida entre os modos. No nosso caso, determinamos que havia uma diferença tão significativa na experiência entre esses dois modos que decidimos confiar em um ponto de interrupção específico. As duas versões têm muitos recursos em comum e tentamos fazer a maioria das coisas com apenas uma implementação de código, mas alguns aspectos da interface funcionam de maneira diferente entre as duas.

Usamos dados do user agent para detectar dispositivos móveis e, em seguida, verificamos o tamanho da janela de visualização para decidir se a interface para dispositivos móveis de tela pequena deve ser usada. É um pouco difícil escolher um ponto de interrupção como deveria ser uma "tela grande", porque é difícil obter um valor confiável do tamanho físico da tela. Felizmente, no nosso caso, não importa se exibimos a interface de tela pequena em um dispositivo sensível ao toque com uma tela grande, porque a ferramenta ainda vai funcionar bem, mas alguns dos botões podem parecer muito grandes. No final, definimos o ponto de interrupção como 1.000 pixels. Se você carregar o site de uma janela com mais de 1.000 pixels (no modo paisagem), receberá a versão para tela grande.

Vamos falar um pouco sobre os dois tamanhos de tela e experiências:

Tela grande compatível com mouse e toque

A versão para tela grande é disponibilizada a todos os computadores desktop compatíveis com mouse e a dispositivos de toque com telas grandes (como o Google Nexus 10). Essa versão está próxima da solução original para computador em que tipos de controles de navegação estão disponíveis, mas adicionamos suporte para toque e alguns gestos. Ajustamos a interface de acordo com o tamanho da janela. Portanto, quando um usuário redimensiona a janela, ele pode remover ou redimensionar parte da interface. Fazemos isso usando consultas de mídia CSS.

Exemplo: quando a altura disponível é menor que 730 pixels, o controle deslizante de zoom no modo Explorar fica oculto:

@media only screen and (max-height: 730px) {
    .zoom-slider {
        display: none;
    }
}

Tela pequena, suporte apenas para toque

Esta versão é veiculada a dispositivos móveis e tablets pequenos (dispositivos Nexus 4 e Nexus 7). Esta versão requer suporte multitoque.

Em dispositivos de tela pequena, precisamos dar ao conteúdo o máximo de espaço possível na tela. Fizemos alguns ajustes para maximizar o espaço, principalmente removendo à vista elementos usados com pouca frequência:

  • O seletor de blocos de construção é minimizado em um seletor de cores durante a construção.
  • Substituímos os controles de zoom e orientação por gestos multitoque.
  • A funcionalidade de tela cheia do Chrome também é útil para aumentar o espaço de tela.
Criar em uma tela grande
Crie em uma tela grande. O seletor de blocos está sempre visível, e há alguns controles no lado direito.
Criar em uma tela pequena
Crie em uma tela pequena. O seletor de blocos está minimizado e alguns botões foram removidos.

Desempenho e suporte do WebGL

Os dispositivos de toque modernos têm GPUs bastante potentes, mas ainda estão longe dos computadores. Por isso, sabíamos que teríamos alguns desafios com o desempenho, principalmente no modo "Explorar 3D", em que é preciso renderizar muitas criações ao mesmo tempo.

Com criatividade, queríamos adicionar alguns novos tipos de peças com formas complexas e até transparência, recursos que normalmente pesam muito na GPU. No entanto, tivemos que oferecer compatibilidade com versões anteriores e continuar oferecendo suporte às criações da primeira versão, então não foi possível definir novas restrições, como reduzir significativamente o número total de peças nas criações.

Na primeira versão do Build, tínhamos um limite máximo de peças que podiam ser usadas em uma criação. Havia um "metro de tijolos" que indicava quantas peças restavam. Na nova implementação, algumas das peças novas afetaram mais o medidor de tijolos do que as peças padrão, reduzindo assim um pouco o número máximo total de peças. Essa era uma maneira de incluir peças novas e, ao mesmo tempo, manter um bom desempenho.

No modo Explorar 3D, há muitas coisas acontecendo ao mesmo tempo: carregamento de texturas de placa de base, criação de imagens, animação e renderização de criações e assim por diante. Isso exige muito da GPU e da CPU. Por isso, criamos muitos perfis de frames no Chrome DevTools para otimizar essas partes o máximo possível. Em dispositivos móveis, decidimos aproximar um pouco o zoom das criações para não precisarmos renderizar tantas criações ao mesmo tempo.

Alguns dispositivos nos fizeram revisitar e simplificar alguns dos sombreadores WebGL, mas sempre encontramos uma maneira de resolvê-los e avançar.

Compatibilidade com dispositivos não WebGL

Queríamos que o site fosse útil, mesmo que o dispositivo do visitante não fosse compatível com a WebGL. Às vezes, é possível representar o 3D de forma simplificada usando uma solução de tela ou recursos CSS3D. Infelizmente, não encontramos uma solução boa o suficiente para replicar os recursos "Build and Explore 3D" sem usar o WebGL.

Para manter a consistência, o estilo visual das criações precisa ser o mesmo em todas as plataformas. Poderíamos ter tentado uma solução 2.5D, mas isso teria tornado as criações diferentes de alguma forma. Também tivemos que pensar em como garantir que as criações criadas com a primeira versão do Build with Chrome fossem iguais e fossem executadas da mesma forma na nova versão do site como na primeira.

O modo Explorar 2D ainda pode ser acessado por dispositivos sem WebGL, mesmo que não seja possível criar novas criações ou explorar em 3D. Assim, os usuários podem ter uma ideia da profundidade do projeto e do que poderiam criar usando essa ferramenta se estivessem em um dispositivo compatível com WebGL. O site pode não ser tão valioso para os usuários sem suporte a WebGL, mas deve ser pelo menos um teaser e envolver os usuários no teste.

Às vezes, simplesmente não é possível manter versões substitutas para soluções WebGL. Há vários motivos possíveis: desempenho, estilo visual, custos de desenvolvimento e manutenção, entre outros. No entanto, quando você decidir não implementar um substituto, deve cuidar dos visitantes sem compatibilidade com WebGL, explicar por que eles não conseguem acessar o site completamente e dar instruções sobre como resolver o problema usando um navegador compatível com WebGL.

Gerenciamento de ativos

Em 2013, o Google lançou uma nova versão do Google Maps com as mudanças mais significativas na interface do usuário desde seu lançamento. Então, decidimos reprojetar o Construa com Chrome para que se ajuste à nova interface do Google Maps e, para isso, levamos outros fatores para o novo design. O novo design é relativamente plano, com cores sólidas e formas simples. Isso nos permitiu usar CSS puro em muitos elementos da interface, minimizando o uso de imagens.

É preciso carregar várias imagens na ferramenta Analisar, imagens de miniatura para as criações, texturas de mapa para as placas de base e, por fim, as criações em 3D. Tomamos muito cuidado para garantir que não haja vazamento de memória quando carregamos novas imagens constantemente.

As criações em 3D são armazenadas em um formato de arquivo personalizado, empacotado como imagem PNG. Manter os dados das criações em 3D armazenados como uma imagem nos permitiu passar os dados diretamente aos sombreadores que renderizavam as criações.

Para todas as imagens geradas pelo usuário, o design nos permitiu usar os mesmos tamanhos de imagem para todas as plataformas, minimizando assim o uso de armazenamento e largura de banda.

Como gerenciar a orientação da tela

É fácil esquecer o quanto a proporção da tela muda ao passar do modo retrato para o modo paisagem ou vice-versa. Você precisa considerar isso desde o início ao fazer a adaptação para dispositivos móveis.

Em um site tradicional com a rolagem ativada, é possível aplicar regras de CSS para criar um site responsivo que reorganiza o conteúdo e os menus. Desde que você use a funcionalidade de rolagem, isso é bastante gerenciável.

Também usamos esse método com Build, mas estávamos um pouco limitados em como resolver o layout, porque precisávamos manter o conteúdo visível o tempo todo e ainda ter acesso rápido a vários controles e botões. Para sites de conteúdo puro, como sites de notícias, um layout fluido faz muito sentido, mas para um aplicativo de jogo como o nosso foi uma dificuldade. Foi difícil encontrar um layout que funcionasse na orientação paisagem e retrato, mantendo uma boa visão geral do conteúdo e uma maneira confortável de interagir. Por fim, decidimos manter o Build apenas no modo paisagem e pedimos para o usuário girar o dispositivo.

A opção "Explorar" foi muito mais fácil de resolver nas duas orientações. Só precisávamos ajustar o nível de zoom do 3D, dependendo da orientação, para conseguir uma experiência consistente.

A maior parte do layout do conteúdo é controlada pelo CSS, mas algumas coisas relacionadas à orientação precisavam ser implementadas em JavaScript. Descobrimos que não havia uma boa solução entre dispositivos para usar a window.Orientation para identificar a orientação. Por isso, no final, estávamos apenas comparando window.innerWidth e window.innerHeight para identificar a orientação do dispositivo.

if( window.innerWidth > window.innerHeight ){
  //landscape
} else {
  //portrait
}

Como adicionar suporte por toque

Adicionar o suporte de toque ao conteúdo da Web é razoavelmente simples. A interatividade básica, como o evento de clique, funciona da mesma forma em computadores e dispositivos com toque, mas quando se trata de interações mais avançadas, você também precisa lidar com os eventos de toque: touchstart, touchmove e touchend. Este artigo aborda os conceitos básicos de como usar esses eventos. O Internet Explorer não é compatível com eventos de toque, mas usa eventos de ponteiro (pointerdown, pointermove, pointer para cima). Eventos de ponteiro foram enviados ao W3C para padronização, mas por enquanto só foram implementados no Internet Explorer.

No modo "Explorar 3D", queríamos a mesma navegação que a implementação padrão do Google Maps: usar um dedo para movimentar o mapa e fazer gesto de pinça com dois dedos para aplicar zoom. Como as criações estão em 3D, também adicionamos o gesto de rotação com dois dedos. Isso normalmente exige o uso de eventos de toque.

Uma boa prática é evitar computação pesada, como atualização ou renderização do 3D nos manipuladores de eventos. Em vez disso, armazene a entrada de toque em uma variável e reaja à entrada no loop de renderização requestAnimationFrame. Isso também facilita a implementação de mouse ao mesmo tempo, basta armazenar os valores de mouse correspondentes nas mesmas variáveis.

Comece inicializando um objeto para armazenar a entrada e adicione o listener de eventos touchstart. Em cada manipulador de eventos, chamamos event.preventDefault(). Isto é para evitar que o navegador continue a processar o evento de toque, o que poderia causar algum comportamento inesperado, como rolar ou dimensionar a página inteira.

var input = {dragStartX:0, dragStartY:0, dragX:0, dragY:0, dragDX:0, dragDY:0, dragging:false};
plateContainer.addEventListener('touchstart', onTouchStart);

function onTouchStart(event) {
  event.preventDefault();
  if( event.touches.length === 1){
    handleDragStart(event.touches[0].clientX , event.touches[0].clientY);
    //start listening to all needed touchevents to implement the dragging
    document.addEventListener('touchmove', onTouchMove);
    document.addEventListener('touchend', onTouchEnd);
    document.addEventListener('touchcancel', onTouchEnd);
  }
}

function onTouchMove(event) {
  event.preventDefault();
  if( event.touches.length === 1){
    handleDragging(event.touches[0].clientX, event.touches[0].clientY);
  }
}

function onTouchEnd(event) {
  event.preventDefault();
  if( event.touches.length === 0){
    handleDragStop();
    //remove all eventlisteners but touchstart to minimize number of eventlisteners
    document.removeEventListener('touchmove', onTouchMove);
    document.removeEventListener('touchend', onTouchEnd);
    //also listen to touchcancel event to avoid unexpected behavior when switching tabs and some other situations
    document.removeEventListener('touchcancel', onTouchEnd);
  }
}

Não estamos fazendo o armazenamento real da entrada nos manipuladores de eventos, mas em gerenciadores separados: handleDragStart, handleDragging e handleDragStop. Isso porque também queremos ser capazes de chamá-los usando manipuladores de eventos do mouse. Lembre-se de que, embora seja improvável, o usuário pode usar o toque e o mouse ao mesmo tempo. Em vez de lidar com esse caso diretamente, apenas garantimos que não haja nenhuma explosão.

function handleDragStart(x ,y ){
  input.dragging = true;
  input.dragStartX = input.dragX = x;
  input.dragStartY = input.dragY = y;
}

function handleDragging(x ,y ){
  if(input.dragging) {
    input.dragDX = x - input.dragX;
    input.dragDY = y - input.dragY;
    input.dragX = x;
    input.dragY = y;
  }
}

function handleDragStop(){
  if(input.dragging) {
    input.dragging = false;
    input.dragDX = 0;
    input.dragDY = 0;
  }
}

Ao fazer animações baseadas em touchmove, muitas vezes é útil armazenar o movimento delta desde o último evento. Por exemplo, usamos isso como um parâmetro para a velocidade da câmera ao se mover por todas as placas de base no Explore, já que você não está arrastando as placas de base, mas, na verdade, está movendo a câmera.

function onAnimationFrame() {
  requestAnimationFrame( onAnimationFrame );

  //execute animation based on input.dragDX, input.dragDY, input.dragX or input.dragY
 /*
  /
  */

  //because touchmove is only fired when finger is actually moving we need to reset the delta values each frame
  input.dragDX=0;
  input.dragDY=0;
}

Exemplo incorporado:arrastar um objeto usando eventos de toque. Implementação parecida com a de arrastar o mapa "Explorar 3D" no Build with Chrome: http://cdpn.io/qDxvo

Gestos multitoque

Existem vários frameworks ou bibliotecas, como Hammer ou QuoJS, que simplificam o gerenciamento de gestos multitoque, mas se você quiser combinar vários gestos e ter controle total, às vezes é melhor fazer isso do zero.

Para gerenciar os gestos de pinça e girar, armazenamos a distância e o ângulo entre dois dedos quando o segundo dedo é colocado na tela:

//variables representing the actual scale/rotation of the object we are affecting
var currentScale = 1;
var currentRotation = 0;

function onTouchStart(event) {
  event.preventDefault();
  if( event.touches.length === 1){
    handleDragStart(event.touches[0].clientX , event.touches[0].clientY);
  }else if( event.touches.length === 2 ){
    handleGestureStart(event.touches[0].clientX, event.touches[0].clientY, event.touches[1].clientX, event.touches[1].clientY );
  }
}

function handleGestureStart(x1, y1, x2, y2){
  input.isGesture = true;
  //calculate distance and angle between fingers
  var dx = x2 - x1;
  var dy = y2 - y1;
  input.touchStartDistance=Math.sqrt(dx*dx+dy*dy);
  input.touchStartAngle=Math.atan2(dy,dx);
  //we also store the current scale and rotation of the actual object we are affecting. This is needed to support incremental rotation/scaling. We can't assume that an object is always the same scale when gesture starts.
  input.startScale=currentScale;
  input.startAngle=currentRotation;
}

No evento touchmove, medimos continuamente a distância e o ângulo entre esses dois dedos. A diferença entre a distância inicial e a atual é usada para definir a escala, e a diferença entre o ângulo inicial e o atual é usada para definir o ângulo.

function onTouchMove(event) {
  event.preventDefault();
  if( event.touches.length  === 1){
    handleDragging(event.touches[0].clientX, event.touches[0].clientY);
  }else if( event.touches.length === 2 ){
    handleGesture(event.touches[0].clientX, event.touches[0].clientY, event.touches[1].clientX, event.touches[1].clientY );
  }
}

function handleGesture(x1, y1, x2, y2){
  if(input.isGesture){
    //calculate distance and angle between fingers
    var dx = x2 - x1;
    var dy = y2 - y1;
    var touchDistance = Math.sqrt(dx*dx+dy*dy);
    var touchAngle = Math.atan2(dy,dx);
    //calculate the difference between current touch values and the start values
    var scalePixelChange = touchDistance - input.touchStartDistance;
    var angleChange = touchAngle - input.touchStartAngle;
    //calculate how much this should affect the actual object
    currentScale = input.startScale + scalePixelChange*0.01;
    currentRotation = input.startAngle+(angleChange*180/Math.PI);
    //upper and lower limit of scaling
    if(currentScale<0.5) currentScale = 0.5;
    if(currentScale>3) currentScale = 3;
  }
}

É possível usar a mudança de distância entre cada evento touchmove de maneira semelhante ao exemplo com o recurso de arrastar, mas essa abordagem geralmente é mais útil quando você deseja um movimento contínuo.

function onAnimationFrame() {
  requestAnimationFrame( onAnimationFrame );
  //execute transform based on currentScale and currentRotation
  /*
  /
  */

  //because touchmove is only fired when finger is actually moving we need to reset the delta values each frame
  input.dragDX=0;
  input.dragDY=0;
}

Você também pode permitir arrastar o objeto enquanto realiza os gestos de pinça e de rotação. Nesse caso, use o ponto central entre os dois dedos como entrada para o manipulador de arrastar.

Exemplo incorporado:rotação e dimensionamento de um objeto em 2D. Veja como o mapa é implementado em Explorar: http://cdpn.io/izloq

Suporte a mouse e toque no mesmo hardware

Atualmente, existem vários laptops, como o Chromebook Pixel, que oferecem suporte a entradas de mouse e por toque. Isso pode causar alguns comportamentos inesperados se você não tomar cuidado.

Um ponto importante é que você não deve apenas detectar o suporte ao toque e ignorar a entrada do mouse, mas oferecer suporte a ambos ao mesmo tempo.

Se você não estiver usando event.preventDefault() nos seus manipuladores de eventos de toque, alguns eventos de mouse emulados também serão disparados para manter a maioria dos sites sem toque otimizados que ainda estão funcionando. Por exemplo, para um único toque na tela, esses eventos podem ser disparados em sequência rápida e nesta ordem:

  1. touchstart
  2. movimento de toque
  3. Touchend
  4. mouseover
  5. mousemove
  6. mousedown
  7. mouseup
  8. clicar

Se você tem interações um pouco mais complexas, esses eventos de mouse podem causar um comportamento inesperado e prejudicar a implementação. Geralmente, é melhor usar event.preventDefault() nos manipuladores de eventos de toque e gerenciar a entrada do mouse em manipuladores de eventos separados. Esteja ciente de que o uso de event.preventDefault() em manipuladores de eventos de toque também evitará alguns comportamentos padrão, como rolagem e o evento de clique.

"No Construa com Chrome, não queríamos que o zoom ocorresse quando alguém tocasse duas vezes no site, apesar de isso ser padrão na maioria dos navegadores. Portanto, usamos a metatag da janela de visualização para informar ao navegador que não deve aplicar zoom quando um usuário toca duas vezes. Isso também remove o atraso de 300 ms nos cliques, o que melhora a capacidade de resposta do site. (O atraso de clique existe para fazer a distinção entre um toque único e um toque duplo quando o zoom por toque duplo está ativado).

<meta name="viewport" content="width=device-width,user-scalable=no">

Lembre-se de que, ao usar esse recurso, cabe a você tornar o site legível em todos os tamanhos de tela, porque o usuário não poderá aumentar o zoom.

Entrada de mouse, toque e teclado

No modo Explorar 3D, queríamos que houvesse três maneiras de navegar pelo mapa: mouse (arrastar), tocar (arrastar, fazer gesto de pinça para aplicar zoom e girar) e teclado (navegar com as teclas de seta). Todos esses métodos de navegação funcionam de maneira um pouco diferente, mas usamos a mesma abordagem em todos eles: definir variáveis em manipuladores de eventos e agir de acordo com elas no loop requestAnimationFrame. O loop requestAnimationFrame não precisa saber qual método é usado para navegar.

Por exemplo, podemos definir o movimento do mapa (dragDX e dragDY) com os três métodos de entrada. Veja a implementação do teclado:

document.addEventListener('keydown', onKeyDown );
document.addEventListener('keyup', onKeyUp );

function onKeyDown( event ) {
  input.keyCodes[ "k" + event.keyCode ] = true;
  input.shiftKey = event.shiftKey;
}

function onKeyUp( event ) {
  input.keyCodes[ "k" + event.keyCode ] = false;
  input.shiftKey = event.shiftKey;
}

//this needs to be called every frame before animation is executed
function handleKeyInput(){
  if(input.keyCodes.k37){
    input.dragDX = -5; //37 arrow left
  } else if(input.keyCodes.k39){
    input.dragDX = 5; //39 arrow right
  }
  if(input.keyCodes.k38){
    input.dragDY = -5; //38 arrow up
  } else if(input.keyCodes.k40){
    input.dragDY = 5; //40 arrow down
  }
}

function onAnimationFrame() {
  requestAnimationFrame( onAnimationFrame );
  //because keydown events are not fired every frame we need to process the keyboard state first
  handleKeyInput();
  //implement animations based on what is stored in input
   /*
  /
  */

  //because touchmove is only fired when finger is actually moving we need to reset the delta values each frame
  input.dragDX = 0;
  input.dragDY = 0;
}

Exemplo incorporado:usando mouse, toque e teclado para navegar: http://cdpn.io/catlf

Resumo

Adaptar o Build with Chrome para oferecer suporte a dispositivos touchscreen com diferentes tamanhos de tela tem sido uma experiência de aprendizado. A equipe não tinha muita experiência nesse nível de interatividade em dispositivos de toque e aprendemos muito ao longo do caminho.

O maior desafio acabou sendo como resolver o design e a experiência do usuário. Os desafios técnicos eram gerenciar muitos tamanhos de tela, eventos de toque e problemas de desempenho.

Embora houvesse alguns desafios com os sombreadores WebGL em dispositivos de toque, isso é algo que funcionou quase melhor do que o esperado. Os dispositivos estão cada vez mais potentes e as implementações WebGL estão melhorando rapidamente. Acreditamos que vamos usar muito mais o WebGL em dispositivos em breve.

Agora, se você ainda não fez isso, crie algo incrível.