Uma abordagem não responsiva para criar apps da Web em vários dispositivos

As consultas de mídia são ótimas, mas…

As media queries são incríveis, uma dádiva para desenvolvedores de sites que querem fazer pequenos ajustes nas folhas de estilo para oferecer uma experiência melhor aos usuários em dispositivos de vários tamanhos. Com as consultas de mídia, você pode personalizar o CSS do seu site dependendo do tamanho da tela. Antes de ler este artigo, saiba mais sobre design responsivo e confira alguns exemplos de uso de consultas de mídia aqui: mediaqueri.es (link em inglês).

Como Brad Frost aponta em um artigo anterior, mudar a aparência é apenas uma das muitas coisas a serem consideradas ao criar para a Web móvel. Se a única coisa que você faz ao criar seu site para dispositivos móveis é personalizar o layout com consultas de mídia, temos a seguinte situação:

  • Todos os dispositivos recebem o mesmo JavaScript, CSS e recursos (imagens, vídeos), resultando em tempos de carregamento mais longos do que o necessário.
  • Todos os dispositivos recebem o mesmo DOM inicial, o que pode forçar os desenvolvedores a escrever CSS muito complicados.
  • Pouca flexibilidade para especificar interações personalizadas adaptadas a cada dispositivo.

Os webapps precisam de mais do que consultas de mídia

Não me entenda mal. Não odeio o design responsivo via consultas de mídia e acho que ele tem um lugar no mundo. Além disso, alguns dos problemas mencionados acima podem ser resolvidos com abordagens como imagens responsivas, carregamento dinâmico de scripts etc. No entanto, em um determinado momento, você pode fazer muitos ajustes incrementais e é melhor veicular versões diferentes.

À medida que as UIs criadas aumentam em complexidade e você se inclina para web apps de página única, é preciso fazer mais para personalizar as UIs de cada tipo de dispositivo. Neste artigo, você vai aprender a fazer essas personalizações com o mínimo de esforço. A abordagem geral envolve classificar o dispositivo do visitante na classe de dispositivo certa e veicular a versão apropriada para ele, maximizando a reutilização de código entre as versões.

Para quais classes de dispositivos você está segmentando?

Existem muitos dispositivos conectados à Internet, e quase todos eles têm navegadores. A dificuldade está na diversidade: laptops Mac, estações de trabalho Windows, iPhones, iPads, smartphones Android com entrada por toque, rolagem, teclados, entrada de voz, dispositivos com sensibilidade à pressão, smartwatches, torradeiras e geladeiras, entre outros. Alguns desses dispositivos são onipresentes, enquanto outros são muito raros.

Vários dispositivos.
Uma variedade de dispositivos (origem).

Para criar uma boa experiência do usuário, você precisa saber quem são seus usuários e quais dispositivos eles estão usando. Se você criar uma interface do usuário para um usuário de computador com mouse e teclado e entregar para um usuário de smartphone, a interface vai ser frustrante porque foi projetada para outro tamanho de tela e outra modalidade de entrada.

Há dois extremos no espectro de abordagens:

  1. Crie uma versão que funcione em todos os dispositivos. A UX vai sofrer como resultado, já que dispositivos diferentes têm considerações de design diferentes.

  2. Crie uma versão para cada dispositivo que você quer oferecer suporte. Isso vai levar muito tempo, porque você vai criar muitas versões do aplicativo. Além disso, quando o próximo smartphone novo chegar (o que acontece aproximadamente a cada semana), você será obrigado a criar outra versão.

Há uma troca fundamental aqui: quanto mais categorias de dispositivos você tiver, melhor será a experiência do usuário, mas mais trabalho será necessário para projetar, implementar e manter.

Criar uma versão separada para cada classe de dispositivo pode ser uma boa ideia por motivos de desempenho ou se as versões que você quer veicular para diferentes classes de dispositivos variarem muito. Caso contrário, o Web design responsivo é uma abordagem perfeitamente razoável.

Uma possível solução

Uma solução é classificar os dispositivos em categorias e projetar a melhor experiência possível para cada uma delas. As categorias escolhidas dependem do seu produto e do usuário-alvo. Confira um exemplo de classificação que abrange bem os dispositivos mais usados na Web atualmente.

  1. telas pequenas + toque (principalmente smartphones)
  2. telas grandes + toque (principalmente tablets)
  3. telas grandes + teclado/mouse (principalmente computadores/notebooks)

Essa é apenas uma das muitas segmentações possíveis, mas uma que faz muito sentido no momento da escrita. Dispositivos móveis sem telas sensíveis ao toque (por exemplo, feature phones e alguns leitores de e-books dedicados) não estão na lista acima. No entanto, a maioria deles tem navegação por teclado ou software leitor de tela instalado, que funciona bem se você criar seu site pensando na acessibilidade.

Exemplos de web apps específicos para um formato

Há muitos exemplos de propriedades da Web que veiculam versões completamente diferentes para diferentes formatos. A Pesquisa Google e o Facebook fazem isso. As considerações incluem desempenho (busca de recursos, renderização de páginas) e experiência do usuário mais geral.

No mundo dos apps nativos, muitos desenvolvedores optam por adaptar a experiência a uma classe de dispositivos. Por exemplo, o Flipboard para iPad tem uma interface muito diferente em comparação com o Flipboard no iPhone. A versão para tablet é otimizada para uso com duas mãos e inversão horizontal, enquanto a versão para smartphone é destinada à interação com uma mão e inversão vertical. Muitos outros aplicativos iOS também oferecem versões significativamente diferentes para smartphones e tablets, como Things (lista de tarefas) e Showyou (vídeo social), mostrados abaixo:

Personalização significativa da interface para smartphones e tablets.
Personalização significativa da interface para smartphones e tablets.

Abordagem 1: detecção do lado do servidor

No servidor, temos uma compreensão muito mais limitada do dispositivo com que estamos lidando. Provavelmente, a pista mais útil disponível é a string do user agent, que é fornecida pelo cabeçalho User-Agent em todas as solicitações. Por isso, a mesma abordagem de detecção de UA funciona aqui. Na verdade, os projetos DeviceAtlas e WURFL já fazem isso e fornecem muitas informações adicionais sobre o dispositivo.

Infelizmente, cada uma delas apresenta desafios próprios. O WURFL é muito grande, contendo 20 MB de XML, o que pode gerar uma sobrecarga significativa do lado do servidor para cada solicitação. Há projetos que dividem o XML por motivos de desempenho. O DeviceAtlas não é de código aberto e exige uma licença paga para uso.

Há alternativas mais simples e sem custo financeiro, como o projeto Detect Mobile Browsers (em inglês). A desvantagem, é claro, é que a detecção de dispositivos será inevitavelmente menos abrangente. Além disso, ele só distingue entre dispositivos móveis e não móveis, oferecendo suporte limitado para tablets apenas por um conjunto ad hoc de ajustes.

Abordagem 2: detecção do lado do cliente

Podemos aprender muito sobre o navegador e o dispositivo do usuário usando a detecção de recursos. As principais coisas que precisamos determinar são se o dispositivo tem capacidade de toque e se é uma tela grande ou pequena.

Precisamos traçar uma linha em algum lugar para distinguir dispositivos de toque pequenos e grandes. E quanto a casos extremos, como o Galaxy Note de 5 polegadas? O gráfico a seguir mostra vários dispositivos Android e iOS populares sobrepostos (com as resoluções de tela correspondentes). O asterisco indica que o dispositivo tem ou pode ter densidade dupla. Embora a densidade de pixels possa ser duplicada, o CSS ainda informa os mesmos tamanhos.

Uma observação rápida sobre pixels em CSS: os pixels de CSS na Web móvel não são os mesmos que os pixels da tela. Os dispositivos iOS Retina introduziram a prática de dobrar a densidade de pixels (por exemplo, iPhone 3GS x 4, iPad 2 x 3). As UAs do Mobile Safari para retina ainda informam a mesma largura do dispositivo para evitar quebrar a Web. Como outros dispositivos (por exemplo, Android) têm telas de maior resolução, eles usam o mesmo truque de largura do dispositivo.

Resolução do dispositivo (em pixels).
Resolução do dispositivo (em pixels).

No entanto, essa decisão é complicada pela importância de considerar os modos retrato e paisagem. Não queremos recarregar a página ou carregar scripts adicionais sempre que reorientamos o dispositivo, embora possamos querer renderizar a página de maneira diferente.

No diagrama a seguir, os quadrados representam as dimensões máximas de cada dispositivo, como resultado da sobreposição dos contornos retrato e paisagem (e completando o quadrado):

Resolução retrato + paisagem (em pixels)
Resolução retrato + paisagem (em pixels)

Ao definir o limite como 650px, classificamos iPhone e Galaxy Nexus como "smalltouch" e iPad e Galaxy Tab como "tablet". O Galaxy Note andrógino, neste caso, é classificado como "smartphone" e recebe o layout de smartphone.

Assim, uma estratégia razoável pode ser assim:

if (hasTouch) {
  if (isSmall) {
    device = PHONE;
  } else {
    device = TABLET;
  }
} else {
  device = DESKTOP;
}

Confira uma amostra mínima da abordagem de detecção de recursos em ação.

A abordagem alternativa aqui é usar a detecção de UA para identificar o tipo de dispositivo. Basicamente, você cria um conjunto de heurísticas e as compara com o navigator.userAgent do usuário. O pseudocódigo é semelhante a este:

var ua = navigator.userAgent;
for (var re in RULES) {
  if (ua.match(re)) {
    device = RULES[re];
    return;
  }
}

Confira um exemplo da abordagem de detecção de UA em ação.

Observação sobre o carregamento do lado do cliente

Se você estiver fazendo a detecção de UA no seu servidor, poderá decidir qual CSS, JavaScript e DOM veicular quando receber uma nova solicitação. No entanto, se você estiver fazendo a detecção do lado do cliente, a situação será mais complexa. Você tem várias opções:

  1. Redirecione para um URL específico do tipo de dispositivo que contenha a versão para esse tipo de dispositivo.
  2. Carregue dinamicamente os recursos específicos do tipo de dispositivo.

A primeira abordagem é simples e exige um redirecionamento como window.location.href = '/tablet'. No entanto, o local agora terá essas informações de tipo de dispositivo anexadas. Por isso, talvez seja melhor usar a API History para limpar o URL. Infelizmente, essa abordagem envolve um redirecionamento, que pode ser lento, principalmente em dispositivos móveis.

A segunda abordagem é muito mais complexa de implementar. Você precisa de um mecanismo para carregar CSS e JS de forma dinâmica e (dependendo do navegador), você não poderá fazer coisas como personalizar <meta viewport>. Além disso, como não há redirecionamento, você fica com o HTML original que foi veiculado. É claro que você pode manipular com JavaScript, mas isso pode ser lento e/ou deselegante, dependendo do seu aplicativo.

Decidir entre cliente ou servidor

Confira as diferenças entre as abordagens:

Cliente Pro:

  • Mais preparado para o futuro, já que é baseado em tamanhos/capacidades de tela, e não no UA.
  • Não é necessário atualizar constantemente a lista de UA.

Servidor profissional:

  • Controle total de qual versão veicular em quais dispositivos.
  • Melhor performance: não há necessidade de redirecionamentos de clientes ou carregamento dinâmico.

Minha preferência pessoal é começar com device.js e detecção do lado do cliente. À medida que o aplicativo evolui, se você perceber que o redirecionamento do lado do cliente é uma desvantagem significativa de desempenho, remova facilmente o script device.js e implemente a detecção de UA no servidor.

Apresentamos o device.js

O Device.js é um ponto de partida para fazer a detecção semântica de dispositivos com base em consultas de mídia sem precisar de uma configuração especial do lado do servidor, economizando o tempo e o esforço necessários para fazer a análise da string do user agent.

A ideia é fornecer uma marcação compatível com mecanismos de pesquisa (link rel=alternate) na parte superior do <head>, indicando quais versões do seu site você quer disponibilizar.

<link rel="alternate" href="http://foo.com" id="desktop"
    media="only screen and (touch-enabled: 0)">

Em seguida, você pode fazer a detecção de UA do lado do servidor e processar o redirecionamento de versão por conta própria ou usar o script device.js para fazer o redirecionamento do lado do cliente com base em recursos.

Para mais informações, consulte a página do projeto device.js e um aplicativo falso que usa device.js para redirecionamento do lado do cliente.

Recomendação: MVC com visualizações específicas do formato

A essa altura, você provavelmente está pensando que estou pedindo para criar três apps completamente separados, um para cada tipo de dispositivo. Não! O compartilhamento de código é a chave.

Esperamos que você tenha usado um framework semelhante ao MVC, como Backbone, Ember etc. Se você usou, conhece o princípio da separação de responsabilidades, especificamente que a interface (camada de visualização) precisa ser dissociada da lógica (camada de modelo). Se você não conhece o assunto, comece com alguns destes recursos sobre MVC e MVC em JavaScript.

A história entre dispositivos se encaixa perfeitamente na sua estrutura MVC atual. Você pode mover facilmente suas visualizações para arquivos separados, criando uma visualização personalizada para cada tipo de dispositivo. Em seguida, você pode veicular o mesmo código em todos os dispositivos, exceto na camada de visualização.

MVC entre dispositivos.
MVC em dispositivos diferentes.

Seu projeto pode ter a seguinte estrutura (é claro que você pode escolher a estrutura que faz mais sentido dependendo do seu aplicativo):

models/ (modelos compartilhados) item.js item-collection.js

controllers/ (controladores compartilhados) item-controller.js

versions/ (coisas específicas do dispositivo) tablet/ desktop/ phone/ (código específico do smartphone) style.css index.html views/ item.js item-list.js

Esse tipo de estrutura permite controlar totalmente quais recursos cada versão carrega, já que você tem HTML, CSS e JavaScript personalizados para cada dispositivo. Isso é muito eficiente e pode levar à maneira mais enxuta e eficaz de desenvolver para a Web em vários dispositivos, sem depender de truques como imagens adaptáveis.

Depois de executar sua ferramenta de build favorita, você vai concatenar e reduzir todos os arquivos JavaScript e CSS em arquivos únicos para um carregamento mais rápido. O HTML de produção vai ficar parecido com o seguinte (para smartphone, usando device.js):

<!doctype html>
<head>
  <title>Mobile Web Rocks! (Phone Edition)</title>

  <!-- Every version of your webapp should include a list of all
        versions. -->
  <link rel="alternate" href="http://foo.com" id="desktop"
      media="only screen and (touch-enabled: 0)">
  <link rel="alternate" href="http://m.foo.com" id="phone"
      media="only screen and (max-device-width: 650px)">
  <link rel="alternate" href="http://tablet.foo.com" id="tablet"
      media="only screen and (min-device-width: 650px)">

  <!-- Viewport is very important, since it affects results of media
        query matching. -->
  <meta name="viewport" content="width=device-width">

  <!-- Include device.js in each version for redirection. -->
  <script src="device.js"></script>

  <link rel="style" href="phone.min.css">
</head>
<body>
  <script src="phone.min.js"></script>
</body>

A consulta de mídia (touch-enabled: 0) não é padrão (implementada apenas no Firefox com um prefixo do fornecedor moz), mas é processada corretamente (graças ao Modernizr.touch) pelo device.js.

Substituição de versão

Às vezes, a detecção de dispositivos pode falhar. Em alguns casos, um usuário pode preferir ver o layout de tablet no smartphone (talvez ele esteja usando um Galaxy Note). Por isso, é importante dar aos usuários a opção de escolher qual versão do site usar se quiserem substituir manualmente.

A abordagem comum é fornecer um link para a versão para computador na versão para dispositivos móveis. Isso é fácil de implementar, mas o device.js oferece suporte a essa funcionalidade com o parâmetro GET device.

Concluindo

Em resumo, ao criar interfaces de página única para vários dispositivos que não se encaixam perfeitamente no mundo do design responsivo, faça o seguinte:

  1. Escolha um conjunto de classes de dispositivos para oferecer suporte e critérios para classificar os dispositivos em classes.
  2. Crie seu app MVC com uma separação clara de responsabilidades, dividindo as visualizações do restante da base de código.
  3. Use device.js para fazer a detecção da classe de dispositivo do lado do cliente.
  4. Quando estiver tudo pronto, empacote seu script e folhas de estilo em um de cada por classe de dispositivo.
  5. Se o desempenho do redirecionamento do lado do cliente for um problema, abandone o device.js e mude para a detecção de UA do lado do servidor.