[javascript] Force DOM redraw/refresh on Chrome/Mac

Every once in a while, Chrome will render perfectly valid HTML/CSS incorrectly or not at all. Digging in through the DOM inspector is often enough to get it to realize the error of its ways and redraw correctly, so it's provably the case that the markup is good. This happens frequently (and predictably) enough in a project I'm working on that I've put code in place to force a redraw in certain circumstances.

This works in most browser/os combinations:

    el.style.cssText += ';-webkit-transform:rotateZ(0deg)'
    el.offsetHeight
    el.style.cssText += ';-webkit-transform:none'

As in, tweak some unused CSS property, then ask for some information that forces a redraw, then untweak the property. Unfortunately, the bright team behind Chrome for the Mac seem to have found a way to get that offsetHeight without redrawing. Thus killing an otherwise useful hack.

Thus far, the best I've come up with to get the same effect on Chrome/Mac is this piece of ugliness:

    $(el).css("border", "solid 1px transparent");
    setTimeout(function()
    {
        $(el).css("border", "solid 0px transparent");
    }, 1000);

As in, actually force the element to jump a bit, then chill a second and jump it back. Making it worse, if you drop that timeout below 500ms (to where it would be less noticeable), it often won't have the desired effect, since the browser won't get around to redrawing before it goes back to its original state.

Anybody care to offer a better version of this redraw/refresh hack (preferably based on the first example above) that works on Chrome/Mac?

This question is related to javascript css macos dom google-chrome

The answer is


CSS only. This works for situations where a child element is removed or added. In these situations, borders and rounded corners can leave artifacts.

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

October 2020, the problem still persists with Chrome 85.

morewry's solution of using transform:translateZ(0) works, actually, any transformation works, including translate(0,0) and scale(1), but if you must update the element again, then the trick is to toggle the transformation, and the best way is to directly remove it, after one frame, using requestAnimationFrame (setTimeout should always be avoided because it will be slower so it can cause glitches).

So, the update one element:

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

I ran into a similar issue and this simple line of JS helped to fix it:

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

In my case it was a bug with a Chrome extension not redrawing the page after changing its CSS from within the extension.


This works for me. Kudos go here.

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

$(el).redraw();

Below css works for me on IE 11 and Edge, no JS needed. scaleY(1) does the trick here. Seems the simplest solution.

.box {
    max-height: 360px;
    transition: all 0.3s ease-out;
    transform: scaleY(1);
}
.box.collapse {
    max-height: 0;
}

We recently encountered this and discovered that promoting the affected element to a composite layer with translateZ fixed the issue without needing extra javascript.

.willnotrender { 
   transform: translateZ(0); 
}

As these painting issues show up mostly in Webkit/Blink, and this fix mostly targets Webkit/Blink, it's preferable in some cases. Especially since many JavaScript solutions cause a reflow and repaint, not just a repaint.


It helped me

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

Not sure exactly what you're trying to achieve but this is a method I have used in the past with success to force the browser to redraw, maybe it will work for you.

// in jquery
$('#parentOfElementToBeRedrawn').hide().show(0);

// in plain js
document.getElementById('parentOfElementToBeRedrawn').style.display = 'none';
document.getElementById('parentOfElementToBeRedrawn').style.display = 'block';

If this simple redraw doesn't work you can try this one. It inserts an empty text node into the element which guarantees a redraw.

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
}

EDIT: In response to Šime Vidas what we are achieving here would be a forced reflow. You can find out more from the master himself http://paulirish.com/2011/dom-html5-css3-performance/


If you want to preserve the styles declared into your stylesheets, better to use something like:

jQuery.fn.redraw = function() {
    this.css('display', 'none'); 
    var temp = this[0].offsetHeight;
    this.css('display', '');
    temp = this[0].offsetHeight;
};

$('.layer-to-repaint').redraw();

NB: with latest webkit/blink, storing the value of offsetHeight is mandatory in order to trigger the repaint, otherwise the VM (for optimizations purposes) will probably skip that instruction.

Update: added the second offsetHeight reading, it is necessary to prevent browser from queueing/caching a following CSS property/class change with the restore of the display value (this way a CSS transition that can follow should be rendered)


2020: Lighter and stronger

The previous solutions don't work anymore for me.

I guess browsers optimize the drawing process by detecting more and more "useless" changes.

This solution makes the browser draw a clone to replace the original element. It works and is probably more sustainable:

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

tested on Chrome 80 / Edge 80 / Firefox 75


None of the above answers worked for me. I did notice that resizing my window did cause a redraw. So this did it for me:

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

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

call this function after 500 milliseconds.


This seems to do the trick for me. Plus, it really doesn't show at all.

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

Hiding an element and then showing it again within a setTimeout of 0 will force a redraw.

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

This is my solution that worked for disappearing content...

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

I ran into this challenge today in OSX El Capitan with Chrome v51. The page in question worked fine in Safari. I tried nearly every suggestion on this page - none worked right - all had side-effects... I ended up implementing the code below - super simple - no side-effects (still works as before in Safari).

Solution: Toggle a class on the problematic element as needed. Each toggle will force a redraw. (I used jQuery for convenience, but vanilla JavaScript should be no problem...)

jQuery Class Toggle

$('.slide.force').toggleClass('force-redraw');

CSS Class

.force-redraw::before { content: "" }

And that's it...

NOTE: You have to run the snippet below "Full Page" in order to see the effect.

_x000D_
_x000D_
$(window).resize(function() {_x000D_
  $('.slide.force').toggleClass('force-redraw');_x000D_
});
_x000D_
.force-redraw::before {_x000D_
  content: "";_x000D_
}_x000D_
html,_x000D_
body {_x000D_
  height: 100%;_x000D_
  width: 100%;_x000D_
  overflow: hidden;_x000D_
}_x000D_
.slide-container {_x000D_
  width: 100%;_x000D_
  height: 100%;_x000D_
  overflow-x: scroll;_x000D_
  overflow-y: hidden;_x000D_
  white-space: nowrap;_x000D_
  padding-left: 10%;_x000D_
  padding-right: 5%;_x000D_
}_x000D_
.slide {_x000D_
  position: relative;_x000D_
  display: inline-block;_x000D_
  height: 30%;_x000D_
  border: 1px solid green;_x000D_
}_x000D_
.slide-sizer {_x000D_
  height: 160%;_x000D_
  pointer-events: none;_x000D_
  //border: 1px solid red;_x000D_
_x000D_
}_x000D_
.slide-contents {_x000D_
  position: absolute;_x000D_
  top: 10%;_x000D_
  left: 10%;_x000D_
}
_x000D_
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>_x000D_
_x000D_
<p>_x000D_
  This sample code is a simple style-based solution to maintain aspect ratio of an element based on a dynamic height.  As you increase and decrease the window height, the elements should follow and the width should follow in turn to maintain the aspect ratio.  You will notice that in Chrome on OSX (at least), the "Not Forced" element does not maintain a proper ratio._x000D_
</p>_x000D_
<div class="slide-container">_x000D_
  <div class="slide">_x000D_
    <img class="slide-sizer" src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7">_x000D_
    <div class="slide-contents">_x000D_
      Not Forced_x000D_
    </div>_x000D_
  </div>_x000D_
  <div class="slide force">_x000D_
    <img class="slide-sizer" src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7">_x000D_
    <div class="slide-contents">_x000D_
      Forced_x000D_
    </div>_x000D_
  </div>_x000D_
</div>
_x000D_
_x000D_
_x000D_


This solution without timeouts! Real force redraw! For Android and iOS.

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

My fix for IE10 + IE11. Basically what happens is that you add a DIV within an wrapping-element that has to be recalculated. Then just remove it and voila; works like a charm :)

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

Sample 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

    };

call function:

$('article.child').redraw(); //<==bad idea

$('#parent').redraw();

Most answers require the use of an asynchroneous timeout, which causes an annoying blink.

But I came up with this one, which works smoothly because it is synchroneous:

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

call window.getComputedStyle() should force a reflow


I had a react component list which when scrolled, then opened another page, then when returning back the list was not rendered on Safari iOS until page was scrolled. So this is the fix.

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

I wanted to return all the states to the previous state (without reloading) including the elements added by jquery. The above implementation not gonna works. and I did as follows.

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

An approach that worked for me on IE (I couldn't use the display technique because there was an input that must not loose focus)

It works if you have 0 margin (changing the padding works as well)

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

Examples related to javascript

need to add a class to an element How to make a variable accessible outside a function? Hide Signs that Meteor.js was Used How to create a showdown.js markdown extension Please help me convert this script to a simple image slider Highlight Anchor Links when user manually scrolls? Summing radio input values How to execute an action before close metro app WinJS javascript, for loop defines a dynamic variable name Getting all files in directory with ajax

Examples related to css

need to add a class to an element Using Lato fonts in my css (@font-face) Please help me convert this script to a simple image slider Why there is this "clear" class before footer? How to set width of mat-table column in angular? Center content vertically on Vuetify bootstrap 4 file input doesn't show the file name Bootstrap 4: responsive sidebar menu to top navbar Stylesheet not loaded because of MIME-type Force flex item to span full row width

Examples related to macos

Problems with installation of Google App Engine SDK for php in OS X dyld: Library not loaded: /usr/local/opt/openssl/lib/libssl.1.0.0.dylib dyld: Library not loaded: /usr/local/opt/icu4c/lib/libicui18n.62.dylib error running php after installing node with brew on Mac Could not install packages due to an EnvironmentError: [Errno 13] How do I install Java on Mac OSX allowing version switching? Git is not working after macOS Update (xcrun: error: invalid active developer path (/Library/Developer/CommandLineTools) Can't compile C program on a Mac after upgrade to Mojave You don't have write permissions for the /Library/Ruby/Gems/2.3.0 directory. (mac user) How can I install a previous version of Python 3 in macOS using homebrew? Could not install packages due to a "Environment error :[error 13]: permission denied : 'usr/local/bin/f2py'"

Examples related to dom

How do you set the document title in React? How to find if element with specific id exists or not Cannot read property 'style' of undefined -- Uncaught Type Error adding text to an existing text element in javascript via DOM Violation Long running JavaScript task took xx ms How to get `DOM Element` in Angular 2? Angular2, what is the correct way to disable an anchor element? React.js: How to append a component on click? Detect click outside React component DOM element to corresponding vue.js component

Examples related to google-chrome

SessionNotCreatedException: Message: session not created: This version of ChromeDriver only supports Chrome version 81 SameSite warning Chrome 77 What's the net::ERR_HTTP2_PROTOCOL_ERROR about? session not created: This version of ChromeDriver only supports Chrome version 74 error with ChromeDriver Chrome using Selenium Jupyter Notebook not saving: '_xsrf' argument missing from post How to fix 'Unchecked runtime.lastError: The message port closed before a response was received' chrome issue? Selenium: WebDriverException:Chrome failed to start: crashed as google-chrome is no longer running so ChromeDriver is assuming that Chrome has crashed WebDriverException: unknown error: DevToolsActivePort file doesn't exist while trying to initiate Chrome Browser How to make audio autoplay on chrome How to handle "Uncaught (in promise) DOMException: play() failed because the user didn't interact with the document first." on Desktop with Chrome 66?