May 20th, 2015
The CSSOM View specification has a handful of widely-implemented properties on the
Element interface related to scrolling:
scrollHeight. These give the current scroll position of the element, and the size of the scrolling area.
scrollLeft can also be set to cause a scroll. When these are used on the root element, they instead reflect the scroll position and scroll area of the viewport. The
clientHeight properties similarly reflect the viewport dimensions instead of the element’s dimensions for the root element.
However, WebKit (Safari) and Blink (Chrome and Opera) do not behave this way. They make the
body element reflect the viewport instead, and the root element returns
0 and does nothing on setting. IE11 and Firefox follow the specification. This is an interoperability problem that Web developers have to work around in some way, possibly by UA sniffing.
We want to fix this bug, but we need your help to fix sites that rely on the bug based on UA sniffing.
What’s bad about the WebKit/Blink behavior?
In general, we want to avoid special-casing the
body element and just let the root element be “the viewport element”. Also, the
body element can be independently scrollable from the viewport, by setting the
overflow property on both
body to something other than
visible. In WebKit and Blink, the scrolling APIs on
body in this case still reflect the viewport, rather than the
body element itself.
Both WebKit and Blink have previously tried to fix this bug, but had to revert because there were Web pages that expected the buggy behavior when the browser identified as “WebKit”.
Project Spartan / Edge
Microsoft’s latest browser engine, codenamed Project Spartan and used in the upcoming Edge browser, also identifies as “WebKit”, like WebKit and Blink, and so they have to copy WebKit/Blink’s bugs in order to be compatible with the Web.
Quirks mode is the mode that browsers use for documents without a doctype or an “old” doctype, to be more compatible with the behavior of old browsers. In quirks mode, all browsers use the
body element to reflect the viewport for the scrolling APIs. This is also specified in the CSSOM View specification. (However, contrary to the behavior of current WebKit and Blink, if the
body is independently scrollable, the scrolling APIs will reflect the element itself instead of the viewport.)
There are several strategies for dealing with this interoperability problem, but they are all problematic in one way or another.
Using APIs on
pageYOffset give the viewport’s scroll position and
window.scrollTo(x, y) scrolls the viewport. This is what e.g. jQuery does.
pageYOffset are not supported in IE8 and below.
Using both elements
For example, for getting:
var y = document.documentElement.scrollTop || document.body.scrollTop;
var y = document.documentElement.scrollTop + document.body.scrollTop;
document.documentElement.scrollTop = y; document.body.scrollTop = y;
Problem: it is annoying to have to use both elements.
document.body and checking if it caused scrolling, then reverting the scroll position
This is what e.g. @mathiasbynens’ jquery-smooth-scrolling script does.
Problem: This is expensive and can cause a visual flash.
scrollHeight to determine the scrolling element
This is what e.g. @dperini’s
getScrollingElement script does.
Problem: This doesn’t work if the document is not scrollable.
Determining the scrolling element in an
This is what @mathiasbynens’
scrollingElement polyfill does. It works regardless of the document itself since it checks in an
Problem: It is complex/expensive.
This is what e.g. Closure currently does.
Problem: This prevents browsers from fixing the bug. We need to fix this!
How do I know if my site is affected?
- Is your page in quirks mode? Check
document.compatModein the console in the browser’s web developer tools. If it says
BackCompat, you’re not affected (but you should not use quirks mode!).
- Run Chrome or Opera with this setting enabled:
chrome://flags/#scroll-top-left-interop. This makes Chrome/Opera use
htmlas the scrolling element (in standards mode).
- See if your site still works. If something is now broken, possibly your site is using UA sniffing to determine
bodyfor scrolling APIs.
Towards a fix:
In order to have a reliable alternative to UA sniffing, we are introducing a new API to get the right element for scrolling the viewport.
- Use the
document.scrollingElementpolyfill and switch from
document.scrollingElementwhen using e.g.
scrollTopto scroll the viewport.
- Feature-check for
scrollingElementsupport and fall back to your current workaround (falling back to a UA check is OK).
- Use Chrome Canary, or Opera developer, based on Chromium 44 or later, or Microsoft Edge. These browsers have native
- Check that your site still works.
If you notice that a JS library uses UA sniffing to get “the scrolling element”, or that a site is broken with
chrome://flags/#scroll-top-left-interop enabled in Chrome or Opera, please file an issue in Chromium’s bug tracker. Include “ScrollTopLeftInterop” in the summary. Thank you!
Browsers disagree on whether
body reflects the viewport for
scrollTop et al. Some sites are using UA sniffing to decide which to use. Browsers want to align and use
html (in standards mode), so sites that UA sniff need to be changed. Use the
document.scrollingElement API if available, and fall back to a polyfill or UA sniffing. If you notice any problems, let us know!