Forçar redesenho / atualização do DOM no Chrome / Mac

215

De vez em quando, o Chrome processa HTML / CSS perfeitamente válidos incorretamente ou nada. Muitas vezes, pesquisar no inspetor DOM é suficiente para que ele perceba o erro de suas formas e redesenha-o corretamente; portanto, é provável que a marcação seja boa. Isso acontece com frequência (e previsivelmente) o suficiente em um projeto no qual estou trabalhando, e coloquei o código para forçar um redesenho em determinadas circunstâncias.

Isso funciona na maioria das combinações de navegadores / sistemas operacionais:

    el.style.cssText += ';-webkit-transform:rotateZ(0deg)'
    el.offsetHeight
    el.style.cssText += ';-webkit-transform:none'

Como em, ajuste algumas propriedades CSS não utilizadas, solicite algumas informações que forçam um redesenho e, em seguida, desative a propriedade. Infelizmente, a brilhante equipe por trás do Chrome para Mac parece ter encontrado uma maneira de obter esse deslocamento de altura sem redesenhar. Assim, matando um hack útil.

Até agora, o melhor que consegui para obter o mesmo efeito no Chrome / Mac é essa parte feia:

    $(el).css("border", "solid 1px transparent");
    setTimeout(function()
    {
        $(el).css("border", "solid 0px transparent");
    }, 1000);

Na verdade, force o elemento a pular um pouco, depois relaxe um segundo e pule de volta. Para piorar, se você diminuir o tempo limite abaixo de 500ms (para onde seria menos perceptível), geralmente não terá o efeito desejado, pois o navegador não será redesenhado antes de voltar ao seu estado original.

Alguém gostaria de oferecer uma versão melhor desse hack de redesenho / atualização (de preferência com base no primeiro exemplo acima) que funciona no Chrome / Mac?

Jason Kester
fonte
Encontrei o mesmo problema há alguns minutos atrás. Altero o elemento para uma div (era uma extensão) e agora o navegador redesenha as alterações corretamente. Eu sei que isso é um pouco antigo, mas isso pode ajudar alguns irmãos por aí, eu acho.
Andrés Torres
2
Por favor, veja a resposta abaixo relacionada à opacidade 0,99 - a melhor resposta aqui - mas não é fácil de encontrar, pois é tão profunda na página.
Grouchal
1
Isso também acontece no Linux :-(
schlingel 9/09/15
1
Você pode substituir o tempo limite por um requestAnimationFrame; nesse caso, você conseguirá a mesma coisa, mas com um atraso de 16ms em vez de 1000ms.
trusktr
3
Por favor, apresentar uma questão de cromo para a prestação inválido. Parece que ninguém o fez, apesar disso há 4 anos.
Bardi Harborow # 20/16

Respostas:

183

Não sei exatamente o que você está tentando alcançar, mas este é um método que usei no passado com sucesso para forçar o navegador a redesenhar, talvez ele funcione para você.

// in jquery
$('#parentOfElementToBeRedrawn').hide().show(0);

// in plain js
document.getElementById('parentOfElementToBeRedrawn').style.display = 'none';
document.getElementById('parentOfElementToBeRedrawn').style.display = 'block';

Se esse redesenho simples não funcionar, tente este. Ele insere um nó de texto vazio no elemento que garante um redesenho.

var forceRedraw = function(element){

    if (!element) { return; }

    var n = document.createTextNode(' ');
    var disp = element.style.display;  // don't worry about previous display style

    element.appendChild(n);
    element.style.display = 'none';

    setTimeout(function(){
        element.style.display = disp;
        n.parentNode.removeChild(n);
    },20); // you can play with this timeout to make it as short as possible
}

EDIT: Em resposta ao Šime Vidas, o que estamos alcançando aqui seria um refluxo forçado. Você pode descobrir mais com o próprio mestre http://paulirish.com/2011/dom-html5-css3-performance/

Juank
fonte
3
No entanto, não é possível redesenhar apenas uma parte da janela de visualização. O navegador sempre redesenha a página da web inteira.
Šime Vidas
38
em vez de $ ('# parentOfElementToBeRedrawn'). hide (). show (); Eu precisava .hide.show (0) para funcionar corretamente no Chrome
l.poellabauer
1
@ l.poellabauer mesmo aqui - isso não faz sentido ... mas obrigado. Como diabos você descobriu ??? :)
JohnIdol
6
Para sua informação, a área relevante em que eu estava trabalhando era uma lista de rolagem infinita. Foi um pouco inviável para ocultar / mostrar toda a UL, assim que eu joguei ao redor e descobriu que se você fizer .hide().show(0)em qualquer elemento (eu escolhi o rodapé da página) deve atualizar a página.
treeface
1
É possível redesenhar apenas parte da janela de visualização. Não há API para isso, mas o navegador pode optar por fazê-lo. A janela de visualização é pintada em azulejos para começar. E as camadas compostas são pintadas de forma independente.
precisa saber é o seguinte
62

Nenhuma das respostas acima funcionou para mim. Notei que redimensionar minha janela causou um redesenho. Então, isso fez isso por mim:

$(window).trigger('resize');
Vincent Sels
fonte
11
dica: isso irá redesenhar sua janela completo, que é o desempenho caro e provavelmente não o OP quer
hereandnow78
7
O redimensionamento da janela sempre aciona um reflow, mas o código $(window).trigger('resize')não, ele apenas lança o evento 'redimensionar' que aciona o manipulador relacionado se ele estiver atribuído.
guari 15/07/2015
1
Você me salva uma semana inteira! Meu problema ocorreu depois de definir <body style = "zoom: 67%">, a página aumentou para o nível esperado, mas a página está congelada e não pode rolar !. Sua resposta resolveu esse problema imediatamente
user1143669 8/16
30

Recentemente, descobrimos isso e descobrimos que a promoção do elemento afetado para uma camada composta com o translateZ 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 muitas soluções JavaScript causam reflow e repintam , não apenas repintam.

morewry
fonte
1
isso funcionou bem para mim quando o problema era um elemento HTML removido sendo desenhado em uma tela, mesmo que a tela fosse continuamente animada.
Knaģis
Isso corrigiu um problema de layout que eu estava tendo neon-animated-pages. Obrigado: +1:
coffeesaga
1
Isso funcionou. O Safari estava deixando elementos que em uma div configurados para exibir nenhum, visível e não selecionável. Redimensionar a janela os fez desaparecer. A adição dessa transformação fez com que os "artefatos" desaparecessem conforme o esperado.
Jeff Mattson
28

Esta solução sem timeouts! Força real redesenhada! Para Android e iOS.

var forceRedraw = function(element){
  var disp = element.style.display;
  element.style.display = 'none';
  var trick = element.offsetHeight;
  element.style.display = disp;
};
aviomaksim
fonte
1
Isso não funciona para mim no Chrome nem no FF no Linux. No entanto, simplesmente acessando element.offsetHeight (sem modificar a propriedade de exibição) faz o trabalho. É como se o navegador pulasse o cálculo offsetHeight quando o elemento não estiver visível.
Grodriguez 22/10
Esta solução funciona perfeitamente para solucionar um bug que encontrei no navegador baseado em cromo usado no "overwolf". Eu estava tentando descobrir como fazer isso e você me salvou do esforço.
Nacitar sevaht
13

Isso funciona para mim. Parabéns aqui .

jQuery.fn.redraw = function() {
    return this.hide(0, function() {
        $(this).show();
    });
};

$(el).redraw();
zupa
fonte
1
Mas deixou alguns 'restos' como 'display: block' etc. que às vezes podem destruir a estrutura da página.
pie6k
Isto deve ser essencialmente o mesmo que:$().hide().show(0)
Mahn
@Mahn você tentou? No meu palpite .hide () é não-bloqueante, portanto, pularia a re-renderização executada no navegador, também conhecido como o ponto principal do método. (E se bem me lembro, eu testei na época.)
zupa
2
O @zupa foi testado e está funcionando no Chrome 38. O truque é show()diferente, show(0)ao especificar uma duração no último, é acionado pelos métodos de animação jQuery em um tempo limite, e não imediatamente, o que dá tempo para renderizar da mesma maneira que hide(0, function() { $(this).show(); })faz .
Mahn
@ Mahn ok boa captura então. Definitivamente, sou a sua solução, se funcionar entre plataformas e não for um recurso recente do jQuery. Como não tenho tempo para testá-lo, vou adicioná-lo como 'pode funcionar'. Você pode editar a resposta, se achar que isso se aplica.
zupa
9

Ocultar um elemento e mostrá-lo novamente dentro de um setTimeout de 0 forçará um redesenho.

$('#page').hide();
setTimeout(function() {
    $('#page').show();
}, 0);
Brady Emerson
fonte
1
Este funcionou para um bug do Chrome em que os filtros SVG às vezes não são redesenhados, bugs.chromium.org/p/chromium/issues/detail?id=231560 . O tempo limite foi importante.
Jason D #
Essa foi a que funcionou para mim enquanto tentava forçar o Chrome a recalcular valores para myElement.getBoundingClientRect(), que estavam sendo armazenados em cache, provavelmente para fins de otimização / desempenho. Simplesmente adicionei um tempo limite de zero milissegundo para a função que adiciona o novo elemento ao DOM e obtém os novos valores depois que o elemento antigo com os valores anteriores (em cache) foi removido do DOM.
TheDarkIn1978 17/07/19
6

Isso parece fazer o truque para mim. Além disso, ele realmente não aparece.

$(el).css("opacity", .99);
setTimeout(function(){
   $(el).css("opacity", 1);
},20);
Xavier Follet
fonte
Ele funcionou para mim na versão 60.0.3112.113 do Chromium em relação a este bug: Problema 727076 . Eficiente e sutil; Muito Obrigado.
Lonnie Melhor
@ wiktus239 Talvez 20 millis sejam muito rápidos e o navegador não tenha tempo para mudar para 0,99, antes da hora de voltar para 1,0? - Esse truque de opacidade funciona para mim de qualquer maneira, para tornar o conteúdo em um iframe visível no iPhone iOS 12 Safari, e alterno a cada 1 000 milis. Isso também está no problema vinculado 727076 no comentário acima (1000 milis).
KajMagnus
4

ligar window.getComputedStyle() deve forçar um reflow

qlqllu
fonte
2
não funcionou para mim, mas é provavelmente porque eu precisava da offsetHeightpropriedade que não é css.
Sebas
3

Encontrei esse desafio hoje no OSX El Capitan com o Chrome v51. A página em questão funcionou bem no Safari. Tentei quase todas as sugestões desta página - nenhuma funcionou corretamente - todas tiveram efeitos colaterais ... Acabei implementando o código abaixo - super simples - sem efeitos colaterais (ainda funciona como antes no Safari).

Solução: alterne uma classe no elemento problemático, conforme necessário. Cada alternância forçará um redesenho. (Eu usei o jQuery por conveniência, mas o baunilha JavaScript não deve ser problema ...)

Alternar classe jQuery

$('.slide.force').toggleClass('force-redraw');

Classe CSS

.force-redraw::before { content: "" }

E é isso...

NOTA: Você precisa executar o snippet abaixo de "Página completa" para ver o efeito.

$(window).resize(function() {
  $('.slide.force').toggleClass('force-redraw');
});
.force-redraw::before {
  content: "";
}
html,
body {
  height: 100%;
  width: 100%;
  overflow: hidden;
}
.slide-container {
  width: 100%;
  height: 100%;
  overflow-x: scroll;
  overflow-y: hidden;
  white-space: nowrap;
  padding-left: 10%;
  padding-right: 5%;
}
.slide {
  position: relative;
  display: inline-block;
  height: 30%;
  border: 1px solid green;
}
.slide-sizer {
  height: 160%;
  pointer-events: none;
  //border: 1px solid red;

}
.slide-contents {
  position: absolute;
  top: 10%;
  left: 10%;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<p>
  This sample code is a simple style-based solution to maintain aspect ratio of an element based on a dynamic height.  As you increase and decrease the window height, the elements should follow and the width should follow in turn to maintain the aspect ratio.  You will notice that in Chrome on OSX (at least), the "Not Forced" element does not maintain a proper ratio.
</p>
<div class="slide-container">
  <div class="slide">
    <img class="slide-sizer" src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7">
    <div class="slide-contents">
      Not Forced
    </div>
  </div>
  <div class="slide force">
    <img class="slide-sizer" src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7">
    <div class="slide-contents">
      Forced
    </div>
  </div>
</div>

Ben Feely
fonte
2
function resizeWindow(){
    var evt = document.createEvent('UIEvents');
    evt.initUIEvent('resize', true, false,window,0);
    window.dispatchEvent(evt); 
}

chame essa função após 500 milissegundos.

Shobhit
fonte
2

Se você deseja preservar os estilos declarados em suas folhas de estilo, é melhor usar algo como:

jQuery.fn.redraw = function() {
    this.css('display', 'none'); 
    var temp = this[0].offsetHeight;
    this.css('display', '');
    temp = this[0].offsetHeight;
};

$('.layer-to-repaint').redraw();

NB: com o webkit / piscamento mais recente, armazenando o valor de offsetHeight é obrigatório para ativar a repintagem; caso contrário, a VM (para fins de otimização) provavelmente ignorará essa instrução.

Atualização: adicionada a segunda offsetHeightleitura, é necessário impedir que o navegador enfileire / armazene em cache uma alteração de propriedade / classe CSS a seguir com a restauração do displayvalor (dessa maneira, uma transição CSS que possa ser seguida deve ser renderizada)

guari
fonte
1

Exemplo de HTML:

<section id="parent">
  <article class="child"></article>
  <article class="child"></article>
</section>

Js:

  jQuery.fn.redraw = function() {
        return this.hide(0,function() {$(this).show(100);});
        // hide immediately and show with 100ms duration

    };

função de chamada:

$('article.child').redraw(); // <== má ideia

$('#parent').redraw();
Mehdi Rostami
fonte
também funciona com em .fadeIn(1)vez de .show(300)para mim - não empurrando o elemento. Então eu acho, é o desencadeamento de display:nonee volta parablock
BananaAcid
0

Uma abordagem que funcionou para mim no IE (não consegui usar a técnica de exibição porque havia uma entrada que não deve perder o foco)

Funciona se você tiver margem 0 (alterar o preenchimento também funciona)

if(div.style.marginLeft == '0px'){
    div.style.marginLeft = '';
    div.style.marginRight = '0px';
} else {
    div.style.marginLeft = '0px';
    div.style.marginRight = '';
}
Rogel Garcia
fonte
0

Apenas CSS. Isso funciona para situações em que um elemento filho é removido ou adicionado. Nessas situações, bordas e cantos arredondados podem deixar artefatos.

el:after { content: " "; }
el:before { content: " "; }
rm.rf.etc
fonte
0

Minha correção para o IE10 + IE11. Basicamente, o que acontece é que você adiciona um DIV dentro de um elemento de quebra automática que precisa ser recalculado. Em seguida, basta removê-lo e pronto; Funciona como um encanto :)

    _initForceBrowserRepaint: function() {
        $('#wrapper').append('<div style="width=100%" id="dummydiv"></div>');
        $('#dummydiv').width(function() { return $(this).width() - 1; }).width(function() { return $(this).width() + 1; });
        $('#dummydiv').remove();
    },
Narayan
fonte
0

A maioria das respostas requer o uso de um tempo limite assíncrono, o que causa um piscar de olhos irritante.

Mas eu vim com este aqui, que funciona sem problemas porque é sincrônico:

var p = el.parentNode,
    s = el.nextSibling;
p.removeChild(el);
p.insertBefore(el, s);
Oriol
fonte
Algum evento anexado a esses elementos ainda funcionaria?
Kerry Johnson
0

Esta é a minha solução que funcionou para desaparecer o conteúdo ...

<script type = 'text/javascript'>
    var trash_div;

    setInterval(function()
    {
        if (document.body)
        {
            if (!trash_div)
                trash_div = document.createElement('div');

            document.body.appendChild(trash_div);
            document.body.removeChild(trash_div);
        }
    }, 1000 / 25); //25 fps...
</script>
Kosmos
fonte
0

Corri para um problema semelhante e esta linha simples de JS ajudou a corrigi-lo:

document.getElementsByTagName('body')[0].focus();

No meu caso, foi um bug com uma extensão do Chrome que não redesenhava a página depois de alterar seu CSS de dentro da extensão.

Rotareti
fonte
4
Isso é quebra da acessibilidade do teclado.
Pangamma
0

Eu queria retornar todos os estados ao estado anterior (sem recarregar), incluindo os elementos adicionados pelo jquery. A implementação acima não funciona. e fiz o seguinte.

// Set initial HTML description
var defaultHTML;
function DefaultSave() {
  defaultHTML = document.body.innerHTML;
}
// Restore HTML description to initial state
function HTMLRestore() {
  document.body.innerHTML = defaultHTML;
}



DefaultSave()
<input type="button" value="Restore" onclick="HTMLRestore()">
Ryosuke Hujisawa
fonte
0

Eu tinha uma lista de componentes de reação que, quando rolada, abriu outra página e, ao retornar, a lista não era renderizada no Safari iOS até a página ser rolada. Então essa é a correção.

    componentDidMount() {
        setTimeout(() => {
            window.scrollBy(0, 0);
        }, 300);
    }
luky
fonte
0

Abaixo o css funciona para mim no IE 11 e no Edge, nenhum JS é necessário. scaleY(1)faz o truque aqui. Parece a solução mais simples.

.box {
    max-height: 360px;
    transition: all 0.3s ease-out;
    transform: scaleY(1);
}
.box.collapse {
    max-height: 0;
}
Edmond Wang
fonte
0

Me ajudou

domNodeToRerender.style.opacity = 0.99;
setTimeout(() => { domNodeToRerender.style.opacity = '' }, 0);
Maksym Shemet
fonte
0

2020: Mais leve e mais forte

As soluções anteriores não funcionam mais para mim.

Eu acho que os navegadores otimizam o processo de desenho detectando mais e mais alterações "inúteis".

Essa solução faz com que o navegador desenhe um clone para substituir o elemento original. Funciona e é provavelmente mais sustentável:

const element = document.querySelector('selector');
if (element ) {
  const clone = element.cloneNode(true);
  element.replaceWith(clone);
}

testado no Chrome 80 / Edge 80 / Firefox 75

JP Duvet
fonte