Forcer le redessin / rafraîchissement DOM sur Chrome / Mac

216

De temps en temps, Chrome rendra HTML / CSS parfaitement valide incorrectement ou pas du tout. Il suffit souvent de creuser dans l'inspecteur DOM pour qu'il se rende compte de l'erreur de ses chemins et redessine correctement, il est donc prouvé que le balisage est bon. Cela se produit assez fréquemment (et de manière prévisible) dans un projet sur lequel je travaille et que j'ai mis en place du code pour forcer un redessin dans certaines circonstances.

Cela fonctionne dans la plupart des combinaisons navigateur / système d'exploitation:

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

Comme dans, ajustez une propriété CSS inutilisée, puis demandez des informations qui forcent un redessin, puis décochez la propriété. Malheureusement, l'équipe brillante derrière Chrome pour Mac semble avoir trouvé un moyen d'obtenir cet offsetHeight sans redessiner. Tuer ainsi un hack autrement utile.

Jusqu'à présent, le meilleur que j'ai trouvé pour obtenir le même effet sur Chrome / Mac est cette laideur:

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

Comme dans, forcez l'élément à sauter un peu, puis refroidissez-le une seconde et sautez-le en arrière. Pire encore, si vous ramenez ce délai en dessous de 500 ms (là où il serait moins visible), il n'aura souvent pas l'effet souhaité, car le navigateur ne se mettra pas à redessiner avant de revenir à son état d'origine.

Quelqu'un veut-il offrir une meilleure version de ce hack de rafraîchissement / rafraîchissement (basé de préférence sur le premier exemple ci-dessus) qui fonctionne sur Chrome / Mac?

Jason Kester
la source
J'ai rencontré le même problème il y a quelques minutes. Je change l'élément pour un div (c'était une durée) et maintenant le navigateur redessine correctement les changements. Je sais que c'est un peu vieux, mais cela peut aider certains frères, je pense.
Andrés Torres
2
Veuillez voir la réponse ci-dessous concernant l'opacité 0.99 - la meilleure réponse ici - mais pas facile à trouver car elle est si profonde sur la page.
Grouchal
1
Cela se produit également sur Linux :-(
schlingel
1
Vous pouvez remplacer le délai d'expiration par un requestAnimationFrame, auquel cas vous obtiendrez la même chose mais avec un décalage de 16 ms au lieu de 1000 ms.
trusktr
3
Veuillez déposer un problème de chrome pour le rendu non valide. Il semble que personne ne l'ait fait, malgré le fait qu'il y a 4 ans.
Bardi Harborow

Réponses:

183

Je ne sais pas exactement ce que vous essayez de réaliser, mais c'est une méthode que j'ai utilisée avec succès dans le passé pour forcer le navigateur à se redessiner, peut-être que cela fonctionnera pour vous.

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

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

Si ce simple redessin ne fonctionne pas, vous pouvez essayer celui-ci. Il insère un nœud de texte vide dans l'élément, ce qui garantit un redessin.

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: En réponse à Šime Vidas ce que nous réalisons ici serait une refusion forcée. Vous pouvez en savoir plus sur le maître lui-même http://paulirish.com/2011/dom-html5-css3-performance/

Juank
la source
3
Afaik, il n'est pas possible de redessiner seulement une partie de la fenêtre. Le navigateur redessine toujours la page Web entière.
Šime Vidas
39
au lieu de $ ('# parentOfElementToBeRedrawn'). hide (). show (); J'avais besoin de .hide.show (0) pour fonctionner correctement dans Chrome
l.poellabauer
1
@ l.poellabauer même ici - cela n'a aucun sens ... mais merci. Comment diable avez-vous découvert ??? :)
JohnIdol
6
Pour info, le domaine pertinent sur lequel je travaillais était une liste de défilement infinie. Il était un peu impossible de masquer / afficher l'intégralité de l'UL, j'ai donc joué et trouvé que si vous le faites .hide().show(0)sur n'importe quel élément (j'ai choisi le pied de page), cela devrait rafraîchir la page.
treeface
1
Il est possible de redessiner seulement une partie de la fenêtre. Il n'y a pas d'API pour le faire, mais le navigateur peut choisir de le faire. Pour commencer, la fenêtre est peinte en tuiles. Et les couches composites sont peintes indépendamment.
morewry
62

Aucune des réponses ci-dessus n'a fonctionné pour moi. J'ai remarqué que le redimensionnement de ma fenêtre a causé un redessin. Donc, ça l'a fait pour moi:

$(window).trigger('resize');
Vincent Sels
la source
11
indice: cela redessinera votre fenêtre complète, ce qui est coûteux en performances et probablement pas ce que OP veut
hereandnow78
7
Le redimensionnement de la fenêtre déclenche toujours une redistribution, mais pas le code $(window).trigger('resize'), il ne déclenche l'événement «resize» qui déclenche le gestionnaire associé que s'il est affecté.
guari
1
Tu me sauves une semaine entière! Mon problème est après avoir défini <body style = "zoom: 67%">, la page a zoomé à mon niveau attendu, mais la page est gelée et ne peut pas défiler !. Votre réponse a résolu ce problème immédiatement
user1143669
30

Nous avons récemment rencontré cela et découvert que la promotion de l'élément affecté en une couche composite avec translateZ corrigeait le problème sans avoir besoin de Javascript supplémentaire.

.willnotrender { 
   transform: translateZ(0); 
}

Comme ces problèmes de peinture apparaissent principalement dans Webkit / Blink, et que ce correctif cible principalement Webkit / Blink, il est préférable dans certains cas. D'autant plus que de nombreuses solutions JavaScript provoquent une refusion et une repeinture , pas seulement une repeinture.

plus
la source
1
cela a bien fonctionné pour moi lorsque le problème était un élément HTML supprimé dessiné sur un canevas même si le canevas était continuellement animé.
Knaģis
Cela a résolu un problème de mise en page avec lequel je rencontrais neon-animated-pages. Merci: +1:
coffeesaga
1
Cela a fonctionné. Safari laissait des éléments qui, dans un ensemble div, n'en afficheraient aucun, visibles et non sélectionnables. Redimensionner la fenêtre les a fait disparaître. L'ajout de cette transformation a fait disparaître les "artefacts" comme prévu.
Jeff Mattson
28

Cette solution sans timeouts! Réel redessiner la force ! Pour Android et iOS.

var forceRedraw = function(element){
  var disp = element.style.display;
  element.style.display = 'none';
  var trick = element.offsetHeight;
  element.style.display = disp;
};
aviomaksim
la source
1
Cela ne fonctionne pas pour moi sur Chrome ni FF sur Linux. Cependant tout simplement l' accès element.offsetHeight (sans modifier la propriété d'affichage) fait le travail. C'est comme si le navigateur sautait le calcul offsetHeight lorsque l'élément n'était pas visible.
Grodriguez
Cette solution fonctionne parfaitement pour contourner un bug que j'ai rencontré dans le navigateur basé sur Chrome utilisé dans "Overwolf". J'essayais de comprendre comment faire et tu m'as épargné l'effort.
nacitar sevaht
13

Cela fonctionne pour moi. Bravo, allez ici .

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

$(el).redraw();
zupa
la source
1
Mais il a laissé des «restes» comme «display: block», etc. qui peuvent parfois détruire la structure de la page.
pie6k
Cela devrait être essentiellement le même que$().hide().show(0)
Mahn
@Mahn l'avez-vous essayé? À mon avis, .hide () est non bloquant, donc il ignorerait le nouveau rendu dans le navigateur, c'est-à-dire tout le point de la méthode. (Et si je me souviens bien, je l'ai testé à l'époque.)
zupa
2
@zupa il est testé et travailler sur Chrome 38. L'astuce est que show()est différent show(0)en ce en spécifiant une durée dans ce dernier, il est déclenché par des méthodes d'animation jQuery dans un délai d' attente plutôt qu'immédiatement qui donne de temps pour prendre de la même manière que le hide(0, function() { $(this).show(); })fait .
Mahn
@Mahn ok, belle prise alors. Je suis définitivement pour votre solution si elle fonctionne sur plusieurs plateformes et n'est pas une fonctionnalité récente de jQuery. Comme je n'ai pas le temps de le tester, je vais l'ajouter comme «peut fonctionner». Vous pouvez modifier la réponse si vous pensez que ce qui précède s'applique.
zupa
9

Masquer un élément et l'afficher à nouveau dans un setTimeout de 0 forcera un nouveau tracé.

$('#page').hide();
setTimeout(function() {
    $('#page').show();
}, 0);
Brady Emerson
la source
1
Celui-ci a fonctionné pour un bogue Chrome où les filtres SVG ne sont parfois pas repeints , bugs.chromium.org/p/chromium/issues/detail?id=231560 . Le délai d'attente était important.
Jason D
C'est celui qui a fonctionné pour moi tout en essayant de forcer Chrome à recalculer les valeurs pour myElement.getBoundingClientRect(), qui étaient mises en cache, probablement à des fins d'optimisation / de performances. J'ai simplement ajouté un délai d'expiration de zéro milliseconde pour la fonction qui ajoute le nouvel élément au DOM et obtient les nouvelles valeurs après que l'ancien élément avec les valeurs précédentes (mises en cache) a été supprimé du DOM.
TheDarkIn1978
6

Cela semble faire l'affaire pour moi. De plus, cela ne se voit vraiment pas du tout.

$(el).css("opacity", .99);
setTimeout(function(){
   $(el).css("opacity", 1);
},20);
Xavier Follet
la source
Cela a fonctionné pour moi dans Chromium version 60.0.3112.113 concernant ce bogue: Problème 727076 . Efficace et subtil; Merci beaucoup.
Lonnie Best
@ wiktus239 Peut-être que 20 millis est trop rapide et que le navigateur n'a pas le temps de passer à .99, avant de revenir à 1.0? - Cette astuce d'opacité fonctionne pour moi de toute façon, pour rendre le contenu dans un iframe visible dans iPhone iOS 12 Safari, et je bascule tous les 1 000 millis. C'est aussi ce qui est dans le problème lié 727076 dans le commentaire ci-dessus (1000 millis).
KajMagnus
4

l'appel window.getComputedStyle()devrait forcer une refusion

qlqllu
la source
2
n'a pas fonctionné pour moi, mais c'est probablement parce que j'avais besoin de la offsetHeightpropriété qui n'est pas CSS.
Sebas
3

J'ai rencontré ce défi aujourd'hui dans OSX El Capitan avec Chrome v51. La page en question a bien fonctionné dans Safari. J'ai essayé presque toutes les suggestions sur cette page - aucune ne fonctionnait correctement - toutes avaient des effets secondaires ... J'ai fini par implémenter le code ci-dessous - super simple - pas d'effets secondaires (fonctionne toujours comme auparavant dans Safari).

Solution: basculez une classe sur l'élément problématique selon vos besoins. Chaque bascule forcera un nouveau tracé. (J'ai utilisé jQuery pour plus de commodité, mais JavaScript vanille ne devrait pas poser de problème ...)

Basculement de classe jQuery

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

Classe CSS

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

Et c'est tout...

Remarque: vous devez exécuter l'extrait de code en dessous de «pleine page» afin de voir l'effet.

$(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
la source
2
function resizeWindow(){
    var evt = document.createEvent('UIEvents');
    evt.initUIEvent('resize', true, false,window,0);
    window.dispatchEvent(evt); 
}

appeler cette fonction après 500 millisecondes.

Shobhit
la source
2

Si vous souhaitez conserver les styles déclarés dans vos feuilles de style, mieux vaut utiliser quelque chose comme:

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: avec le dernier webkit / blink, le stockage de la valeur de offsetHeightest obligatoire afin de déclencher le repeindre, sinon la VM (à des fins d'optimisation) sautera probablement cette instruction.

Mise à jour: ajout de la deuxième offsetHeightlecture, il est nécessaire d'empêcher le navigateur de mettre en file d'attente / de mettre en cache un changement de propriété / classe CSS suivant avec la restauration de la displayvaleur (de cette façon, une transition CSS qui peut suivre doit être rendue)

guari
la source
1

Exemple de code 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

    };

fonction d'appel:

$('article.child').redraw(); // <== mauvaise idée

$('#parent').redraw();
Mehdi Rostami
la source
fonctionne également avec .fadeIn(1)au lieu de .show(300)pour moi - ne pas pousser l'élément autour. Donc je suppose que c'est le déclenchement display:noneet le retour deblock
BananaAcid
0

Une approche qui a fonctionné pour moi sur IE (je ne pouvais pas utiliser la technique d'affichage car il y avait une entrée qui ne devait pas perdre le focus)

Cela fonctionne si vous avez 0 marge (la modification du rembourrage fonctionne également)

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

CSS uniquement. Cela fonctionne pour les situations où un élément enfant est supprimé ou ajouté. Dans ces situations, les bordures et les coins arrondis peuvent laisser des artefacts.

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

Ma solution pour IE10 + IE11. Fondamentalement, ce qui se passe est que vous ajoutez un DIV dans un élément enveloppant qui doit être recalculé. Il suffit ensuite de le retirer et le tour est joué; fonctionne comme un charme :)

    _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
la source
0

La plupart des réponses nécessitent l'utilisation d'un délai d'attente asynchrone, ce qui provoque un clignotement gênant.

Mais je suis venu avec celui-ci, qui fonctionne bien car il est synchrone:

var p = el.parentNode,
    s = el.nextSibling;
p.removeChild(el);
p.insertBefore(el, s);
Oriol
la source
Des événements liés à ces éléments fonctionneraient-ils toujours?
Kerry Johnson
0

C'est ma solution qui a fonctionné pour la disparition de contenu ...

<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
la source
0

J'ai rencontré un problème similaire et cette simple ligne de JS a aidé à le résoudre:

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

Dans mon cas, il s'agissait d'un bug avec une extension Chrome ne redessinant pas la page après avoir changé son CSS depuis l'extension.

Rotareti
la source
4
Cela rompt l'accessibilité du clavier.
Pangamma
0

Je voulais ramener tous les états à l'état précédent (sans rechargement) y compris les éléments ajoutés par jquery. L'implémentation ci-dessus ne fonctionnera pas. et j'ai fait comme suit.

// 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
la source
0

J'avais une liste de composants React qui, lorsqu'elle était défilée, avait ouvert une autre page, puis lors du retour, la liste n'était pas rendue sur Safari iOS jusqu'à ce que la page défile. C'est donc le correctif.

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

Ci-dessous, css fonctionne pour moi sur IE 11 et Edge, aucun JS n'est nécessaire. scaleY(1)fait l'affaire ici. Semble la solution la plus simple.

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

Ça m'a aidé

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

2020: plus léger et plus fort

Les solutions précédentes ne fonctionnent plus pour moi.

Je suppose que les navigateurs optimisent le processus de dessin en détectant de plus en plus de changements "inutiles".

Cette solution fait que le navigateur dessine un clone pour remplacer l'élément d'origine. Cela fonctionne et est probablement plus durable:

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

testé sur Chrome 80 / Edge 80 / Firefox 75

Couette JP
la source