Erzwingen Sie das Neuzeichnen / Aktualisieren von DOM auf Chrome / Mac


215

Von Zeit zu Zeit rendert Chrome perfekt gültiges HTML / CSS falsch oder gar nicht. Das Durchstöbern des DOM-Inspektors reicht oft aus, um den Fehler auf seinen Wegen zu erkennen und korrekt neu zu zeichnen. Daher ist das Markup nachweislich gut. Dies passiert in einem Projekt, an dem ich arbeite, häufig (und vorhersehbar) genug, dass ich Code eingerichtet habe, um unter bestimmten Umständen ein Neuzeichnen zu erzwingen.

Dies funktioniert in den meisten Browser / Betriebssystem-Kombinationen:

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

Optimieren Sie wie in eine nicht verwendete CSS-Eigenschaft, fordern Sie Informationen an, die ein erneutes Zeichnen erzwingen, und deaktivieren Sie dann die Eigenschaft. Leider scheint das kluge Team hinter Chrome für den Mac einen Weg gefunden zu haben, diese Offset-Höhe zu erreichen, ohne sie neu zu zeichnen. So wird ein ansonsten nützlicher Hack getötet.

Das Beste, was ich mir bisher ausgedacht habe, um den gleichen Effekt auf Chrome / Mac zu erzielen, ist dieses Stück Hässlichkeit:

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

Wie in, zwingen Sie das Element tatsächlich, ein wenig zu springen, kühlen Sie dann eine Sekunde und springen Sie es zurück. Erschwerend kommt hinzu, dass wenn Sie dieses Zeitlimit unter 500 ms fallen lassen (wo es weniger auffällt), es oft nicht den gewünschten Effekt hat, da der Browser nicht zum Neuzeichnen kommt, bevor er in seinen ursprünglichen Zustand zurückkehrt.

Möchte jemand eine bessere Version dieses Redraw / Refresh-Hacks (vorzugsweise basierend auf dem ersten Beispiel oben) anbieten, die auf Chrome / Mac funktioniert?


Ich bin vor ein paar Minuten auf dasselbe Problem gestoßen. Ich ändere das Element für ein div (es war eine Spanne) und jetzt zeichnet der Browser die Änderungen korrekt neu. Ich weiß, dass dies ein bisschen alt ist, aber das kann einigen Brüdern da draußen helfen, denke ich.
Andrés Torres

2
Bitte beachten Sie die Antwort unten in Bezug auf Deckkraft 0,99 - die beste Antwort hier - aber nicht leicht zu finden, da sie so tief auf der Seite liegt.
Grouchal

1
Dies passiert auch unter Linux :-(
schlingel

1
Sie können das Zeitlimit durch einen requestAnimationFrame ersetzen. In diesem Fall erreichen Sie dasselbe, jedoch mit einer Verzögerung von 16 ms anstelle von 1000 ms.
Trusktr

3
Bitte reichen Sie ein Chromium-Problem für das ungültige Rendering ein. Es scheint, dass niemand dies getan hat, obwohl dies vor 4 Jahren war.
Bardi Harborow

Antworten:


183

Ich bin mir nicht sicher, was genau Sie erreichen möchten, aber dies ist eine Methode, die ich in der Vergangenheit mit Erfolg angewendet habe, um den Browser zum Neuzeichnen zu zwingen. Vielleicht funktioniert sie für Sie.

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

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

Wenn dieses einfache Neuzeichnen nicht funktioniert, können Sie es versuchen. Es fügt einen leeren Textknoten in das Element ein, der ein erneutes Zeichnen garantiert.

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: Als Reaktion auf Šime Vidas würden wir hier einen erzwungenen Reflow erreichen. Weitere Informationen erhalten Sie vom Meister selbst. Http://paulirish.com/2011/dom-html5-css3-performance/


3
Afaik, es ist nicht möglich, nur einen Teil des Ansichtsfensters neu zu zeichnen. Der Browser zeichnet immer die gesamte Webseite neu.
Šime Vidas

38
anstelle von $ ('# parentOfElementToBeRedrawn'). hide (). show (); Ich brauchte .hide.show (0), um in Chrome richtig zu funktionieren
l.poellabauer

1
@ l.poellabauer gleich hier - das macht keinen Sinn ... aber danke. Wie zum Teufel hast du das herausgefunden ??? :)
JohnIdol

6
Zu Ihrer Information, der relevante Bereich, an dem ich arbeitete, war eine unendliche Bildlaufliste. Es war ein bisschen nicht möglich, die gesamte UL auszublenden / anzuzeigen, also habe ich herumgespielt und festgestellt, dass die Seite aktualisiert werden sollte , wenn Sie dies .hide().show(0)für ein Element tun (ich habe die Seitenfußzeile gewählt).
Baumgesicht

1
Es ist möglich, nur einen Teil des Ansichtsfensters neu zu zeichnen. Es gibt keine API dafür, aber der Browser kann dies auswählen. Das Ansichtsfenster ist zunächst mit Kacheln bemalt. Und Verbundschichten werden unabhängig voneinander gestrichen.
Morewry

62

Keine der oben genannten Antworten hat bei mir funktioniert. Ich habe festgestellt, dass die Größenänderung meines Fensters zu einem Neuzeichnen führte. Das hat es also für mich getan:

$(window).trigger('resize');

11
tipp: dies wird dein komplettes fenster neu zeichnen, was leistungsintensiv ist und wahrscheinlich nicht das, was OP will
hereandnow78

7
Das Ändern der Fenstergröße löst immer einen Reflow aus, der Code $(window).trigger('resize')jedoch nicht. Es wird nur das Ereignis "Größenänderung" ausgelöst, das den zugehörigen Handler auslöst, wenn er zugewiesen ist.
Guari

1
Du rettest mich eine ganze Woche! Mein Problem ist, dass nach dem Festlegen von <body style = "zoom: 67%"> die Seite auf die erwartete Stufe gezoomt wurde, die Seite jedoch eingefroren ist und nicht gescrollt werden kann. Ihre Antwort löste dieses Problem sofort
user1143669

30

Wir sind kürzlich darauf gestoßen und haben festgestellt, dass das Heraufstufen des betroffenen Elements auf eine zusammengesetzte Ebene mit translateZ das Problem behoben hat, ohne dass zusätzliches Javascript erforderlich ist.

.willnotrender { 
   transform: translateZ(0); 
}

Da diese Malprobleme hauptsächlich in Webkit / Blink auftreten und dieses Update hauptsächlich auf Webkit / Blink abzielt, ist es in einigen Fällen vorzuziehen. Zumal viele JavaScript-Lösungen einen Reflow und ein Repaint verursachen , nicht nur ein Repaint.


1
Dies funktionierte gut für mich, als das Problem darin bestand, dass ein entferntes HTML-Element auf eine Leinwand gezeichnet wurde, obwohl die Leinwand kontinuierlich animiert wurde.
Knaģis

Dies behebt ein Layoutproblem, mit dem ich hatte neon-animated-pages. Vielen Dank: +1:
Coffeesaga

1
Das hat funktioniert. Safari hinterließ Elemente, die in einem Div-Set keine sichtbar, nicht auswählbar und nicht auswählbar waren. Durch Ändern der Fenstergröße wurden sie ausgeblendet. Durch Hinzufügen dieser Transformation verschwanden die "Artefakte" wie erwartet.
Jeff Mattson

28

Diese Lösung ohne Timeouts! Echte Kraft neu zeichnen! Für Android und iOS.

var forceRedraw = function(element){
  var disp = element.style.display;
  element.style.display = 'none';
  var trick = element.offsetHeight;
  element.style.display = disp;
};

1
Dies funktioniert weder bei Chrome noch bei FF unter Linux. Der einfache Zugriff auf element.offsetHeight (ohne die Anzeigeeigenschaft zu ändern) funktioniert jedoch . Es ist so, als würde der Browser die OffsetHeight-Berechnung überspringen, wenn das Element nicht sichtbar ist.
Grodriguez

Diese Lösung funktioniert perfekt, um einen Fehler zu umgehen, den ich in dem in "overwolf" verwendeten chrombasierten Browser festgestellt habe. Ich habe versucht herauszufinden, wie das geht, und Sie haben mir die Mühe erspart.
Nacitar Sevaht

13

Das funktioniert bei mir. Kudos gehen hier .

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

$(el).redraw();

1
Aber es blieben einige "Überreste" wie "display: block" usw. übrig, die manchmal die Seitenstruktur zerstören können.
Pie6k

Dies sollte im Wesentlichen das gleiche sein wie$().hide().show(0)
Mahn

@ Mahn hast du es versucht? Meiner Meinung nach ist .hide () nicht blockierend, daher würde es das erneute Rendern im Browser überspringen, auch bekannt als der ganze Sinn der Methode. (Und wenn ich mich richtig erinnere, habe ich es damals getestet.)
zupa

2
@zupa Es ist getestet und funktioniert mit Chrome 38. Der Trick show()unterscheidet sich show(0)darin, dass es durch Angabe einer Dauer in letzterem durch jQuery-Animationsmethoden in einem Timeout ausgelöst wird und nicht sofort, was Zeit zum Rendern auf die gleiche Weise hide(0, function() { $(this).show(); })gibt .
Mahn

@ Mahn okay, dann schöner Fang. Ich bin auf jeden Fall für Ihre Lösung, wenn sie plattformübergreifend funktioniert und keine aktuelle jQuery-Funktion ist. Da ich nicht die Zeit habe, es zu testen, werde ich es hinzufügen, da es möglicherweise funktioniert. Sie können die Antwort gerne bearbeiten, wenn Sie der Meinung sind, dass dies zutrifft.
zupa

9

Wenn Sie ein Element ausblenden und dann innerhalb eines setTimeout von 0 erneut anzeigen, wird ein Neuzeichnen erzwungen.

$('#page').hide();
setTimeout(function() {
    $('#page').show();
}, 0);

1
Dieser funktionierte für einen Chrome-Fehler, bei dem SVG-Filter manchmal nicht neu gestrichen werden . Bugs.chromium.org/p/chromium/issues/detail?id=231560 . Das Timeout war wichtig.
Jason D

Dies ist diejenige, die für mich funktioniert hat, als ich versucht habe, Chrome zu zwingen, Werte neu zu berechnen, für myElement.getBoundingClientRect()die zwischengespeichert wurde, wahrscheinlich aus Optimierungs- / Leistungsgründen. Ich habe einfach ein Zeitlimit von null Millisekunden für die Funktion hinzugefügt, die das neue Element zum DOM hinzufügt und die neuen Werte abruft, nachdem das alte Element mit den vorherigen (zwischengespeicherten) Werten aus dem DOM entfernt wurde.
TheDarkIn1978

6

Dies scheint den Trick für mich zu tun. Außerdem wird es überhaupt nicht angezeigt.

$(el).css("opacity", .99);
setTimeout(function(){
   $(el).css("opacity", 1);
},20);

In Chromium Version 60.0.3112.113 hat es in Bezug auf diesen Fehler funktioniert : Problem 727076 . Effizient und subtil; Danke vielmals.
Lonnie Best

@ wiktus239 Vielleicht sind 20 Millis zu schnell und der Browser hat keine Zeit, auf 0,99 zu wechseln, bevor er wieder auf 1,0 wechselt? - Dieser Deckkrafttrick funktioniert sowieso für mich, um Inhalte in einem Iframe in iPhone iOS 12 Safari sichtbar zu machen, und ich schalte alle 1 000 Millis um. Das steht auch in der verlinkten Ausgabe 727076 im obigen Kommentar (1000 Millis).
KajMagnus

4

Aufruf window.getComputedStyle()sollte einen Reflow erzwingen


2
hat bei mir nicht funktioniert, aber das liegt wahrscheinlich daran, dass ich die offsetHeightEigenschaft brauchte, die nicht CSS ist.
Sebas

3

Ich bin heute in OSX El Capitan mit Chrome v51 auf diese Herausforderung gestoßen. Die betreffende Seite hat in Safari einwandfrei funktioniert. Ich habe fast jeden Vorschlag auf dieser Seite ausprobiert - keiner hat richtig funktioniert - alle hatten Nebenwirkungen ... Am Ende habe ich den folgenden Code implementiert - super einfach - keine Nebenwirkungen (funktioniert immer noch wie zuvor in Safari).

Lösung: Schalten Sie nach Bedarf eine Klasse für das problematische Element um. Bei jedem Umschalten wird ein Neuzeichnen erzwungen. (Ich habe jQuery verwendet, aber Vanille-JavaScript sollte kein Problem sein ...)

jQuery Class Toggle

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

CSS-Klasse

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

Und das ist es...

HINWEIS: Sie müssen das Snippet unter "Ganzseite" ausführen, um den Effekt zu sehen.

$(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>


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

Rufen Sie diese Funktion nach 500 Millisekunden auf.


2

Wenn Sie die in Ihren Stylesheets deklarierten Stile beibehalten möchten, verwenden Sie besser Folgendes:

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

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

Hinweis: Beim neuesten Webkit / Blink ist das Speichern des Werts von offsetHeightobligatorisch, um das Repaint auszulösen. Andernfalls überspringt die VM (zu Optimierungszwecken) diese Anweisung wahrscheinlich.

Update: In der zweiten offsetHeightLesung muss verhindert werden, dass der Browser eine folgende Änderung der CSS-Eigenschaft / Klasse mit der Wiederherstellung des displayWerts in die Warteschlange stellt / zwischenspeichert (auf diese Weise sollte ein CSS-Übergang gerendert werden, der folgen kann).


1

Beispiel-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

    };

Aufruffunktion:

$('article.child').redraw(); // <== schlechte Idee

$('#parent').redraw();

funktioniert auch mit .fadeIn(1)statt .show(300)für mich - das Element nicht herumschieben. Also ich denke, es ist das Auslösen von display:noneund zurück zublock
BananaAcid

0

Ein Ansatz, der für mich im IE funktioniert hat (ich konnte die Anzeigetechnik nicht verwenden, da es eine Eingabe gab, die den Fokus nicht verlieren darf).

Es funktioniert, wenn Sie einen Rand von 0 haben (das Ändern der Polsterung funktioniert auch)

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

0

Nur CSS. Dies funktioniert in Situationen, in denen ein untergeordnetes Element entfernt oder hinzugefügt wird. In diesen Situationen können Ränder und abgerundete Ecken Artefakte hinterlassen.

el:after { content: " "; }
el:before { content: " "; }

0

Mein Fix für IE10 + IE11. Grundsätzlich passiert, dass Sie einen DIV innerhalb eines Wrapping-Elements hinzufügen, das neu berechnet werden muss. Dann entfernen Sie es einfach und voila; klappt wunderbar :)

    _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();
    },

0

Die meisten Antworten erfordern die Verwendung einer asynchronen Zeitüberschreitung, die ein störendes Blinken verursacht.

Aber ich habe mir dieses ausgedacht, das reibungslos funktioniert, weil es synchron ist:

var p = el.parentNode,
    s = el.nextSibling;
p.removeChild(el);
p.insertBefore(el, s);

Würden an diese Elemente angehängte Ereignisse noch funktionieren?
Kerry Johnson

0

Dies ist meine Lösung, die zum Verschwinden von Inhalten funktioniert hat ...

<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>

0

Ich bin auf ein ähnliches Problem gestoßen, und diese einfache Zeile von JS hat geholfen, es zu beheben:

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

In meinem Fall war es ein Fehler mit einer Chrome-Erweiterung, die die Seite nicht neu zeichnete, nachdem das CSS innerhalb der Erweiterung geändert wurde.


4
Dies unterbricht die Zugänglichkeit der Tastatur.
Pangamma

0

Ich wollte alle Zustände auf den vorherigen Zustand zurücksetzen (ohne neu zu laden), einschließlich der von jquery hinzugefügten Elemente. Die obige Implementierung wird nicht funktionieren. und ich tat wie folgt.

// 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()">

0

Ich hatte eine Reaktionskomponentenliste, die beim Scrollen eine andere Seite öffnete und bei der Rückkehr die Liste unter Safari iOS erst gerendert wurde, wenn die Seite gescrollt wurde. Das ist also die Lösung.

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

0

Unten funktioniert CSS für mich unter IE 11 und Edge, kein JS erforderlich. scaleY(1)macht den Trick hier. Scheint die einfachste Lösung zu sein.

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

0

Es hat mir geholfen

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

0

2020: Leichter und stärker

Die vorherigen Lösungen funktionieren bei mir nicht mehr.

Ich denke, Browser optimieren den Zeichenprozess, indem sie immer mehr "nutzlose" Änderungen erkennen.

Diese Lösung lässt den Browser einen Klon zeichnen, um das ursprüngliche Element zu ersetzen. Es funktioniert und ist wahrscheinlich nachhaltiger:

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

getestet auf Chrome 80 / Edge 80 / Firefox 75

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.