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.
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. scroll-behavior: smooth; In documentation sites, the # anchor is often used to jump to a specific position, creating a tab‑switching effect.
To enable smooth scrolling, apply the following CSS to the scroll container:
.scroll-ctn {
display: block;
width: 100%;
height: 300px;
overflow-y: scroll;
scroll-behavior: smooth;
border: 1px solid grey;
}The scroll-behavior: smooth property makes the default scroll animation smooth.
Compatibility
IE and iOS have poor support; a polyfill may be required.
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:
// First form
const x = 0, y = 200;
element.scrollTo(x, y);
// Second form
const options = { top: 200, left: 0, behavior: 'smooth' };
element.scrollTo(options);The behavior option can be auto (instant) or smooth (animated).
Other APIs include setting scrollTop and scrollLeft directly, which have excellent compatibility:
// Set vertical scroll distance
container.scrollTop = 200;
// Set horizontal scroll distance
container.scrollLeft = 200;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:
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>
);
}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 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:
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>
);
}The utility listScroll encapsulates smooth scrolling with a fallback timeout to guarantee the callback execution:
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);
};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.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
