ChatGPT解决这个技术问题 Extra ChatGPT

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

每隔一段时间,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);

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

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

几分钟前我遇到了同样的问题。我更改了 div 的元素(它是一个跨度),现在浏览器正确地重绘了更改。我知道这有点老了,但我认为这可以帮助一些兄弟。
请参阅下面与不透明度 0.99 相关的答案——这里的最佳答案——但不容易找到,因为它在页面上太深了。
这也发生在 Linux 上:-(
您可以用 requestAnimationFrame 替换超时,在这种情况下,您将实现相同的效果,但延迟为 16 毫秒而不是 1000 毫秒。
file a Chromium issue查看无效渲染。似乎没有人这样做,尽管这是 4 年前的事了。

J
Juank

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

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

编辑:作为对 Šime Vidas 的回应,我们在这里实现的将是强制回流。您可以从大师本人那里了解更多信息http://paulirish.com/2011/dom-html5-css3-performance/


Afaik,不可能只重绘视口的一部分。浏览器总是重绘整个网页。
而不是 $('#parentOfElementToBeRedrawn').hide().show();我需要 .hide.show(0) 在 Chrome 中正常工作
@l.poellabauer 在这里也一样-这没有意义……但是谢谢。你怎么知道的??? :)
仅供参考,我正在研究的相关领域是一个无限滚动列表。隐藏/显示整个 UL 有点不可行,所以我玩了一下,发现如果你对 any 元素执行 .hide().show(0)(我选择了页脚),它应该刷新页面。
可以只重绘视口的一部分。没有 API 可以这样做,但浏览器可以选择这样做。视口一开始是用瓷砖绘制的。复合层是独立绘制的。
V
Vincent Sels

以上答案都不适合我。我确实注意到调整窗口大小确实会导致重绘。所以这为我做了:

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

提示:这将重绘您的完整窗口,这会导致性能代价高昂,并且可能不是 OP 想要的
调整窗口大小总是会触发重排,但代码 $(window).trigger('resize') 不会,它只会抛出触发相关处理程序的“resize”事件(如果已分配)。
你救了我一个星期!我的问题是设置 后,页面缩放到了我预期的水平,但是页面被冻结并且无法滚动!。您的回答立即解决了这个问题
m
morewry

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

.willnotrender { 
   transform: translateZ(0); 
}

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


当问题是在画布上绘制已删除的 HTML 元素时,这对我来说很好,即使画布是连续动画的。
这解决了我在使用 neon-animated-pages 时遇到的布局问题。谢谢 :+1:
这行得通。 Safari 将 div 中的元素设置为不显示、可见且不可选择。调整窗口大小使它们消失。添加此转换导致“工件”按预期消失。
非常感谢!有效。现在是 2020 年,这个 hack 是从 15 年 1 月开始的。知道为什么这个问题没有得到解决吗?
z-index: 0;,似乎也有效,与此答案中的 transform 相比,它导致的 z 分层中断(在我的用例中)更少。
a
aviomaksim

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

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

这对我在 Chrome 和 Linux 上都不起作用。然而,简单地访问 element.offsetHeight (不修改 display 属性)确实有效。当元素不可见时,浏览器会跳过 offsetHeight 计算。
该解决方案非常适合解决我在“overwolf”中使用的基于 chrome 的浏览器中遇到的错误。我试图弄清楚如何做到这一点,而您为我节省了精力。
z
zupa

这对我有用。 Kudos go here

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

$(el).redraw();

但它留下了一些“残留物”,例如“显示:块”等,有时会破坏页面结构。
这应该与 $().hide().show(0) 基本相同
@Mahn 你试过了吗?在我的猜测中 .hide() 是非阻塞的,因此它会跳过浏览器中的重新渲染运行,也就是该方法的全部要点。 (如果我没记错的话,我当时测试过。)
@zupa 它已经过测试并在 Chrome 38 上运行。诀窍在于 show()show(0) 的不同之处在于,通过在后者中指定持续时间,它由 jQuery 动画方法在超时而不是立即触发,从而有时间呈现以与 hide(0, function() { $(this).show(); }) 相同的方式。
@Mahn 好吧,那很好。如果它可以跨平台工作并且不是最近的 jQuery 功能,我绝对适合您的解决方案。由于我没有时间对其进行测试,因此我将其添加为“可能有效”。如果您认为上述内容适用,欢迎您编辑答案。
B
Brady Emerson

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

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

这适用于 SVG 过滤器有时不会重新绘制的 Chrome 错误,bugs.chromium.org/p/chromium/issues/detail?id=231560。超时很重要。
这是在尝试强制 Chrome 重新计算正在缓存的 myElement.getBoundingClientRect() 的值时对我有用的方法,可能是出于优化/性能目的。我只是为将新元素添加到 DOM 并在从 DOM 中删除具有先前(缓存)值的旧元素后获取新值的函数添加了一个零毫秒超时。
X
Xavier Follet

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

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

它在 Chromium 版本 60.0.3112.113 中对我有用,关于这个错误:Issue 727076。高效而微妙;非常感谢。
@wiktus239 也许 20 毫秒太快了,浏览器没有时间更改为 .99,在时间更改回 1.0 之前? — 无论如何,这种不透明度技巧对我有用,让 iframe 中的内容在 iPhone iOS 12 Safari 中可见,我每 1000 毫秒切换一次。这也是上面评论中链接问题 727076 中的内容(1000 毫秒)。
@LonnieBest 你知道是否还有其他问题导致了这个问题?就像你的 DOM 的大小一样。这个解决方案在应用程序的一个部分对我有用,但在另一部分却没有,所以我怀疑还有别的东西在起作用。
@UcheOzoemena 是的,如果我没记错的话,那个项目的 DOM 元素比平常多得多(主要是多行表)。如果此技巧对您不起作用,请考虑将 setTimeout(function(){},0); 添加到组合中,以便稍后进行调整。
@UcheOzoemena 假设上面的技巧在 setTimeout 函数的主体中,您可以尝试将第二个参数增加到远大于零的值,以查看您是否最终可以使技巧起作用。当您进行异步编程时,它会涉及;您可能必须创建一个承诺,告诉您其他所有操作何时完成,然后执行此操作。奇怪的是,你在正确的时间之前就做了这个把戏。
S
Sebas

调用 window.getComputedStyle() 应该强制重排


对我不起作用,但这可能是因为我需要不是 css 的 offsetHeight 属性。
J
J.P. Duvet

2020:更轻、更强

以前的解决方案对我来说不再适用。

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

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

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

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


如果您将事件侦听器连接到您的元素,这不是一个好主意。替换元素后,您必须将它们重新添加到元素中。
这在 2021 年的 Chrome 中无法循环使用。
如果您有事件侦听器,这将不起作用。该活动将不再有效。
B
Ben Feely

我今天在使用 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 { 内容:“”; } html, 身体 { 高度: 100%;宽度:100%;溢出:隐藏; } .slide-container { 宽度:100%;高度:100%;溢出-x:滚动;溢出-y:隐藏;空白:nowrap;左填充:10%;填充权:5%; } .slide { 位置:相对;显示:内联块;高度:30%;边框:1px 纯绿色; } .slide-sizer { 高度:160%;指针事件:无; //border: 1px 纯红色; } . 幻灯片内容 { 位置:绝对;前10名%;左:10%; }

这个示例代码是一个简单的基于样式的解决方案根据动态高度保持元素的纵横比。当您增加和减少窗口高度时,元素应该跟随并且宽度应该依次跟随以保持纵横比。您会注意到,在 OSX 上的 Chrome 中(至少),“非强制”元素没有保持适当的比例。

< div class="slide-contents"> 不强制
强制


g
guari

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

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


r
rm.rf.etc

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

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

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

500 毫秒后调用此函数。


为什么不在 100 毫秒后呢?
这个想法是不使用超时。上面的一些答案是一种干净而简单的方法。
E
Edmond Wang

下面的 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;
}

M
Maksym Shemet

它帮助了我

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

O
Octo Poulos

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

澄清一下:我最初发布时发布的转换解决方案确实需要 translateZ 并且不需要切换。该修复基于这样一个事实,即当时只有 3d 转换被可靠地提升了复合层。复合层可以独立重新绘制,因此优先绘制:因此不太可能在优化中被跳过。 5 年后……我敢打赌,会有微妙但重大的变化。
这对我有用。感谢您的发表。
k
kkleejoe

20ms 后这对我有用:

const forceRedraw = elem => {
  const t = elem.ownerDocument.createTextNode(' ') ;
  elem.appendChild(t) ;
  setTimeout(() => { elem.removeChild(t) }, 0) ;
}

R
Rogel Garcia

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

如果您的边距为 0,它就可以工作(更改填充也可以)

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

M
Mahdi Rostami

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

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

O
Oriol

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

但我想出了这个,它运行顺利,因为它是同步的:

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

这些元素的任何附加事件是否仍然有效?
K
Kosmo零

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

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

R
Ryosuke Hujisawa

我想将所有状态返回到以前的状态(不重新加载),包括 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()">

R
Rotareti

我遇到了类似的问题,这行简单的 JS 有助于解决它:

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

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


这是破坏键盘可访问性。
l
luky

我有一个反应组件列表,当滚动时,然后打开另一个页面,然后返回时,列表不会在 Safari iOS 上呈现,直到页面滚动。所以这就是修复。

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