Translator: sea_ljf

The original link

Some scrolling effects are so fascinating that you don’t know how to do them, and this article will help you uncover them. We’ll introduce you to the latest JavaScript and CSS features based on the latest technologies and specifications that (when put into practice) will make your pages scroll smoother, prettier, and perform better.

Most web pages can’t display all of their content on one screen, so scrolling is essential for users. It is a challenge for front-end engineers and UX designers to provide a good scrolling experience across browsers while keeping up with the design. Although Web standards are evolving faster than ever, code implementation often lags behind. Here are some common examples of scrolling to see if your solution has been replaced by a more elegant one.

Vanishing scroll bar

Over the past three decades, the look of the scroll bar has changed to match design trends, with designers experimenting with colors, shadows, the shape of the up and down arrows, and rounded corners of the border. Here’s how things have changed on Windows:

(Scroll bar on Windows)

In 2011, Apple designers took inspiration from ios and set the direction for defining a “beautiful” scrollbar. All scroll bars disappear from the Mac and no longer take up any page space, reappearing only when the user triggers a scroll (some users make it unhidden).

(Scroll bar on Mac)

The scroll bar’s quiet demise didn’t bother Apple fans, who were used to scrolling on iphones and ipads and quickly got used to the design. Most developers and designers agree that this is “good news” because calculating the width of the scrollbar is a chore.

However, we live in a world where there are many operating systems and browsers with different implementations (of scrolling). If you’re a Web developer like us, you can’t just ignore the scrollbar problem.

Here are some tips to make scrolling a better experience for your users.

Hidden but scrollable

Let’s start with a classic example of modal boxes. When it is opened, the main page should stop scrolling. The CSS provides the following shortcuts:

body {
  overflow: hidden;
}

Copy the code

But the code above has a few undesirable side effects:

(Note the red haircut)

In this example, for demonstration purposes, we set up the forced display scroll bar on the Mac system so that the user experience is similar to that of a Windows user.

How can we solve this problem? If we know the width of the scroll bar, we can set a margin to the right of the main page every time the modal box appears.

Because the width of the scroll bar varies from operating system to operating system and browser to browser, it’s not easy to get its width. On the Mac, the scroll bar is the same 15px in any browser, but on Windows, developers can go crazy:

(Width of “a hundred flowers bloom”)

Note that the results are based only on the latest version of the browser on Windows. Previous versions may have been different, and no one knows what will happen in the future.

Instead of guessing, you can calculate its width using JavaScript:

const outer = document.createElement('div');
const inner = document.createElement('div');
outer.style.overflow = 'scroll';
document.body.appendChild(outer);
outer.appendChild(inner);
const scrollbarWidth = outer.offsetWidth - inner.offsetWidth;
document.body.removeChild(outer);

Copy the code

Although there are only seven lines of code (which measure the width of the scroll bar), there are several lines of code that manipulate the DOM. Avoid DOM manipulation if not necessary (for performance reasons).

Another way to solve this problem is to keep the scrollbar when the modal box appears. Here is a pure CSS implementation based on this idea:

html {
  overflow-y: scroll;
}

Copy the code

Although the “modal frame jitter” problem was solved, the overall look was affected by an unusable scroll bar, which was definitely a design flaw.

In our opinion, a better solution would be to hide the scroll bar entirely. This can be done purely with CSS. This method is not exactly the same as macOS, and the scrollbar is still invisible while scrolling. Scrollbars are always invisible, whereas pages can be scrolled. For Chrome, Safari, and Opera, you can use the following CSS:

.container::-webkit-scrollbar {
  display: none;
}

Copy the code

The following code is available for IE or Edge:

.container {
  -ms-overflow-style: none;
}

Copy the code

As for Firefox, unfortunately, there is no way to hide the scroll bar.

As you can see, there are no silver bullets. Any solution has its pros and cons and should be chosen according to the needs of your project.

Appearance of controversy

Admittedly, scrollbars don’t look good on some operating systems. Some designers like to have complete control over the style of their application, down to every detail. There are hundreds of libraries on GitHub that use JavaScript to replace the default implementation of the system scrollbar with custom effects.

But what if you want to customize a scrollbar based on your existing browser? There is (sadly) no universal API, and each browser has its own unique code implementation.

Although Internet Explorer after version 5.5 allows you to change the style of the scrollbar, it only allows you to change the color of the scrollbar. Here’s how to redraw the drag part with the arrow:

body {
  scrollbar-face-color: blue;
}

Copy the code

But just changing colors doesn’t do much to improve the user experience. WebKit’s developers came up with a solution for the scrollbar style in 2009. Here is the code to emulate the macOS scrollbar style using the -webkit prefix in browsers that support the style:

::-webkit-scrollbar {
  width: 8px;
}
::-webkit-scrollbar-thumb {
  background-color: #c1c1c1;
  border-radius: 4px;
}

Copy the code

Chrome, Safari, Opera and even UC browser or Samsung’s own desktop browser support CSS. Edge also has plans to implement them. But three years on, the plan is still a medium priority.

When we talk about customizing scrollbars, the Mozilla Foundation basically ignores the designers’ needs. A request to change the scrollbar style was made 17 years ago. Just a few months ago, Jeff Griffiths (director of Firefox) finally answered this question:

“Unless someone on the team is interested, I don’t care.”

To be fair, from the W3C’s point of view, although WebKit’s implementation is widely supported, it is still not a standard. The existing draft for changing the style of the scrollbar is based on IE: you can only change its color.

The controversy continues with the submission of a request to support scrollbar style modification issues as WebKit does. If you want to influence the CSS working group, it’s time to get involved. This may not be the highest priority, but when standardized (as WebKit does with changing scrollbar styles), it will make life a lot easier for many front-end engineers and designers.

Smooth operation experience

The most common task for scrolling is navigation (jump) of the login page. Typically, this is done through anchor point links. You only need to know the id of the element:

<a href=""#section"">Section</a>
Copy the code

Clicking on the link will jump to the block, and UX designers generally insist that the process should move smoothly. GitHub has plenty of built wheels (to help you solve this problem), but they all use JavaScript to some extent. The same effect can be achieved with a single line of code. More recently, element.scrollintoView () in the DOM API allows smooth scrolling by passing in configuration objects:

elem.scrollIntoView({
  behavior: 'smooth'
});

Copy the code

However, this property is less compatible and is still scripted. If possible, use as few extra scripts as possible.

Fortunately, there is a brand new CSS property (still in the working draft) that can change the behavior of an entire page scroll in a single line of code.

html {
  scroll-behavior: smooth;
}

Copy the code

The results are as follows:

(Jumping from one block to another)

(Rolling smoothly)

You can experiment with this property on Codepen. At the time of writing, Scroll behavior is only supported on Chrome, Firefox, and Opera, but we expect it to be widely supported because CSS is a much more elegant way to solve page scrolling problems. And more in line with the “progressive enhancement” model.

Viscous CSS

Another common requirement is to dynamically freeze elements based on the scrolling direction, known as the “sticky” (position: sticky in CSS) effect.

(A sticky element)

In the old days, implementing a “sticky” element required writing complex scroll handlers to calculate the size of the element. The function has a harder time handling the tiny delay between “sticking” and “not sticking,” causing jitter to appear. (” sticky “Element) by JavaScript to have performance problems, especially in (to) call [Element. GetBoundingClientRect ()] (developer.mozilla.org/en-US/docs/…

Not long ago, CSS implemented the Position: sticky property. We can achieve the desired effect by specifying the offset (in one direction).

.element {
  position: sticky;
  top: 50px;
}

Copy the code

The rest is up to the browser. You can try it out on Codepen. At the time of writing, Position: Sticky works well on all types of browsers (including mobile), so if you’re still using JavaScript to solve this problem, it’s time to switch to a CSS-only implementation.

Fully use function throttling

From the browser’s point of view, scrolling is an event, so JavaScript uses a standardized event listener called addEventListener to handle it:

window.addEventListener('scroll', () => {
  const scrollTop = window.scrollY;
  /* doSomething with scrollTop */
});

Copy the code

Users tend to scroll frequently, but if the scroll event is triggered too often, it can cause performance problems, which can be optimized by using the function throttling technique.

window.addEventListener('scroll', throttle(() => {
  const scrollTop = window.scrollY;
  /* doSomething with scrollTop */
}));

Copy the code

You need to define a throttling function that wraps the original event listener, reducing the number of times the wrapped function is executed, allowing it to execute only once at a fixed interval:

function throttle(action, wait = 1000) { let time = Date.now(); return function() { if ((time + wait - Date.now()) < 0) { action(); time = Date.now(); }}}Copy the code

In order to make the (throttle) after rolling smoother, you can through the use of the window. The requestAnimationFrame throttling () to realize functions:

function throttle(action) { let isRunning = false; return function() { if (isRunning) return; isRunning = true; window.requestAnimationFrame(() => { action(); isRunning = false; }); }}Copy the code

Of course, you can do this with existing open source wheels, like Lodash. You can visit Codepen to see the difference between the above solution and _.Throttle in Lodash.

It doesn’t matter which library you use, just remember to optimize your scroll handlers when needed.

Display in a window

When you need to implement lazy loading or infinite scrolling of images, you need to determine whether the element is present in the window. It can deal with in the event listeners, the most common solution is to use lement. GetBoundingClientRect () :

window.addEventListener('scroll', () => {
  const rect = elem.getBoundingClientRect();
  const inViewport = rect.bottom > 0 && rect.right > 0 &&
                     rect.left < window.innerWidth &&
                     rect.top < window.innerHeight;
});

Copy the code

The problem with this code is that it triggers backflow every time getBoundingClientRect is called, which severely affects performance. Calling getBoundingClientRect in event handlers is particularly bad, and even function throttling may not help performance much. (Backflow is when the browser redraws an element locally or as a whole, requiring recalculation of the element’s position and shape in the document.)

After 2016, problems can be addressed using the Intersection Observer API. It allows you to track the crossover state of a target element with its ancestor element or window. In addition, you can optionally trigger the callback function even if only part of the element appears in the window, even if it is only a pixel:

const observer = new IntersectionObserver(callback, options);

observer.observe(element);

Copy the code

(Click here to see the DOM properties and methods that trigger backflow.)

This API is widely supported, but there are still some browsers that need polyfill. Still, it is the best solution for now.

Rolling boundary problem

If your popup or drop-down list is scrollable, it’s important to understand the issue of chain scrolling: when the user scrolls to the end of the popup or drop-down list, the entire page starts scrolling.

(Performance of chain rolling)

When the scroll element reaches the bottom, you can fix this problem by (changing) the overflow property of the page or by undoing the default behavior in the scroll element’s scroll event handler.

If you choose to use JavaScript, remember that the scroll event to handle is not the scroll event, but the wheel event that is triggered every time the user uses a mouse scroll wheel or trackpad:

function handleOverscroll(event) {
  const delta = -event.deltaY;
  if (delta < 0 && elem.offsetHeight - delta > elem.scrollHeight - elem.scrollTop) {
    elem.scrollTop = elem.scrollHeight;
    event.preventDefault();
    return false;
  }
  if (delta > elem.scrollTop) {
    elem.scrollTop = 0;
    event.preventDefault();
    return false;
  }
  return true;
}

Copy the code

Unfortunately, this solution is not very reliable. It can also have a negative impact on performance.

Excessive scrolling is especially bad on mobile. Loren Brichter created a new “pull to refresh” gesture on the iOS Tweetie app that caused a stir in the UX community: various apps, including Twitter and Facebook, adopted it.

When this feature appeared in Chrome on Android, the problem was that it would refresh the entire page instead of loading more content, making it a problem for developers to implement “pull-down refresh” in their apps.

The CSS uses the overscroll behavior property to solve the problem. It solves the problem of pull-down flusher and chain scrolling by controlling the behavior of elements when they scroll to the end, and also includes platform-specific values: Glow for Android and Rubber Band for Apple.

Now, the problem in the GIF above can be solved in Chrome, Opera, or Firefox with the following line of code:

.element {
  overscroll-behavior: contain;
}

Copy the code

To be fair, IE implements the (unique) -ms-scroll-chaining attribute with Edge to control chained scrolling, but it can’t handle all cases. Fortunately, according to this information, Microsoft’s browser is already ready to implement the overscroll behavior property.

After the touch screen

Scrolling on touch devices is a big topic that deserves another article. However, since many developers neglect this aspect, it needs to be mentioned.

(The scrolling gesture is so ubiquitous that it’s such a crazy idea to solve the problem of scrolling addiction.)

How often do people around you move their fingers up and down on a smartphone screen? Often, right? You’re probably doing it as you read this.

When you move your finger across the screen, you expect the content to move smoothly and smoothly.

Apple pioneered and patented inertial scrolling. It’s rapidly becoming the standard for user interaction and we’ve taken it for granted.

But as you may have noticed, while mobile systems do implement inertial scrolling on a page for you, it’s frustrating that when an element in a page scrolls, it doesn’t, even though the user also expects it.

Here’s a CSS solution that looks more like a hack:

.element {
  -webkit-overflow-scrolling: touch;
}

Copy the code

Why is this a hack? First, it only works on browsers that support the (WebKit) prefix. Second, it only works on touchscreen devices. Finally, if the browser doesn’t support it, are you just going to ignore it? But anyway, it’s a solution, and you can try it.

On touch devices, another consideration is how developers handle possible performance issues when touchStart and TouchMove events are triggered, which can have a significant impact on the user’s scrolling experience. The whole problem is described in detail here. Simply put, modern browsers know how to smooth the scrolling, but it might take as much as 500 milliseconds to wait for the Event handler to finish, just to verify that it has event.preventDefault () to cancel the default.

Even an empty event listener that never cancels any behavior can have a negative impact on performance as browsers still expect preventDefault calls.

To tell browsers exactly not to worry about undoing the default behavior, there is a less obvious feature in the WHATWG DOM standard. Passive event listeners are well-supported by the browser. The event listener takes an optional object as an argument, telling the browser that the event handler will never cancel the default behavior when an event is raised. Calling preventDefault in the event handler will no longer have the effect.

element.addEventListener('touchstart', e => {
  /* doSomething */
}, { passive: true });


Copy the code

There is also a polyfill for browsers that do not support this parameter. The video clearly shows the impact of this improvement.

Why change an old technology that worked so well?

In the modern Internet, it is no longer reasonable to rely too much on JavaScript to achieve the same interaction effect across browsers, “cross-browser compatibility” has become a thing of the past, and more CSS properties and DOM API methods are gradually being supported by major browsers.

In our opinion, progressive enhancement is the best thing to do when you have a particularly cool scrolling effect in your project.

You should provide all the basic user experience you can and gradually provide a better experience on more advanced browsers.

Use polyfills when necessary, they don’t create (unnecessary) dependencies, and once (a polyfill supports a property) is widely supported, you can easily remove it.

Six months ago, before this article was written, the properties we described were only supported by a small number of browsers. At the time of publication, these attributes are widely supported.

Perhaps by now, as you scroll through this article, browsers will already support this property, making it easier to program and making your applications smaller to pack.


Thanks for reading so far! Checking your browser’s update log and being active in the discussion can help drive Web standards in the right direction. I wish you all smooth sailing and smooth sliding (rolling) into the future!