Reputation: 15291
I have a simple page where I am going to place a large form on the right side, and a navigation menu on the left allowing the user to jump to sections of the large form (this could also be used for large articles too - for now to mock the size I have added a large bottom padding with CSS). The right column has been styled to fit the height of the window using height: 100vh.
When clicking a link on the right and the section offsetTop
is greater than its parent's scrollTop
the scrolling is smooth. I use a JavaScript setTimeout
and I set the timeout's interval using a loop... this is great! However should I want to scroll up, that being the section's offsetTop is less than its parent's scrollTop
I get problems - it is janky, slow and doesn't really work... I have been looking at this for too long so if someone can help I'd be most appreciative. This is my JavaScript function
function scrollToAchor(where, event) {
event.stopPropagation();
var element = document.querySelector('.aims-form__form').querySelector('a[name="' + where + '"]');
var to = element.offsetTop - 30;
var from = document.querySelector('.aims-form__form').scrollTop
let timeOut = 0;
if(to >= from) {
for(let i = from; i <= to; i+=5) {
// this works and is great!!!
setTimeout(function () {
document.querySelector('.aims-form__form').scrollTop = i;
}, i/2);
}
} else {
for(let k = from; k >= to; k-=5) {
// I need to set the timeout Interval so the animation is smooth but it is really slow
// with the above setInterval above we are counting up... How do I get this smooth
timeOut = timeOut + (to + 15) / 2;
setTimeout(function () {
document.querySelector('.aims-form__form').scrollTop = i;
}, timeOut);
}
}
}
This shouldn't phase me but I can't get my head around it and now I have a mental block. To understand the scrolling function better here is the HTML, however to get a full picture (including the necessary CSS) here is a jsbin of the full thing https://jsbin.com/nidevaj/edit?html,css,js,output
<div class="aims-form">
<div class="aims-form__navigation">
<ul>
<li>
<a onClick="scrollToAchor('section1', event)" class="aims-form__anchor">Section 1</a>
</li>
<li>
<a onClick="scrollToAchor('section2', event)" class="aims-form__anchor">Section 2</a>
</li>
<li>
<a onClick="scrollToAchor('section3', event)" class="aims-form__anchor">Section 3</a>
</li>
<li>
<a onClick="scrollToAchor('section4', event)" class="aims-form__anchor">Section 4</a>
</li>
</ul>
</div>
<div class="aims-form__form">
<section>
<a name="section1"></a>
Section 1
<br>
... content lots of content
</section>
<section>
<a name="section2"></a>
Section 2
<br>
... content lots of content
</section>
<section>
<a name="section3"></a>
Section 3
<br>
... content lots of content
</section>
<section>
<a name="section4"></a>
Section 4
<br>
... content lots of content
</section>
</div>
</div>
Upvotes: 1
Views: 838
Reputation: 89224
You can set the CSS property scroll-behavior
to smooth
(supported by most modern browsers) which removes the need for Javascript. Just give the anchor tags a href
of #
plus the name
of the anchor tag to scroll to and allow the normal scrolling to occur, except much more smooth.
section{
margin: 500px 0px;
}
html, body{
scroll-behavior: smooth;
}
<div class="aims-form">
<div class="aims-form__navigation">
<ul>
<li>
<a href="#section1">Section 1</a>
</li>
<li>
<a href="#section2">Section 2</a>
</li>
<li>
<a href="#section3">Section 3</a>
</li>
<li>
<a href="#section4">Section 4</a>
</li>
</ul>
</div>
<div class="aims-form__form">
<section>
<a name="section1"></a>
Section 1
<br>
... content lots of content
</section>
<section>
<a name="section2"></a>
Section 2
<br>
... content lots of content
</section>
<section>
<a name="section3"></a>
Section 3
<br>
... content lots of content
</section>
<section>
<a name="section4"></a>
Section 4
<br>
... content lots of content
</section>
</div>
</div>
If you want to use Javascript, you can use a for
loop with window.scrollTo
and setTimeout
as well as setInterval
for smooth scrolling. I have written a function for this. To scroll to an element, provide the element's offsetTop
as the pos
argument.
function scrollToSmoothly(pos, time) {
if (isNaN(pos)) {
throw "Position must be a number";
}
if (pos < 0) {
throw "Position can not be negative";
}
var currentPos = window.scrollY || window.screenTop;
if (currentPos < pos) {
var t = 10;
for (let i = currentPos; i <= pos; i += 10) {
t += 10;
setTimeout(function() {
window.scrollTo(0, i);
}, t / 2);
}
} else {
time = time || 2;
var i = currentPos;
var x;
x = setInterval(function() {
window.scrollTo(0, i);
i -= 10;
if (i <= pos) {
clearInterval(x);
}
}, time);
}
}
section{
margin: 500px 0px;
}
<div class="aims-form">
<div class="aims-form__navigation">
<ul>
<li>
<a onClick="scrollToAchor('section1', event)" class="aims-form__anchor">Section 1</a>
</li>
<li>
<a onClick="scrollToAchor('section2', event)" class="aims-form__anchor">Section 2</a>
</li>
<li>
<a onClick="scrollToAchor('section3', event)" class="aims-form__anchor">Section 3</a>
</li>
<li>
<a onClick="scrollToAchor('section4', event)" class="aims-form__anchor">Section 4</a>
</li>
</ul>
</div>
<div class="aims-form__form">
<section>
<a name="section1"></a>
Section 1
<br>
... content lots of content
</section>
<section>
<a name="section2"></a>
Section 2
<br>
... content lots of content
</section>
<section>
<a name="section3"></a>
Section 3
<br>
... content lots of content
</section>
<section>
<a name="section4"></a>
Section 4
<br>
... content lots of content
<button onClick="scrollToTop()">
Back To Top
</button>
</section>
</div>
</div>
<script>
function scrollToSmoothly(pos, time) {
if (isNaN(pos)) {
throw "Position must be a number";
}
if (pos < 0) {
throw "Position can not be negative";
}
var currentPos = window.scrollY || window.screenTop;
if (currentPos < pos) {
var t = 10;
for (let i = currentPos; i <= pos; i += 10) {
t += 10;
setTimeout(function() {
window.scrollTo(0, i);
}, t / 2);
}
} else {
time = time || 2;
var i = currentPos;
var x;
x = setInterval(function() {
window.scrollTo(0, i);
i -= 10;
if (i <= pos) {
clearInterval(x);
}
}, time);
}
}
function scrollToAchor(where, event) {
event.stopPropagation();
var element = document.querySelector('.aims-form__form').querySelector('a[name="' + where + '"]');
scrollToSmoothly(element.offsetTop);
}
function scrollToTop(){
scrollToSmoothly(0);
}
</script>
If you want to have the contents of a div
scroll smoothly, you can use the above function, replacing window.scrollTo
with elem.scrollTop
and passing in the element's offsetTop
as the pos
to scroll to the element.
section{
margin: 500px 0px;
}
<div style="border: 1px solid black; margin: auto; height: 250px; width: 50%; overflow-y: auto;" id="parent">
<div class="aims-form">
<div class="aims-form__navigation">
<ul>
<li>
<a onClick="scrollToAnchor('section1', event)" class="aims-form__anchor">Section 1</a>
</li>
<li>
<a onClick="scrollToAnchor('section2', event)" class="aims-form__anchor">Section 2</a>
</li>
<li>
<a onClick="scrollToAnchor('section3', event)" class="aims-form__anchor">Section 3</a>
</li>
<li>
<a onClick="scrollToAnchor('section4', event)" class="aims-form__anchor">Section 4</a>
</li>
</ul>
</div>
<div class="aims-form__form">
<section>
<a name="section1"></a>
Section 1
<br>
... content lots of content
</section>
<section>
<a name="section2"></a>
Section 2
<br>
... content lots of content
</section>
<section>
<a name="section3"></a>
Section 3
<br>
... content lots of content
</section>
<section>
<a name="section4"></a>
Section 4
<br>
... content lots of content
<p/>
<button onClick="scrollToTopOfDiv()">
Back To Top
</button>
</section>
</div>
</div>
</div>
<script>
function scrollToSmoothlyInsideElement(elem, pos, time) {
if (isNaN(pos)) {
throw "Position must be a number";
}
if (pos < 0) {
throw "Position can not be negative";
}
var currentPos = elem.scrollTop;
if (currentPos < pos) {
var t = 10;
for (let i = currentPos; i <= pos; i += 10) {
t += 10;
setTimeout(function() {
elem.scrollTop = i;
}, t / 2);
}
} else {
time = time || 2;
var i = currentPos;
var x;
x = setInterval(function() {
elem.scrollTop = i;
i -= 10;
if (i <= pos) {
clearInterval(x);
}
}, time);
}
}
function scrollToAnchor(where, event){
event.stopPropagation();
var element = document.querySelector('.aims-form__form').querySelector('a[name="' + where + '"]');
var parent = document.querySelector('#parent');
scrollToSmoothlyInsideElement(parent, element.offsetTop);
}
function scrollToTopOfDiv(){
var elem = document.querySelector('#parent');
scrollToSmoothlyInsideElement(elem, 0);
}
</script>
For a more comprehensive list of methods for smooth scrolling, see my answer here.
Upvotes: 1