How can I force WebKit to redraw/repaint to propagate style changes?

Viewed 191416

I have some trivial JavaScript to effect a style change:

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

This works fine with the latest versions of FF, Opera and IE, but fails on the latest versions of Chrome and Safari.

It affects two descendants, which happen to be siblings. The first sibling updates, but the second doesn’t. A child of the second element also has focus and contains the <a> tag that contains the above code in an onclick attribute.

In the Chrome “Developer Tools” window if I nudge (e.g. uncheck & check) any attribute of any element, the second sibling updates to the correct style.

Is there a workaround to easily and programmatically “nudge” WebKit into doing the right thing?

32 Answers

I use the transform: translateZ(0); method but in some cases it is not sufficient.

I'm not fan of adding and removing a class so i tried to find way to solve this and ended up with a new hack that works well :

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

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

This answer is for those, who is struggling with @danorton's solution using Angular 10. This solution

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

works for me only with "buildOptimizer": false in angular.json file. Looks like optimizer breaks this somehow.

I found that just adding a content style to the element forced it to repaint, this should be a different value every time you want it to redraw and doesn't need to be on a pseudo element.

.selector {
    content: '1'
}

I tried morewry answer but it does not work for me. I had trouble to have the same clientWidth with safari comparing to others browsers and this code solved the problem:

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

It is really strange because if I try again to get the clientWidth after get_safe_value, I obtain a bad value with safari, the getter has to be between sty.transform = "translateZ(1px)"; and sty.transform = "";

The other solution that works definitively is

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

The problem is that you lose focus and scroll states.

This will force repaint while avoid flickering, existing element tinkering and any layout issue...

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

This code will rerender css

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

Setting the transform CSS to scale(0.9999) apparently works in the newest 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);
}

I also faced the same issue working with Angular 10. I tried many solutions but nothing seems to be working. The fix which worked for me is handling it through JS. I just removed the element from DOM and added again using ngIf.

HTML:

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

JS:

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