The specification says
The scrolling box is scrolled in a smooth fashion
using a user-agent-defined timing function over a user-agent-defined period of time.
User agents should follow platform conventions, if any.
Right away that makes it a complicated question to which there is likely no answer, or at least no answer which will reliably stay put.
Browsers might
- Aim for a target speed rather than duration, and so scroll for a longer period of time if there are several pages to scroll
- Adjust the scrolling behaviour if many nested scrolling panels are scrolling at once, such as sequencing them one after another (I see some code in Chromium which might be doing something like this)
- Allow it to be user-configurable, so people with poor vision or motion sickness can tweak it to their liking, or disable it
- Defer to the operating system, which might have its own quirks and customizations
- Change any of this with no notice, or the underlying OS might
Here is an excerpt of a comment in some Firefox code to do with smooth scrolling. I did not dig in to whether this is actually strictly relevant to the sorts of scroll you are doing, but it gives an idea:
* |Smooth| scrolls have a symmetrical acceleration and deceleration curve
* modeled with a set of splines that guarantee that the destination will be
* reached over a fixed time interval. |Smooth| will only be smooth if smooth
* scrolling is actually enabled. This behavior is utilized by keyboard and
* mouse wheel scrolling events.
*
* |SmoothMsd| implements a physically based model that approximates the
* behavior of a mass-spring-damper system. |SmoothMsd| scrolls have a
* non-symmetrical acceleration and deceleration curve, can potentially
* overshoot the destination on intermediate frames, and complete over a
* variable time interval. |SmoothMsd| will only be smooth if cssom-view
* smooth-scrolling is enabled.
And here is a little bit of code you can use to test for yourself. In my experimentation I'm seeing the duration varying based on how far it scrolls, both in Firefox and Chromium, and I'm seeing different speeds in each of those.
const qs = document.querySelector.bind(document);
const viewportHeightInput = qs("#viewport-height");
const contentHeightInput = qs("#content-height");
const viewport = qs("#viewport");
const content = qs("#content");
const output = qs("#output");
function update() {
viewport.style.height = `${viewportHeightInput.value}px`;
content.style.height = `${contentHeightInput.value}px`;
}
update();
viewportHeightInput.addEventListener("input", update);
contentHeightInput.addEventListener("input", update);
qs("#to-top").addEventListener("click", () => {
start = performance.now();
scrollEvents = 0;
updateScrollEvents();
viewport.scrollTo({
behavior: "smooth",
top: 0,
})
});
qs("#to-bottom").addEventListener("click", () => {
start = performance.now();
scrollEvents = 0;
updateScrollEvents();
viewport.scrollTo({
behavior: "smooth",
top: viewport.scrollHeight - viewport.clientHeight,
})
});
const scrollEventsOutput = qs("#scroll-events");
const elapsedOutput = qs("#elapsed");
let scrollEvents = 0;
let start = performance.now();
let last = null;
viewport.addEventListener("scroll", () => {
last = performance.now();
scrollEvents++;
updateScrollEvents();
});
function updateScrollEvents() {
scrollEventsOutput.value = scrollEvents;
elapsedOutput.value = last == null ? 0 : last - start;
}
#controls {
display: grid;
grid-template-columns: 10rem 1fr;
}
#controls fieldset {
display: contents;
}
#viewport {
overflow: auto;
border: thick solid orange;
margin: 4rem 0;
}
#content {
background-image: linear-gradient(to bottom left, black, white);
position: relative;
}
#content::before,
#content::after {
position: absolute;
left: 0;
background-color: black;
color: white;
display: block;
}
#content::before {
content: "start";
top: 0;
}
#content::after {
content: "end";
bottom: 0;
}
<div id="controls">
<fieldset>
<label for="viewport-height">Viewport height</label>
<input id="viewport-height" type="number" min="1" value="400">
</fieldset>
<fieldset>
<label for="content-height">Content height</label>
<input id="content-height" type="number" min="1" value="1000">
</fieldset>
<fieldset>
<label>Triggers</label>
<ul>
<li><button id="to-top">Scroll to top</button></li>
<li><button id="to-bottom">Scroll to bottom</button></li>
</ul>
</fieldset>
<fieldset>
<label for="scroll-events">Scroll events</label>
<input id="scroll-events" type="number" readonly value="0">
</fieldset>
<fieldset>
<label for="elapsed">Milliseconds between start of scroll and last scroll event</label>
<input id="elapsed" type="number" readonly value="0">
</fieldset>
</div>
<div id="viewport">
<div id="content"></div>
</div>