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

189,606

Solution 1

I found some complicated suggestions and many simple ones that didn’t work, but a comment to one of them by Vasil Dinkov provided a simple solution to force a redraw/repaint that works just fine:

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

I’ll let someone else comment if it works for styles other than “block”.

Thanks, Vasil!

Solution 2

We recently encountered this and discovered that promoting the affected element to a composite layer with translateZ in CSS 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 the accepted answer almost certainly causes a reflow and repaint, not just a repaint.

Webkit and Blink have been working hard on rendering performance, and these kinds of glitches are the unfortunate side effect of optimizations that aim to reduce unnecessary flows and paints. CSS will-change or another succeeding specification will be the future solution, most likely.

There are other ways to achieve a composite layer, but this is the most common.

Solution 3

danorton solution didn't work for me. I had some really weird problems where webkit wouldn't draw some elements at all; where text in inputs wasn't updated until onblur; and changing className would not result in a redraw.

My solution, I accidentally discovered, was to add a empty style element to the body, after the script.

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

That fixed it. How weird is that? Hope this is helpful for someone.

Solution 4

Since the display + offset trigger didn't work for me, I found a solution here:

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

i.e.

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

Solution 5

I was suffering the same issue. danorton's 'toggling display' fix did work for me when added to the step function of my animation but I was concerned about performance and I looked for other options.

In my circumstance the element which wasn't repainting was within an absolutely position element which did not, at the time, have a z-index. Adding a z-index to this element changed the behaviour of Chrome and repaints happened as expected -> animations became smooth.

I doubt that this is a panacea, I imagine it depends why Chrome has chosen not to redraw the element but I'm posting this specific solution here in the help it hopes someone.

Cheers, Rob

tl;dr >> Try adding a z-index to the element or a parent thereof.

Share:
189,606

Related videos on Youtube

danorton
Author by

danorton

See my website for more information, including contact information.

Updated on May 07, 2022

Comments

  • danorton
    danorton almost 2 years

    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?

    • Joshua
      Joshua over 10 years
      I think I'm experiencing this problem on Android 4.2.2 (Samsung I9500) when wrapping my canvas app in a WebView. Ridiculous!
    • vsync
      vsync over 7 years
      what-forces-layout.md a very good reading place
  • danorton
    danorton over 13 years
    Curiously, this seems to set a different “dirty” bit than the one that caused the first sibling to update properly. This fragment makes everything repaint even if I put it before the original fragment!
  • Ansel Halliburton
    Ansel Halliburton about 12 years
    To avoid flickering you may try 'inline-block', 'table' or 'run-in' instead of 'none', but this may have side-effects. Also, a timeout of 0 triggers a reflow just like querying offsetHeight does: sel.style.display = 'run-in'; setTimeout(function () { sel.style.display = 'block'; }, 0);
  • bPratik
    bPratik about 12 years
    I used this in a hack I was trying, but instead of scale(1), I assigned it to itself as element.style.webkitTransform = element.style.webkitTransform. The reason for this being that setting it to the former was distorting the page slightly for absolutely positioned elements!
  • Dr. Max Völkel
    Dr. Max Völkel almost 12 years
    We work in GWT and use just sel.style.display='block'; which did the trick.
  • rkulla
    rkulla over 11 years
    This answer is still useful 2 years later. I just used it to fix a Chrome-only issue where CSS box shadows remained on the screen after resizing the browser in certain ways. Thanks!
  • Tschallacka
    Tschallacka over 11 years
    Now, 22 november 2012, this also solves my CSS 3D problem on chrome on terminal server clients where it would only render perspective properly after a redraw. LOVE the code and it's still actual.
  • hobberwickey
    hobberwickey over 11 years
    I'd upvote 1,000,000 times if I could. This is the only way I could get chrome to repaint a stupid div I was dragging around. By the way, you don't have to add a style element (at least I didn't) any element will work. I made a div and gave it an id so I could delete then add the element on each step, so as not to fill the DOM up with useless tags.
  • James Westgate
    James Westgate over 11 years
    Yes, this worked for a Safari 5 on OS X issue - thank you very much
  • bendman
    bendman over 11 years
    just used this mixed with Pumbaa80's comment on another answer. The fix I ended up with was var div = $('<div>').appendTo(element); setTimeout(function(){ div.remove(); }, 0);
  • pdelanauze
    pdelanauze about 11 years
    This single line is working great for me ! $('<style></style>').appendTo($(document.body)).remove();
  • mrbinky3000
    mrbinky3000 about 11 years
    This is the only answer I've found so far that works. I was trying to remove a css3 transition applied to the left property, change the left position, and then re-apply the css3 transition so that future changes to left would be animated. Javascript was being too efficient and only applied the final css change. So basically, this snippet forced the browser to accept the first change, pause, then accept the second change.
  • RaphaelDDL
    RaphaelDDL about 11 years
    @danorton You answered in 2010. It's 2013 and the bug is still around. Thanks. By the way, anyone know if there is any issue registered at webkit tracker?
  • Marcel Falliere
    Marcel Falliere almost 11 years
    @pdelanauze answer works perfect. I have a redraw problem sing css3 translate and transform in ios.
  • ProblemsOfSumit
    ProblemsOfSumit over 10 years
    i can't believe this is (still) a bug. I mean... 10 years of web-dev and this is the first time I experience this one.
  • ProblemsOfSumit
    ProblemsOfSumit over 10 years
    PS.: for me the above solution didn't work anymore. Instead i used this (jQuery): sel.hide().show(0); Source: stackoverflow.com/questions/8840580/…
  • Andrew Bullock
    Andrew Bullock over 10 years
    All you need to do is read a size property, you dont need to change it back and forth.
  • Ryan Wheale
    Ryan Wheale over 10 years
    Just know that this forces a repaint of the entire page, whereas most of the time you only want to repaint a certain section of the page...
  • trisweb
    trisweb over 10 years
    Brilliant. This is awesome and fixed the problem for me. A+++ would z-index again.
  • ravb79
    ravb79 over 10 years
    Solved my problem with reinitializing the table render after resizing the window. Awesome. And on a sidenote, wtf webkit browsers?
  • Andrea
    Andrea over 10 years
    Thank you so much. The Nintendo 3DS's browser uses an old version of WebKit and it wasn't redrawing the canvas. Doing this after each draw fixed everything!
  • Eric_WVGG
    Eric_WVGG about 10 years
    Still a bug. Pops up when doing an Angularjs fill of a floated element.
  • mbokil
    mbokil about 10 years
    To get this to work in Chrome I had to add: $('body').addClass('dummyclass').delay(0).removeClass('dummy‌​class');
  • bosgood
    bosgood about 10 years
    Thanks for this fix! In my case, was a Retina Mac/Chrome-only edge case with an Ember.Input. Doesn't even flicker!
  • Charlie Martin
    Charlie Martin almost 10 years
    I tried everything above this, but only this one worked for me. WTF webkit! This is not computer science! This is black magic!
  • davidtbernal
    davidtbernal almost 10 years
    This worked for me, and didn't have problems flickering or resetting the scroll position that the display solution did.
  • Ricky Boyce
    Ricky Boyce over 9 years
    Did not work in my situation dealing with scroll-bars and transforms.
  • Dsyko
    Dsyko over 9 years
    Thanks! This worked for my issue as well, and is a lot more light weight that a lot of the javascript solutions above.
  • Jack
    Jack over 9 years
    Changing the visibility would probably be better than changing the display property.
  • danorton
    danorton over 9 years
    @Jack, it might be better if it works. It might also depend on exactly what style change needs triggering.
  • gustavohenke
    gustavohenke over 9 years
    Worked for me when using angular-carousel!
  • Grodriguez
    Grodriguez over 9 years
    This does not work for me on Chrome or FF, however simply accessing sel.offsetHeight (without touching the display property) does work. Seems that the browser is skipping the calculation of offsetWidth/offsetHeight when the element is hidden..
  • Dan
    Dan over 9 years
    This does not work for me in Chrome for mac. My case is applying transition and then change transform and opacity. The only solution I have found is to wait 100 milliseconds and then apply opacity.
  • dev_willis
    dev_willis over 9 years
    Still a bug. Reading the offsetHeight by itself did not fix the problem for me and changing the display property was impractical so I used the visibility property and it worked! I placed the following in my jQuery.animate() callback function: this.style.visibility = "hidden"; this.offsetHeight; this.style.visibility = "visible"; I needed all 3 lines to make it work. The animation is still not visible all the time but when it's finished it will show up in the right place rather than partially somewhere else or not at all.
  • dev_willis
    dev_willis over 9 years
    Incidentally, it had to be done on the specific positioned element that I wanted to fix. Applying this to the container that I was actually animating did not help.
  • stiller_leser
    stiller_leser over 9 years
    It's 2015 by now and this answer is still relevant. Took me two hours to find. Thanks!
  • Kevin Beal
    Kevin Beal almost 9 years
    If this hack doesn't work for you, you may need to just travel up the DOM tree and affect that element until it does. I tried every solution offered on this page and this is the only one that works, albeit not on the element affected itself.
  • Anjum....
    Anjum.... almost 9 years
    Opss some how i haven't seen that. @morewry Thanks for pointing out
  • k29
    k29 over 8 years
    Setting a timeout of 0 proved useful for me in a different but similar situation. The redraw wasn't occuring before jquery unobtrusive validation froze up the screen while it parsed a large form. Thought I'd comment in case someone out there is in the same situation. The other solutions did not work in this scenario.
  • Sam Plus Plus
    Sam Plus Plus over 8 years
    Anyone know if there is an open issue with chrome or Webkit on this?
  • ChrisW
    ChrisW over 8 years
    appendChild is a DOM method, not a jQuery method.
  • Timo
    Timo over 8 years
    Fixed my issue where border-radius was lost in an element inside a sliding panel
  • jayaram S
    jayaram S over 8 years
    Amazingly this bug / fix still applies in 2016 - Javafx webview which uses webkit rendering engine. The issue i ran into was that on selecting checkboxes that were embedded in an svg, the check mark does not appear / go away. if you resize the window, it redraws and paints the correct state of the checkbox.
  • Chris
    Chris about 8 years
    Looks like it's working for me too. Used this to fix a rendering issue with Mobile Safari
  • Quispie
    Quispie about 8 years
    Works for cordova projects as well!
  • Herc
    Herc almost 8 years
    I had a Cordova app using a lot of dynamic SVG. Worked fine everywhere except iOS7, where if just wouldn't display the SVG elements on startup. Added a simple $('body')[0].style.webkitTransform = 'scale(1)'; to the initialisation code and the problem disappeared!
  • teddy
    teddy almost 8 years
    Happened on FF43, maybe due to my framework. But that solotion worked (i use jquery version: hide, offset, show)
  • Adam Marshall
    Adam Marshall almost 8 years
    Worked for me for a -webkit-line-clamp that didn't refresh
  • relic
    relic over 7 years
    Content that is dynamically generated into a container with flex-direction: column-reverse; applied is horribly unreliable on mobile devices about repainting children. Accessing offsetHeight, while toggling visibility seems to have cleared up most of the issues I was having.
  • Haqa
    Haqa over 7 years
    The problem with this approach is that you loose any event handlers you have registered on the object or its descendants.
  • Lain
    Lain over 7 years
    Worked for me on ipad(6.0) with -webkit-transform: translate(-50%, -50%).
  • fantasticrice
    fantasticrice about 7 years
    This was the solution I needed to fix iOS Webkit repainting issues in a scrolling full screen Reveal Modal.
  • Ignas2526
    Ignas2526 about 7 years
    This solved my issue of Chrome not properly updating children's width and height inside of block which was being resized by JavaScript. The element was using CSS calc and Viewport units. I used sel.style.display='table'; sel.offsetHeight; sel.style.display='block'; (initial state was block).
  • Amin Mozafari
    Amin Mozafari about 7 years
    I cannot thank you enough for your workaround which is the only one that worked for me. Yes, it's 2017 and the issue lingers on. My problem was related to an RTL language page which would render blank if manually refreshed on Android mobile devices. The z-index and pointer-events rules are superfluous here.
  • Bob S
    Bob S almost 7 years
    had to use JS to trigger this:$this.css("transform", ""); setTimeout(function() { $this.css("transform","translateZ(0)"); }, 30);
  • Ben West
    Ben West almost 7 years
    I found this to be a non-starter because of flashing and the fact that it only seems to work with a subset of issues that pause app/ui painting.
  • cjohansson
    cjohansson almost 7 years
    Thanks, finally something that worked in my case with a position: absolute; div that is hidden with display: none;
  • Ahmed Musallam
    Ahmed Musallam almost 7 years
    This fixes my issue, forces safari to relayout. However, I used zoom instead of padding: @-webkit-keyframes safariBugFix {from { zoom: 100%; } to { zoom: 99.99999%; }}
  • user21820
    user21820 over 6 years
    @Web_Designer: Are you aware that the code given in your comment is actually invalid? If you think it is valid, paste it into the web console and run it.
  • Lionel Gaillard
    Lionel Gaillard over 6 years
    If, like me, your linter complaints about the second line, disable it on this particular line with a line comment: "// eslint-disable-line" for ESLint or "// jshint ignore:line" for JSHint
  • dylnmc
    dylnmc over 6 years
    I prefer this method to danorton's method due to its simplicity. It also worked for me when needing to refresh webkit after moving an absolutely-positioned div with text inside in javascript. However, I did it inside javascript like objThatDoesNotUpdate.style.transform = "translateZ(0)"; :) I guess you could also do objThatDoesNotUpdate.classList.add("willNotRender"); objThatDoesNotUpdate.classList.remove("willNotRender"); (not tested)
  • Oliboy50
    Oliboy50 almost 6 years
    thanks!!!! I was wasting my time because of a webkit bug (firefox works perfectly) caused by a background-image trick (to only apply opacity to background rather than the whole div content)
  • Justin Noel
    Justin Noel almost 6 years
    I've used danorton's fix before but was really struggling with the flash of content from setting display to none. Your solution worked out great for me with no content flash!
  • Andrew
    Andrew almost 6 years
    This solved a problem I was having where I had a bunch of absolutely positioned divs on top of an image. Each div has a different background color. For some reason they wouldn't update on Safari or IE. This solution fixes both browsers. Thanks so much!
  • setholopolus
    setholopolus over 5 years
    This is not working as of right now, on the newest Safari and iOS.
  • Low
    Low over 5 years
    This worked for Ionic Segment change. Apply the class to a wrapper div INSIDE <ion-content> tag.
  • EricG
    EricG over 5 years
    @setholopolus which versoin(s) would that be?
  • setholopolus
    setholopolus over 5 years
    iOS 11+. I have Safari 11.1.2, I'm not sure about other versions of Safari.
  • Jeph
    Jeph about 5 years
    This worked for me with a conditional div under Angular 6 not rendering once shown. I appreciate it.
  • CWSpear
    CWSpear about 5 years
    Original question was asked in 2010. This answer was written in 2014 and is a great solution. It's 2019 and this is still an issue (albeit only in Safari)... Well played, WebKit.
  • ekalchev
    ekalchev over 4 years
    Great! I was trying to animate the scrollbars too and that is what I needed! Btw better use overflow: overlay for animated scrollbars
  • Nikos
    Nikos over 3 years
    safari sucks, the new ie6
  • Nikos
    Nikos over 3 years
    try 2020 lol, will it help me? stackoverflow.com/questions/63690664/…
  • Johann
    Johann over 3 years
    2020 and this bug still exists but your solution took care of it a decade later.
  • Daphoque
    Daphoque over 3 years
    holly cow ! 8 hours to find why my iframe content appears wrongly (element present but with no opacity ??) ... this fixed, it. Be careful the redrawing is done only if the css value is changing, calling this twice will do only one redraw.
  • hudidit
    hudidit over 3 years
    2021 and this still works! BTW setting display to none is just fine, because the DOM won't be updated before the current call stack execution ends.
  • ptim
    ptim almost 3 years
    Note that will-update is available in Safari > 9 🎉
  • Max
    Max almost 3 years
    This did the trick for me too, I had the issue on safari where overflow did not repaint correctly after a class toggle. Using sel.style.display='table' instead of sel.style.display='none' allowed to keep other css transitions working, otherwise it would just jump from one state to another.
  • The Sloth
    The Sloth over 2 years
    Thankyou !! I'm almost tempted to but a translateZ(0) on everything just to avoid these endless safari bugs