I've been having a lot of trouble with bad performance on IE (all versions including IE11) in a HTML/SVG javascript-powered widget, only when the widget is hosted on a certain page.
After identifying that the main cause of the slowdown was paint / render layer redraws, and exhausting the information about these I could get out of IE Developer Tools, I resorted to trial and error turning off ancestor classes one at a time until the performance improved; then, on identifying the class, turning off style rules one at a time.
My entire problem seems to come down to a single overflow: hidden;
rule on an ancestor several divs up the tree.
The difference it makes is incredible: with overflow: hidden;
up the tree, a simple user interaction (highlighting an SVG path, generating a HTML text label, showing the label and positioning it relative to the SVG path and container) maxes out the processor, reduces the UI frame rate to zero and freezes everything dead for between 1,000 and 4,000 miliseconds per interaction. Without overflow: hidden;
on the ancestor, it completes in tens of miliseconds and the frame rate never drops below half (non-IE browsers are the same regardless of overflow: hidden;
).
Here's the profile with
overflow: hidden;
on the ancestor, profiling both on and off interactions, filtered to paint events:Here's the profile without
overflow: hidden;
on the ancestor, profiling both on and off interactions, filtered to paint events. The only change was ticking or unticking the tick box next to theoverflow: hidden;
style in the DOM inspector, and it doesn't matter in which order I do the tests:
I don't want to just override this overflow: hidden;
as a sticky plaster and say job done, not understanding how this happens and risking the problem re-occurring with other seemingly trivial CSS changes. I'd much prefer to understand why overflow: hidden;
makes such a difference and address that in a robust way which works regardless of the overflow rule applied.
Sadly I can't post a full demo but here's a summary of the relevant part of the DOM structure with comments on the layout-related styles:
<div class="responsive-grid">
<!-- ...lots of nested divs that simply inherit styles, I can't change this aspect of the Drupal layout -->
<div id="panel-5" class="col-12"> <!-- width: 100%; float: left -->
<!-- this is the first element IE looks at for offsetWidth when doing the changes below -->
<!-- ...a few more nested divs without layout-changing styles -->
<div class="panel"> <!-- overflow: hidden; clear: both; border: 1px; -->
<!-- this is the element where removing the overflow: hidden changes everything -->
<!-- I'm not sure what clear:both is for here, since no siblings. Seems redundant -->
<!-- ...a few more nested divs with no style rules, some contain <p>s <h2>s etc... -->
<div class="container"> <!-- position: relative; -->
<div class="sub-column col-8"> <!-- width: 66%; display: inline-block -->
<div class="square"> <!-- width: 100%; padding-bottom: 100%; position: relative -->
<svg viewbox="0 0 500 500" preserveAspectRatio="XMinYMin meet" ...>
<!-- svg position: absolute; width:100%; height: 100% -->
Many paths here
<div class="label"> <!-- fixed width in pixels; position: absolute -->
Some text here
</div>
</div>
</div>
<div class="sub-column col-4"> <!-- width: 33%; display: inline-block -->
<div class="sidebar">
Many interactive controls here
<!-- .square, svg andd .sidebar contain the only elements that are updated -->
</div>
</div>
</div>
<!-- some more ordinary position: static text containers -->
</div>
</div>
</div>
What could possibly be happening here, and is there any way it can be prevented without removing / forbidding overflow: hidden;
on the ancestor element?
I've seen How to avoid the performance cost of overflow:hidden? but both questions and answers appear to be specific to HTML tables and an old Webkit bug since fixed.
They also seem specific to cases where content clipped by the overflow gets needlessly painted; a quirk of my case is that the overflow: hidden;
isn't actually clipping much (if anything) on this page (but I can't just remove it because it's part of template affecting hundreds of other pages, where it does have an effect).
Update: The plot thickens. I've managed to replicate the problem with my widget in a simpler HTML structure, and discovered that the problem only occurs if both overflow: hidden;
and border-radius
(in my case, 3px) are set on the same container. With either one but not the other, the problem disappears.
After a lot more testing I think I'm starting to understand what's going on here. This is purely based on observation though, so I'd still be keen for a more authoritative answer if anyone has one.
What causes it?
It seems to happen only if all these are true:
overflow: hidden
andborder-radius
(or-ms-border-radius
). In my testing, it doesn't happen if different ancestors in the same branch have these styles.position: absolute;
orposition: relative;
The problem also seems to be more pronounced in proportion to the number of elements affected by
position: absolute;
/relative
and their complexity. In cases where there were SVG paths in a responsively scaling %-width SVG container with % padding-bottom for fixed aspect ratio, for example, the problem was very pronounced; if this branch was givenposition: static
but another branch had %-width divs with aposition: absolute;
ancestor, then the problem was still observable compared to removing one ofoverflow: hidden;
orborder-radius
, but was much less severe.But why?
I don't have any definitive answer, but I've got a plausible theory that seems to fit the facts. Ironically, it'd be a backfiring attempt at performance optimisation by IE.
I noticed that
offsetWidth
calculations for elements between X and the paths were going all the way to the paths, which made no sense to me and prompted a related question because paths within an SVG container surely can't influence the layout outside the container.I also noticed while researching this that other browsers - particularly, an older version of Chrome - seemed to have a different problem: elements that should have been hidden were being rendered, causing slowdowns.
Putting these together, I think there's a plausible explanation for what's going on here. If true, ironically, my performance woes were being caused by a backfiring attempt by IE to optimise performance and avoid problems like the above-linked now-fixed Chrome issue.
If this theory is true, something like this would be happening within IE:
overflow: hidden;
and concludes that it can improve performance by identifying elements that are wholly outside the element's bounds before doing redraw / reflow / paint etc events on them.position: absolute;
andposition: relative;
further down the DOM and concludes that these and their children might potentially be wholly outside the container and could potentially be disregarded like thisborder-radius
, but in such cases it's trivial and takes miliseconds.overflow: hidden
element is not a rectangle and therefore a more complex calculation is needed to determine what is out of boundsHow to fix it?
If you can, simply move the
overflow: hidden
orborder-radius
to a child element so they're not both on the same element. Job done.For me, however, I'm making a plugin that needs to be capable of being dropped in anywhere, and won't have any control over the pages its deployed on. I'm not aware of any way I could force IE to turn this behaviour off.
The best approach I can think of, is to assume that the
border-radius
style is non-essential for aesthetics and that theoverflow: hidden;
might be essential for structure, and so, if the browser is IE, look up the ancestor tree and removeborder-radius
from any element that has both it andoverflow: hidden;
.My application already uses jQuery, so this test looks something like this:
With CSS like:
The problem was brought to Microsoft's attention in this bug report written by Joppe Kroon in February 2014:
The only significant reply from Microsoft was published in April 2016. It does not provide a solution or a workaround:
The status of the bug is : Closed as Won't fix