Aplicando o progressive enhancement

Sempre desejou utilizar recursos modernos do HTML5 e do CSS3, mas era impedido por ter que suportar navegadores mais antigos? Seu site para de funcionar com JavaScript desabilitado?

Neste post, mostrarei maneiras de aplicar o conceito de progressive enhancement atacando a tríade estrutura, estilo e comportamento para ajudá-lo na difícil tarefa de agradar gregos e troianos que chegam em seu website.

Uma analogia simples

Havia um grande rio que cortava duas cidades e dois concorrentes no negócio de travessia. O primeiro utilizava canoa e o segundo jet ski. O primeiro, para melhorar a experiência de seus usuários, adicionou um motor à canoa.

Tudo funcionava perfeitamente até que houve escassez de combustível. Sem energia, o jet ski deixou de funcionar e nenhuma travessia era feita. Com a canoa, ainda era possível navegar, mesmo sem o motor do concorrente, permitindo que os usuários ainda acessem este recurso.

E o Progressive enhancement?

O conceito de progressive enhancement define que a construção de uma página parte de uma base comum e garantida de executar nos mais diversos navegadores para depois acrescentar pequenas melhorias mesmo que só funcionem em navegadores modernos.

Se alguma dessas melhorias não for suportada pelo navegador, o usuário ainda assim conseguirá acessar o website, mesmo que tenha sua experiência reduzida.

Este conceito não se aplica uniformemente numa página e deve ser pensando isoladamente para a estrutura, estilo e comportamento. Cada ponto da tríade se comporta diferentemente quando degradado, isto é, quando não é suportado pelo navegador.

Uma maneira de pensar em cada ponto é através do critério fail-safe.

O critério fail-safe

O critério fail-safe diz que, se um determinado recurso não é suportado pelo navegador, isso não deve resultar em erro, até mesmo sem haver a necessidade de tratamento especial pelo programador. Estão incluídos neste critério o  HTML e o CSS.

No HTML, quando usamos alguma tag desconhecida pelo navegador, nenhum erro é gerado, porque a tag é simplesmente ignorada.

Um exemplo prático disso é a utilização da tag <nav> do HTML5. Podemos utilizá-la em nossa marcação visando melhorar a semântica de nossa página, mas se o navegador não a suportar, a lista com os links de navegação ainda continuará acessível:

<nav>
<ul>
	<li><a href="”#”">Produtos</a></li>
	<li><a href="”#”">Promoções</a></li>
	<li><a href="”#”">Contato</a></li>
</ul>
</nav>

Este comportamento existe desde que web é web e seu benefício se coaduna com o progressive enhancement de maneira out of the box, sem a intervenção do programador.

O programador front-end pode utilizar tags mais modernas com a certeza de que navegadores que não as suportarem as ignorarão. Como nenhum erro é gerado, o usuário consegue acessar o recurso desejado.

Tags não suportadas são ignoradas, logo, não podem ser estilizadas. isto é um problema, principalmente se você precisa estilizar uma tag contêiner como <section> do HTML5. Todas as versões do Internet Explorer inferiores a versão 9 sofrem deste problema.

Este problema é resolvido através do hack html5shiv, que torna estilizáveis as tags do HTML5 nessas versões do IE. Este hack é carregado através de um comentário condicional.

Comentário Condicional

Um comentário condicional é uma maneira de carregar scripts e estilos condicionados à versão do Internet Explorer.

Um ponto forte desta técnica é que apenas o IE consegue entendê-lo, sendo ignorado pelos demais browsers. No exemplo abaixo, serão realizados ajustes específicos para cada versão do IE:

<!--[if IE 6]>
<link rel="stylesheet" type="text/css" href="apenas-para-ie6.css">
<![endif]-->
<!--[if IE 7]>
<link rel="stylesheet" type="text/css" href="apenas-para-ie7.css">
<![endif]-->

O Internet Explorer 10 não suportará comentários condicionais.

CSS e progressive enhancement

Assim como o HTML, o CSS também é fail-safe: se alguma propriedade não existir, ela será ignorada sem comprometer o acesso ao recurso pelo usuário. Além disso, essa característica pode proporcionar ao mesmo tempo resultados louváveis.

Um exemplo clássico de design progressivo:

h1{
  background-color: rgb(127, 214, 110);
  background-color: rgba(127, 214, 110, .1);
}

No exemplo acima, o seletor aplica à propriedade background-color uma cor através de rgb. Esta propriedade repete-se logo em seguida, mas recebendo a mesma cor através de  rbga com suporte à  transparência. O primeiro possui ampla cobertura pelos navegadores, já o segundo, nem tanto.

Acontece que, após a primeira propriedade ter sido atribuída, ela será sobrescrita com o valor da segunda, apenas se o navegador a suportar. Sendo assim, aquele desprovido de transparência terá seu fundo estanque. Nenhum erro ocorrerá, consagrando a natureza fail-safe do CSS.

Você pode encontrar mais exemplos no excelente post css3 e progressive enhancement.

Estilos conflitantes

Diferente do HTML, um design progressivo com CSS demanda do programador front-end um pouco mais de atenção:

h1 {
 display: inline-block;
 border-style:solid;
 border-width: 1px 1px 4px 4px;
 box-shadow: -3px 3px 2px;
}

Para criar um efeito de sombra, foi utilizada a propriedade border-width com suporte consistente em diversos navegadores. Logo em seguida, a propriedade box-shadow do CSS3, com suporte mais reduzido do que o primeiro, mas com uma experiência visual aprimorada.

Caso o browser não suporte box-shadow, a propriedade border-width será aplicada, mas se a primeira também for suportada, teremos duas propriedades funcionando concomitantemente, o que é um problema.

Repare que aqui não é uma questão de fail-safe, apesar dele também poder fazer parte da equação, mas uma questão condicional, ou seja, aplicação de estilos condicionada ao suporte ou não de deterninado recurso.

JavaScript não-obstrusivo e progressive enhancement

O JavaScript em sua natureza não é fail-safe, sendo o ponto da tríade mais problemático.
No lugar de aplicar o conceito fail-safe, aplica-se a técnica de JavaScript não-obstrusivo.

A técnica de JavaScript não-obstrusivo, a grosso modo, parte da premissa na qual usuários sem suporte à JavaScript conseguirão consumir a página, pois o não funcionamento de scripts não bloqueará o acesso ao conteúdo.

Esta técnica muda a maneira pela qual o JavaScript é visto, considerando-o como um “plus” e nunca um recurso fundamental para o funcionamento da página.

É devido a essas características que esta técnica se coaduna com o conceito de progressive enhancement, pois uma base sólida e garantida de funcionar nos mais diversos navegadores é uma base sem JavaScript. Um exemplo:

window.localStorage.cep = "XXXXX-YYY";

O código acima guarda o CEP digitado pelo usuário para que ele não tenha que digitá-lo toda vez, mas não funcionará e gerará um erro caso o browser não suporte localStorage do HTML5, como é o caso do IE 7.0 entre outros.

A ausência desse recurso tornará menos agradável a experiência do usuário (ter que digitar toda vez), mas ele poderá continuar comprando, por exemplo.

Feature Detection

Uma das formas de contornar o problema acima é utilizar a técnica de feature detection, que consiste em testar a existência de determinada feature do navegador, permitindo que o programador decida o que fazer. Assim temos:

if (window.localStorage){
  window.localStorage.cep = “XXXXX-YYY”;
}

Polyfill

O programador, após ter elaborado sua lógica de detecção, no lugar de simplesmente deixar de utilizar a feature não suportada, pode criar seu próprio “tapa buraco” ou apelar para bibliotecas de terceiros que mimetizem a feature original.

Estas bibliotecas de terceiros são chamada de polyfills. Por exemplo, para localStorage é possível utilizar o polyfill https://gist.github.com/350433.

Lembre-se que mesmo que você utilize um polyfil, ele deve ser um “plus”, e não algo sem o qual seu website não funcionará.

Feature Detection com Modernizr

Nem sempre é fácil desenvolver algoritmos de detecção de recurso com no exemplo acima. Além disso, o algoritmo do exemplo é falho, pois a presença de qualquer objeto com o nome localStorage, pode causar falso positivo.

Uma biblioteca que auxilia no processo de detecção é o Modernizr. Há uma série de verificações, inclusive para os recursos mais recentes do HTML5.

Uma vez detectada a ausência de determinado recurso, basta escolher o polyfill (o seu ou de terceiros) de interesse que o Modernizr se encarregará de carregá-lo para você.

<script type="text/javascript" src="script/modernizr-custom.js"></script>
<script type="text/javascript">
  Modernizr.load({
  test: Modernizr.localstorage, 
  nope: ['script/localstorage-polyfill.js']
});
</script>

É possível personalizar o build do Modernizr com os testes de interesse em seu próprio site, diminuindo assim o tamanho da biblioteca final.

Aplicação condicional de estilos

O próprio Modernizr ajuda a resolver o problema do nosso CSS, quando temos duas propriedades que não podem ser aplicadas concomitantemente, aplicando-as condicionalmente. Como?

O Modernizr adiciona automaticamente na tag HTML uma classe para cada recurso que detecta e se o recurso não for suportado, ele receberá o prefixo “no-“.

O exemplo abaixo ilustra o suporte ao localStorage e ausência ao box-shadow:

<html class="localstorage no-boxshadow"]
<!-- restante do html -->

Agora, só resta alterar o CSS:

/* aplicado apenas se box-shadow for suportado */
.boxshadow h1 {
   box-shadow: -3px 3px 2px
}

/* aplicado apenas se box-shadow não for suportado */
.no-boxshadow h1 {
   border-width: 1px 1px 4px 4px;
}

O exemplo acima leva em consideração as classes adicionadas automaticamente pelo Modernizr, garantindo a aplicação condicional de estilos.

E se o JavaScript estiver indisponível?

O Modernizr possui um mecanismo de fallback limitado, porém não menos útil para situações nas quais o suporte ao JavaScript esteja indisponível.

A equipe do Modernizr sugere que o programador front-end adicione a classe “no-js” na tag <html>. Quando o Modernizr é carregado (JavaScript habilitado), ele automaticamente mudará a tag “no-js” para “js”. Esse comportamento possibilita aplicar estilos condicionais baseados na disponibilidade ou não do JavaScript:

.boxshadow h1 {
   box-shadow: -3px 3px 2px
}
/* classe .no-js adicionada */
.no-boxshadow h1, .no-js h1 {
    border-width: 1px 1px 4px 4px;
}

Uma possível crítica para esta solução aparece quando o navegador sem suporte à JavaScript tiver suporte à propriedade box-shadow, pois será aplicado o estilo básico utilizando o truque com bordas, ainda sim haverá conformidade com o conceito de progressive enhancement, garantindo uma base sólida de funcionar nos mais diversos navegadores.

Conclusão

Aplicar o conceito de progressive enhancement em cada ponto da tríade estrutura, estilo e comportamento é um quebra-cabeça que demanda ainda mais do programador-front.

Novos problemas necessitam de novas soluções, como é o caso da pluralidade de dispositivos móveis, TV’s e até geladeiras que hoje acessam nossos websites.

Existem técnicas e recursos prontos para serem utilizados, o progressive enhancement é uma deles, ainda temos o responsive design entre outros.

Twitter: @flaviohalmeida

16 Comentários

  1. Roberto Shizuo 31/10/2012 at 14:09 #

    excelente post! realmente é algo bem relacionado com a tal da web responsiva que o Sergio fala no site dele. pra falar a verdade, não vejo tanta diferença.

  2. Flávio Almeida 31/10/2012 at 17:00 #

    Olá Shizuo, obrigado! Esses conceitos se aproximam bastante. Eu posso implementar um design responsivo que é aplicado progressivamente tirando vantagem de novos recursos disponibilizados pelos navegadores.

  3. Lucas Polo 31/10/2012 at 18:49 #

    Muito bom o artigo.
    Muitas vezes eu ficava preocupado sobre essas questões de compatibilidade, mas dessa forma que você apresentou o desenvolvimento front-end muda muito positivamente. Acho que o segredo é o que você destacou em todo o texto, deixar o site funcional, e depois aplicar objetos que enriquecem o site, mas diminuem a compatibilidade.

  4. Flávio Almeida 31/10/2012 at 19:26 #

    Realmente Lucas, se o site já tem uma base funcional, o que for “extra” e não funcionar para determinado browser não impedirá o usuário de utilizar essa base. Mas como disse no post, tem que ser tudo de caso pensado. O que dificulta é quando esta base funcional só é pensada depois do site pronto. É como Aristóteles dizia: “Bem começado, metade feito”. Abraço.

  5. Gilson 01/11/2012 at 13:06 #

    Flávio Almeida, você esta de parabéns!!
    Todas essas questões são importantes, pois traz para reflexão os problemas que enfrentamos com a diversidades de browsers etc..

  6. Márcio Gonçalves 07/11/2012 at 11:15 #

    Muito bom o artigo, Flávio. Parabéns! Abraço.

  7. Leandro Rodrigues 07/11/2012 at 13:34 #

    Muito obrigado pelo artigo. Realmente o Progressive Enhancement é outro patamar de desenvolvimento.

  8. Adnan Neser Junior 07/11/2012 at 15:22 #

    Muito bom o artigo! Parabéns!
    Considero extremamente importante ter um front-end flexível o suficiente para funcionar nos mais diversos browsers existentes sem apresentar erros que acabem com a navegação já que hoje em dia uma parcela considerável do tráfego da web está vindo de aparelhos móveis e esse tipo de acesso tende a crescer ainda mais.

    Um abraço.

  9. Java Progressivo 13/11/2012 at 13:28 #

    Parabéns pela aula e por compartilhar sua experiência profissional…essas coisas são importantes e são pouco ensinadas em cursos, se aprende mais na hora de trabalhar.

  10. Flávio Almeida 13/11/2012 at 13:42 #

    Olá! Progressivo, esta é uma preocupação que temos aqui, tanto isso é verdade, que boa parte do que escrevi neste post esta presente nos treinamentos WD-43 e WD-47. É claro que nestes treinamentos a abordagem é mais prática e menos teórica do que aqui.

  11. Rafael 19/11/2012 at 13:48 #

    Obrigado pelo conteúdo de qualidade Caelum! Excelente aula!

  12. Leonardo Araújo 20/11/2012 at 09:03 #

    Não tem haver com o post, porém os posts da caelum são sempre excelentes, porém estão vindo com uma constância menor … gostaria que voltasse a quantidade do que era antes (a qualidade continua excelente)

  13. Abraão Isvi 20/11/2012 at 13:30 #

    Muito bom!

  14. Rasa 27/06/2014 at 10:57 #

    Flávio,

    No exemplo do CSS condicional:

    /* aplicado apenas se box-shadow for suportado */
    .boxshadow h1 {
    box-shadow: -3px 3px 2px
    }

    /* aplicado apenas se box-shadow não for suportado */
    .no-boxshadow h1 {
    border-width: 1px 1px 4px 4px;
    }

    A declaração deve ser nessa ordem certo? Se eu colocar no meu arquivo css o .no-boxshadow h1 antes do .boxshadow não daria certo a condição certo?

  15. Vanderson Assis 09/03/2016 at 16:54 #

    Ótimo post, obrigado pelo aprendizado!

Deixe uma resposta