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

时间:2010-08-14 22:25:47

标签: css google-chrome safari 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上失败。

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

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

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

30 个答案:

答案 0 :(得分:295)

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

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

如果它适用于“阻止”以外的样式,我会让别人发表评论。

谢谢,Vasil!

答案 1 :(得分:87)

我们最近遇到过这种情况,并发现在CSS中使用composite layer将受影响的元素提升为translateZ可以解决问题而无需额外的JavaScript。

.willnotrender { 
   transform: translateZ(0); 
}

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

Webkit和Blink一直致力于渲染性能,这些故障是优化的不幸副作用,旨在减少不必要的流量和涂料。 CSS will-change或其他后续规范将是未来的解决方案,很有可能。

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

答案 2 :(得分:51)

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

我意外地发现,我的解决方案是在脚本之后向主体添加一个空样式元素。

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

修复了它。这有多奇怪?希望这对某人有帮助。

答案 3 :(得分:32)

由于显示器+偏移触发器对我不起作用,我在这里找到了一个解决方案:

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

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

答案 4 :(得分:22)

我遇到了同样的问题。 danorton的'切换显示'修复程序在添加到动画的步进功能时对我有用,但我关注性能并且我寻找其他选项。

在我的情况下,没有重新绘制的元素在绝对位置元素内,当时没有z-index。在此元素中添加z-index会改变Chrome的行为,并且重新按照预期发生 - &gt;动画变得流畅。

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

干杯, 罗布

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

答案 5 :(得分:16)

以下作品。它只需要在纯CSS中设置一次。它比JS功能更可靠。表现似乎没有受到影响。

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

答案 6 :(得分:12)

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

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

它对我有用。

答案 7 :(得分:6)

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

使用:

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

但是,有时我注意到你必须直接在有问题的元素上调用redraw()。有时重绘父元素不会解决孩子遇到的问题。

关于浏览器呈现元素的方式的好文章:Rendering: repaint, reflow/relayout, restyle

答案 8 :(得分:6)

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

如果有人遇到同样的问题,我通过调用此函数解决了这个问题:

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

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

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

答案 9 :(得分:4)

我在使用position: absolute的另一个div中插入了许多div时出现此问题,插入的div没有position属性。当我将其更改为position:relative时,它工作正常。 (真的很难确定问题)

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

答案 10 :(得分:4)

我无法相信这在2014年仍然是一个问题。我在滚动时刷新页面左下角的固定位置标题框时遇到了这个问题,标题是“鬼”。它在屏幕上的方式。在尝试了以上所有内容之后没有成功,我注意到很多事情要么是由于创建非常短的DOM重新布局而导致的问题很慢/导致有些不自然的感觉滚动等......

我最终使用pointer-events: none制作了一个固定位置,全尺寸div并对该元素应用了danorton的答案,这似乎强制在整个屏幕上重绘而不会干扰DOM。 / p>

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

答案 11 :(得分: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证明在当前事件循环之外移动第二个样式更改至关重要。

答案 12 :(得分:2)

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

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

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

答案 13 :(得分:1)

由于每个人似乎都有自己的问题和解决方案,我想我会添加一些对我有用的东西。在Android 4.1上使用当前的Chrome,试图在一个带有overflow:hidden的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或桌面浏览器上似乎没有任何损害。

答案 14 :(得分:1)

我正在研究离子html5应用程序,在几个屏幕上我有absolute定位元素,当在IOS设备中向上或向下滚动时(iPhone 4,5,6,6 +)我有重新绘制错误。

尝试了许多解决方案,除了这个解决了我的问题之外,没有一个能够正常工作。

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

.fixRepaint{
    transform: translateZ(0);
}

这解决了我的问题,它可能对某人有所帮助

答案 15 :(得分:0)

我发现这种方法在处理转换时非常有用

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

答案 16 :(得分: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);
}

答案 17 :(得分:0)

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

想要动态更改锚点内的文本。 “搜索”一词。创建了一个带有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);
    }

答案 18 :(得分:0)

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

我有一个在页面内容上打开/关闭的下拉菜单。菜单关闭后(仅在webkit浏览器中),工件被留在页面内容上。 “display / offsetHeight”黑客工作的唯一方法是将它应用到身体上,这看起来很讨厌。

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

  1. 在元素开始动画之前,添加一个定义“-webkit-backface-visibility:hidden;”的类。在元素上(你也可以使用内联样式,我猜)
  2. 完成动画后,删除课程(或样式)
  3. 这仍然非常hacky(它使用CSS3属性强制硬件渲染),但至少它只影响有问题的元素,并且在PC和Mac上同时适用于safari和chrome。

答案 19 :(得分:0)

这似乎与此相关:jQuery style not being applied in Safari

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

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

这会迫使safari重绘。

答案 20 :(得分:0)

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

&#13;
&#13;
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;
}
&#13;
<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>
&#13;
&#13;
&#13;

在戳戳和刺激之后的一天半,并且对这里提供的 hacky 解决方案不满意,我发现这个问题是由于它在绘制新内容时似乎将元素保留在内存中一。解决方案是使SVG上的linearGradient的ID唯一,即使它每页只使用一次。

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

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

我希望这个答案可以让其他人为他们的键盘节省一些时间。

答案 21 :(得分:0)

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

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

答案 22 :(得分:0)

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

.selector {
    content: '1'
}

答案 23 :(得分: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

问题在于您失去了焦点和滚动状态。

答案 24 :(得分:0)

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

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

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

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

答案 25 :(得分: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());  
    });
}

答案 26 :(得分:0)

此代码将重新渲染 css

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

答案 27 :(得分:0)

将转换 CSS 设置为 class MyParentComponent extends Component { constructor (props) { super(props); this.sheetRef = React.createRef(); } componentDidMount () { this.sheetRef.current.open(); } render () { return ( <Fragment> <MyChildComponent {...otherProps} sheetRef={this.sheetRef} /> </Fragment> ); } } 显然适用于最新的 chrome。

scale(0.9999)

答案 28 :(得分:0)

我发现@morewry 的 excellent answer 解决了我的问题,而且此后 will-change 属性已到达。

<块引用>

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

就我而言,仅 will-change: transform; 的值在 Safari 14 中是有效的。

.wont-update {
    will-change: transform;

    /* for Safari < 9.1 */
    transform: translateZ(0);
}

答案 29 :(得分:-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上正确呈现。