我怎样才能强迫WebKit重绘/重绘来传播样式变化?

我有一些简单的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。

它会影响两个后代,而这两个后代恰好是兄弟姐妹。第一个兄弟更新,但第二个没有。第二个元素的子元素也有焦点,并包含& lt; a>标记,该标记在onclick属性中包含上述代码。

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

是否有一种解决方法可以简单地通过编程“推动”WebKit做正确的事情?

192490 次浏览

我发现一些复杂的建议和许多简单的建议都不起作用,但Vasil Dinkov对其中一个的注释提供了一个简单的解决方案来强制重绘/重绘,工作得很好:

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

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

谢谢,Vasil !

丹诺顿方案对我不起作用。我有一些非常奇怪的问题,webkit不会画一些元素在所有;输入中的文本直到onblur才更新;并且改变className不会导致重绘。

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

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

这就解决了问题。这有多奇怪?希望这对某些人有所帮助。

我今天偶然发现了这个: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()。有时重绘父元素并不能解决子元素正在经历的问题。

关于浏览器呈现元素方式的好文章:渲染:重绘,回流/重新布局,重设样式

由于显示+偏移触发不适合我,我在这里找到了一个解决方案:

< a href = " http://mir.aculo。我们/ 2009/09/25 / force-redraw-dom-technique-for-webkit-based-browsers / noreferrer“rel = > http://mir.aculo.us/2009/09/25/force-redraw-dom-technique-for-webkit-based-browsers/ < / >

即。

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

这对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);
}

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

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

我也有同样的问题。danorton的“切换显示”修复在添加到我的动画的阶跃功能时确实为我工作,但我担心性能,我寻找其他选项。

在我的情况下,没有重绘的元素位于绝对位置元素中,当时它没有z指数。添加一个z-index到这个元素改变了Chrome的行为和重绘发生的预期->动画变得流畅。

我怀疑这是一个灵丹妙药,我想这取决于为什么 Chrome已经选择不重新绘制元素,但我在这里发布这个具体的解决方案,以帮助它希望有人。

< p >欢呼, Rob < / p >

尝试为元素或其父元素添加z-index。

出于某种原因,我无法让danorton的答案工作,我可以看到它应该做什么,所以我稍微调整了一下:

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

这对我很有效。

“display/offsetHeight”hack在我的情况下不起作用,至少当它应用于动画元素时。

我有一个下拉菜单,在页面内容上打开/关闭。在菜单关闭后,工件被留在页面内容上(仅在webkit浏览器中)。唯一的方法,“显示/offsetHeight”hack工作是如果我应用到身体,这似乎令人讨厌。

然而,我确实找到了另一个解决方案:

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

这仍然是相当棘手的(它使用CSS3属性强制硬件渲染),但至少它只影响有问题的元素,并为我在safari和chrome的PC和Mac上工作。

我来到这里,因为我需要在修改css后重新绘制Chrome中的滚动条。

如果有人有同样的问题,我通过调用这个函数来解决它:

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

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

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

这似乎与在Safari中不应用jQuery样式有关

在第一个回应中建议的解决方案在这些场景中对我来说工作得很好,即:在进行样式更改后应用并删除一个虚拟类到主体:

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

这将迫使safari重新绘制。

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

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

因为每个人似乎都有自己的问题和解决方案,我想我应该添加一些适合我的东西。在Android 4.1与当前的Chrome,试图拖动一个画布在一个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);
}

没有屏幕闪烁或真正的更新,并清理自己。没有全局变量或类作用域变量,只有局部变量。在移动Safari/iPad或桌面浏览器上似乎没有任何损害。

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

.willnotrender {
transform: translateZ(0);
}

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

Webkit和Blink一直在努力提高渲染性能,而这些小故障是优化的副作用,目的是减少不必要的流程和油漆。CSS会改变或其他后续规范将是未来的解决方案,最有可能。

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

以上建议对我没用。但是下面这个可以。

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

Search

脚本内容:

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

我有这个问题,许多div被插入到另一个div与position: absolute,插入的div没有位置属性。当我把它改为position:relative时,它工作得很好。(真的很难找到问题所在)

在我的例子中,是Angular用ng-repeat插入的元素。

我不敢相信在2014年这仍然是一个问题。我在滚动页面时刷新页面左下角的固定位置标题框时就遇到了这个问题,标题会在屏幕上方“鬼影”。在尝试了以上所有的事情都没有成功之后,我注意到很多事情要么很慢/导致问题,因为创建非常短的DOM转播等,导致有点不自然的感觉滚动等…

我最终用pointer-events: none创建了一个固定的位置,全尺寸的div,并对该元素应用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';

一个简单的解决方案与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上正确渲染。

我在ionic html5应用程序上工作,在少数屏幕上我有absolute定位元素,当在IOS设备(iPhone 4,5,6,6 +)中向上或向下滚动时,我有重刷错误。

尝试了许多解决方案,没有一个是有效的,只有这个解决了我的问题。

我在这些绝对位置元素上使用了css类.fixRepaint

.fixRepaint{
transform: translateZ(0);
}

这已经解决了我的问题,它可能会帮助别人

并不是说这个问题需要另一个答案,而是我发现,在我的特定情况下,仅仅改变一点颜色就会迫使我重新绘制。

//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被证明是将第二个样式更改移到当前事件循环之外的关键。

对我来说唯一有效的解决方案类似于sowasred2012的答案:

$('body').css('display', 'table').height();
$('body').css('display', 'block');
我在页面上有很多问题块,所以我改变了根元素的display属性。 我使用display: table;代替display: none;,因为none将重置滚动偏移量

我遇到了一个SVG的问题,当方向在某些情况下发生改变时,它在Chrome for Android上消失了。下面的代码没有重现它,但是我们的设置。

.
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上的线性梯度的ID是唯一的,即使它在每页只使用一次。

这可以通过许多不同的方式实现,但对于我们的angular应用程序,我们使用lodash uniqueId函数向作用域添加一个变量:

Angular指令(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}})"/>

我希望这个答案能让其他人省下一天砸键盘的时间。

我建议使用一种不那么粗鲁和更正式的方式来强制回流:使用forceDOMReflowJS。在您的示例中,您的代码将如下所示。

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

我发现,只是添加一个内容样式的元素强制它重新绘制,这应该是一个不同的值,每次你想要它重新绘制,而不需要在一个伪元素。

.selector {
content: '1'
}
我试了更多的答案,但对我不起作用。 我有麻烦有相同的clientWidth与safari与其他浏览器相比,这段代码解决了问题:

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 = ""; < / p >

另一个肯定有效的解决方法是

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

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

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

我不喜欢添加和删除一个类,所以我试图找到解决这个问题的方法,最后用一个新的黑客,工作得很好:

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


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

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

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

这段代码将重新渲染css

 document.body.style.display = 'flex';
setTimeout(() => (document.body.style.display = ''), 0);

将转换CSS设置为scale(0.9999)显然在最新的chrome中有效。

function redraw(node){
// Adjust the 200 as fastest as you can
// or, change the setTimeout to requestAnimationFrame as soon as the element
// is drawn
setTimeout(() => (node.style.transform = "scale(0.9999)"), 200);
}

我发现我的问题已经被@morewry的优秀的答案解决了,而且will-change属性已经到达。

CSS将会改变,或者其他后续的规范将是未来的解决方案,很可能。

在我的例子中,在Safari 14中单独使用will-change: transform;值是有效的。

.wont-update {
will-change: transform;


/* for Safari < 9.1 */
transform: translateZ(0);
}
这个答案是给那些正在为使用Angular 10的@danorton解决方案而挣扎的人的。 这个解决方案< / p >
sel.style.display='none';
sel.offsetHeight;
sel.style.display='';

仅适用于angular.json文件中的"buildOptimizer": false。 看起来优化器以某种方式破坏了这个

我在使用Angular 10时也遇到了同样的问题。我尝试了许多方法,但似乎都不起作用。 对我来说有效的修复是通过JS处理它。我只是从DOM中删除了元素,并使用ngIf再次添加

HTML:

<div *ngIf="showElement">
Contents of your element
</div>

JS:

this.showElement = false;
setTimeout(() => {
this.showElement = true;
}, 10);

我想补充一点,现在需要在父节点上设置样式。

 sel.parentNode.style.display = 'none';
sel.parentNode.offsetHeight;
sel.parentNode.style.display = '';

在当前版本的Safari(15.6)中,这是唯一适用于我的解决方案。