Wymuś przerysowanie / odświeżenie DOM w Chrome / Mac


215

Od czasu do czasu Chrome renderuje całkowicie poprawny kod HTML / CSS niepoprawnie lub wcale. Zagłębianie się w inspektora DOM często wystarcza, aby uświadomić sobie błąd na swoich drogach i poprawnie przerysować, więc możliwe jest, że znaczniki są dobre. Zdarza się to dość często (i przewidywalnie) w projekcie, nad którym pracuję, że wprowadziłem kod, aby wymusić przerysowanie w pewnych okolicznościach.

Działa to w większości kombinacji przeglądarki / systemu operacyjnego:

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

Jak na początku, popraw nieużywaną właściwość CSS, a następnie poproś o informacje, które wymuszają przerysowanie, a następnie popraw właściwość. Niestety, świetny zespół, który stoi za Chrome na Maca, wydaje się znaleźć sposób na uzyskanie offsetuHeight bez przerysowywania. W ten sposób zabicie innego użytecznego hacka.

Do tej pory najlepsze, co wymyśliłem, aby uzyskać ten sam efekt w Chrome / Mac, to brzydota:

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

Jak w rzeczywistości, faktycznie zmuś element do skoku, a następnie zrelaksuj się przez chwilę i przeskocz z powrotem. Co gorsza, jeśli upuścisz ten limit poniżej 500 ms (do poziomu, który byłby mniej zauważalny), często nie będzie miał pożądanego efektu, ponieważ przeglądarka nie przejdzie do przerysowywania, zanim powróci do pierwotnego stanu.

Czy ktoś chce zaoferować lepszą wersję tego przerysowania / odświeżenia (najlepiej w oparciu o pierwszy przykład powyżej), która działa na Chrome / Mac?


Kilka minut temu napotkałem ten sam problem. Zmieniam element div (był to zakres), a teraz przeglądarka poprawnie przerysowuje zmiany. Wiem, że to trochę stare, ale myślę, że może to pomóc niektórym braciom.
Andrés Torres,

2
Zobacz odpowiedź poniżej dotyczącą krycia 0,99 - najlepsza odpowiedź tutaj - ale nie jest łatwa do znalezienia, ponieważ jest tak głęboko na stronie.
Grouchal

1
Dzieje się tak również w systemie Linux :-(
schlingel

1
Możesz zamienić limit czasu na requestAnimationFrame, w którym to przypadku osiągniesz to samo, ale z opóźnieniem 16 ms zamiast 1000 ms.
trusktr

3
Proszę zgłosić problem Chromium dla nieważnego renderingu. Wygląda na to, że nikt tego nie zrobił, pomimo tego 4 lata temu.
Bardi Harborow

Odpowiedzi:


183

Nie jestem pewien, co dokładnie próbujesz osiągnąć, ale jest to metoda, którą z powodzeniem stosowałem, aby zmusić przeglądarkę do przerysowania, być może zadziała.

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

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

Jeśli to proste przerysowanie nie działa, możesz spróbować tego. Wstawia pusty element tekstowy do elementu, co gwarantuje przerysowanie.

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
}

EDYCJA: W odpowiedzi na Šime Vidasa osiągamy tutaj wymuszony przepływ. Możesz dowiedzieć się więcej od samego mistrza http://paulirish.com/2011/dom-html5-css3-performance/


3
Afaik, nie można przerysować tylko części rzutni. Przeglądarka zawsze przerysowuje całą stronę internetową.
Šime Vidas,

38
zamiast $ ('# parentOfElementToBeRedrawn'). hide (). show (); Potrzebowałem .hide.show (0) do poprawnego działania w Chrome
l.poellabauer

1
@ l.poellabauer to samo tutaj - to nie ma sensu ... ale dzięki. Jak do cholery się dowiedziałeś ??? :)
JohnIdol

6
Do Twojej wiadomości, odpowiednim obszarem, nad którym pracowałem, była nieskończona lista przewijania. To było trochę niepraktyczne, aby ukryć / pokazać całą UL, więc grałem dookoła i stwierdził, że jeśli .hide().show(0)na każdym elemencie (wybrałem stopce strony) należy odświeżyć stronę.
treeface

1
Możliwe jest przerysowanie tylko części rzutni. Nie ma do tego interfejsu API, ale przeglądarka może to zrobić. Rzutnia jest na początku pomalowana w kafelki. A warstwy kompozytowe są malowane niezależnie.
morewry 15.01.2015

62

Żadna z powyższych odpowiedzi nie działała dla mnie. Zauważyłem, że zmiana rozmiaru mojego okna spowodowała przerysowanie. Zrobiło to dla mnie:

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

11
wskazówka: spowoduje to przerysowanie całego okna, co jest kosztowne i prawdopodobnie nie jest tym, czego chce OP
dziś

7
Zmiana rozmiaru okna zawsze wyzwala przepływ, ale kod $(window).trigger('resize')nie, powoduje jedynie zdarzenie „resize”, które wyzwala powiązany moduł obsługi, jeśli jest przypisany.
guari,

1
Ratujesz mnie cały tydzień! Mój problem występuje po ustawieniu <body style = "zoom: 67%"> strona została powiększona do oczekiwanego poziomu, ale strona jest zamrożona i nie można przewijać !. Twoja odpowiedź natychmiast rozwiązała ten problem
1143669

30

Niedawno się z tym spotkaliśmy i odkryliśmy, że promowanie dotkniętego elementu do warstwy kompozytowej za pomocą translateZ rozwiązało problem bez potrzeby dodatkowego javascript.

.willnotrender { 
   transform: translateZ(0); 
}

Ponieważ te problemy z malowaniem pojawiają się głównie w Webkit / Blink, a ta poprawka dotyczy głównie Webkit / Blink, w niektórych przypadkach lepiej jest. Zwłaszcza, że ​​wiele rozwiązań JavaScript powoduje ponowne wlanie i odmalowanie , a nie tylko odmalowanie.


1
działało to dla mnie dobrze, gdy problem polegał na tym, że element HTML był rysowany na płótnie, mimo że płótno było ciągle animowane.
Knaģis

To rozwiązało problem z układem, który miałem neon-animated-pages. Dziękujemy: +1:
coffeesaga

1
To zadziałało. Safari pozostawia elementy, które w zestawie div nie wyświetlają żadnych, są widoczne i nie można ich wybrać. Zmiana rozmiaru okna sprawiła, że ​​zniknęły. Dodanie tej transformacji spowodowało, że „artefakty” zniknęły zgodnie z oczekiwaniami.
Jeff Mattson

28

To rozwiązanie bez limitów czasu! Prawdziwa siła przerysowania! Dla Androida i iOS.

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

1
To nie działa dla mnie w Chrome ani FF w systemie Linux. Jednak po prostu dostępu element.offsetHeight (bez modyfikowania właściwości wyświetlania) wykonuje pracę. To tak, jak przeglądarka pomija obliczenia offsetHeight, gdy element nie jest widoczny.
Grodriguez

To rozwiązanie działa idealnie w celu obejścia błędu napotkanego w przeglądarce Chrome używanej w „overwolf”. Próbowałem wymyślić, jak to zrobić, a ty oszczędziłeś mi wysiłku.
nacitar sevaht

13

To działa dla mnie. Kudos idź tutaj .

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

$(el).redraw();

1
Ale pozostawiło trochę „pozostałości” jak „display: block” itp., Które czasami mogą zniszczyć strukturę strony.
pie6k

Powinno to być zasadniczo takie samo jak$().hide().show(0)
Mahn

@Mahn próbowałeś? Moim zdaniem .hide () nie blokuje, więc pomijałby ponowne renderowanie w przeglądarce, czyli cały punkt metody. (I jeśli dobrze pamiętam, wtedy to przetestowałem.)
zupa,

2
@zupa jest przetestowany i działa na Chrome 38. Trik polega na tym, że show()różni się od show(0)tego, że określając czas trwania w drugim przypadku, jest uruchamiany przez metody animacji jQuery w limicie czasu, a nie natychmiast, co daje czas na renderowanie w ten sam sposób, co hide(0, function() { $(this).show(); })robi .
Mahn,

@Mahn dobrze, więc dobry połów. Jestem zdecydowanie za twoim rozwiązaniem, jeśli działa na wielu platformach i nie jest najnowszą funkcją jQuery. Ponieważ nie mam czasu na testowanie, dodam to jako „może działać”. Możesz edytować odpowiedź, jeśli uważasz, że powyższe mają zastosowanie.
zupa

9

Ukrywanie elementu, a następnie pokazanie go ponownie w ciągu setTimeout 0 spowoduje wymuszenie przerysowania.

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

1
Ten działał w przypadku błędu Chrome, w którym filtry SVG czasami nie odmalowują , bugs.chromium.org/p/chromium/issues/detail?id=231560 . Limit czasu był ważny.
Jason D

Ten działał dla mnie, próbując zmusić Chrome do ponownego obliczenia wartości myElement.getBoundingClientRect(), które były buforowane, prawdopodobnie w celach optymalizacji / wydajności. Po prostu dodałem limit czasu zero milisekund dla funkcji, która dodaje nowy element do DOM i pobiera nowe wartości po usunięciu starego elementu z poprzednich (buforowanych) wartości z DOM.
TheDarkIn1978 17.07.17

6

Wydaje mi się, że to załatwia sprawę. Poza tym tak naprawdę wcale się nie pokazuje.

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

Działa dla mnie w wersji Chromium 60.0.3112.113 w odniesieniu do tego błędu: Problem 727076 . Wydajny i subtelny; Wielkie dzięki.
Lonnie Best,

@ wiktus239 Może 20 milis jest za szybkie, a przeglądarka nie ma czasu na zmianę na .99, zanim czas na powrót do 1.0? - Ta sztuczka krycia i tak działa dla mnie, aby zawartość w ramce iframe była widoczna w iPhonie iOS 12 Safari, a ja przełączam co 1000 milis. Tak jest również w powiązanym wydaniu 727076 w powyższym komentarzu (1000 millis).
KajMagnus

4

połączenie window.getComputedStyle()powinno wymusić ponowne wycieki


2
nie działało dla mnie, ale prawdopodobnie dlatego, że potrzebowałem offsetHeightwłaściwości, która nie jest css.
Sebas,

3

Spotkałem się dzisiaj z tym wyzwaniem w OSX El Capitan z Chrome v51. Ta strona działała poprawnie w przeglądarce Safari. Próbowałem prawie każdej sugestii na tej stronie - żadna nie działała prawidłowo - wszystkie miały skutki uboczne ... Skończyło się na implementacji poniższego kodu - bardzo proste - żadnych efektów ubocznych (nadal działa jak poprzednio w Safari).

Rozwiązanie: w razie potrzeby przełącz klasę na problematycznym elemencie. Każde przełączenie wymusi przerysowanie. (Dla wygody użyłem jQuery, ale waniliowy JavaScript nie powinien stanowić problemu ...)

jQuery Class Toggle

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

Klasa CSS

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

I to wszystko...

UWAGA: Aby zobaczyć efekt, musisz uruchomić fragment kodu poniżej „Cała strona”.

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

wywołaj tę funkcję po 500 milisekundach.


2

Jeśli chcesz zachować style zadeklarowane w arkuszach stylów, lepiej użyć czegoś takiego:

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

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

Uwaga: z najnowszym zestawem web / blink, przechowującym wartość offsetHeight jest obowiązkowe w celu uruchomienia odświeżania, w przeciwnym razie VM (dla celów optymalizacji) prawdopodobnie pominie tę instrukcję.

Aktualizacja: dodano drugi offsetHeightodczyt, konieczne jest, aby przeglądarka nie ustawiała w kolejce / buforowała następującą zmianę właściwości / klasy CSS z przywróceniem displaywartości (w ten sposób należy renderować przejście CSS, które można wykonać)


1

Przykładowy 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

    };

funkcja wywołania:

$('article.child').redraw(); // <== zły pomysł

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

działa również z .fadeIn(1)zamiast zamiast .show(300)dla mnie - nie wypychając elementu dookoła. display:noneblock
Sądzę

0

Podejście, które zadziałało dla mnie w IE (nie mogłem użyć techniki wyświetlania, ponieważ było wejście, które nie może stracić ostrości)

Działa, jeśli masz 0 marginesów (zmiana również wypełnienia działa)

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

0

Tylko CSS. Działa to w sytuacjach, gdy element potomny jest usuwany lub dodawany. W takich sytuacjach granice i zaokrąglone rogi mogą pozostawić artefakty.

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

0

Moja poprawka do IE10 + IE11. Zasadniczo dzieje się tak, że dodajesz DIV do elementu owijającego, który musi zostać ponownie obliczony. Następnie po prostu usuń go i voila; działa jak marzenie :)

    _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

Większość odpowiedzi wymaga użycia asynchronicznego limitu czasu, co powoduje denerwujące mrugnięcie.

Ale wymyśliłem ten, który działa płynnie, ponieważ jest synchroniczny:

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

Czy jakieś dołączone zdarzenia do tych elementów nadal będą działać?
Kerry Johnson

0

To jest moje rozwiązanie, które działało na znikanie treści ...

<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

Natrafiłem na podobny problem i ta prosta linia JS pomogła go naprawić:

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

W moim przypadku był to błąd polegający na tym, że rozszerzenie Chrome nie przerysowywało strony po zmianie CSS z tego rozszerzenia.


4
To zrywa dostępność klawiatury.
Pangamma

0

Chciałem przywrócić wszystkie stany do poprzedniego stanu (bez przeładowywania), w tym elementy dodane przez jquery. Powyższe wdrożenie nie zadziała. i zrobiłem w następujący sposób.

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

Miałem listę reagujących składników, która po przewinięciu otworzyła kolejną stronę, a po powrocie lista nie była renderowana w Safari iOS, dopóki strona nie została przewinięta. To jest poprawka.

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

0

Poniżej css działa dla mnie na IE 11 i Edge, nie wymaga JS. scaleY(1)robi lewę tutaj. Wydaje się najprostszym rozwiązaniem.

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

0

Pomogło mi to

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

0

2020: Lżejszy i silniejszy

Poprzednie rozwiązania już dla mnie nie działają.

Sądzę, że przeglądarki optymalizują proces rysowania, wykrywając coraz więcej „bezużytecznych” zmian.

To rozwiązanie powoduje, że przeglądarka rysuje klon, aby zastąpić oryginalny element. Działa i prawdopodobnie jest bardziej zrównoważony:

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

testowany na Chrome 80 / Edge 80 / Firefox 75

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.