I recently decided to solve this problem in a situation where jQuery was not an option, so I register my solution here only for posterity.
var scroll = (function() { var elementPosition = function(a) { return function() { return a.getBoundingClientRect().top; }; }; var scrolling = function( elementID ) { var el = document.getElementById( elementID ), elPos = elementPosition( el ), duration = 400, increment = Math.round( Math.abs( elPos() )/40 ), time = Math.round( duration/increment ), prev = 0, E; function scroller() { E = elPos(); if (E === prev) { return; } else { prev = E; } increment = (E > -20 && E < 20) ? ((E > - 5 && E < 5) ? 1 : 5) : increment; if (E > 1 || E < -1) { if (E < 0) { window.scrollBy( 0,-increment ); } else { window.scrollBy( 0,increment ); } setTimeout(scroller, time); } else { el.scrollTo( 0,0 ); } } scroller(); }; return { To: scrolling } })(); scroll.To('elementID');
The scroll() function uses the module’s scroll.To('id') template to pass the identifier of the target element to its scrolling() function via scroll.To('id') , which sets the values to be used using the scroller() function.
Structure
In scrolling() :
el : DOM targetelPos : returns a function via elememtPosition() , which with each call gives the position of the target element relative to the top of the page.duration : transition time in milliseconds.increment : divides the starting position of the target element into 40 steps.time : sets the time of each step.prev : the previous position of the target element in scroller() .E : holds the position of the target element in scroller() .
The actual work is done using the scroller() function, which continues to call itself (via setTimeout() ) until the target element is at the top of the page or the page can scroll more.
Each time scroller() is called, it checks the current position of the target element (held in the E variable), and if it is > 1 OR < -1 , and if the page still scrolls, shifts the window by increment pixels - up or down, if E is a positive or negative value. If E is neither > 1 OR < -1 , or E === prev , the function stops. I added the DOMElement.scrollTo() method at completion to make sure the target element was hacked at the top of the window (and not that you would notice it on a part of the pixel!).
The if on line 2 of scroller() checks if the page scrolls (in cases where the target can be at the bottom of the page and the page cannot scroll further) by checking E against its previous position ( prev ).
The triple condition below it reduces the value of increment , as E approaches zero. This stops the page overflowing in one direction, and then bounces back to re-adjust the other, and then bounces back to flip another, ping-pong style, to infinity and beyond.
If your page is larger than c.4000px, you can increase the values in the first term expression (here by +/- 20) and / or the divider, which sets the value of increment (here to 40).
When playing with duration , a divisor that sets increment , and triple-state scroller() should allow you to customize the function to fit your page.