I’m trying to implement a smooth scroll with scrollTo
. I cannot control the speed if I use the option scrollIntoView({ behavior: 'smooth' }
. Hence I use the combination of performance.now()
and requestAnimationFrame
to achieve the effect.
It works well on mousewheel event, the problem is that I have three buttons, it should go to the relative section as I click. However it doesn’t work for the button.
The mouse wheel event is using the window.scrollTo(0, startY + distance * progress);
while the button use the window.scrollTo(0, manualPosition * progress);
where the manualPosition
is the section position by lis[index].getBoundingClientRect().top
.
Please try to run the code and click the button, or use mouse wheel to see the effect.
(function () {
const lis = document.querySelectorAll('.section'),
btnsNav = document.querySelectorAll('.button'),
speed = 500;
let currentSection = 0,
isScrolling = false;
function smoothScrollTo(time, direction, mode = 'auto', index = 1) {
if (isScrolling) {
return;
}
isScrolling = true;
const start = performance.now(),
startY = window.scrollY,
distance =
direction === 'up' ? -window.innerHeight : window.innerHeight;
function animateScroll() {
const now = performance.now(),
elapsed = now - start,
progress = elapsed / time,
manualPosition = lis[index].getBoundingClientRect().top;
if (mode === 'auto') {
window.scrollTo(0, startY + distance * progress);
} else if (mode === 'manual') {
window.scrollTo(0, manualPosition * progress);
}
if (elapsed < time) {
requestAnimationFrame(animateScroll);
} else {
isScrolling = false;
}
}
requestAnimationFrame(animateScroll);
if (direction === 'down') {
currentSection++;
} else if (direction === 'up') {
currentSection--;
}
}
document.addEventListener('wheel', function (event) {
if (event.deltaY > 0 && currentSection < lis.length - 1) {
smoothScrollTo(speed, 'down');
} else if (event.deltaY < 0 && currentSection > 0) {
smoothScrollTo(speed, 'up');
}
});
for (let index = 0; index < btnsNav.length; index++) {
btnsNav[index].addEventListener('click', function () {
smoothScrollTo(speed, 'down', 'manual', index);
});
}
})();
body {
margin: 0;
}
#div {
position: fixed;
top: 0;
left: 0;
display: flex;
column-gap: 10px;
}
.section {
height: 100vh;
width: 100%;
}
.section:nth-of-type(1) {
background-color: red;
}
.section:nth-of-type(2) {
background-color: yellow;
}
.section:nth-of-type(3) {
background-color: green;
}
<div id="div">
<button class="button" type="button">Go to 1</button>
<button class="button" type="button">Go to 2</button>
<button class="button" type="button">Go to 3</button>
</div>
<section class="section"></section>
<section class="section"></section>
<section class="section"></section>
2
Answers
Issues in the code
I found two problems in your code,
The btnsNav variable: You have been storing .section in this variable and , attaching the event listener there, which is incorrect. Change it to
.button
When you are calling the
smoothScrollTo
function in the for loop, you are passingmanual
as themode
. Passing the mode as auto seems to resolve the issue.Note: There are still some more changes required in the logic which I will leave you to fix as I have been able to go to the required page downwards but the button click does not take us upward to the page. Cheers!
Hints: The direction you are passing on the
smoothScrollTo
in the for loop is always down. Instead of using ‘up’/’down’ I would usecurrentSection
and the index being passed to identify whether to scroll up or down. Simplify the logic 😉Code with fixes:
You can actually achieve this effect without any javascript at all – it’s possible with HTML + CSS.
There are three steps:
scroll-behavior: smooth
to the document root (ie. the<html>
element):id
to each.section
to indicate the hashfragment link destinations:Working Example: