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


215

Chrome有时会不正确地显示或完全不显示完全有效的HTML / CSS。通过DOM检查器进行挖掘通常足以使它意识到其方式的错误并正确重绘,因此可以证明标记是好的。在我正在研究的项目中,这种情况经常发生(并且可以预测)到足以在某些情况下强制执行重绘的代码位置。

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

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

如前所述,调整一些未使用的CSS属性,然后要求一些信息以强制重绘,然后取消调整该属性。不幸的是,Mac版Chrome背后的聪明团队似乎找到了一种无需重新绘制即可获取offsetHeight的方法。从而杀死本来有用的黑客。

到目前为止,我想出的在Chrome / Mac上获得相同效果的最好方法是:

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

在这种情况下,实际上迫使元素跳一点,然后冷静一秒钟再跳回去。更糟糕的是,如果将超时时间降低到500ms以下(不太明显),则通常不会达到预期的效果,因为浏览器在返回原始状态之前无法进行重绘。

有人在乎提供适用于Chrome / Mac的此重绘/刷新hack(最好基于上述第一个示例)的更好版本吗?


几分钟前,我遇到了同样的问题。我更改了div的元素(它是一个跨度),现在浏览器正确地重新绘制了更改。我知道这有点旧,但是我认为这可以帮助一些兄弟。
安德烈斯托雷斯

2
请查看以下与不透明度0.99相关的答案-此处的最佳答案-但由于页面上的内容太深,因此不容易找到。
Grouchal

1
这也发生在Linux上:-(
schlingel

1
您可以将timeout替换为requestAnimationFrame,在这种情况下,您将获得相同的效果,但需要16ms而不是1000ms的延迟。
trusktr '16

3
请针对无效渲染提交Chromium问题。尽管这是4年前,但似乎没有人这样做。
Bardi Harborow '16

Answers:


183

不确定您要实现的目标是什么,但这是我过去成功使用的一种方法,可以成功地强制浏览器重绘,也许它会为您工作。

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


3
Afaik,不可能仅重绘视口的一部分。浏览器始终会重绘整个网页。
森那维达斯

38
而不是$('#parentOfElementToBeRedrawn')。hide()。show(); 我需要.hide.show(0)才能在Chrome中正常工作
l.poellabauer 2012年

1
@ l.poellabauer在这里一样-这没有道理...但是,谢谢。你怎么知道的?:)
JohnIdol 2012年

6
仅供参考,我正在研究的相关领域是无限滚动列表。隐藏/显示整个UL有点不可行,所以我玩了一遍,发现如果.hide().show(0)任何元素(我选择了页脚)进行操作,都应该刷新页面。
treeface

1
可以仅重绘视口的一部分。没有API可以这样做,但是浏览器可以选择这样做。视口首先以瓷砖绘制。并单独绘制复合层。
morewry

62

上面的答案对我都不起作用。我确实注意到,调整窗口大小确实会导致重画。因此,这为我做到了:

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

11
提示:这将重新绘制您的完整窗口,这是性能成本
很高

7
调整窗口大小始终会触发重排,但是代码$(window).trigger('resize')不会触发重排,如果分配了它,它只会引发“ resize”事件,该事件会触发相关的处理程序。
guari

1
你救了我整整一个星期!我的问题是设置<body style =“ zoom:67%”>后,页面缩放到了我的预期水平,但是页面被冻结并且无法滚动!您的答案立即解决了此问题
user1143669'6

30

我们最近遇到了这个问题,并发现,使用translateZ将受影响的元素提升为复合层可以解决此问题,而无需使用额外的javascript。

.willnotrender { 
   transform: translateZ(0); 
}

由于这些绘画问题主要出现在Webkit / Blink中,并且此修复程序主要针对Webkit / Blink,因此在某些情况下更可取。特别是因为许多JavaScript的解决方案事业回流和重绘,而不仅仅是一个重绘。


1
当问题是在画布上绘制一个已删除的HTML元素时,即使画布一直处于动画状态,这对我来说也很好。
Knaģis

这解决了我遇到的布局问题neon-animated-pages。谢谢:+1:
coffeesaga '17

1
这工作了。Safari保留了在div中设置为不显示,不可见且不可选择的元素。调整窗口大小会使它们消失。添加此转换会导致“工件”按预期消失。
Jeff Mattson

28

这个解决方案没有超时!真重绘!适用于Android和iOS。

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

1
这不适用于我在Chrome上或FF在Linux上。然而仅仅访问element.offsetHeight(无需修改显示属性)工作。就像在元素不可见时浏览器将跳过offsetHeight计算一样。
Grodriguez 2014年

该解决方案非常适合解决我在“狼人”中使用的基于chrome的浏览器中遇到的错误。我试图弄清楚该如何做,您省了我很多精力。
nacitar sevaht,2015年

13

这对我有用。 荣誉来到这里

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

$(el).redraw();

1
但是它留下了一些“遗留物”,例如“ display:block”等,有时会破坏页面结构。
pie6k 2014年

这基本上应与$().hide().show(0)
Mahn

@Mahn你尝试了吗?以我的猜测,.hide()是非阻塞的,因此它将跳过浏览器中的重新渲染,也就是方法的整个要点。(如果我没记错的话,我会对其进行测试。)
zupa 2014年

2
@zupa它的测试,并在Chrome 38工作的诀窍是,show()不同于show(0)在通过指定在后者的持续时间,它是由一个超时的jQuery动画方法触发而立即赋予时间以在以同样的方式呈现hide(0, function() { $(this).show(); })呢。
Mahn 2014年

@Mahn好吧,那就赶上。如果它可以跨平台运行并且不是最新的jQuery功能,那么我绝对适合您的解决方案。由于我没有时间进行测试,因此将其添加为“可能有效”。如果您认为以上情况适用,欢迎您编辑答案。
zupa 2014年

9

隐藏一个元素,然后在setTimeout为0内再次显示它会强制重画。

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

1
这个代码适用于Chrome的一个bug,其中SVG过滤器有时不重绘bugs.chromium.org/p/chromium/issues/detail?id=231560。超时很重要。
杰森D'

这是我尝试强制Chrome重新计算myElement.getBoundingClientRect()已缓存的值(可能出于优化/性能目的)时对我有用的一种。我只是为该函数添加了一个零毫秒超时,该超时将新元素添加到DOM并在从DOM中删除了具有先前(缓存)值的旧元素之后获取了新值。
TheDarkIn1978 '17

6

这似乎对我有用。另外,它根本不显示。

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

它在Chromium版本60.0.3112.113中为我解决了该错误:问题727076。高效而细腻;非常感谢。
Lonnie Best

@ wiktus239也许20毫秒太快了,浏览器没有时间更改为.99,而又没有时间更改为1.0?—无论如何,这种不透明技巧对我来说都是有效的,以使iframe中的内容在iPhone iOS 12 Safari中可见,并且我每切换1000毫秒。这也是上面评论中的链接问题727076中的内容(1000毫秒)。
KajMagnus

4

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


2
不适用于我,但这可能是因为我需要的offsetHeight不是CSS 的属性。
塞巴2015年

3

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

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

jQuery类切换

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

CSS类

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


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

500毫秒后调用此函数。


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转换)


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(); // <==坏主意

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

也可以.fadeIn(1)代替.show(300)我使用-不推元素。所以我想,这是触发display:none和回block
BananaAcid

0

在IE上对我有用的一种方法(我不能使用显示技术,因为有输入不能放宽焦点)

如果您的边距为0,则可以使用(更改填充也可以)

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

0

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

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

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

0

大多数答案都需要使用异步超时,这会引起烦人的眨眼。

但是我想出了这个,它可以同步工作,因此很顺利:

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

这些元素的任何附加事件仍然有效吗?
凯里·约翰逊

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>

0

我遇到了类似的问题,这条简单的JS代码帮助解决了这一问题:

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

就我而言,这是一个Chrome扩展程序的错误,该错误是在扩展程序中更改CSS后未重绘页面。


4
这会破坏键盘的可访问性。
Pangamma

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

0

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

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

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


0

2020年:更轻更强大

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

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

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

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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.