I noticed that Safari is way slower at updating CSS element positions when they are changed in javascript.
For instance, we can fix an element's position to scrolling in 2 ways: via javascript and via the position: fixed CSS value, as illustrated in this Fiddle: http://jsfiddle.net/76tnuwmp/4/
In browsers like Chrome and Firefox, the user can't visibly see a difference in the 2 methods while scrolling.
However, in Safari, users can visibly see the lag in updating for the box fixed with javascript.
Is there something fundamentally different about how Safari updates element positions? Does it perhaps perform compositing and layering at a slower rate than other browsers do? Or is it just slow at one or more of these steps?
Related
Recently I've been working on building a tree-view component library for Angular, as ngx-tree.
Problem
Soon after I figured out how to implement a virtual-scroll feature for this library for performance with large data set, and got it to run properly in Firefox, I am trapped by a weird scrolling behavior in Blink-included browsers(like Chromium, Chrome, Opera).
Demo link
Here is the demo plunkr -- https://embed.plnkr.co/xMpmK5EBC46tDKpYFpw8 see Update #1 below
Situation
In Firefox, Edge and IE 11, my library with virtual scroll feature works in expectation with smooth scrolling.
However, in Chrome and Opera, when I scroll to some positions inside the tree-view, the position of the scrollbar(which is the scrollTop property of scroll area) jump up and down, causes a flickering tree-view and break down the virtual-scroll feature.
browser detail version
Chrome - 59.0.3071.115
Firefox - 54.0
Edge - 40.15063.0.0
other browsers
In China, there are some shelled browsers derived from Chromium project(like 360se, QQ browser, Sogou browser, UC browser) with older version of V8 and blink. And they don't have that weird scroll behavior.
Some Assumption
Is it caused by some optimization on the scroll implementation(like smooth scrolling) by the Chromium team?
Hope to get some guides!!! (≧﹏ ≦)
Update #1
Demo link update with event log: https://embed.plnkr.co/GpQBZsguhZZOQWWbZnqI/
One must test the scrolling before opening devtool to see the logs
I have to explain a little more how the virtual-scrolling works and what's causing the flickering.
First, see the design of Virtual Scrolling.
A key point of virtual scrolling is, we create a fake scroll area the same size as that area without virtual scrolling instrumented. So in an optimal situation, the scrollbar position of the scroll area ought not to change until some expected events trigger its changes. For scroll event, we update the view for every animation frame.
Within a height-fixed scrolling area, we assume a truth that the scrollTop property will not change with a large percentage, if we simulate the those unrendered elements' height properly(there could be a bit of deviation for the calculation accuracy) within an animation frame.
However, based on my observed results, the blink-series browsers seems to perform a different strategy to update the scrollTop of the scrollable element. Its timing to update the scrollTop is different than none-blink-series browsers. That all I've figured out so far.
Sample gifs
Here I made some gifs to show the output of Chrome, Firefox, and Edge.
Chrome
Firefox
Edge
You're pulling the library from the virtual-scroll-demo-branch branch:
'ngx-tree': 'https://rawgit.com/e-cloud/ngx-tree/virtual-scroll-demo-branch/src/lib',
That branch is 105 commits behind master. It incorrectly sets margin-top on one of the inner elements. This is fixed in newer versions.
Edit: The developer actually referenced this Stack Overflow question in their commit message.
In the following fiddle, there is significant jitter while scrolling. The jitter is noticed only in Safari on Mac, while Chrome and Firefox scrolls the page smoothly.
https://jsfiddle.net/saptarshi17/akxuLL9x/
The jitter is a result of applying parallax effect on the first paragraph. The parallax is implemented by dynamically calculating the top css property triggered by scroll events. I am noticing this on Mac OSX El Capitan, not sure about Windows.
So far, I tried:
Added a mousewheel event-listener in addition to scroll as per this accepted answer on SO.
Tried wrapping the css("top", ...) function call within requestAnimationFrame()
Changed div from relative to absolute to avoid costly reflows.
Added preserve3d property to "hardware accelerate" position calculation as per some comments somewhere on SO.
Since 2 and 3 haven't helped, I assume the core issue has much to do with Safari's scroll implementation and less about frame-rate optimizations. There is a delay Safari's predictive rendering and the subsequent handler call which offsets the position. This results in 2 renders causing the jitter. While I understand Safari's intention behind the first render which works in most cases, this seems to be wrong in cases when the developer wants to override positioning. Maybe there is a way to tell Safari to disable the predictive rendering.
In my html document, there are some tiles with two pictures (one jpg as a background and one png with transparency as a foreground) and with a hover effect:
On the current tile which you are hovering, the image gets zoomed where your mouse is and the front image gets moved away from the cursor.
While moving the cursor only horizontally, all vertical pictures are animated too and the other way round.
Here is an example with all html, javascript and css:
http://jsfiddle.net/Lmcn0sxw/6/
The effect is working (with a few bugs, but that's not important).
The animations are easily added with javascript and transform3d where item is the current tile with the class .item.
Variables like topRatioFron are calculated from the current mouse position relative to the current tile.
item.find('.front').css('transform', 'translate3d(0,' + topRatioFront + 'px,0)');
item.find('.back').css('transform', 'translate3d(0,' + topRatioBack + 'px,0)');
There are some variations which you can see in the jsfiddle javascript code.
The main tile gets animated with a matrix3d effect:
self.find(itemClass).css(
'transform',
'matrix3d(1,0,0.00,' + leftRatio + ',0.00,1,0.00,' + topRatio + ',0,0,1,0,0,0,0,1)'
);
In the Google Chrome Browser on Linux Mint, it works perfectly. On Google Chrome on Windows, it works too. In Mozilla Firefox, it's not as perfect as in Google Chrome, but it's okay.
The actual problem
A friend opened this site on a Mac with Safari, and all animations were really laggy (it looked like it was shaking). Another friend opened this on a Mac in the Chrome browser, and it was shaking too, but this time not in Safari.
How can I test or find out what this is causing? It can't be the performance of the computer they used, because this site with the same effect is working perfectly in ALL browsers, regardless of the operating system.
What I tried
First, I used translate instead of translate3d (I read that the latter is faster), but It didn't help.
I later added a function called requestAnimationFrame which can help rendering animations. The result was the same.
The second problem
On Safari (I tried it with v8.0.7), the matrix3d transform works, but all other transforms don't work, not on the current tile and not on the others, but CanIUse tells me that transform3d is supported by Safari 8. When I inspect the item with the matrix3d transform, I can see it in the DOM tree being updated, but all of the elements from .back or .front, I can't see the transform3d added, and I have no idea how to fix this.
I believe that this is the cause of your lag - offset = self.offset().
On each frame you query the DOM, which forces it to render the layout, and cause the stuttering.
Instead read the offset once before the animations start.
You can also add to each element will-change: transform before the animation start.
I have a fairly simple button symbol that I'm adding dynamically to the stage. The button has a background layer and a text layer. Everything works fine in the standard browsers but on iPad the background layer unexpectedly covers the text (as if on a higher z-index though no value shows in the DOM and attempts to override the z-index via css do nothing). Further more this issue only seems to trigger when I go to a label in the button such as;
button.stop("Idle");
Has anyone else had random layering issue on iPad that may relate to this?
For anyone who may be searching; Apparently vector shapes do not appear smoothly and consistently in all webkit browsers (the primary culprit mentioned was Android native browser). As an artificial optimization you can apply a 3D transform (but without actually scaling or rotating) in order to improve the vector rendering. In Adobe Edge this is done by applying a 'translateZ" transform to all of the elements on the stage. However this also causes random issues with any item I'm trying to layer with "z-index".
The solution is to clear out this transform on the problematic elements (or edit the edge classes to removed them globally) like so;
buttonFrameSym.$("select_arrow").css("-webkit-transform", "none");
My users and I are running into a rendering glitch in Chrome only (on both Windows and Mac) where an overlaid div that I'm using for on-hover tooltip-style "popouts"(see first image below) does not get rendered properly in certain cases (see second image below). In all other browsers I've tested, it works as expected.
Here's how the hover popouts are supposed to look (and what happens in Firefox, Safari, IE):
Here's what happens in Chrome:
You can see it in action on this site if you look at May 24 using a browser window width of ~ 1200px (significnatly wider or narrower windows do not seem to work). The glitch only affects the popouts in the bottom right of the menu that are popping left, e.g. those on May 24. Hovers using the same exact mechanism higher up in the page work just fine. Glitched popouts are invisible (except for part of the carat), but if you click on the link to lock the popout in place and then hold left click while moving your mouse around as if to "select text" in the area where the popout should be, it will then render partially. Also if I open dev tools and try to select the popout, it will render just fine at that point.
I've been looking at this all day and trying different work arounds with opacity, z-index, etc. and getting nowhere. Does this glitch ring any bells for anyone? Is there a way to force Chrome to render the div, once its been positioned and unhidden? I'm fine with any work-around or hack.
I use a custom (and fairly complicated) jquery plugin for popouts. If it would be helpful to see the non-minified javascript for the plugin, I can post or provide a link to that, but general guidance that leads me to a work around will be sufficient to be accepted as an answer.
Edit: My Browser Build: 26.0.1410.65
(Per my comments)
This does indeed seem to be a bug in Chrome, though without a smaller test case to reproduce it, it could be very hard to track down. You may want to report it to the Chrome team with as much information as possible.
In support of my "it's a bug" assertion:
The hidden/clipped elements become visible when they are selected.
The elements underneath the hidden/clipped elements are not clickable.
This indicates that z-index and height is correct.
It only happens under very specific circumstances; the rest of the items with the same style work fine. The same item may work fine at a slightly bigger/smaller screen width.
Applying a 3D transform fixes it.
The problem goes away when I apply a CSS transform such as scale3d or translate3d. I imagine this is because certain CSS properties cause the browser to switch to GPU acceleration.
In this case, switching to the fast path for rendering seems to alter the drawing sequence enough to fix the problem.
Super hacky but this fixes it for me:
$('.drop-link.food').on('hover',function() {
$('.tool-tip').css('overflow', 'hidden').height();
$('.tool-tip').css('overflow', 'auto');
});
Obviously this isn't a "good" solution, and even remaining hacky you could probably optimize it to only force the redraw on the tooltip it needs to, but hopefully it helps...
Another clue:
$('.drop-link').on('hover',function() {
$(this).siblings('.tool-tip').css('display','block');
});
This won't fix it right away, but it seems like if this is there, once you've hovered on something, it will work the next time you hover on it.
Not sure if this helps with your situation, but over the last couple of days I've started to notice that certain site elements on Facebook and Weight Watchers no longer show up. Specifically it seems to be affecting items that (I believe) to be controlled by or dependent on Javascript. When I call up these sites in Firefox and Safari they work as expected.