在Chrome / Mac上强制DOM重绘/刷新

时间:2012-01-12 18:47:31

标签: javascript css macos dom google-chrome

每隔一段时间,Chrome就会错误地呈现完全有效的HTML / CSS,或者根本不呈现。通过DOM检查器深入挖掘通常足以让它实现其方式的错误并正确地重绘,因此可以证明标记是好的。这种情况在我正在进行的项目中经常(并且可预测地)发生,因为我已经在适当的位置强制重新绘制代码。

这适用于大多数浏览器/操作系统组合:

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

同样,调整一些未使用的CSS属性,然后询问一些强制重绘的信息,然后取消对该属性的破坏。不幸的是,Chrome for Mac背后的聪明团队似乎找到了一种方法来获得offsetHeight而无需重绘。从而杀死了一个有用的黑客。

到目前为止,我在Chrome / Mac上获得同样效果的最佳效果就是这个丑陋:

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

同样,实际上强制元素跳跃一点,然后冷却一秒钟然后再跳回来。更糟糕的是,如果你将超时时间降低到500毫秒以下(不太明显),它通常不会产生预期的效果,因为浏览器在返回其原始状态之前不会重新绘制。

是否有人愿意提供适用于Chrome / Mac的重绘/刷新黑客(最好基于上面的第一个示例)的更好版本?

24 个答案:

答案 0 :(得分:172)

不确定您要实现的目标,但这是我过去使用的一种方法,可以强制浏览器重绘,也许它会对您有效。

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

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

如果这个简单的重绘不起作用,你可以尝试这个。它会在元素中插入一个空文本节点,以保证重绘。

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
}
编辑:为响应ŠimeVidas,我们在这里取得的成果将是强制回流。您可以从主人自己http://paulirish.com/2011/dom-html5-css3-performance/

中找到更多信息

答案 1 :(得分:61)

上述答案都不适合我。我注意到调整大小我的窗口确实导致了重绘。所以这对我来说是这样的:

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

答案 2 :(得分:25)

此解决方案没有超时!真正的强制重绘!适用于Android和iOS。

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

答案 3 :(得分:23)

我们最近遇到过这种情况,并发现将受影响的元素推广到composite layer translateZ并修复了问题,而无需额外的javascript。

.willnotrender { 
   transform: translateZ(0); 
}

由于这些绘画问题主要出现在Webkit / Blink中,并且此修复主要针对Webkit / Blink,因此在某些情况下更为可取。特别是因为许多JavaScript解决方案会导致reflow and repaint,而不仅仅是重新绘制。

答案 4 :(得分:12)

这对我有用。 Kudos go here

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

$(el).redraw();

答案 5 :(得分:7)

隐藏元素然后在setTimeout为0的情况下再次显示它将强制重绘。

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

答案 6 :(得分:4)

这似乎对我有用。此外,它根本没有显示。

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

答案 7 :(得分:4)

呼叫window.getComputedStyle()应强制重排

答案 8 :(得分:3)

我今天在OSX El Capitan使用Chrome v51遇到了这个挑战。有问题的页面在Safari中运行良好。我在这个页面上几乎尝试了所有的建议 - 没有一个正常 - 都有副作用......我最终实现了下面的代码 - 超级简单 - 没有副作用(在Safari中仍然像以前一样)。

解决方案:根据需要在有问题的元素上切换一个类。每个切换都会强制重绘。 (为方便起见,我使用了jQuery,但是vanilla JavaScript应该没有问题......)

jQuery Class Toggle

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

CSS Class

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

就是这样......

注意:您必须在“整页”下面运行代码段才能看到效果。

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

答案 9 :(得分:2)

2020:更轻更强

以前的解决方案对我而言不再有效。

我猜想浏览器会通过检测越来越多的“无用”更改来优化绘图过程。

此解决方案使浏览器绘制一个克隆来替换原始元素。它有效并且可能更可持续:

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

已在Chrome 80 / Edge 80 / Firefox 75上测试

答案 10 :(得分:2)

如果要保留声明到样式表中的样式,最好使用以下内容:

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

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

注意:使用最新的webkit / blink时,必须存储offsetHeight的值才能触发重绘,否则VM(出于优化目的)可能会跳过该指令。

更新:添加了第二个offsetHeight读数,有必要阻止浏览器通过恢复display值来排队/缓存以下CSS属性/类更改(这样一种CSS转换,可以跟随应该呈现)

答案 11 :(得分:1)

  

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

    };
  

通话功能:

$('article.child').redraw(); //&lt; ==坏主意

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

答案 12 :(得分:0)

一种适用于IE的方法(我无法使用显示技术,因为有一个输入不能忽略焦点)

如果你有0个余量(改变填充也可以)

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

答案 13 :(得分:0)

大多数答案都需要使用异步超时,这会导致恼人的眨眼。

但是我想出了这个,因为它是同步的,所以它很顺利:

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

答案 14 :(得分:0)

这是我的解决方案,适用于消失的内容...

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

答案 15 :(得分:0)

我对IE10 + IE11的修复。基本上发生的事情是你在一个必须重新计算的包装元素中添加一个DIV。然后把它取下来瞧瞧像魅力一样:)

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

答案 16 :(得分:0)

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

在500毫秒后调用此函数。

答案 17 :(得分:0)

我遇到了类似的问题,这个简单的JS系列帮助解决了这个问题:

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

在我的情况下,Chrome扩展程序中的一个错误是在从扩展程序中更改CSS后重绘页面。

答案 18 :(得分:0)

我想将所有状态返回到先前的状态(无需重新加载),包括jquery添加的元素。上述实现将无法正常工作。我做了如下。

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

答案 19 :(得分:0)

我有一个反应组件列表,当滚动该组件时,然后打开另一个页面,然后返回时,直到滚动页面,该列表才在Safari iOS上呈现。这就是解决方法。

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

答案 20 :(得分:0)

下面的CSS在IE 11和Edge上为我工作,不需要JS。 scaleY(1)在这里起作用。似乎是最简单的解决方案。

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

答案 21 :(得分:0)

对我有帮助

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

答案 22 :(得分:0)

仅限CSS。这适用于删除或添加子元素的情况。在这些情况下,边框和圆角可能会留下瑕疵。

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

答案 23 :(得分:0)

2020年10月,Chrome 85仍然存在该问题。

morewry使用transform:translateZ(0)的解决方案有效,实际上,任何变换都有效,包括translate(0,0)和scale(1),但是如果您必须再次更新元素,那么诀窍是切换转换,最好的方法是在一帧后使用requestAnimationFrame直接将其删除(应始终避免使用setTimeout,因为它会更慢,因此会引起故障)。

因此,更新一个元素:

    function refresh_element(node) {
        // you can use scale(1) or translate(0, 0), etc
        node.style.setProperty('transform', 'translateZ(0)');
        // this will remove the property 1 frame later
        requestAnimationFrame(() => {
            node.style.removeProperty('transform');
        });
    }