Frontend Development 16 min read

Mastering Smooth Scrolling: CSS, JavaScript Methods and Edge‑Case Solutions

This article explores how to implement smooth scrolling using CSS and JavaScript, compares native scroll APIs, demonstrates practical React examples, explains how to differentiate user‑initiated and script‑driven scrolling, and provides compatibility tips and workarounds for common pitfalls.

Tencent IMWeb Frontend Team
Tencent IMWeb Frontend Team
Tencent IMWeb Frontend Team
Mastering Smooth Scrolling: CSS, JavaScript Methods and Edge‑Case Solutions

Introduction

Implementing list scrolling interactions can become complex; this article discusses practical uses of scroll, including CSS smooth scrolling, JavaScript scroll methods, and distinguishing user‑initiated from script‑driven scrolling.

CSS Smooth Scrolling

One‑line style improvement

Adding

scroll-behavior: smooth;

to a scrollable container improves the user experience in frequent scrolling scenarios.

<code>scroll-behavior: smooth;</code>

In documentation sites, the

#

anchor is often used to jump to a specific position, creating a tab‑switching effect.

CSS smooth scroll example
CSS smooth scroll example

To enable smooth scrolling, apply the following CSS to the scroll container:

<code>.scroll-ctn {
  display: block;
  width: 100%;
  height: 300px;
  overflow-y: scroll;
  scroll-behavior: smooth;
  border: 1px solid grey;
}</code>

The

scroll-behavior: smooth

property makes the default scroll animation smooth.

Compatibility

IE and iOS have poor support; a polyfill may be required.

Compatibility chart
Compatibility chart

Important notes

When

scroll-behavior: smooth

is set on a container, it overrides the JavaScript

behavior: auto

option, so immediate scrolling will not work.

The property also affects the browser’s Ctrl+F search behavior, making it smooth.

JavaScript Scroll Methods

Basic methods

scrollTo : scroll to a target position.

scrollBy : scroll relative to the current position.

scrollIntoView : bring an element into the viewport.

scrollIntoViewIfNeeded : bring an element into view only if it is outside.

Two calling forms of

scrollTo

:

<code>// First form
const x = 0, y = 200;
element.scrollTo(x, y);
// Second form
const options = { top: 200, left: 0, behavior: 'smooth' };
element.scrollTo(options);</code>

The

behavior

option can be

auto

(instant) or

smooth

(animated).

Other APIs include setting

scrollTop

and

scrollLeft

directly, which have excellent compatibility:

<code>// Set vertical scroll distance
container.scrollTop = 200;
// Set horizontal scroll distance
container.scrollLeft = 200;</code>

Applications

Typical scenarios:

Component initialization – scroll to a target.

Clicking a bottom‑page element – trigger page‑turn scrolling.

Example: after a list component mounts, automatically scroll to the third item using

scroll-behavior: smooth

and a React

useEffect

hook:

<code>import React, { useEffect, useRef } from "react";
import "./styles.css";

export default function App() {
  const listRef = useRef({ cnt: undefined, items: [] });
  const listItems = ["A", "B", "C", "D"];
  useEffect(() => {
    // Scroll to third item
    listRef.current.items[2].scrollIntoView();
  }, []);
  return (
    <div className="App">
      <ul className="list-ctn" ref={ref => (listRef.current.cnt = ref)}>
        {listItems.map((item, index) => (
          <li className="list-item" ref={ref => (listRef.current.items[index] = ref)} key={item}>
            {item}
          </li>
        ))}
      </ul>
    </div>
  );
}
</code>

The four possible ways to scroll a container are:

Assign

scrollTop

directly.

Call

scrollTo

with coordinates.

Call

scrollTo

with an options object.

Use

scrollIntoView

/

scrollIntoViewIfNeeded

on an element.

Distinguishing User Scroll from Script Scroll

Background

In a subtitle‑synchronization scenario, automatic paging should stop when the user manually scrolls, and a “return to current position” button should resume automatic paging.

User vs script scroll demo
User vs script scroll demo

User scroll

Typical user scroll actions include mouse wheel, arrow keys, page‑up/down, etc.

Script scroll

Any scroll triggered by code is considered script scroll. A flag can be set on the

onScroll

handler to differentiate the two.

Implementation

React example that tracks scroll state, distinguishes user and script scrolling, and provides a button to trigger script scrolling:

<code>import throttle from "lodash.throttle";
import React, { useRef, useState } from "react";
import { listScroll } from "./utils";
import "./styles.css";

export default function App() {
  const [wording, setWording] = useState("Waiting");
  const cacheRef = useRef({ isScriptScroll: false, cnt: null, scrollTop: 0 });

  const onScroll = throttle(() => {
    if (cacheRef.current.isScriptScroll) {
      setWording("Script scrolling");
    } else {
      cacheRef.current.scrollTop = cacheRef.current.cnt.scrollTop;
      setWording("User scrolling");
    }
  }, 200);

  const scriptScroll = () => {
    cacheRef.current.scrollTop += 600;
    cacheRef.current.isScriptScroll = true;
    listScroll(cacheRef.current.cnt, cacheRef.current.scrollTop, () => {
      setWording("Script scroll finished");
      cacheRef.current.isScriptScroll = false;
    });
  };

  return (
    <div className="App">
      <button className="btn" onClick={() => scriptScroll()}>Trigger script scroll</button>
      <p className="wording">Current state: {wording}</p>
      <ul className="list-ctn" onScroll={onScroll} ref={ref => (cacheRef.current.cnt = ref)}>
        {Array.from({ length: 1000 }, (_, i) => i + 1).map(item => (
          <li className="list-item" key={item}>{item}</li>
        ))}
      </ul>
    </div>
  );
}
</code>

The utility

listScroll

encapsulates smooth scrolling with a fallback timeout to guarantee the callback execution:

<code>export const listScroll = (element, targetPos, callback) => {
  const { scrollHeight: listHeight } = element;
  if (targetPos < 0 || targetPos > listHeight) return callback?.();
  element.scrollTo({ top: targetPos, left: 0, behavior: "smooth" });
  if (!callback) return;
  if (Math.abs(element.scrollTop - targetPos) <= 10) return callback();
  setTimeout(() => callback(), 1000);
};
</code>

Conclusion

The article introduced native scroll APIs such as

scrollIntoView

, highlighted common pitfalls, and provided a reference implementation for distinguishing user scroll from script scroll. Smooth scrolling may appear trivial, but it often hides considerable complexity that developers should consider during design and implementation.

JavaScriptReactCSSscrollscrollIntoViewscrollTopsmooth scrolling
Tencent IMWeb Frontend Team
Written by

Tencent IMWeb Frontend Team

IMWeb Frontend Community gathering frontend development enthusiasts. Follow us for refined live courses by top experts, cutting‑edge technical posts, and to sharpen your frontend skills.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.