如何强制WebKit重绘/重绘以传播样式更改?


247

我有一些琐碎的JavaScript可以实现样式更改:

sel = document.getElementById('my_id');
sel.className = sel.className.replace(/item-[1-9]-selected/,'item-1-selected');
return false;

在最新版本的FF,Opera和IE上可以正常使用,但在最新版本的Chrome和Safari上无法使用。

它影响了两个后代,它们恰好是兄弟姐妹。第一个同级更新,但第二个不更新。第二个元素的子元素也具有焦点,并包含<a>标记,该标记在onclick属性中包含上述代码。

如果我微调(例如取消选中和选中)任何元素的任何属性,则在Chrome“开发人员工具”窗口中,第二个兄弟姐妹将更新为正确的样式。

是否有一种解决方法可以轻松地以编程方式“推动” WebKit做正确的事情?


我想将我的canvas应用包装在WebView中时,在Android 4.2.2(Samsung I9500)上遇到此问题。荒谬!
乔什(Josh)

3
what-forces-layout.md一个非常良好的阅读的地方
VSYNC

Answers:


312

我发现了一些复杂的建议,许多简单的建议都行不通,但是Vasil Dinkov对其中的一个建议提供了一个简单的解决方案,可以强制重新绘制/重新绘制效果很好:

sel.style.display='none';
sel.offsetHeight; // no need to store this anywhere, the reference is enough
sel.style.display='';

如果它适用于“ block”以外的其他样式,我会让其他人评论。

谢谢,瓦西尔!


32
为了避免闪烁,你可以尝试'inline-block''table''run-in'代替'none',但这可能有副作用。同样,超时0会触发重排,就像查询offsetHeight一样:sel.style.display = 'run-in'; setTimeout(function () { sel.style.display = 'block'; }, 0);
user123444555621

15
两年后,这个答案仍然有用。我只是用它来解决仅Chrome浏览器的问题,即在以某些方式调整浏览器大小后,CSS框阴影仍保留在屏幕上。谢谢!
rkulla

5
@danorton您在2010年回答。现在是2013年,错误仍然存​​在。谢谢。顺便说一句,有人知道在webkit跟踪器中是否注册了任何问题?
RaphaelDDL

13
PS:对我来说,以上解决方案不再起作用。相反,我使用了这个(jQuery):sel.hide()。show(0); 资料来源:stackoverflow.com/questions/8840580/…–
ProblemsOfSumit

7
您需要做的只是读取一个size属性,而无需来回更改它。
安德鲁·布洛克

93

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

.willnotrender { 
   transform: translateZ(0); 
}

由于这些绘画问题主要出现在Webkit / Blink中,并且此修复程序主要针对Webkit / Blink,因此在某些情况下更可取。特别是由于接受的答案几乎肯定会导致回流和重绘只是一个重绘。

Webkit和Blink在渲染性能方面一直在努力,这些故障是优化的不幸副作用,该优化旨在减少不必要的流动和绘制。 CSS可能会更改或将来的另一个规范成为将来的解决方案。

还有其他方法可以实现复合层,但这是最常见的方法。


1
使用角度轮播时为我工作!
gustavohenke

1
解决了我的问题,即滑动面板内的元素中的边界半径丢失了
Timo 2015年

1
也适用于cordova项目!
Quispie '16

1
为我工作了一个不会刷新的-webkit-line-clamp
Adam Marshall

1
使用-webkit-transform在ipad(6.0)上为我工作:translate(-50%,-50%)。
Lain 2016年

51

danorton解决方案不适用于我。我遇到了一些非常奇怪的问题,其中webkit根本不会绘制一些元素。直到onblur才更新输入中的文本;并且更改className不会导致重画。

我偶然发现的解决方案是在脚本之后在正文中添加一个空的样式元素。

<body>
...
<script>doSomethingThatWebkitWillMessUp();</script>
<style></style>
...

这样就解决了。那有多奇怪?希望这对某人有帮助。


3
如果可以的话,我将投票1,000,000次。这是我可以让chrome重新绘制我拖动的愚蠢div的唯一方法。顺便说一句,您不必添加样式元素(至少我没有),任何元素都可以使用。我做了一个div并为其指定了ID,这样我就可以删除它,然后在每个步骤中添加该元素,以免在DOM中填充无用的标签。
hobberwickey 2012年

6
只是将其与旁遮普语80对另一个答案的评论混合使用。我最终解决的方法是var div = $('<div>').appendTo(element); setTimeout(function(){ div.remove(); }, 0);
bentman

33
这条单行对我来说很棒!$('<style></style>').appendTo($(document.body)).remove();
pdelanauze

1
@pdelanauze答案很完美。我在iOS中使用css3转换和转换时遇到重画问题。
Marcel Falliere

11
只是知道这会迫使整个页面重新
粉刷

33

由于display + offset触发器对我不起作用,因此我在这里找到了解决方案:

http://mir.aculo.us/2009/09/25/force-redraw-dom-technique-for-webkit-based-browsers/

element.style.webkitTransform = 'scale(1)';

3
我在尝试使用的hack中使用了此方法,但没有scale(1)将其分配为element.style.webkitTransform = element.style.webkitTransform。之所以这样,是因为将其设置为前者会稍微扭曲页面的absolute位置!
bPratik

这对我有用,并且没有出现闪烁或重置display解决方案所需的滚动位置的问题。
davidtbernal

我有一个使用大量动态SVG的Cordova应用程序。除了iOS7以外,其他所有地方都可以正常工作,因为iOS7不会在启动时显示SVG元素。添加了一个简单的$('body')[0] .style.webkitTransform ='scale(1)'; 初始化代码,问题消失了!
赫尔茨(Herc)

在最新的Safari和iOS上,目前无法正常使用。
setholopolus

@setholopolus那是哪个versoin?
EricG '18

23

我也遇到了同样的问题。将danorton的“切换显示”修复功能添加到动画的步进功能后,对我来说确实有用,但我担心性能,因此我在寻找其他选项。

在我的情况下,没有重新绘制的元素在绝对位置元素内,该元素当时没有z索引。向此元素添加z-index会更改Chrome的行为,并且按预期进行重新绘制->动画变得流畅。

我怀疑这是灵丹妙药,我想这取决于为什么 Chrome选择不重绘该元素,但我在此发布此特定解决方案是希望它对某人有所帮助。

干杯,罗布

tl; dr >>尝试将z-index添加到元素或其父元素。


8
辉煌。这很棒,并为我解决了问题。A +++会再次z-index。
trisweb 2013年

在我处理滚动条和转换的情况下不起作用。
Ricky Boyce 2014年

16

以下作品。在纯CSS中只需设置一次。而且它比JS函数更可靠地工作。性能似乎不受影响。

@-webkit-keyframes androidBugfix {from { padding: 0; } to { padding: 0; }}
body { -webkit-animation: androidBugfix infinite 1s; }

看起来它也对我有用。用它来解决Mobile Safari的渲染问题
Chris

这解决了我的问题,迫使Safari进行中继。但是,我使用了zoom而不是填充:@-webkit-keyframes safariBugFix {from { zoom: 100%; } to { zoom: 99.99999%; }}
Ahmed Musallam

13

由于某种原因,我无法获得danorton的工作答案,我可以看到它应该做什么,因此我对此进行了一些调整:

$('#foo').css('display', 'none').height();
$('#foo').css('display', 'block');

它为我工作。


7
我尝试了以上所有步骤,但只有这一项对我有用。WTF Webkit!这不是计算机科学!这是黑魔法!
查理·马丁

7

我来到这里是因为我需要在更改其CSS之后重新绘制Chrome中的滚动条。

如果有人遇到相同的问题,我可以通过调用以下函数来解决:

//Hack to force scroll redraw
function scrollReDraw() {
    $('body').css('overflow', 'hidden').height();
    $('body').css('overflow', 'auto');
}

此方法不是最佳解决方案,但它可能适用于所有情况,隐藏并显示需要重绘的元素可以解决所有问题。

这是我用过的小提琴:http : //jsfiddle.net/promatik/wZwJz/18/


1
大!我也在尝试使滚动条动起来,这就是我所需要的!顺便说一句更好地使用溢出:动画滚动条的叠加
ekalchev

6

我今天偶然发现了这个问题:prototype.js的Element.redraw()

使用:

Element.addMethods({
  redraw: function(element){
    element = $(element);
    var n = document.createTextNode(' ');
    element.appendChild(n);
    (function(){n.parentNode.removeChild(n)}).defer();
    return element;
  }
});

但是,我注意到有时您必须直接对有问题的元素调用redraw()。有时重新绘制父元素并不能解决孩子遇到的问题。

关于浏览器渲染元素的方式的好文章:渲染:重画,重排/重排,重设样式


1
appendChild是DOM方法,而不是jQuery方法。
ChrisW 2015年

4

我在使用另一个div插入了多个div时遇到了这个问题position: absolute,插入的div没有position属性。当我将position:relative其更改为正常时。(很难找出问题所在)

就我而言,Angular用插入元素ng-repeat


1
谢谢!这也适用于我的问题,并且比上面的许多javascript解决方案轻得多。
Dsyko 2014年

4

我不敢相信这在2014年仍然是一个问题。在滚动时刷新页面左下角的固定位置标题框时,标题只是在屏幕上“重影”。在尝试了上述所有方法都没有成功之后,我注意到很多事情是由于创建非常短的DOM中继而导致的缓慢/导致问题等,从而导致滚动感觉有些不自然。

我最终做了一个固定位置的全尺寸div pointer-events: none使用danorton的答案对该元素,这似乎迫使整个屏幕上进行了重绘而不干扰DOM。

HTML:

<div id="redraw-fix"></div>

CSS:

div#redraw-fix {
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    z-index: 25;
    pointer-events: none;
    display: block;
}

JS:

sel = document.getElementById('redraw-fix');
sel.style.display='none';
sel.offsetHeight; // no need to store this anywhere, the reference is enough
sel.style.display='block';

对于您的变通办法(这是唯一为我工作的变通办法),我感激不尽。是的,这是2017年,问题仍然存在。我的问题与RTL语言页面有关,如果在Android移动设备上手动刷新,该页面将显示空白。在z-indexpointer-events这里的规则是多余的。
阿敏·莫扎法里

我以前使用过danorton的修复程序,但确实在内容的闪烁中挣扎,将显示设置为无。您的解决方案对我来说非常有效,而且没有内容更新!
贾斯汀·诺埃尔

3

并不是说这个问题需要另一个答案,但是我发现在我的特定情况下,只需一点一点地改变颜色就可以强制重涂。

//Assuming black is the starting color, we tweak it by a single bit
elem.style.color = '#000001';

//Change back to black
setTimeout(function() {
    elem.style.color = '#000000';
}, 0);

setTimeout证明移动当前事件循环之外的第二作风转变的关键。


1
在不同但相似的情况下,将超时设置为0证明对我很有用。在解析大型表单时,jQuery非侵入式验证冻结屏幕之前,不会发生重绘。以为我会发表评论,以防有人遇到同样的情况。其他解决方案在这种情况下不起作用。
k29

2

唯一适用于我的解决方案类似于sowasred2012的答案:

$('body').css('display', 'table').height();
$('body').css('display', 'block');

页面上有很多问题块,因此我更改display了root元素的属性。而且我使用display: table;代替display: none;,因为none它将重置滚动偏移量。


1

由于每个人似乎都有自己的问题和解决方案,所以我认为我会添加一些对我有用的东西。在使用当前Chrome的Android 4.1上,尝试将一个画布在带有溢出:隐藏的div内拖动,除非我向父div添加了元素(否则不会造成任何伤害),否则无法重绘。

var parelt = document.getElementById("parentid");
var remElt = document.getElementById("removeMe");
var addElt = document.createElement("div");
addElt.innerHTML = " "; // Won't work if empty
addElt.id="removeMe";
if (remElt) {
    parelt.replaceChild(addElt, remElt);
} else {
    parelt.appendChild(addElt);
}

没有屏幕闪烁或真实更新,也不会自己清理。没有全局或类范围的变量,只有局部变量。在Mobile Safari / iPad或桌面浏览器上似乎没有任何伤害。


1

我在Ionic html5应用程序上工作,在几个屏幕上我已经absolute定位了元素,当在IOS设备(iPhone 4,5,6,6+)上向上或向下滚动时,我有重画错误。

尝试了许多解决方案,但除了解决我的问题外,他们都没有工作。

.fixRepaint在那些绝对位置元素上使用了CSS类

.fixRepaint{
    transform: translateZ(0);
}

这已经解决了我的问题,可能会对某些人有所帮助


1
抱歉,我怎么还没有看到。@morewry感谢您指出
....

1

我使用该transform: translateZ(0);方法,但在某些情况下还不够。

我不喜欢添加和删除类,所以我试图找到解决方法,最后得到了一个效果很好的新技巧:

@keyframes redraw{
    0% {opacity: 1;}
    100% {opacity: .99;}
}

// ios redraw fix
animation: redraw 1s linear infinite;

0

这对JS很好

sel.style.display='none';
sel.offsetHeight; // no need to store this anywhere, the reference is enough
sel.style.display='block';

但是在Jquery中,尤其是当您只能使用$(document).ready并且由于任何特定原因而无法绑定到对象的.load事件时,以下操作将起作用。

您需要获取对象/ div的OUTER(MOST)容器,然后将其所有内容删除到变量中,然后重新添加。它将使外部容器内完成的所有更改都可见。

$(document).ready(function(){
    applyStyling(object);
    var node = $("div#body div.centerContainer form div.centerHorizontal").parent().parent();
    var content = node.html();
    node.html("");
    node.html(content);
}

0

我发现此方法在处理过渡时非常有用

$element[0].style.display = 'table'; 
$element[0].offsetWidth; // force reflow
$element.one($.support.transition.end, function () { 
    $element[0].style.display = 'block'; 
});

0

在我看来,“ display / offsetHeight” hack无效,至少在将其应用于动画元素时无效。

我有一个下拉菜单,正在页面内容上打开/关闭。菜单关闭后,工件才留在页面内容上(仅在webkit浏览器中)。“ display / offsetHeight” hack唯一可行的方法是将其应用于身体,这似乎很讨厌。

但是,我确实找到了另一种解决方案:

  1. 在元素开始设置动画之前,添加一个定义“ -webkit-backface-visibility:hidden;”的类。在元素上(我猜你也可以使用内联样式)
  2. 完成动画制作后,删除类(或样式)

这仍然很棘手(它使用CSS3属性来强制进行硬件渲染),但是至少它只影响所涉及的元素,并且在PC和Mac上的Safari和chrome上都为我工作。


0

这似乎与此有关:jQuery样式未在Safari中应用

在这些情况下,第一个响应中建议的解决方案对我来说效果很好,即:在进行样式更改后,将假类应用于主体并从其删除:

$('body').addClass('dummyclass').removeClass('dummyclass');

这迫使野生动物园重新绘制。


为了使它在Chrome中正常工作,我必须添加:$('body')。addClass('dummyclass')。delay(0).removeClass('dummyclass');
mbokil 2014年

0

以上建议对我没有用。但是下面的一个。

要动态更改锚点内的文本。单词“搜索”。创建一个带有id属性的内部标签“ font”。使用javascript管理内容(如下)

搜索

脚本内容:

    var searchText = "Search";
    var editSearchText = "Edit Search";
    var currentSearchText = searchText;

    function doSearch() {
        if (currentSearchText == searchText) {
            $('#pSearch').panel('close');
            currentSearchText = editSearchText;
        } else if (currentSearchText == editSearchText) {
            $('#pSearch').panel('open');
            currentSearchText = searchText;
        }
        $('#searchtxt').text(currentSearchText);
    }

0

某些情况下更改方向后,SVG在Android版Chrome上消失了,我遇到了问题。下面的代码不会重现它,但它是我们的设置。

body {
  font-family: tahoma, sans-serif;
  font-size: 12px;
  margin: 10px;
}
article {
  display: flex;
}
aside {
  flex: 0 1 10px;
  margin-right: 10px;
  min-width: 10px;
  position: relative;
}
svg {
  bottom: 0;
  left: 0;
  position: absolute;
  right: 0;
  top: 0;
}
.backgroundStop1 {
  stop-color: #5bb79e;
}
.backgroundStop2 {
  stop-color: #ddcb3f;
}
.backgroundStop3 {
  stop-color: #cf6b19;
}
<article>
  <aside>
    <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="100%" width="100%">
      <defs>
        <linearGradient id="IndicatorColourPattern" x1="0" x2="0" y1="0" y2="1">
          <stop class="backgroundStop1" offset="0%"></stop>
          <stop class="backgroundStop2" offset="50%"></stop>
          <stop class="backgroundStop3" offset="100%"></stop>
        </linearGradient>
      </defs>
      <rect x="0" y="0" rx="5" ry="5" width="100%" height="100%" fill="url(#IndicatorColourPattern)"></rect>
    </svg>
  </aside>
  <section>
    <p>Donec et eros nibh. Nullam porta, elit ut sagittis pulvinar, lacus augue lobortis mauris, sed sollicitudin elit orci non massa. Proin condimentum in nibh sed vestibulum. Donec accumsan fringilla est, porttitor vestibulum dolor ornare id. Sed elementum
      urna sollicitudin commodo ultricies. Curabitur tristique orci et ligula interdum, eu condimentum metus eleifend. Nam libero augue, pharetra at maximus in, pellentesque imperdiet orci.</p>
    <p>Fusce commodo ullamcorper ullamcorper. Etiam eget pellentesque quam, id sodales erat. Vestibulum risus magna, efficitur sed nisl et, rutrum consectetur odio. Sed at lorem non ligula consequat tempus vel nec risus.</p>
  </section>
</article>

经过戳刺和催促之后的一天半,对骇客不满意这里提供解决方案,我发现这个问题是由于在绘制新元素时似乎将元素保留在内存中这一事实造成的。解决方案是使SVG上的linearGradient的ID唯一,即使每页只使用一次。

这可以通过许多不同的方法来实现,但是对于我们的角度应用程序,我们使用了lodash uniqueId函数将变量添加到范围中:

角度指令(JS):

scope.indicatorColourPatternId = _.uniqueId('IndicatorColourPattern');

HTML更新:

第5行: <linearGradient ng-attr-id="{{indicatorColourPatternId}}" x1="0" x2="0" y1="0" y2="1">

第11行: <rect x="0" y="0" rx="5" ry="5" width="100%" height="100%" ng-attr-fill="url(#{{indicatorColourPatternId}})"/>

我希望这个答案可以使别人节省一天的面部敲击键盘的时间。


0

我建议使用一种不那么骇人,更正式的方法来强制进行重排:使用forceDOMReflowJS。在您的情况下,您的代码将如下所示。

sel = document.getElementById('my_id');
forceReflowJS( sel );
return false;

0

我发现,仅向元素添加内容样式就可以强制其重新绘制,每次您要重新绘制时,它都应该是一个不同的值,并且不必位于伪元素上。

.selector {
    content: '1'
}

0

我尝试了更多疑惑的答案,但对我不起作用。与其他浏览器相比,我很难在Safari上使用相同的clientWidth,此代码解决了该问题:

var get_safe_value = function(elm,callback){
    var sty = elm.style
    sty.transform = "translateZ(1px)";
    var ret = callback(elm)//you can get here the value you want
    sty.transform = "";
    return ret
}
// for safari to have the good clientWidth
var $fBody = document.body //the element you need to fix
var clientW = get_safe_value($fBody,function(elm){return $fBody.clientWidth})

真的很奇怪,因为如果我再次尝试在get_safe_value之后获取clientWidth,则我使用safari获取了错误的值,则getter必须介于 sty.transform = "translateZ(1px)";sty.transform = "";

另一个确定有效的解决方案是

var $fBody = document.body //the element you need to fix
$fBody.style.display = 'none';
var temp = $.body.offsetHeight;
$fBody.style.display = ""
temp = $.body.offsetHeight;

var clientW = $fBody.clientWidth

问题是您失去焦点和滚动状态。


0

这将强制重涂,同时避免闪烁,现有元素修补和任何布局问题...

function forceRepaint() {
    requestAnimationFrame(()=>{
      const e=document.createElement('DIV');
      e.style='position:fixed;top:0;left:0;bottom:0;right:0;background:#80808001;\
               pointer-events:none;z-index:9999999';
      document.body.appendChild(e);
      requestAnimationFrame(()=>e.remove());  
    });
}

-1

一个简单的jQuery解决方案:

$el.html($el.html());

要么

element.innerHTML = element.innerHTML;

有一个SVG,它在添加到html时没有显示。

可以在屏幕上显示svg元素后添加。

更好的解决方案是使用:

document.createElementNS('http://www.w3.org/2000/svg', 'svg');

并使用jQuery:

$(svgDiv).append($(document.createElementNS('http://www.w3.org/2000/svg', 'g'));

这将在Chrome上正确呈现。


这种方法的问题是,您将丢失在该对象或其子代上注册的所有事件处理程序。
哈卡
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.