Introdução
O HTML5 oferece ótimas ferramentas para melhorar a aparência visual dos aplicativos da Web. Isso é especialmente verdadeiro no campo das animações. No entanto, com esse novo poder, também vêm novos desafios. Na verdade, esses desafios não são tão novos assim, e às vezes pode fazer sentido perguntar ao seu vizinho amigável, o programador do Flash, como ele superou coisas semelhantes no passado.
De qualquer forma, quando você trabalha com animação, é extremamente importante que os usuários percebam essas animações como suaves. É preciso entender que a fluidez das animações não pode ser criada simplesmente aumentando os frames por segundo além de qualquer limite cognitivo. Infelizmente, nosso cérebro é mais inteligente do que isso. Você vai aprender que 30 quadros de animação por segundo (QPS) reais são muito melhores do que 60 QPS com apenas alguns quadros perdidos no meio. As pessoas odeiam ser irregulares.
Este artigo vai tentar fornecer as ferramentas e técnicas para melhorar a experiência do seu aplicativo.
A estratégia
Não queremos desencorajar você a criar apps incríveis e visualmente impressionantes com HTML5.
Quando você notar que o desempenho pode ser um pouco melhor, volte aqui e leia sobre como melhorar os elementos do seu aplicativo. É claro que isso pode ajudar a fazer algumas coisas corretamente, mas nunca deixe que isso atrapalhe sua produtividade.
Fidelidade visual com HTML5
Aceleração de hardware
A aceleração de hardware é um marco importante para o desempenho geral da renderização no navegador. O esquema geral é descarregar tarefas que seriam calculadas pela CPU principal para a unidade de processamento gráfico (GPU) no adaptador gráfico do computador. Isso pode gerar ganhos de desempenho enormes e reduzir o consumo de recursos em dispositivos móveis.
Esses aspectos do documento podem ser acelerados pela GPU
- Composição de layout geral
- Transições CSS3
- Transformações 3D do CSS3
- Desenho em tela
- Desenho 3D do WebGL
Embora a aceleração de tela e do WebGL sejam recursos especiais que podem não se aplicar ao seu aplicativo específico, os três primeiros aspectos podem ajudar a acelerar praticamente todos os apps.
O que pode ser acelerado?
A aceleração de GPU funciona transferindo tarefas bem definidas e específicas para hardwares de uso especial. O esquema geral é que o documento é dividido em várias "camadas" que são invariáveis em relação aos aspectos acelerados da página. Essas camadas são renderizadas usando o pipeline de renderização tradicional. A GPU é usada para compor as camadas em uma única página, aplicando os "efeitos" que podem ser acelerados em tempo real. Um resultado possível é que um objeto animado na tela não exija um único "relayout" da página enquanto a animação acontece.
O que você precisa entender é que precisa facilitar para o mecanismo de renderização identificar quando ele pode aplicar a magia da aceleração da GPU. Veja o exemplo a seguir:
Embora isso funcione, o navegador não sabe que você está executando algo que deve ser percebido como uma animação suave por um ser humano. Considere o que acontece quando você alcança a mesma aparência visual usando transições CSS3:
A forma como o navegador implementa essa animação fica completamente oculta do desenvolvedor. Isso significa que o navegador pode aplicar truques, como a aceleração da GPU, para alcançar o objetivo definido.
Há duas flags de linha de comando úteis para o Chrome ajudar a depurar a aceleração da GPU:
--show-composited-layer-borders
mostra uma borda vermelha em torno de elementos que estão sendo manipulados no nível da GPU. É bom para confirmar que as manipulações ocorrem na camada da GPU.--show-paint-rects
todas as mudanças que não são de GPU são pintadas, e isso gera uma borda clara em torno de todas as áreas que são repintadas. É possível ver o navegador otimizando as áreas de pintura em ação.
O Safari tem flags de execução semelhantes descritas aqui.
Transições CSS3
As transições CSS tornam a animação de estilo trivial para todos, mas também são um recurso de desempenho inteligente. Como uma transição CSS é gerenciada pelo navegador, a fidelidade da animação pode ser muito melhorada e, em muitos casos, acelerada pelo hardware. Atualmente, o WebKit (Chrome, Safari, iOS) tem transformações CSS aceleradas por hardware, mas isso está chegando rapidamente a outros navegadores e plataformas.
Você pode usar eventos transitionEnd
para criar scripts em combinações poderosas, mas, no momento, capturar todos os eventos de fim de transição com suporte significa observar webkitTransitionEnd transitionend oTransitionEnd
.
Muitas bibliotecas já introduziram APIs de animação que usam transições, se presentes, e retornam à animação padrão do estilo DOM. scripty2, transição YUI, jQuery animate enhanced.
CSS3 Translate
Tenho certeza de que você já animou a posição x/y de um elemento na página. Você provavelmente manipulou as propriedades de esquerda e superior do estilo inline. Com as transformações 2D, podemos usar a funcionalidade translate()
para replicar esse comportamento.
Podemos combinar isso com a animação DOM para usar a melhor coisa possível.
<div style="position:relative; height:120px;" class="hwaccel">
<div style="padding:5px; width:100px; height:100px; background:papayaWhip;
position:absolute;" id="box">
</div>
</div>
<script>
document.querySelector('#box').addEventListener('click', moveIt, false);
function moveIt(evt) {
var elem = evt.target;
if (Modernizr.csstransforms && Modernizr.csstransitions) {
// vendor prefixes omitted here for brevity
elem.style.transition = 'all 3s ease-out';
elem.style.transform = 'translateX(600px)';
} else {
// if an older browser, fall back to jQuery animate
jQuery(elem).animate({ 'left': '600px'}, 3000);
}
}
</script>
Usamos o Modernizr para testar recursos de transformações 2D e transições CSS. Se for o caso, usaremos o translate para mudar a posição. Se isso for animado usando uma transição, há uma boa chance de o navegador poder acelerar o hardware. Para dar ao navegador outro empurrão na direção certa, vamos usar o "tiro CSS mágico" acima.
Se o navegador tiver menos recursos, vamos usar o jQuery para mover o elemento. Você pode usar o plug-in de polyfill do jQuery Transform (link em inglês) do Louis-Remi Babe para automatizar todo o processo.
window.requestAnimationFrame
O requestAnimationFrame
foi introduzido pela Mozilla e iterado pelo WebKit com o objetivo de fornecer uma API nativa para executar animações, sejam elas baseadas em DOM/CSS ou em <canvas>
ou WebGL. O navegador pode otimizar animações simultâneas em um único ciclo de refluxo e repintura, levando a uma animação de maior fidelidade. Por exemplo, animações baseadas em JS sincronizadas com transições CSS ou SVG SMIL. Além disso, se você estiver executando o loop de animação em uma guia que não está visível, o navegador não vai mantê-lo em execução, o que significa menos uso de CPU, GPU e memória, levando a uma duração muito maior da bateria.
Para mais detalhes sobre como e por que usar requestAnimationFrame
, consulte o artigo requestAnimationFrame para animação inteligente de Paul Irish.
Criação de perfil
Quando você descobre que a velocidade do seu aplicativo pode ser melhorada, é hora de analisar o perfil para descobrir onde as otimizações podem gerar o maior benefício. As otimizações geralmente têm um impacto negativo na manutenção do código-fonte e, portanto, só devem ser aplicadas quando necessário. O perfil informa quais partes do código vão gerar os maiores benefícios quando a performance delas for melhorada.
Análise de desempenho do JavaScript
Os profilers JavaScript fornecem uma visão geral do desempenho do seu aplicativo no nível da função JavaScript, medindo o tempo necessário para executar cada função individual do início ao fim.
O tempo de execução bruto de uma função é o tempo total necessário para executá-la de cima para baixo. O tempo de execução líquido é o tempo de execução bruto menos o tempo que levou para executar as funções chamadas pela função.
Algumas funções são chamadas com mais frequência do que outras. Os profilers geralmente mostram o tempo que todas as invocações levaram para serem executadas, além do tempo médio, mínimo e máximo de execução.
Para mais detalhes, consulte os documentos das ferramentas do desenvolvedor do Chrome sobre a criação de perfis.
O DOM
A performance do JavaScript tem uma forte influência na fluidez e na capacidade de resposta do seu aplicativo. É importante entender que, embora os profilers de JavaScript meçam o tempo de execução do JavaScript, eles também medem indiretamente o tempo gasto em operações do DOM. Essas operações do DOM geralmente são o principal problema de performance.
function drawArray(array) {
for(var i = 0; i < array.length; i++) {
document.getElementById('test').innerHTML += array[i]; // No good :(
}
}
Por exemplo, no código acima, quase não há tempo para executar o JavaScript real. Ainda é muito provável que a função drawArray apareça nos seus perfis porque ela interage com o DOM de maneira muito ineficiente.
Dicas e truques
Funções anônimas
Não é fácil criar perfis de funções anônimas porque elas não têm um nome pelo qual possam aparecer no perfilador. Há duas maneiras de lidar com isso:
$('.stuff').each(function() { ... });
Reescrita para:
$('.stuff').each(function workOnStuff() { ... });
Não é comum saber que o JavaScript oferece suporte a expressões de função de nomeação. Isso fará com que elas apareçam perfeitamente no perfilador. Há um problema com essa solução: a expressão nomeada coloca o nome da função no escopo lexical atual. Isso pode sobrescrever outros símbolos. Portanto, tenha cuidado.
Como criar perfis de funções longas
Imagine que você tem uma função longa e suspeita que uma pequena parte dela pode ser a causa dos seus problemas de desempenho. Há duas maneiras de descobrir qual parte está com problemas:
- O método correto: refatorar o código para não incluir funções longas.
- O método maligno de fazer as coisas: adicione instruções no código na forma de funções de chamada própria com nome. Se você tiver um pouco de cuidado, isso não vai mudar a semântica e vai fazer com que partes da função apareçam como funções individuais no perfilador:
js function myLongFunction() { ... (function doAPartOfTheWork() { ... })(); ... }
Não se esqueça de remover essas funções extras depois que o perfilamento for concluído ou até mesmo usá-las como ponto de partida para refatorar o código.
Criação de perfil do DOM
As ferramentas de desenvolvimento mais recentes do Chrome Web Inspector contêm a nova "Visualização de linha do tempo", que mostra uma linha do tempo das ações de baixo nível realizadas pelo navegador. Você pode usar essas informações para otimizar suas operações de DOM. Você deve reduzir o número de "ações" que o navegador precisa realizar enquanto o código é executado.
A visualização em linha do tempo pode gerar uma quantidade imensa de informações. Portanto, tente criar casos de teste mínimos que possam ser executados de forma independente.
A imagem acima mostra a saída da visualização de linha do tempo para um script muito simples. O painel esquerdo mostra as operações realizadas pelo navegador em ordem cronológica, enquanto a linha do tempo no painel direito mostra o tempo real consumido por uma operação individual.
Mais informações sobre a visualização em linha do tempo. Uma ferramenta alternativa para criar perfis no Internet Explorer é o DynaTrace Ajax Edition.
Estratégias de criação de perfis
Aspectos únicos
Quando você quiser fazer o perfil do seu aplicativo, tente isolar os aspectos da funcionalidade que podem causar lentidão o mais próximo possível. Em seguida, tente fazer uma execução de perfil que execute apenas partes do código relevantes para esses aspectos do aplicativo. Isso facilita a interpretação dos dados de perfil porque eles não são misturados com caminhos de código não relacionados ao problema real. Bons exemplos para aspectos individuais do app são:
- Tempo de inicialização (ative o criador de perfil, recarregue o aplicativo, aguarde a inicialização ser concluída e pare o criador de perfil.
- Clique em um botão e na animação subsequente (iniciar o criador de perfil, clicar no botão, esperar até que a animação seja concluída e parar o criador de perfil).
Criação de perfil da GUI
Executar apenas a parte certa do aplicativo pode ser mais difícil em um programa de GUI do que quando você otimiza, por exemplo, o ray tracer do mecanismo 3D. Por exemplo, quando você quer criar um perfil do que acontece quando clica em um botão, pode acionar eventos de passagem do cursor não relacionados que tornam seus resultados menos conclusivos. Tente evitar isso :)
Interface programática
Há também uma interface programática para ativar o depurador. Isso permite um controle preciso sobre quando a criação de perfil começa e termina.
Comece a criar um perfil com:
console.profile()
Parar de criar perfis com:
console.profileEnd()
Repetibilidade
Ao criar perfis, verifique se você consegue reproduzir os resultados. Só então você vai poder dizer se as otimizações realmente melhoraram as coisas. Além disso, o perfil de nível de função é feito no contexto de todo o computador. Não é uma ciência exata. As execuções de perfil individuais podem ser influenciadas por muitas outras coisas que acontecem no seu computador:
- Um timer não relacionado no seu próprio app que é acionado enquanto você mede outra coisa.
- O coletor de lixo fazendo o trabalho dele.
- Outra guia no navegador fazendo um trabalho pesado na mesma linha de execução.
- Outro programa no computador que consome a CPU, tornando o aplicativo mais lento.
- Mudanças repentinas no campo gravitacional da Terra.
Também faz sentido executar o mesmo caminho de código várias vezes em uma sessão de criação de perfil. Assim, você diminui a influência dos fatores acima, e as partes lentas podem se destacar ainda mais.
Medir, melhorar, medir
Quando você identificar um ponto lento no programa, tente pensar em maneiras de melhorar o comportamento de execução. Depois de mudar o código, o perfil será atualizado. Se você estiver satisfeito com o resultado, prossiga. Se não houver uma melhoria, provavelmente você precisará reverter a mudança e não deixar o "porque não faz mal".
Estratégias de otimização
Minimizar a interação com o DOM
Um tema comum para melhorar a velocidade dos aplicativos cliente da Web é minimizar a interação com o DOM. Embora a velocidade dos mecanismos JavaScript tenha aumentado em uma ordem de magnitude, o acesso ao DOM não ficou mais rápido na mesma taxa. Isso também não vai acontecer por razões muito práticas (coisas como layout e desenho em uma tela levam tempo).
Armazenar em cache os nós do DOM
Sempre que você recuperar um nó ou uma lista de nós do DOM, pense se é possível reutilizá-los em uma computação posterior (ou até mesmo na próxima iteração do loop). Isso geralmente acontece se você não adicionar ou excluir nós na área relevante.
Antes:
function getElements() {
return $('.my-class');
}
Depois:
var cachedElements;
function getElements() {
if (cachedElements) {
return cachedElements;
}
cachedElements = $('.my-class');
return cachedElements;
}
Valores de atributo do cache
Da mesma forma que você pode armazenar em cache os nós DOM, também é possível armazenar em cache os valores dos atributos. Imagine que você está animando um atributo do estilo de um nó. Se você souber que você (como nessa parte do código) é o único que vai tocar nesse atributo, poderá armazenar em cache o último valor em cada iteração para não precisar lê-lo repetidamente.
Antes:
setInterval(function() {
var ele = $('#element');
var left = parseInt(ele.css('left'), 10);
ele.css('left', (left + 5) + 'px');
}, 1000 / 30);
Depois:
js
var ele = $('#element');
var left = parseInt(ele.css('left'), 10);
setInterval(function() {
left += 5;
ele.css('left', left + 'px');
}, 1000 / 30);
Mover a manipulação do DOM para fora dos loops
Os loops costumam ser pontos de acesso para otimização. Tente pensar em maneiras de separar o processamento de números reais para trabalhar com o DOM. Muitas vezes, é possível fazer um cálculo e, depois, aplicar todos os resultados de uma só vez.
Antes:
document.getElementById('target').innerHTML = '';
for(var i = 0; i < array.length; i++) {
var val = doSomething(array[i]);
document.getElementById('target').innerHTML += val;
}
Depois:
var stringBuilder = [];
for(var i = 0; i < array.length; i++) {
var val = doSomething(array[i]);
stringBuilder.push(val);
}
document.getElementById('target').innerHTML = stringBuilder.join('');
Desenhos e fluxos novamente
Como discutido anteriormente, o acesso ao DOM é relativamente lento. Ele fica muito lento quando o código está lendo um valor que precisa ser recalculado porque o código modificou recentemente algo relacionado no DOM. Portanto, evite misturar o acesso de leitura e gravação ao DOM. O ideal é que seu código seja sempre agrupado em duas fases:
- Fase 1: ler os valores do DOM necessários para o código
- Fase 2: modificar o DOM
Tente não programar um padrão como:
- Fase 1: ler valores do DOM
- Fase 2: modificar o DOM
- Fase 3: leia mais
- Fase 4: modifique o DOM em outro lugar.
Antes:
function paintSlow() {
var left1 = $('#thing1').css('left');
$('#otherThing1').css('left', left);
var left2 = $('#thing2').css('left');
$('#otherThing2').css('left', left);
}
Depois:
function paintFast() {
var left1 = $('#thing1').css('left');
var left2 = $('#thing2').css('left');
$('#otherThing1').css('left', left);
$('#otherThing2').css('left', left);
}
Esse conselho deve ser considerado para ações que ocorrem em um contexto de execução do JavaScript. (por exemplo, em um manipulador de eventos, em um manipulador de intervalos ou ao processar uma resposta ajax).
A execução da função paintSlow()
acima cria esta imagem:
A mudança para a implementação mais rápida gera esta imagem:
Essas imagens mostram que reordenar a maneira como o código acessa o DOM pode melhorar muito a performance de renderização. Nesse caso, o código original precisa recalcular os estilos e layout da página duas vezes para criar o mesmo resultado. Uma otimização semelhante pode ser aplicada a basicamente todos os códigos do "mundo real" e gerar resultados realmente impressionantes.
Leia mais: Rendering: repaint, reflow/relayout, restyle, por Stoyan Stefanov
Redraws e o loop de eventos
A execução do JavaScript no navegador segue um modelo de "loop de eventos". Por padrão, o navegador está em um estado "inativo". Esse estado pode ser interrompido por eventos de interações do usuário ou por coisas como timers do JavaScript ou callbacks do Ajax. Sempre que um pedaço de JavaScript é executado em um ponto de interrupção, o navegador geralmente espera até que ele termine e pinte a tela novamente. Pode haver exceções para JavaScripts extremamente longos ou em casos como caixas de alerta que interrompem a execução do JavaScript.
Consequências
- Se os ciclos de animação do JavaScript levarem mais de 1/30 segundos para serem executados, não será possível criar animações suaves porque o navegador não vai ser repintado durante a execução do JS. Quando você também espera processar eventos do usuário, precisa ser muito mais rápido.
- Às vezes, é útil atrasar algumas ações do JavaScript para um pouco mais tarde.
Por exemplo,
setTimeout(function() { ... }, 0)
Isso informa ao navegador para executar o callback assim que o loop de eventos ficar ocioso novamente (alguns navegadores vão esperar pelo menos 10 ms). Isso vai criar dois ciclos de execução do JavaScript muito próximos no tempo. Ambos podem acionar uma nova pintura da tela, o que pode dobrar o tempo gasto com a pintura. Se isso realmente aciona duas pinturas depende de heurísticas no navegador.
Versão normal:
function paintFast() {
var height1 = $('#thing1').css('height');
var height2 = $('#thing2').css('height');
$('#otherThing1').css('height', '20px');
$('#otherThing2').css('height', '20px');
}
Vamos adicionar um atraso:
function paintALittleLater() {
var height1 = $('#thing1').css('height');
var height2 = $('#thing2').css('height');
$('#otherThing1').css('height', '20px');
setTimeout(function() {
$('#otherThing2').css('height', '20px');
}, 10)
}
A versão atrasada mostra que o navegador pinta duas vezes, embora as duas mudanças na página sejam apenas 1/100 de um segundo.
Inicialização lenta
Os usuários querem apps da Web que carreguem rápido e sejam responsivos. No entanto, os usuários têm diferentes limites para o que consideram lento, dependendo da ação que realizam. Por exemplo, um app nunca deve fazer muitas computações em um evento de passagem do cursor, porque isso pode criar uma experiência ruim para o usuário enquanto ele continua movendo o mouse. No entanto, os usuários estão acostumados a aceitar um pequeno atraso depois de clicar em um botão.
Portanto, pode ser sensato mover o código de inicialização para ser executado o mais tarde possível (por exemplo, quando o usuário clica em um botão que ativa um componente específico do aplicativo).
Antes:
js
var things = $('.ele > .other * div.className');
$('#button').click(function() { things.show() });
Depois:
js
$('#button').click(function() { $('.ele > .other * div.className').show() });
Delegação de eventos
A distribuição de manipuladores de eventos em uma página pode levar um tempo relativamente longo e também pode ser trabalhosa quando os elementos são substituídos dinamicamente, o que exige a reinserção dos manipuladores de eventos nos novos elementos.
A solução neste caso é usar uma técnica chamada delegação de eventos. Em vez de anexar gerenciadores de eventos individuais a elementos, a natureza de expansão de muitos eventos do navegador é usada anexando o gerenciador de eventos a um nó pai e verificando o nó de destino do evento para saber se ele é de interesse.
No jQuery, isso pode ser facilmente expresso como:
$('#parentNode').delegate('.button', 'click', function() { ... });
Quando não usar a delegação de eventos
Às vezes, o oposto pode ser verdadeiro: você está usando a delegação de eventos e está tendo um problema de desempenho. Basicamente, a delegação de eventos permite um tempo de inicialização de complexidade constante. No entanto, o preço da verificação se um evento é de interesse precisa ser pago em cada invocação desse evento. Isso pode ser caro, especialmente para eventos que ocorrem com frequência, como "mouseover" ou até mesmo "mousemove".
Problemas e soluções comuns
As coisas que faço em $(document).ready
demoram muito
Conselho pessoal de Malte: nunca faça nada em $(document).ready
. Tente enviar o documento na forma final. Você pode registrar listeners de eventos, mas apenas usando o seletor de ID e/ou a delegação de eventos. Para eventos caros, como "mousemove", adie o registro até que eles sejam necessários (evento de passagem do cursor sobre o elemento relevante).
E se você realmente precisar fazer algo, como fazer uma solicitação Ajax para receber dados reais e mostrar uma animação legal, talvez seja melhor incluir a animação como um URI de dados se for um GIF animado ou algo parecido.
Desde que adicionei um filme em Flash à página, tudo está muito lento
Adicionar o Flash a uma página sempre vai diminuir um pouco a renderização, porque o layout final da janela precisa ser "negociado" entre o navegador e o plug-in do Flash. Quando não for possível evitar completamente o uso do Flash nas suas páginas, defina o parâmetro "wmode" do Flash como o valor "window" (que é o padrão). Isso desativa a capacidade de compor elementos HTML e Flash. Não será possível ver um elemento HTML que está acima do filme em Flash, e o filme em Flash não pode ser transparente. Isso pode ser um inconveniente, mas vai melhorar significativamente sua performance. Por exemplo, confira como o youtube.com evita colocar camadas acima do player de filme principal.
Estou salvando coisas no localStorage, e agora meu aplicativo fica travando
Escrever no localStorage é uma operação síncrona que envolve a inicialização do disco rígido. Nunca faça operações síncronas de "longa execução" durante animações. Mova o acesso ao localStorage para um local no código em que você tem certeza de que o usuário está inativo e nenhuma animação está em andamento.
O perfil aponta para um seletor jQuery muito lento
Primeiro, verifique se o seletor pode ser executado pelo document.querySelectorAll. Você pode testar isso no console JavaScript. Se houver uma exceção, reescreva o seletor para não usar nenhuma extensão especial do framework JavaScript. Isso vai acelerar o seletor em navegadores modernos em uma ordem de magnitude.
Se isso não ajudar ou se você também quiser ser rápido em navegadores modernos, siga estas diretrizes:
- Seja o mais específico possível no lado direito do seletor.
- Use um nome de tag que você não usa com frequência como a parte do seletor à direita.
- Se nada ajudar, pense em reescrever as coisas para que você possa usar um seletor de ID.
Todas essas manipulações do DOM levam muito tempo
Um monte de inserções, remoções e atualizações de nós do DOM pode ser muito lento. Isso geralmente pode ser otimizado gerando uma string grande de HTML e usando domNode.innerHTML = newHTML
para substituir o conteúdo antigo. Isso pode ser muito ruim para a manutenção e pode criar links de memória no IE. Portanto, tenha cuidado.
Outro problema comum é que o código de inicialização pode criar muito HTML. Por exemplo, um plug-in do jQuery que transforma uma caixa de seleção em um monte de divs porque é isso que os designers queriam sem saber das práticas recomendadas de UX. Se você realmente quer que sua página seja rápida, nunca faça isso. Em vez disso, envie toda a marcação do lado do servidor na forma final. Isso também tem muitos problemas. Pense bem se a velocidade vale a pena.
Ferramentas
- JSPerf: comparações de pequenos snippets de JavaScript
- Firebug: para criar perfis no Firefox
- Ferramentas para desenvolvedores do Google Chrome (disponível como WebInspector no Safari)
- DOM Monster: para otimizar a performance do DOM
- DynaTrace Ajax Edition: para otimizações de perfil e pintura no Internet Explorer