March 8, 2017 10:17 am

Scrolling on the web: A primer

By / Program Manager, Microsoft Edge

Scrolling is one of the oldest interactions on the web. Long before we had pull-to-refresh or infinite-loading lists, the humble scrollbar solved the web’s original scaling problem: how can we interact with content that’s stretched beyond the available viewport?

Animations by Rachel Nabors

Today, scrolling is still the most fundamental interaction on the web, and perhaps the most misunderstood. For instance, do you know the difference between the following scenarios?

  • User scrolls with two fingers on a touch pad
  • User scrolls with one finger on a touch screen
  • User scrolls with a mouse wheel on a physical mouse
  • User clicks the sidebar and drags it up and down
  • User presses up, down, PageUp, PageDown, or spacebar keys on a keyboard

If you ask the average web user (or even the average web developer!) they might tell you that these interactions are all equivalent. The truth is far more interesting.

As it turns out, all five of these input methods have vastly different characteristics, especially when it comes to performance and cross-browser behavior. Some of them (such as touch screen scrolling) are likely to be smooth even on a page with heavy JavaScript usage, whereas others (such as keyboard scrolling) will make the same page feel laggy and unresponsive. Furthermore, some kinds of scrolling can be slowed down by DOM event handlers, whereas others won’t. What’s going on here?

To answer that question, and to understand how to unlock the smoothest-possible scrolling on your website, let’s take a step back and understand how browsers deal with multithreading and input.

The multithreaded web

Conceptually, the web is a single-threaded environment. JavaScript blocks the DOM, and the DOM blocks JavaScript, because both are fighting over the same thread – frequently referred to as the “main thread” or “UI thread.”

For instance, if you were to add this (horrible) JavaScript snippet to your page, you’d immediately see jankiness across the entire user experience:

setInterval(() => {
  var start =;
  while ( - start < 500) {/* wheeeee! */}
}, 1000);

While this JavaScript is spinning in a useless loop, buttons don’t work, form elements are unresponsive, and even animated GIFs grind to a halt – for all intents and purposes, the page is frozen. You can see this in action in a simple demo.

Furthermore, if you try to scroll with the keyboard up/down arrows, the page remains predictably stuck until the JavaScript stops running. All of this is strong evidence for our view of the web as a single-threaded environment.

There’s a curious anomaly, though: if you try to scroll using touch screen scrolling, the page happily moves up and down, even while JavaScript is blocking nearly everything else on the page. This also works for touch pad scrolling, mouse wheel scrolling, and click-and-drag scrolling (depending on your browser).

Somehow, certain scroll actions can manipulate the page state, even while everything else – buttons, input, GIFs – are totally frozen. How can we square this with our theory of the single-threaded web?

A tale of two threads

As it turns out, the whole “browsers are single-threaded” story is largely true, but there are important exceptions. Scrolling, in all its various flavors, is one of those exceptions.

Over the years, browser vendors have recognized that offloading work to background threads can yield enormous improvements to smoothness and responsiveness. Scrolling, being so important to the core user experience of every browser, was quickly identified as a ripe target for such optimizations. Nowadays, every major browser engine (Blink, EdgeHTML, Gecko, WebKit) supports off-main-thread scrolling to one degree or another (with Firefox being the most recent member of the club, as of Firefox 46).

With off-thread scrolling, even a noisy page will appear smooth as you scroll through it, because all scrolling is handled on a separate thread. It’s only when you try to interact with the page through some non-scrolling mechanism – tapping a button, typing into an input, clicking a link – that the façade fades away and the parlor trick is revealed for what it is. (Considering how well it works, though, it’s a great trick!)

There is a tradeoff with asynchronous scrolling, however, which is a common effect called checkerboarding, so named because of the way it originally appeared on Safari for iOS (as gray-and-white checkers). In most modern browsers, it manifests as a blank space that appears when you’ve scrolled faster than the browser can paint. This isn’t perfect, but it’s a worthy tradeoff compared to blocked, jerky, or unresponsive scrolling.

Unfortunately, it’s not always easy to move scrolling to a background thread. Browsers can only do this if the host operating system allows for concurrent input, and it can vary from device to device. In particular, keyboard input is not as optimized as input from mouse or touch devices, ultimately leading to laggier keyboard scrolling across browsers.

A little history is instructive here. When operating systems like Windows and macOS were first designed, one thread was all you got, and so there was little foresight to allow for concurrent input. It was only when multi-core machines started to appear that OSes began to retro-fit concurrency into their design.

So, in the same way that vestigial organs can reveal an animal’s evolutionary history, the single-threaded origin of operating systems starts to peek through the seams when you look at scrolling input across the web. It’s only if the host operating system allows for concurrent input – from a mouse, keyboard, or other device – that browsers can efficiently optimize scrolling to be unaffected by long-running JavaScript operations that hog the main thread.

On the Microsoft Edge team, though, we’ve been making strides to ensure that scrolling remains smooth and responsive, regardless of the scrolling method you prefer. As of EdgeHTML 14 (which shipped in the Windows 10 Anniversary Update), we support off-thread scrolling for the following input methods:

  • Single-finger, touchscreen
  • Two-finger, touchpad
  • Mouse wheel
  • Sidebar

If you compare Edge to other desktop browsers, you’ll find that it’s the only one to support asynchronous sidebar scrolling  – i.e. clicking-and-dragging the scroll handle, or clicking on the trackbar or arrows. (In fact, this is a feature we quietly introduced in the Anniversary Update!)

Testing on Windows 10 (14393, Surface Book) and macOS Sierra (10.12, MacBook Air), we can derive the following results:

Two-finger trackpad Touch Mouse wheel Sidebar Keyboard
Edge 14 (Windows) Y Y Y Y N
Chrome 56 (Windows) Y Y Y N N
Firefox 51 (Windows) N N N N N
Chrome 56 (MacOS) Y N/A Y N N
Firefox 51 (MacOS) Y N/A Y N N
Safari 10.1 (MacOS) Y N/A Y N N

As this table demonstrates*, scrolling performance can vary dramatically from browser to browser, and even from OS to OS. If you’re only testing on one browser with one scrolling method, then you might be getting a very narrow view of your site’s performance as it’s actually experienced by users!

Overall, though, it should be clear that scrolling has a special place on the web, and browsers have worked very hard to make sure that scrolling is snappy and responsive. However, there are subtle ways that a web developer can inadvertently disable a browser’s built-in optimizations. Let’s take a look at how web developers can influence browser scrolling, for good and bad.

How event listeners interfere with scrolling

Off-thread scrolling represents a great gain in efficiency – scrolling and JavaScript are completely decoupled, allowing them to work in parallel without blocking each other.

Anyone who’s been building web pages for a while, though, probably knows how to introduce a choke point between JavaScript and scrolling:

window.addEventListener(“wheel”, function (e) {
  e.preventDefault(); // oh no you don’t!

When we attach a “wheel” listener that calls event.preventDefault(), it will 100% block scrolling, for both wheel and touch pad scrolling. And obviously, if scrolling is blocked, then off-thread scrolling is also blocked.

What’s less obvious, though, is the impact in this case:

window.addEventListener(“wheel”, function (e) {
  // innocent listener, not calling preventDefault()

You might naïvely think that a function that doesn’t call preventDefault() can’t block scrolling at all, or that, at the very worst, it can only block for the duration of the function itself. The truth, however, is that even an empty listener will totally block scrolling until any JavaScript operation on the page has finished, which you can verify with this demo.

Even though the wheel listener has nothing to do with our big blocking JavaScript operation, they share the same JavaScript event loop, and so the background thread must wait for the longer JavaScript operation to finish before it can get a response from the listener function.

Why does it have to wait? Well, JavaScript is a dynamic language, and the browser can’t know for sure that preventDefault() will never get called. Even if it’s obvious to the developer that the function is just doing a simple console.log(), browser vendors have opted not to take any chances. In fact, even an empty function() {} will exhibit this behavior.

Note that this applies to more than just “wheel” events: on touchscreen devices, scrolling can also be blocked by “touchstart” or “touchmove” listeners. One should be very careful when adding listeners to a page, because of this impact on performance!

There are a few scroll-related JavaScript APIs, however, that don’t block scrolling. The “scroll” event, somewhat counterintuitively, can’t block scrolling because it fires after the scroll event, and thus it isn’t cancelable. Also, the new Pointer Events API, which was pioneered in IE and Microsoft Edge and has recently started to appear in Chrome and Firefox, has an explicit design goal to avoid unintentional scroll-blocking.

Even in cases where we absolutely need to listen to “wheel” or “touchstart” events, there are certain tricks that web developers can employ to ensure that scrolling remains on the fast path. Let’s take a look at a few of these tricks.

Global vs local event listeners

In the example above, we covered the case of global listeners (i.e. listeners attached to the “window” or “document”). But what about listeners on individually scrolling elements?

In other words, imagine a page that is scrollable, but there is also a subsection of the page that itself is independently scrollable. Will the browser block the entire page’s scrolling if you only attach a listener to the scrollable subsection?

.addEventListener(“wheel”, function (e) {
  // In theory, I can only block scrolling on the div itself!

If you test this out using a simple demo page, you’ll find that both Microsoft Edge and Safari will allow the document to scroll smoothly, even if there’s a scroll listener on the scrollable div, and the page has heavy JavaScript operations running.

Here is a chart of the browsers and their behaviors:

Two-finger touchpad Touch Mouse wheel Click-and-drag Keyboard
Edge 14 Desktop (Windows) Y Y Y Y N
Chrome 56 Desktop (Windows) N Y N N N
Firefox 51 Desktop (Windows) N N N N N
Chrome 56 Desktop (MacOS) N N/A N N N
Firefox 51 Desktop (MacOS) Y N/A Y N N
Safari 10.1 (MacOS) Y N/A Y N N


These results show* that there’s a potential optimization that web developers can employ to take advantage of these browser features. Instead of attaching wheel/touch listeners to the entire document, it’s preferable to add listeners to a targeted subsection of the document, so that scrolling can remain smooth for unaffected parts of the page. In other words, rather than delegating wheel/touchstart listeners to the highest-possible level, it’s best to keep them isolated to the element that needs the scroll event.

Sadly, not all JavaScript frameworks follow this practice – in particular, React tends to add global listeners to the entire document even if it should only apply to a subsection of the page. However, there is an open issue for this very problem, and the React folks have said they would be happy to accept a pull request. (Kudos to the React folks for being so receptive when we provided this feedback!)

Passive event listeners

Avoiding global listeners for wheel/touchstart is a good practice, but sometimes it just isn’t possible, depending on the effect you’re trying to achieve. And in a way, it may feel silly that a simple event listener should force the browser to halt the world, just on the offchance that it might call preventDefault().

Luckily there is a new feature that is just starting to appear in browsers, where web developers can explicitly mark a listener as “passive” and thus avoid waiting:

window.addEventListener(“wheel”, function (e) {
  // Not calling preventDefault()!
}, { passive: true } // I pinkie-swear I won't call preventDefault()

With this fix in place, the browser will treat scrolling exactly as if the “wheel” listener had never been added. This feature is already available in the latest versions of Chrome, Firefox, and Safari, and should appear soon in an upcoming release of Microsoft Edge. (Note: you will need to use feature detection to support browsers that don’t have passive event listeners.)

For some events (notably “touchstart,” and “touchmove”) Chrome has also opted for an intervention in version 56 to make them passive by default. Be aware of these subtle browser differences when you add event listeners!


As we’ve seen, scrolling on the web is a fantastically complicated process, and all the browsers are at various stages of improving their performance. In general, though, we can land on some solid pieces of advice for web developers.

First off, it’s best to avoid attaching wheel or touch listeners to the global document or window objects, and instead add them to smaller scrollable elements. Developers should also use passive event listeners whenever possible, with feature detection to avoid compatibility issues. Using Pointer Events (there is a polyfill) and “scroll” listeners are also surefire ways to prevent unintentional scroll-blocking.

Hopefully this post has provided some helpful guidance for web developers, as well as a peek into how browsers work under the covers. No doubt, as browsers evolve and the web continues to grow, scrolling mechanics will become even more complex and sophisticated.

On the Microsoft Edge team, we’re excited to keep innovating in this space and to provide smoother scrolling for more websites and more users. Let’s hear it for the humble scrollbar, the oldest and most nuanced interaction on the web!

Nolan Lawson, Program Manager, Microsoft Edge

* These results were collected using the latest version of each browser as of February 2017. Since then, Firefox 52 has updated their scrolling support, and now matches Edge 14’s behavior in all tests except those for sidebar scrolling. We look forward to all browsers improving their scrolling implementations and making the web faster and more responsive!

Updated March 16, 2017 10:57 am

Join the conversation

  1. Unless it goes in one of the 5 categories, there is one way of scrolling that is missing in this article: pressing middle mouse and moving the mouse.
    Some website are so awfully done they don’t even allow it.

  2. Sometimes it feels like I know more about how Edge scrolling works than Microsoft Does. I (and several others) have been reporting the bug where Edge doesn’t fire “wheel” events for 2-finger touchpad scrolling on Precision Touchpads (those included on Microsoft’s own Surface devices) for years now. It also affects the new on-screen virtual touchpad coming in the Windows 10 Creator’s Update.

    Please will someone break through whatever road blocks the Edge PMs have put up in getting these bugs fixed? Why do I care more about your browser working properly than you do?

    BUG 1:

    BUG 2:

    Contrary to your chart in this post, Edge is the only browser that doesn’t fire “wheel” events for 2-finger scrolling on a precision touchpad. Chrome Does. Firefox Does. Even Brave does. Only Edge doesn’t.

    This also breaks all UWP apps built in JavaScript that rely on “wheel” events… Such as a little app called Flipboard.

    C’mon Edge Team. What do I have to do to convince someone there to finally fix this???

  3. “whereas others (such as keyboard scrolling) will make the same page feel laggy and unresponsive.”

    This would only be true for pages that had poorly implemented scripting. The “Smooth” scrolling that many browsers/OSs use to smoothly scroll the (typically) 3 lines a mouse wheel scrolls are actually LESS responsive than instantly scrolling. If an application delays the expected change in the display (moving down 3 lines) just to animate, this is the opposite of responsive.

    I think this article improperly represents ‘responsiveness’. I intentionally turn off smooth scrolling in every place I can (Windows, IE, chrome, etc) just to avoid this often infuriating unresponsive smooth scrolling.

    That said, if keyboard scrolling uses the event thread and wheel scrolling uses a second thread, I would agree that would (at least more likely) be more responsive.

  4. Edge breaks 2 finger trackpad scrolling (wheel event doesn’t fire), which is hilarious because this issue has existed for a long time now although it being completely obvious. You’re the last vendor that should hold lectures about scrolling if you can’t get your own house in order.

  5. Actually the blocking on JavaScript wheel event handler is an important one to get around, as there is increase number of sites implement “infinite scrolling” that attempts to load and render new content only if user scrolls down.
    It could be great if we can just skip loading items in between and directly load the previous, current and next page on the point I scroll to.
    Maybe it would help if there is split event “mousewheel” and “aftermousewheel” so whoever want to load content (or do other heavy task with it) with page movement can attach to “aftermousewheel” event and free the page for scrolling.

  6. Hi Guys,

    And thank you Nolan, for a excellent article and perfectly timely for my interests, to boot!

    Can i ask, is Edge for WP10 under a separate PM?

    I show my ignorance, but is Edge actually a universal app / codebase across WP10 and windows for x64?

    I’m desperate to find any source e.g. table / chart making clear differences we should know about, from a height and up close and personal.

    This is for a position i need to evaluate here, whether I am obliged to express a view about the decision generally we will take developing UI for a trading front end. It’s not lightweight – this is sell side for publishers and screens full of high res adverts have to be reviewed by a human for undesirable or batting content. It’s primarily print, too, so high res means multi MB thumbnails.

    The core is openGl, so > webGL and some other decisions i mentioned here, are straightforward enough for now.

    Your article just came to my notice at the very best time possible!

    Because I’m not even aware what happens to JavaScript threading when multiple windows are open on screen simultaneously. If current browsers cut cycles to all but the tab with cursor focus. .

    Thankfully I’m not front end on this, im not remotely a web developer, I’m on infrastructure delivery with overlap strongly about UX/UI interaction with delivery e.g. Latency, but trade confirmation would be a straddle across UX and database and wan, because of prices moving, so adjusting bid / offer controls has to be end to end. (i love it “just” being the “latency guy”, i get to poke in everywhere and make friends from who just fixed bad cabling dropping a customer round trips, to CxOs, but originally wanted to be a UX guy, dreamed of working at CSERIAC WRIGHT PATTERSON AFB on the next fighter*… I still think web attention to.human computer factors omits** the human)


    CAN anyone see if I’m thinking so badly wrong here, that lack of coffee at 0200 cannot excuse me?

    If user tabs through the screen, but habitually then interacts with keyboard, scrolling the wheel is the remaining interaction… So tab to land on a select advert or position for advert> then this is selected (simplified to cut out our app) by space at like any image selector, or scrolling down to show more of image (in the app this brings overlay price details which are refreshed again if stale) …

    Basically should i be expecting that people best ensure that this element has its own listener?

    And same for each and every display element?

    Because I’m sure walking into a fun code review come Monday morning, and this week is a bit of a “last arguments Gentlemen please!” for registering any major concerns.

    I’m frankly worried now if even Edge can handle this. The number of elements per page can range from a handful to thousands. Sure cache and step thru results. But visual human recognition is a major factor, here. As is the installation case: Customers have 4K projectors to display this data. They expect this data visually.

    Now graphics, webGL, now compiling to ECMA primitives, SIMD use, …

    … The browser has handled some stuff of hell we thrown at it. Edge i mean. But having myself last looked at web “design” ca. 2010, when I was confused by friends i was used to hearing about Adobe product hassle, ask what is a API … we’ll it’s a new world and i don’t suss people new new frameworks – that’s fine with developing situations. (but legitimate to complain about is frameworks that are more patterns, and which make assumptions about interpreter optimisations or DOM etc efficiency.. It’s fundamentslly bad in my book to sell people “solutions” as something expected you learn long term, sold by people solving short term problems against unpredictably moving targets whose actual life purpose is to resolve the same issues. It’s *how* React etc are sold that’s wrong. And i accept that they get more sold more badly by secondary tertiary and infinite tiers of web des/dev. That only makes it more wrong in the first place. ***)

    (Edge is sole target, that may seem nuts, but we control the client endpoint machine at this stage, think BloombergTerminal. For very similar reasons. Delivery browser at all, was to open up SharePoint embedding and related opportunity. Report writing and even buying decisions happen on a very low level. Mortgage bond traders don’t inspect individual deals. Ours might. They might be a tiny sales team, or not a sales team at all, but their publication could be Ingenia which is sent to Royal Institute of Engineering membes. With only a couple adverts, from Arup, Rolls Royce… Bloopers aren’t acceptable. Nota Bene, the decision was to target Chrome once people could be put fully on the job. We are using no cross browser framework. We won’t accentuate anything on our own, but features in one browser that are unique, will get used if nice to use. We chose Edge first, because of the per tab VM isolation offered on 10SPE another licenses. Customers want our application away from theirs. Many have in house apps they won’t show us. Or even big advertisers! Without the Edge VM – not merely sand box – we either were not going there, integrating by API feed only, as does Bloomberg, or would be delivering VDI exclusively **** )


    So do I need to get the FE guys and girls to profile the effect of per cell listeners already?

    How much for context switch anyone guess?

    I mean is a listener ever gonna de delayed after I tab thru a hundred calls each with a listener in them?

    (and we’ve a tab in main viewport listener, switching to per cell listeners, plural, as keyboard and mouse are immediately likely inputs)

    I guess I’m asking anyway.

    Thank you truly for this article.

    Like i said, it’s perfectly timed for my work as now.

    ~~~| i left a few footnotes, sorry |~~~

    * Crew Systems Ergonomic Research Centre… A acronym i beg i messed up just there again… Key devs for the F14 cockpit instruments. Geek childhood dream job. Sadly i think lots of papers they were once happy to provide (this was 91 i think, so a gopher was asking bit much..) got reclassified post 911. A takeaway i recall, sticking in my mind since then, was rejecting other than 4:3 screen ratios. Combinations of human eye scan rate eliminated other shapes. Critically, our visual acuity centres hard to five to ten degrees. That is why you scan a page like a beam in CRT. So smaller squarer displays are good in a heads up environment. Also sixty degrees field at a metre is beyond which you’ve peripheral only and falling motion sensitivity. I doubt it’s a coincidence that the Surface Studio screen fills these limits. Regardless the immutable factor is human brains and eyes. Oh, one from my own work: Avoid red back lighting in low light suites, by subtractively increasing blue, this is off the charts bad with uncalibrated screen shining blue at you to start. Anyhow we struggle to focus on blue. Your concentration will fall off a cliff. Noted because of SO agitated reading in bed, Hue lights on dim reds… Tests came back consistently. Got my job introd as “fixing it so you see your computer easily” at parties.. But hey, that’s half the battle.

    **. When i say modern UX/UI “omits the human” i mean most every ux hi guy out there is a designer applying visual anecdotiana as rules. Now i don’t mean that to denigrate as it might sound: The nature of art involves anecdotiana, there are very few rules. Artists don’t need color science. But it might help. And fantastic UIs have been developed by artists and programmers who never were exposed to a human test lab. In fact, this area interested me, because i knew id not got chops to be a artist, but i got to work with and satisfy related urges, pursuing design utility. (which became the latency guy conveniently thanks to so many desktop modes, thanks MSDN types for all those! And DCOM/CORBA you beautiful things, you made my job exist, so long as i never called it hide and seek for mouse events and serialisation units) .

    I am going to be brash and just say we’ll not look back fondly on the present mess. (you can say that for dcom/corba but Don Box is king again or at least most princely, over Azure way, so my nineties are finally paying for all those book shelves i had to build!) And positive and note there’s scant few university places capable of teaching the hard science, oh forget the dozen disciplined you need to get serious… So by making a generation of FEDs (apparently front end developers… Is this reall used terminology?) tear out their hair, ar least we’ll get a crop of some much greater number who are skilled in the art, even if not formally trained. This generation will get into studying when older and mote stable, fill in gaps, and by the time I’m just needing apps to work for seniors, ill benefit directly!


    I’ve a great friend, who first programmed in the 80s as a kid, like I. And so many, BASIC meant we learned from the program listings and the interpreter.

    My friend chose another career,

    But came back to programming, because of View Source!

    So I and i know my friend, are in love with away such opportunity was MADE possible.

    But frameworks don’t make the first day easier.

    Just i reckon they could.

    Try Spring Boot in Java, for beginners”magic”.

    Because you can break that magic down ro the fancy footwork, and reach fundamental appreciation of Java head to toe, and regardless of learner ability, they scope a important appreciation early on.

    But if you walk the tree of. React, can you derive the same lesson, that will be as enduring and effective across abilities?

    You could take F# and show it as very fancy C++, and whilst you’d be pushing edge cases at the limits of standards by some appreciations, you’d be teaching fundamental limits of C++ including philosophic ones, and showing F# to be far friendlier once some of the magic is appreciated.

    I could maybe have just said “don’t teach fast moving subjects”.

    But i see it as if someone thinks by learning React, they’re going to understand how JavaScript arose from Smalltalk and the first interpreter was built. Scheme. Sorry, my bad, sure it was Scheme at root of JavaScript ‘s creators mind, SICP the inspiration.

    Hmm, clickbaity summary:” React tutorials claim SICP substitution equality, demand course credits…”

    And isn’t it all over with webASM?

    Can i not port WPF prinitives etc?

    Frankly, will web sites be arguing how best to cache a DLL, that a page links to?

    >>>>> ======+++++=====<<<<50% failure)

    … And all code is really ours….

    … So its ” simple ” to support…

    … We can justify stronger support contracts *because it assits our development directly*



    i think most would have to say they got hired since it began, cannot answer…

    —- with a little help, we hope to change all that for people. But it’s hard to turn into product. So we came up with a pinot in side of mouth plan
    .. At which point ill bid you goodbye, say hello to my VS15 remote desk, and thank you one last time.

    Ouch, 10 pages in word on my phone…

    If you read all this thank you and i hope something anything i hard won transmitted

  7. Ok, so the next challenge for JS devs is to enable programmatic “scrolling”, it’s insane that something so innate to the web is virtually impossible.