Chrome / Mac'te DOM yeniden çizme / yenileme işlemini zorlama


216

Arada sırada Chrome mükemmel şekilde geçerli HTML / CSS’yi yanlış veya hiç göstermeyecektir. DOM denetçisini incelemek, yollarının hatasını fark etmesini ve doğru bir şekilde yeniden çizmesini sağlamak için genellikle yeterlidir, bu nedenle işaretlemenin iyi olması muhtemeldir. Bu, belirli durumlarda yeniden çizmeye zorlamak için kod koyduğum üzerinde çalıştığım bir projede sık sık (ve tahmin edilebilir şekilde) oluyor.

Bu, çoğu tarayıcı / işletim sistemi kombinasyonunda çalışır:

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

Olduğu gibi, kullanılmayan bazı CSS mülklerini değiştirin, sonra yeniden çizmeyi zorlayan bazı bilgileri isteyin, ardından özelliği gevşetin. Ne yazık ki, Mac için Chrome'un arkasındaki parlak ekip, yeniden çizmeden bu ofsetHeight'ı elde etmenin bir yolunu bulmuş gibi görünüyor. Böylece başka türlü yararlı bir kesmek öldürmek.

Şimdiye kadar, Chrome / Mac üzerinde aynı etkiyi elde etmek için bulduğum en iyi şey bu çirkinlik parçası:

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

İçinde olduğu gibi, aslında elemanı biraz atlamaya zorlayın, sonra bir saniye soğutun ve geri atlayın. Daha da kötüsü, bu zaman aşımını 500 ms'nin altına düşürürseniz (daha az farkedilebilecek bir yere), genellikle istenen etkiye sahip olmaz, çünkü tarayıcı orijinal durumuna geri dönmeden önce yeniden çizilmeye başlamaz.

Chrome / Mac üzerinde çalışan bu yeniden çizim / yenileme saldırısının (tercihen yukarıdaki ilk örneğe dayanarak) daha iyi bir sürümünü sunmayı düşünen var mı?


Birkaç dakika önce aynı problemle karşılaştım. Bir div öğesi (bir açıklıktı) öğesini değiştirdim ve şimdi tarayıcı değişiklikleri doğru bir şekilde yeniden çiziyor. Bunun biraz eski olduğunu biliyorum ama bence bazı desteklere yardımcı olabilir.
Andrés Torres

2
Opaklık 0,99 ile ilgili aşağıdaki cevaba bakınız - buradaki en iyi cevap - ancak sayfanın çok derin olduğu için bulmak kolay değil.
Grouchal

1
Bu Linux'ta da olur :-(
schlingel

1
Zaman aşımını bir requestAnimationFrame ile değiştirebilirsiniz, bu durumda aynı şeyi elde edersiniz, ancak 1000 ms yerine 16 ms gecikme ile.
trusktr

3
Lütfen geçersiz oluşturma için bir Chromium sorunu bildirin. Görünüşe göre 4 yıl önce olmasına rağmen hiç kimse bunu yapmadı.
Bardi Harborow

Yanıtlar:


183

Tam olarak ne elde etmeye çalıştığınızdan emin değilim, ancak bu, tarayıcıyı yeniden çizmeye zorlamak için geçmişte başarıyla kullandığım bir yöntem, belki sizin için çalışacaktır.

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

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

Bu basit yeniden çizim işe yaramazsa, bunu deneyebilirsiniz. Yeniden çizmeyi garanti eden öğeye boş bir metin düğümü ekler.

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
}

DÜZENLEME: Šime Vidas'a yanıt olarak burada elde ettiğimiz şey zorunlu bir yeniden akış olacaktır. Ustanın kendisinden daha fazla bilgi edinebilirsiniz http://paulirish.com/2011/dom-html5-css3-performance/


3
Afaik, görünümün sadece bir kısmını yeniden çizmek mümkün değil. Tarayıcı her zaman tüm web sayfasını yeniden çizer.
Šime Vidas

39
$ yerine ('# parentOfElementToBeRedrawn'). hide (). show (); Chrome'da düzgün çalışması için .hide.show (0) adresine ihtiyacım vardı
l.poellabauer

1
@ l.poellabauer burada aynı - bu hiç mantıklı değil ... ama teşekkürler. Nasıl anladın ??? :)
JohnIdol

6
FYI, üzerinde çalıştığım ilgili alan sonsuz bir kaydırma listesiydi. Tüm UL'yi gizlemek / göstermek biraz işe yaramazdı, bu yüzden oynadım ve herhangi bir öğe .hide().show(0)üzerinde yaparsanız (sayfa altbilgisini seçtim) sayfayı yenilemesi gerektiğini buldum.
treeface

1
Görünüm penceresinin yalnızca bir bölümünü yeniden çizmek mümkündür. Bunu yapmak için API yok, ancak tarayıcı bunu yapmayı seçebilir. Görünüm başlamak için fayans boyalı. Ve kompozit katmanlar bağımsız olarak boyanır.
morewry

62

Yukarıdaki cevapların hiçbiri benim için işe yaramadı. Penceremi yeniden boyutlandırmanın yeniden çizime neden olduğunu fark ettim. Bu benim için yaptı:

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

11
ipucu: Bu, performansın pahalı olduğu ve muhtemelen OP'nin istediği şey olmayan tüm pencerenizi yeniden
çizecek

7
Pencerenin yeniden boyutlandırılması her zaman bir yeniden akışı tetikler, ancak kod $(window).trigger('resize')yapmaz, yalnızca atanmışsa ilgili işleyiciyi tetikleyen 'resize' olayını atar.
guari

1
Beni bir hafta kurtardın! Sorunum <body style = "zoom: 67%"> ayarından sonra, sayfa beklenen seviyeme yakınlaştırıldı, ancak sayfa donuyor ve kaydırılamıyor !. Cevabınız bu sorunu hemen çözdü
user1143669

30

Kısa bir süre önce bununla karşılaştık ve etkilenen öğeyi translateZ ile kompozit bir katmana yükseltmenin, fazladan javascript gerekmeden sorunu çözdüğünü keşfettik .

.willnotrender { 
   transform: translateZ(0); 
}

Bu boyama sorunları çoğunlukla Webkit / Blink'te göründüğünden ve bu düzeltme çoğunlukla Webkit / Blink'i hedeflediğinden, bazı durumlarda tercih edilir. Özellikle birçok JavaScript çözümü yalnızca yeniden boyama değil, yeniden akışa ve yeniden boyamaya neden olduğu için.


1
tuval sürekli olarak animasyonlu olmasına rağmen, bir tuval üzerine çizilen kaldırılmış bir HTML öğesi olduğunda bu benim için iyi çalıştı.
Knaģis

Bu, yaşadığım bir düzen sorununu çözdü neon-animated-pages. Teşekkür ederim: +1:
coffeesaga

1
Bu işe yaradı. Safari, div kümesinde hiçbiri görünmeyecek ve seçilemeyen öğeler bırakıyordu. Pencereyi yeniden boyutlandırmak onları yok etti. Bu dönüşümü eklemek "eserler" in beklendiği gibi ortadan kalkmasına neden oldu.
Jeff Mattson

28

Zaman aşımı olmadan bu çözüm! Gerçek güç yeniden çizildi! Android ve iOS için.

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

1
Bu benim için Chrome'da ne de Linux'ta FF için işe yaramıyor. Ancak basitçe (ekran özelliği değiştirmeden) element.offsetHeight erişen yapar işi. Öğe görünür olmadığında tarayıcı ofsetHeight hesaplamasını atlayacak gibi.
Grodriguez

Bu çözüm, "overwolf" da kullanılan krom tabanlı tarayıcıda karşılaştığım bir hatayı gidermek için mükemmel bir şekilde çalışır. Bunu nasıl yapacağımı anlamaya çalışıyordum ve sen beni çabadan kurtardın.
nacitar sevaht

13

Bu benim için çalışıyor. Şeref buraya .

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

$(el).redraw();

1
Ancak bazen sayfa yapısını yok edebilecek bazı 'kalıntılar' gibi 'display: block' vb. Bıraktı.
pie6k

Bu aslında$().hide().show(0)
Mahn

@ Denedin mi? Tahminimce .hide () engellemez, bu nedenle tarayıcıda yeniden oluşturma işlemini atlar, yani yöntemin bütün noktası. (Ve doğru hatırlarsam, o zaman test ettim.)
zupa

2
@zupa o olarak test ve Chrome 38'de çalışan hile olduğunu show()farklıdır show(0)ikincisi cinsinden bir süre belirterek, hemen zaman verir ki aynı şekilde işlemek için oldukça bir zaman aşımı içinde jQuery animasyon yöntemlerle tetiklenen ki hide(0, function() { $(this).show(); })yapar .
Mahn

@Mahn tamam güzel yakala o zaman. Çapraz platformda çalışıyorsa ve yeni bir jQuery özelliği değilse kesinlikle çözümünüz için kullanıyorum. Test etmek için zamanım olmadığından, 'işe yarayabilir' olarak ekleyeceğim. Yukarıdakilerin geçerli olduğunu düşünüyorsanız cevabı düzenleyebilirsiniz.
zupa

9

Bir öğeyi gizlemek ve sonra tekrar bir setTimeout 0 içinde göstermek yeniden çizmeyi zorlar.

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

1
Bu, SVG filtrelerinin bazen yeniden boyamadığı bir Chrome hatası için çalıştı, bugs.chromium.org/p/chromium/issues/detail?id=231560 . Zaman aşımı önemliydi.
Jason D

Bu, Chrome'u myElement.getBoundingClientRect()önbelleğe alınan değerleri muhtemelen optimizasyon / performans amaçları için yeniden hesaplamaya zorlarken benim için çalışan bir şeydi . Sadece DOM için yeni öğe ekler ve önceki (önbelleğe alınmış) değerleri eski eleman DOM kaldırıldıktan sonra yeni değerleri alır işlevi için sıfır milisaniye zaman aşımı ekledi.
TheDarkIn1978

6

Bu benim için hile yapıyor gibi görünüyor. Artı, gerçekten hiç göstermiyor.

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

Benim için Chromium Sürüm 60.0.3112.113'te şu hata ile ilgili çalıştı: Sayı 727076 . Verimli ve süptil; çok teşekkürler.
Lonnie Best

@ wiktus239 Belki 20 milis çok hızlıdır ve tarayıcının 1.0'a geri dönmeden önce .99'a geçmesi için zamanı yoktur? - Bu opaklık hilesi, iPhone iOS 12 Safari'de bir iframe'deki içerikleri görünür hale getirmek için yine de benim için çalışıyor ve her 1000 milde bir geçiş yapıyorum. Yukarıdaki yorumda (1000 milis) 727076 bağlantılı sayısında da bu var.
KajMagnus

4

çağrı window.getComputedStyle()yeniden akışı zorlamalı


2
benim için işe yaramadı, ama muhtemelen offsetHeightcss olmayan özelliği gerekli çünkü .
Sebas

3

Bu zorluğa bugün Chrome v51 ile OSX El Capitan'da rastladım. Söz konusu sayfa Safari'de iyi çalıştı. Bu sayfadaki hemen hemen her öneriyi denedim - hiçbiri doğru çalışmadı - hepsinin yan etkileri vardı ... Aşağıdaki kodu uyguladım - süper basit - yan etki yok (hala Safari'de olduğu gibi çalışıyor).

Çözüm: Sorunlu öğe üzerindeki bir sınıfı gerektiği gibi değiştirin. Her geçiş, yeniden çizmeyi zorlar. (Kolaylık sağlamak için jQuery kullandım, ancak vanilya JavaScript sorun olmamalı ...)

jQuery Sınıfı Geçişi

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

CSS Sınıfı

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

Ve bu kadar...

NOT: Efekti görmek için snippet'i "Tam Sayfa" altında çalıştırmanız gerekir.

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

500 milisaniye sonra bu işlevi çağırın.


2

Stil sayfalarınıza bildirilen stilleri korumak istiyorsanız, aşağıdaki gibi bir şey kullanmak daha iyi:

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

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

Not: en son webkit / yanıp sönme ile, boyamayı offsetHeighttetiklemek için değerini saklamak zorunludur, aksi takdirde VM (optimizasyon amaçlı) muhtemelen bu talimatı atlayacaktır.

Güncelleme: ikinci offsetHeightokuma eklendi, tarayıcının displaydeğerin geri yüklenmesi ile aşağıdaki CSS özellik / sınıf değişikliğini kuyruğa almasını / önbelleğe almasını önlemek gerekir (bu şekilde takip edebilecek bir CSS geçişi gerçekleştirilmelidir)


1

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

    };

çağrı fonksiyonu:

$('article.child').redraw(); // <== kötü fikir

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

benim için .fadeIn(1)yerine de çalışır .show(300)- elemanı itmek değil. Sanırım, tetikleyici display:noneve geriblock
BananaAcid

0

IE'de benim için çalışan bir yaklaşım (ekran tekniğini kullanamadım çünkü odaklanmaması gereken bir girdi vardı)

0 marjınız varsa çalışır (dolgu işlerini de değiştirir)

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

0

Yalnızca CSS. Bu, bir alt öğenin kaldırıldığı veya eklendiği durumlarda işe yarar. Bu durumlarda, sınırlar ve yuvarlatılmış köşeler eserler bırakabilir.

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

0

IE10 + IE11 için düzeltmem. Temel olarak olan şey, yeniden hesaplanması gereken bir sarma elemanının içine bir DIV eklemektir. Sonra sadece çıkarın ve voila; tıkır tıkır çalışıyor :)

    _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

Çoğu cevap, sinir bozucu bir göz kırpmasına neden olan zaman uyumsuz bir zaman aşımı kullanımını gerektirir.

Ama ben senkronize olduğu için sorunsuz çalışan bu bir ile geldi:

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

Bu öğelere eklenen herhangi bir etkinlik hala çalışır mı?
Kerry Johnson

0

Bu, kaybolan içerik için çalışan çözümüm ...

<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

Benzer bir sorunla karşılaştım ve JS'nin bu basit satırı düzeltmeye yardımcı oldu:

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

Benim durumumda, bir Chrome uzantısında, CSS'sini uzantı içinden değiştirdikten sonra sayfayı yeniden çizmeyen bir hataydı.


4
Bu klavye erişilebilirliğini kırıyor.
Pangamma

0

Jquery tarafından eklenen öğeler de dahil olmak üzere tüm devletleri (yeniden yüklemeden) önceki durumuna döndürmek istedim. Yukarıdaki uygulama işe yaramayacak. ve ben şöyle yaptım.

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

Kaydırıldığında, daha sonra başka bir sayfa açan, ardından geri döndüğünüzde sayfa kaydırılana kadar Safari iOS'ta liste oluşturulmamış bir tepki bileşen listesi vardı. Yani düzeltme bu.

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

0

Aşağıda css IE 11 ve Edge'de benim için çalışıyor, JS gerekmiyor. scaleY(1)burada hile yapar. En basit çözüm görünüyor.

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

0

Bana yardımcı oldu

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

0

2020: Daha hafif ve daha güçlü

Önceki çözümler artık benim için çalışmıyor.

Sanırım tarayıcılar giderek daha fazla "yararsız" değişiklik tespit ederek çizim sürecini optimize ediyor.

Bu çözüm, tarayıcının orijinal öğeyi değiştirmek için bir klon çizmesini sağlar. Çalışıyor ve muhtemelen daha sürdürülebilir:

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

Chrome 80 / Edge 80 / Firefox 75 üzerinde test edildi

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.