Como forçar o WebKit a redesenhar / repintar para propagar alterações de estilo?

247

Eu tenho algum JavaScript trivial para efetuar uma alteração de estilo:

sel = document.getElementById('my_id');
sel.className = sel.className.replace(/item-[1-9]-selected/,'item-1-selected');
return false;

Isso funciona bem com as versões mais recentes do FF, Opera e IE, mas falha nas versões mais recentes do Chrome e Safari.

Afeta dois descendentes, que são irmãos. O primeiro irmão é atualizado, mas o segundo não. Um filho do segundo elemento também tem foco e contém a tag <a> que contém o código acima em um atributo onclick .

Na janela "Ferramentas do desenvolvedor" do Chrome, se eu cutucar (por exemplo, desmarcar e marcar) qualquer atributo de qualquer elemento, o segundo irmão será atualizado para o estilo correto.

Existe uma solução alternativa para "fácil" e de forma programática "induzir" o WebKit a fazer a coisa certa?

danorton
fonte
Acho que estou enfrentando esse problema no Android 4.2.2 (Samsung I9500) ao agrupar meu aplicativo de tela em um WebView. Ridículo!
21713 Josh
3
what-forces-layout.md um lugar de leitura muito bom
vsync

Respostas:

312

Encontrei algumas sugestões complicadas e muitas simples que não funcionaram, mas um comentário de Vasil Dinkov a uma delas forneceu uma solução simples para forçar um redesenho / repintura que funciona muito bem:

sel.style.display='none';
sel.offsetHeight; // no need to store this anywhere, the reference is enough
sel.style.display='';

Vou deixar outra pessoa comentar se funcionar para outros estilos além de "bloquear".

Obrigado, Vasil!

danorton
fonte
32
Para evitar oscilações 'inline-block', tente 'table'ou 'run-in'não 'none', mas isso pode ter efeitos colaterais. Além disso, um tempo limite de 0 aciona um reflow, assim como a consulta offsetHeight:sel.style.display = 'run-in'; setTimeout(function () { sel.style.display = 'block'; }, 0);
user123444555621
15
Essa resposta ainda é útil 2 anos depois. Eu apenas o usei para corrigir um problema apenas do Chrome, onde as sombras das caixas CSS permaneciam na tela depois de redimensionar o navegador de determinadas maneiras. Obrigado!
Rkulla
5
@danorton Você respondeu em 2010. É 2013 e o bug ainda está por aí. Obrigado. A propósito, alguém sabe se há algum problema registrado no webkit tracker?
RaphaelDDL
13
PS .: para mim, a solução acima não funcionava mais. Em vez disso, usei isso (jQuery): sel.hide (). Show (0); Fonte: stackoverflow.com/questions/8840580/...
ProblemsOfSumit
7
Tudo o que você precisa fazer é ler uma propriedade de tamanho, não é necessário alterá-la.
Andrew Bullock
93

Recentemente, descobrimos isso e descobrimos que a promoção do elemento afetado para uma camada composta com translateZ em CSS corrigia o problema sem a necessidade de JavaScript extra.

.willnotrender { 
   transform: translateZ(0); 
}

Como esses problemas de pintura aparecem principalmente no Webkit / Blink, e essa correção visa principalmente o Webkit / Blink, é preferível em alguns casos. Especialmente porque a resposta aceita quase certamente causa um reflow e repintar , não apenas repintar.

O Webkit e o Blink têm trabalhado duro no desempenho da renderização e esses tipos de falhas são o infeliz efeito colateral das otimizações que visam reduzir fluxos e tintas desnecessários. A mudança de CSS ou outra especificação subsequente será a solução futura, provavelmente.

Existem outras maneiras de obter uma camada composta, mas essa é a mais comum.

morewry
fonte
1
Trabalhou para mim ao usar carrossel angular !
gustavohenke
1
Fixa o meu problema onde border-radius estava perdido em um elemento dentro de um painel deslizante
Timo
1
Trabalha para projetos cordova também!
Quispie
1
Trabalhou para mim por um -webkit-line-clamp que não atualizar
Adam Marshall
1
Trabalhei para mim no ipad (6.0) com -webkit-transform: translate (-50%, -50%).
Lain
51

A solução danorton não funcionou para mim. Eu tive alguns problemas realmente estranhos nos quais o webkit não desenhava alguns elementos; onde o texto nas entradas não foi atualizado até o onblur; e alterar className não resultaria em um redesenho.

Descobri acidentalmente que minha solução era adicionar um elemento de estilo vazio ao corpo, após o script.

<body>
...
<script>doSomethingThatWebkitWillMessUp();</script>
<style></style>
...

Isso consertou. Quão estranho é isso? Espero que isso seja útil para alguém.

Gerben
fonte
3
Eu votaria 1.000.000 vezes, se pudesse. Esta é a única maneira de conseguir que o chrome repintasse uma div estúpida que eu estava arrastando. A propósito, você não precisa adicionar um elemento de estilo (pelo menos não o fiz); qualquer elemento funcionará. Fiz uma div e dei um ID para que eu pudesse excluir e adicionar o elemento em cada etapa, para não encher o DOM com tags inúteis.
hobberwickey
6
usei isso misturado com o comentário de Pumbaa80 em outra resposta. A correção com a qual eu acabei foivar div = $('<div>').appendTo(element); setTimeout(function(){ div.remove(); }, 0);
bendman
33
Esta linha única está funcionando muito bem para mim! $('<style></style>').appendTo($(document.body)).remove();
Pdelanauze
1
A resposta @pdelanauze funciona perfeitamente. Eu tenho um problema de redesenho cantar css3 translate e transformar em ios.
Marcel Falliere
11
Só sei que este forças repintar da página inteira, enquanto que na maioria das vezes você só quer pintar uma determinada seção da página ...
Ryan Wheale
33

Como o gatilho display + offset não funcionou para mim, encontrei uma solução aqui:

http://mir.aculo.us/2009/09/25/force-redraw-dom-technique-for-webkit-based-browsers/

ie

element.style.webkitTransform = 'scale(1)';
EricG
fonte
3
Eu usei isso em um truque que estava tentando, mas, em vez de scale(1), atribuí-o a si mesmo como element.style.webkitTransform = element.style.webkitTransform. A razão para isso é que configurá-lo para o anterior estava distorcendo a página ligeiramente para absoluteelementos posicionados!
bPratik
Isso funcionou para mim e não teve problemas de oscilação ou redefinição da posição de rolagem que a displaysolução fez.
Davidtbernal
Eu tinha um aplicativo Cordova usando muito SVG dinâmico. Funcionou bem em todos os lugares, exceto no iOS7, onde se simplesmente não exibisse os elementos SVG na inicialização. Adicionado um simples $ ('body') [0] .style.webkitTransform = 'scale (1)'; para o código de inicialização e o problema desapareceu!
Herc 23/05
Isso não está funcionando no momento, no mais novo Safari e iOS.
Setholopolus 31/08/19
@setholopolus, que versoin (s) seria isso?
EricG
23

Eu estava sofrendo o mesmo problema. A correção 'alternar exibição' do danorton funcionou para mim quando adicionada à função step da minha animação, mas eu estava preocupada com o desempenho e procurei outras opções.

Na minha circunstância, o elemento que não estava pintando estava dentro de um elemento absolutamente de posição que, na época, não possuía um índice z. A adição de um índice z a esse elemento alterou o comportamento do Chrome e as repetições ocorreram conforme o esperado -> as animações se tornaram suaves.

Duvido que isso seja uma panacéia, imagino que depende do motivo pelo qual o Chrome optou por não redesenhar o elemento, mas estou postando esta solução específica aqui na ajuda que ele espera de alguém.

Cheers, Rob

tl; dr >> Tente adicionar um índice z ao elemento ou a um pai dele.

Rob Murphy
fonte
8
Brilhante. Isso é incrível e corrigiu o problema para mim. Um +++ z-indexaria novamente.
trisweb
Não funcionou na minha situação ao lidar com barras de rolagem e transformações.
Ricky Boyce
16

Os seguintes trabalhos. Ele só precisa ser definido uma vez em CSS puro. E funciona de maneira mais confiável que uma função JS. O desempenho parece não ser afetado.

@-webkit-keyframes androidBugfix {from { padding: 0; } to { padding: 0; }}
body { -webkit-animation: androidBugfix infinite 1s; }
sumos
fonte
Parece que está funcionando para mim também. Usei isso para corrigir um problema de renderização no Mobile Safari
Chris
Isso corrige meu problema, força o safari a retransmitir. No entanto, eu usei em zoomvez de preenchimento:@-webkit-keyframes safariBugFix {from { zoom: 100%; } to { zoom: 99.99999%; }}
Ahmed Musallam
13

Por alguma razão, não consegui que a resposta de danorton funcionasse, pude ver o que era para fazer, então ajustei um pouco isso:

$('#foo').css('display', 'none').height();
$('#foo').css('display', 'block');

e funcionou para mim.

sowasred2012
fonte
7
Eu tentei de tudo acima disso, mas apenas este funcionou para mim. Webkit WTF! Isso não é ciência da computação! Isso é magia negra!
Charlie Martin
7

Eu vim aqui porque precisava redesenhar as barras de rolagem no Chrome depois de alterar o css.

Se alguém está tendo o mesmo problema, resolvi-o chamando esta função:

//Hack to force scroll redraw
function scrollReDraw() {
    $('body').css('overflow', 'hidden').height();
    $('body').css('overflow', 'auto');
}

Esse método não é a melhor solução, mas pode funcionar com tudo, ocultar e mostrar o elemento que precisa ser redesenhado pode resolver todos os problemas.

Aqui está o violino onde eu o usei: http://jsfiddle.net/promatik/wZwJz/18/

António Almeida
fonte
1
Ótimo! Eu estava tentando animar as barras de rolagem também e era disso que eu precisava! Btw melhor aproveitamento overflow: sobreposição de barras de rolagem animadas
ekalchev
6

Eu me deparei com isso hoje: Element.redraw () para prototype.js

Usando:

Element.addMethods({
  redraw: function(element){
    element = $(element);
    var n = document.createTextNode(' ');
    element.appendChild(n);
    (function(){n.parentNode.removeChild(n)}).defer();
    return element;
  }
});

No entanto, notei às vezes que você deve chamar redraw () no elemento problemático diretamente. Às vezes, redesenhar o elemento pai não resolverá o problema que o filho está enfrentando.

Bom artigo sobre como os navegadores renderizam elementos: Renderização: repintar, refluir / retransmitir, restyle

Adam Eberlin
fonte
1
appendChildé um método DOM, não um método jQuery.
ChrisW
4

Eu tive esse problema com um número de divs que foram inseridos em outra div com position: absolute, as divs inseridas não tinham atributo de posição. Quando eu mudei isso position:relative, funcionou bem. (foi realmente difícil identificar o problema)

No meu caso, os elementos foram inseridos pelo Angular com ng-repeat.

JustGoscha
fonte
1
Obrigado! Isso funcionou para o meu problema também e é muito mais leve que muitas das soluções javascript acima.
Dsyko
4

Não posso acreditar que isso ainda é um problema em 2014. Acabei de ter esse problema ao atualizar uma caixa de legenda de posição fixa na parte inferior esquerda da página durante a rolagem, a legenda seria 'fantasma' na tela. Depois de tentar tudo o que foi descrito acima sem sucesso, notei que muitas coisas estavam lentas / causando problemas devido à criação de relayouts DOM muito curtos, etc, causando uma sensação pouco natural de rolagem, etc.

Acabei fazendo uma posição fixa, div em tamanho real pointer-events: nonee aplicando a resposta de danorton a esse elemento, que parece forçar um redesenho em toda a tela sem interferir no DOM.

HTML:

<div id="redraw-fix"></div>

CSS:

div#redraw-fix {
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    z-index: 25;
    pointer-events: none;
    display: block;
}

JS:

sel = document.getElementById('redraw-fix');
sel.style.display='none';
sel.offsetHeight; // no need to store this anywhere, the reference is enough
sel.style.display='block';
waffl
fonte
Não posso agradecer o suficiente por sua solução alternativa, que é a única que funcionou para mim. Sim, é 2017 e o problema permanece. Meu problema estava relacionado a uma página de idioma RTL que ficaria em branco se atualizada manualmente em dispositivos móveis Android. As regras z-indexe pointer-eventssão supérfluas aqui.
Amin Mozafari
Eu usei a correção de danorton antes, mas estava realmente lutando com o flash do conteúdo, de configurar a exibição para nenhuma. Sua solução funcionou muito bem para mim, sem conteúdo em flash!
23718 Justin Justin
3

Não que essa pergunta precise de outra resposta, mas descobri que simplesmente mudar a cor um pouco forçou a redesenhar em minha situação específica.

//Assuming black is the starting color, we tweak it by a single bit
elem.style.color = '#000001';

//Change back to black
setTimeout(function() {
    elem.style.color = '#000000';
}, 0);

O resultado setTimeoutfoi crítico para mover a segunda mudança de estilo para fora do loop de eventos atual.

TMan
fonte
1
Definir um tempo limite de 0 se mostrou útil para mim em uma situação diferente, mas semelhante. O redesenho não estava ocorrendo antes da validação discreta do jquery congelar a tela enquanto analisava uma forma grande. Pensei em comentar se alguém lá fora está na mesma situação. As outras soluções não funcionaram nesse cenário.
k29 24/08
2

A única solução que funciona para mim é semelhante à resposta de sowasred2012:

$('body').css('display', 'table').height();
$('body').css('display', 'block');

Como tenho muitos bloqueios de problemas na página, altero a displaypropriedade do elemento raiz. E eu uso em display: table;vez de display: none;, porque noneirá redefinir o deslocamento da rolagem.

Ilya Sviridenko
fonte
2

Eu uso o transform: translateZ(0);método, mas em alguns casos não é suficiente.

Eu não sou fã de adicionar e remover uma classe, então tentei encontrar uma maneira de resolver isso e acabei com um novo hack que funciona bem:

@keyframes redraw{
    0% {opacity: 1;}
    100% {opacity: .99;}
}

// ios redraw fix
animation: redraw 1s linear infinite;
Guillaume Gautier
fonte
1

Como todo mundo parece ter seus próprios problemas e soluções, achei que acrescentaria algo que funcione para mim. No Android 4.1 com o Chrome atual, tentando arrastar uma tela dentro de uma div com estouro: oculto, não era possível redesenhar a menos que eu adicionasse um elemento à div pai (onde isso não causaria nenhum dano).

var parelt = document.getElementById("parentid");
var remElt = document.getElementById("removeMe");
var addElt = document.createElement("div");
addElt.innerHTML = " "; // Won't work if empty
addElt.id="removeMe";
if (remElt) {
    parelt.replaceChild(addElt, remElt);
} else {
    parelt.appendChild(addElt);
}

Nenhuma cintilação na tela ou atualização real e limpeza depois de mim. Não há variáveis ​​de escopo global ou de classe, apenas locais. Não parece prejudicar nada no Mobile Safari / iPad ou nos navegadores de desktop.

Eric Ruck
fonte
1

Estou trabalhando no aplicativo iônico html5, em algumas telas absoluteposicionei o elemento, ao rolar para cima ou para baixo em dispositivos IOS (iPhone 4,5,6, 6+), repintei o bug.

Tentei muitas soluções nenhuma delas estava funcionando, exceto esta resolver o meu problema.

Eu uso classe css .fixRepaintnesses elementos de posições absolutas

.fixRepaint{
    transform: translateZ(0);
}

Isso corrigiu meu problema, pode ser útil para alguém

Anjum ....
fonte
1
Opss alguns como eu não vi isso. @morewry Obrigado por apontar
Anjum ....
0

Isso é bom para JS

sel.style.display='none';
sel.offsetHeight; // no need to store this anywhere, the reference is enough
sel.style.display='block';

Mas no Jquery, e principalmente quando você pode usar apenas $ (document) .ready e não pode se vincular a um evento .load de um objeto por qualquer motivo específico, o seguinte funcionará.

Você precisa obter o contêiner OUTER (MOST) dos objetos / divs, remover todo o seu conteúdo em uma variável e adicioná-lo novamente. Tornará visíveis TODAS as alterações feitas no contêiner externo.

$(document).ready(function(){
    applyStyling(object);
    var node = $("div#body div.centerContainer form div.centerHorizontal").parent().parent();
    var content = node.html();
    node.html("");
    node.html(content);
}
WiR3D
fonte
0

Eu achei esse método útil ao trabalhar com transições

$element[0].style.display = 'table'; 
$element[0].offsetWidth; // force reflow
$element.one($.support.transition.end, function () { 
    $element[0].style.display = 'block'; 
});
jschr
fonte
0

o hack "display / offsetHeight" não funcionou no meu caso, pelo menos quando foi aplicado ao elemento que está sendo animado.

Eu tinha um menu suspenso que estava sendo aberto / fechado sobre o conteúdo da página. os artefatos estavam sendo deixados no conteúdo da página depois que o menu foi fechado (apenas nos navegadores de kit da web). a única maneira que o hack "display / offsetHeight" funcionou é se eu o aplicasse ao corpo, o que parece desagradável.

no entanto, eu encontrei outra solução:

  1. antes que o elemento comece a animar, adicione uma classe que defina "-webkit-backface-visible: hidden;" no elemento (você também pode usar o estilo embutido, eu acho)
  2. quando terminar de animar, remova a classe (ou estilo)

isso ainda é bastante hacky (usa uma propriedade CSS3 para forçar a renderização do hardware), mas pelo menos afeta apenas o elemento em questão e funcionou para mim no safari e no chrome no PC e Mac.

tedtedson
fonte
0

Isso parece relacionado a isso: o estilo jQuery não está sendo aplicado no Safari

A solução sugerida na primeira resposta funcionou bem para mim nesses cenários, a saber: aplicar e remover uma classe fictícia do corpo depois de fazer as alterações de estilo:

$('body').addClass('dummyclass').removeClass('dummyclass');

Isso força o safari a redesenhar.

Steve
fonte
Para que isso funcionasse no Chrome, tive que adicionar: $ ('body'). AddClass ('dummyclass'). Delay (0) .removeClass ('dummyclass');
mbokil
0

as sugestões acima não funcionaram para mim. mas o abaixo faz.

Deseja alterar o texto dentro da âncora dinamicamente. A palavra "Pesquisar". Criou uma tag interna "font" com um atributo de ID. Gerenciei o conteúdo usando javascript (abaixo)

Procurar

conteúdo do script:

    var searchText = "Search";
    var editSearchText = "Edit Search";
    var currentSearchText = searchText;

    function doSearch() {
        if (currentSearchText == searchText) {
            $('#pSearch').panel('close');
            currentSearchText = editSearchText;
        } else if (currentSearchText == editSearchText) {
            $('#pSearch').panel('open');
            currentSearchText = searchText;
        }
        $('#searchtxt').text(currentSearchText);
    }
ganesh
fonte
0

Eu estava tendo um problema com um SVG que estava desaparecendo no Chrome para Android quando a orientação foi alterada em determinadas circunstâncias. O código abaixo não o reproduz, mas é a configuração que tivemos.

body {
  font-family: tahoma, sans-serif;
  font-size: 12px;
  margin: 10px;
}
article {
  display: flex;
}
aside {
  flex: 0 1 10px;
  margin-right: 10px;
  min-width: 10px;
  position: relative;
}
svg {
  bottom: 0;
  left: 0;
  position: absolute;
  right: 0;
  top: 0;
}
.backgroundStop1 {
  stop-color: #5bb79e;
}
.backgroundStop2 {
  stop-color: #ddcb3f;
}
.backgroundStop3 {
  stop-color: #cf6b19;
}
<article>
  <aside>
    <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="100%" width="100%">
      <defs>
        <linearGradient id="IndicatorColourPattern" x1="0" x2="0" y1="0" y2="1">
          <stop class="backgroundStop1" offset="0%"></stop>
          <stop class="backgroundStop2" offset="50%"></stop>
          <stop class="backgroundStop3" offset="100%"></stop>
        </linearGradient>
      </defs>
      <rect x="0" y="0" rx="5" ry="5" width="100%" height="100%" fill="url(#IndicatorColourPattern)"></rect>
    </svg>
  </aside>
  <section>
    <p>Donec et eros nibh. Nullam porta, elit ut sagittis pulvinar, lacus augue lobortis mauris, sed sollicitudin elit orci non massa. Proin condimentum in nibh sed vestibulum. Donec accumsan fringilla est, porttitor vestibulum dolor ornare id. Sed elementum
      urna sollicitudin commodo ultricies. Curabitur tristique orci et ligula interdum, eu condimentum metus eleifend. Nam libero augue, pharetra at maximus in, pellentesque imperdiet orci.</p>
    <p>Fusce commodo ullamcorper ullamcorper. Etiam eget pellentesque quam, id sodales erat. Vestibulum risus magna, efficitur sed nisl et, rutrum consectetur odio. Sed at lorem non ligula consequat tempus vel nec risus.</p>
  </section>
</article>

Dia e meio depois, depois de cutucar e cutucar e não estar satisfeito com as soluções hacky oferecidas aqui, descobri que o problema era causado pelo fato de parecer manter o elemento na memória enquanto desenhava um novo. A solução foi tornar o ID do linearGradient no SVG exclusivo, mesmo que fosse usado apenas uma vez por página.

Isso pode ser conseguido de várias maneiras diferentes, mas para nosso aplicativo angular usamos a função lodash uniqueId para adicionar uma variável ao escopo:

Diretiva Angular (JS):

scope.indicatorColourPatternId = _.uniqueId('IndicatorColourPattern');

Atualizações HTML:

Linha 5: <linearGradient ng-attr-id="{{indicatorColourPatternId}}" x1="0" x2="0" y1="0" y2="1">

Linha 11: <rect x="0" y="0" rx="5" ry="5" width="100%" height="100%" ng-attr-fill="url(#{{indicatorColourPatternId}})"/>

Espero que essa resposta poupe a alguém mais dias do que esmagar seu teclado.

Jamie Barker
fonte
0

Eu recomendaria uma maneira menos burra e mais formal de forçar um reflow: use forceDOMReflowJS . No seu caso, seu código teria a seguinte aparência.

sel = document.getElementById('my_id');
forceReflowJS( sel );
return false;
Jack Giffin
fonte
0

Descobri que apenas a adição de um estilo de conteúdo ao elemento obrigava a redesenhar, esse deveria ser um valor diferente toda vez que você deseja redesenhar e não precisa estar em um pseudo-elemento.

.selector {
    content: '1'
}
Steve
fonte
0

Tentei uma resposta mais difícil, mas não funciona para mim. Eu tive problemas para ter o mesmo clientWidth com o safari em comparação com outros navegadores e esse código resolveu o problema:

var get_safe_value = function(elm,callback){
    var sty = elm.style
    sty.transform = "translateZ(1px)";
    var ret = callback(elm)//you can get here the value you want
    sty.transform = "";
    return ret
}
// for safari to have the good clientWidth
var $fBody = document.body //the element you need to fix
var clientW = get_safe_value($fBody,function(elm){return $fBody.clientWidth})

É realmente estranho, porque se eu tentar novamente obter o clientWidth após get_safe_value, obtenho um valor ruim no safari, o getter deve estar entre sty.transform = "translateZ(1px)"; esty.transform = "";

A outra solução que funciona definitivamente é

var $fBody = document.body //the element you need to fix
$fBody.style.display = 'none';
var temp = $.body.offsetHeight;
$fBody.style.display = ""
temp = $.body.offsetHeight;

var clientW = $fBody.clientWidth

O problema é que você perde o foco e os estados de rolagem.

Bormat
fonte
0

Isso forçará a repintura enquanto evita oscilações, ajustes de elementos existentes e qualquer problema de layout ...

function forceRepaint() {
    requestAnimationFrame(()=>{
      const e=document.createElement('DIV');
      e.style='position:fixed;top:0;left:0;bottom:0;right:0;background:#80808001;\
               pointer-events:none;z-index:9999999';
      document.body.appendChild(e);
      requestAnimationFrame(()=>e.remove());  
    });
}
user2026189
fonte
-1

Uma solução simples com jquery:

$el.html($el.html());

ou

element.innerHTML = element.innerHTML;

Tinha um SVG que não estava aparecendo quando foi adicionado ao html.

Isso pode ser adicionado depois que os elementos svg estiverem na tela.

Melhor solução é usar:

document.createElementNS('http://www.w3.org/2000/svg', 'svg');

e com jQuery:

$(svgDiv).append($(document.createElementNS('http://www.w3.org/2000/svg', 'g'));

isso será renderizado corretamente no Chrome.

Yaron Shamir
fonte
O problema dessa abordagem é que você perde os manipuladores de eventos registrados no objeto ou em seus descendentes.
Haqa