Smooth scroll without the use of jQuery
I'm coding up a page where I only want to use raw JavaScript code for UI without any interference of plugins or frameworks.
And now I'm struggling with finding a way to scroll over the page smoothly without jQuery.
Try this smooth scrolling demo, or an algorithm like:
- Get the current top location using
self.pageYOffset
- Get the position of element till where you want to scroll to:
element.offsetTop
- Do a for loop to reach there, which will be quite fast or use a timer to do smooth scroll till that position using
window.scrollTo
See also the other popular answer to this question.
Andrew Johnson's original code:
function currentYPosition() {
// Firefox, Chrome, Opera, Safari
if (self.pageYOffset) return self.pageYOffset;
// Internet Explorer 6 - standards mode
if (document.documentElement && document.documentElement.scrollTop)
return document.documentElement.scrollTop;
// Internet Explorer 6, 7 and 8
if (document.body.scrollTop) return document.body.scrollTop;
return 0;
}
function elmYPosition(eID) {
var elm = document.getElementById(eID);
var y = elm.offsetTop;
var node = elm;
while (node.offsetParent && node.offsetParent != document.body) {
node = node.offsetParent;
y += node.offsetTop;
} return y;
}
function smoothScroll(eID) {
var startY = currentYPosition();
var stopY = elmYPosition(eID);
var distance = stopY > startY ? stopY - startY : startY - stopY;
if (distance < 100) {
scrollTo(0, stopY); return;
}
var speed = Math.round(distance / 100);
if (speed >= 20) speed = 20;
var step = Math.round(distance / 25);
var leapY = stopY > startY ? startY + step : startY - step;
var timer = 0;
if (stopY > startY) {
for ( var i=startY; i<stopY; i+=step ) {
setTimeout("window.scrollTo(0, "+leapY+")", timer * speed);
leapY += step; if (leapY > stopY) leapY = stopY; timer++;
} return;
}
for ( var i=startY; i>stopY; i-=step ) {
setTimeout("window.scrollTo(0, "+leapY+")", timer * speed);
leapY -= step; if (leapY < stopY) leapY = stopY; timer++;
}
}
Related links:
- https://www.sitepoint.com/smooth-scrolling-vanilla-javascript/
- https://github.com/zengabor/zenscroll/blob/dist/zenscroll.js
- https://github.com/cferdinandi/smooth-scroll/blob/master/src/js/smooth-scroll.js
- https://github.com/alicelieutier/smoothScroll/blob/master/smoothscroll.js
edit: this answer has been written in 2013. Please check Cristian Traìna's comment below about requestAnimationFrame
I made it. The code below doesn't depend on any framework.
Limitation : The anchor active is not written in the url.
Version of the code : 1.0 | Github : https://github.com/Yappli/smooth-scroll
(function() // Code in a function to create an isolate scope
{
var speed = 500;
var moving_frequency = 15; // Affects performance !
var links = document.getElementsByTagName('a');
var href;
for(var i=0; i<links.length; i++)
{
href = (links[i].attributes.href === undefined) ? null : links[i].attributes.href.nodeValue.toString();
if(href !== null && href.length > 1 && href.substr(0, 1) == '#')
{
links[i].onclick = function()
{
var element;
var href = this.attributes.href.nodeValue.toString();
if(element = document.getElementById(href.substr(1)))
{
var hop_count = speed/moving_frequency
var getScrollTopDocumentAtBegin = getScrollTopDocument();
var gap = (getScrollTopElement(element) - getScrollTopDocumentAtBegin) / hop_count;
for(var i = 1; i <= hop_count; i++)
{
(function()
{
var hop_top_position = gap*i;
setTimeout(function(){ window.scrollTo(0, hop_top_position + getScrollTopDocumentAtBegin); }, moving_frequency*i);
})();
}
}
return false;
};
}
}
var getScrollTopElement = function (e)
{
var top = 0;
while (e.offsetParent != undefined && e.offsetParent != null)
{
top += e.offsetTop + (e.clientTop != null ? e.clientTop : 0);
e = e.offsetParent;
}
return top;
};
var getScrollTopDocument = function()
{
return document.documentElement.scrollTop + document.body.scrollTop;
};
})();
Native browser smooth scrolling in JavaScript is like this:
// scroll to specific values,
// same as window.scroll() method.
// for scrolling a particular distance, use window.scrollBy().
window.scroll({
top: 2500,
left: 0,
behavior: 'smooth'
});
// scroll certain amounts from current position
window.scrollBy({
top: 100, // negative value acceptable
left: 0,
behavior: 'smooth'
});
// scroll to a certain element
document.querySelector('.hello').scrollIntoView({
behavior: 'smooth'
});
Algorithm
Scrolling an element requires changing its scrollTop
value over time. For a given point in time, calculate a new scrollTop
value. To animate smoothly, interpolate using a smooth-step algorithm.
Calculate scrollTop
as follows:
var point = smooth_step(start_time, end_time, now);
var scrollTop = Math.round(start_top + (distance * point));
Where:
start_time
is the time the animation started;end_time
is when the animation will end(start_time + duration)
;start_top
is thescrollTop
value at the beginning; anddistance
is the difference between the desired end value and the start value(target - start_top)
.
A robust solution should detect when animating is interrupted, and more. Read my post about Smooth Scrolling without jQuery for details.
Demo
See the JSFiddle.
Implementation
The code:
/**
Smoothly scroll element to the given target (element.scrollTop)
for the given duration
Returns a promise that's fulfilled when done, or rejected if
interrupted
*/
var smooth_scroll_to = function(element, target, duration) {
target = Math.round(target);
duration = Math.round(duration);
if (duration < 0) {
return Promise.reject("bad duration");
}
if (duration === 0) {
element.scrollTop = target;
return Promise.resolve();
}
var start_time = Date.now();
var end_time = start_time + duration;
var start_top = element.scrollTop;
var distance = target - start_top;
// based on http://en.wikipedia.org/wiki/Smoothstep
var smooth_step = function(start, end, point) {
if(point <= start) { return 0; }
if(point >= end) { return 1; }
var x = (point - start) / (end - start); // interpolation
return x*x*(3 - 2*x);
}
return new Promise(function(resolve, reject) {
// This is to keep track of where the element's scrollTop is
// supposed to be, based on what we're doing
var previous_top = element.scrollTop;
// This is like a think function from a game loop
var scroll_frame = function() {
if(element.scrollTop != previous_top) {
reject("interrupted");
return;
}
// set the scrollTop for this frame
var now = Date.now();
var point = smooth_step(start_time, end_time, now);
var frameTop = Math.round(start_top + (distance * point));
element.scrollTop = frameTop;
// check if we're done!
if(now >= end_time) {
resolve();
return;
}
// If we were supposed to scroll but didn't, then we
// probably hit the limit, so consider it done; not
// interrupted.
if(element.scrollTop === previous_top
&& element.scrollTop !== frameTop) {
resolve();
return;
}
previous_top = element.scrollTop;
// schedule next frame for execution
setTimeout(scroll_frame, 0);
}
// boostrap the animation process
setTimeout(scroll_frame, 0);
});
}
I've made an example without jQuery here : http://codepen.io/sorinnn/pen/ovzdq
/**
by Nemes Ioan Sorin - not an jQuery big fan
therefore this script is for those who love the old clean coding style
@id = the id of the element who need to bring into view
Note : this demo scrolls about 12.700 pixels from Link1 to Link3
*/
(function()
{
window.setTimeout = window.setTimeout; //
})();
var smoothScr = {
iterr : 30, // set timeout miliseconds ..decreased with 1ms for each iteration
tm : null, //timeout local variable
stopShow: function()
{
clearTimeout(this.tm); // stopp the timeout
this.iterr = 30; // reset milisec iterator to original value
},
getRealTop : function (el) // helper function instead of jQuery
{
var elm = el;
var realTop = 0;
do
{
realTop += elm.offsetTop;
elm = elm.offsetParent;
}
while(elm);
return realTop;
},
getPageScroll : function() // helper function instead of jQuery
{
var pgYoff = window.pageYOffset || document.body.scrollTop || document.documentElement.scrollTop;
return pgYoff;
},
anim : function (id) // the main func
{
this.stopShow(); // for click on another button or link
var eOff, pOff, tOff, scrVal, pos, dir, step;
eOff = document.getElementById(id).offsetTop; // element offsetTop
tOff = this.getRealTop(document.getElementById(id).parentNode); // terminus point
pOff = this.getPageScroll(); // page offsetTop
if (pOff === null || isNaN(pOff) || pOff === 'undefined') pOff = 0;
scrVal = eOff - pOff; // actual scroll value;
if (scrVal > tOff)
{
pos = (eOff - tOff - pOff);
dir = 1;
}
if (scrVal < tOff)
{
pos = (pOff + tOff) - eOff;
dir = -1;
}
if(scrVal !== tOff)
{
step = ~~((pos / 4) +1) * dir;
if(this.iterr > 1) this.iterr -= 1;
else this.itter = 0; // decrease the timeout timer value but not below 0
window.scrollBy(0, step);
this.tm = window.setTimeout(function()
{
smoothScr.anim(id);
}, this.iterr);
}
if(scrVal === tOff)
{
this.stopShow(); // reset function values
return;
}
}
}
Modern browsers has support for CSS "scroll-behavior: smooth" property. So, we even don't need any Javascript at all for this. Just add this to the body element, and use usual anchors and links. scroll-behavior MDN docs
I recently set out to solve this problem in a situation where jQuery wasn't an option, so I'm logging my solution here just 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
}
})();
/* usage */
scroll.To('elementID');
The scroll()
function uses the Revealing Module Pattern to pass the target element's id to its scrolling()
function, via scroll.To('id')
, which sets the values used by the scroller()
function.
Breakdown
In scrolling()
:
el
: the target DOM objectelPos
: returns a function viaelememtPosition()
which gives the position of the target element relative to the top of the page each time it's called.duration
: transition time in milliseconds.increment
: divides the starting position of the target element into 40 steps.time
: sets the timing of each step.prev
: the target element's previous position inscroller()
.E
: holds the target element's position inscroller()
.
The actual work is done by 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 no more.
Each time scroller()
is called it checks the current position of the target element (held in variable E
) and if that is > 1
OR < -1
and if the page is still scrollable shifts the window by increment
pixels - up or down depending if E
is a positive or negative value. When E
is neither > 1
OR < -1
, or E
=== prev
the function stops. I added the DOMElement.scrollTo()
method on completion just to make sure the target element was bang on the top of the window (not that you'd notice it being out by a fraction of a pixel!).
The if
statement on line 2 of scroller()
checks to see if the page is scrolling (in cases where the target might be towards the bottom of the page and the page can scroll no further) by checking E
against its previous position (prev
).
The ternary condition below it reduce the increment
value as E
approaches zero. This stops the page overshooting one way and then bouncing back to overshoot the other, and then bouncing back to overshoot the other again, ping-pong style, to infinity and beyond.
If your page is more that c.4000px high you might want to increase the values in the ternary expression's first condition (here at +/-20) and/or the divisor which sets the increment
value (here at 40).
Playing about with duration
, the divisor which sets increment
, and the values in the ternary condition of scroller()
should allow you to tailor the function to suit your page.
N.B.Tested in up-to-date versions of Firefox and Chrome on Lubuntu, and Firefox, Chrome and IE on Windows8.
I've made something like this. I have no idea if its working in IE8. Tested in IE9, Mozilla, Chrome, Edge.
function scroll(toElement, speed) {
var windowObject = window;
var windowPos = windowObject.pageYOffset;
var pointer = toElement.getAttribute('href').slice(1);
var elem = document.getElementById(pointer);
var elemOffset = elem.offsetTop;
var counter = setInterval(function() {
windowPos;
if (windowPos > elemOffset) { // from bottom to top
windowObject.scrollTo(0, windowPos);
windowPos -= speed;
if (windowPos <= elemOffset) { // scrolling until elemOffset is higher than scrollbar position, cancel interval and set scrollbar to element position
clearInterval(counter);
windowObject.scrollTo(0, elemOffset);
}
} else { // from top to bottom
windowObject.scrollTo(0, windowPos);
windowPos += speed;
if (windowPos >= elemOffset) { // scroll until scrollbar is lower than element, cancel interval and set scrollbar to element position
clearInterval(counter);
windowObject.scrollTo(0, elemOffset);
}
}
}, 1);
}
//call example
var navPointer = document.getElementsByClassName('nav__anchor');
for (i = 0; i < navPointer.length; i++) {
navPointer[i].addEventListener('click', function(e) {
scroll(this, 18);
e.preventDefault();
});
}
Description
pointer
—get element and chceck if it has attribute "href" if yes, get rid of "#"elem
—pointer variable without "#"elemOffset
—offset of "scroll to" element from the top of the page
You can use
document.querySelector('your-element').scrollIntoView({behavior: 'smooth'});
If you want to scroll top the top of the page, you can just place an empty element in the top, and smooth scroll to that one.
You can also use Scroll Behaviour Property. for example add below line to your css
html{
scroll-behavior:smooth;
}
and this will result a native smooth scrolling feature . Read More about Scroll behavior
You can use a for loop with window.scrollTo and setTimeout to scroll smoothly with plain Javascript. To scroll to an element with my scrollToSmoothly
function: scrollToSmoothly(elem.offsetTop)
(assuming elem
is a DOM element). You can use this to scroll smoothly to any y-position in the document.
function scrollToSmoothly(pos, time){
/*Time is only applicable for scrolling upwards*/
/*Code written by hev1*/
/*pos is the y-position to scroll to (in pixels)*/
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);
}
}
Demo:
<button onClick="scrollToDiv()">Scroll To Element</button>
<div style="margin: 1000px 0px; text-align: center;">Div element<p/>
<button onClick="scrollToSmoothly(Number(0))">Scroll back to top</button>
</div>
<script>
function scrollToSmoothly(pos, time){
/*Time is only applicable for scrolling upwards*/
/*Code written by hev1*/
/*pos is the y-position to scroll to (in pixels)*/
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 scrollToDiv(){
var elem = document.querySelector("div");
scrollToSmoothly(elem.offsetTop);
}
</script>
<script>
var set = 0;
function animatescroll(x, y) {
if (set == 0) {
var val72 = 0;
var val73 = 0;
var setin = 0;
set = 1;
var interval = setInterval(function() {
if (setin == 0) {
val72++;
val73 += x / 1000;
if (val72 == 1000) {
val73 = 0;
interval = clearInterval(interval);
}
document.getElementById(y).scrollTop = val73;
}
}, 1);
}
}
</script>
x = scrollTop
y = id of the div that is used to scroll
Note: For making the body to scroll give the body an ID.
Here is my solution. Works in most browsers
document.getElementById("scrollHere").scrollIntoView({behavior: "smooth"});
document.getElementById("end").scrollIntoView({behavior: "smooth"});
body {margin: 0px; display: block; height: 100%; background-image: linear-gradient(red, yellow);}
.start {display: block; margin: 100px 10px 1000px 0px;}
.end {display: block; margin: 0px 0px 100px 0px;}
<div class="start">Start</div>
<div class="end" id="end">End</div>
참고URL : https://stackoverflow.com/questions/10063380/smooth-scroll-without-the-use-of-jquery
'IT TIP' 카테고리의 다른 글
MANIFEST.in ignored on “python setup.py install” - no data files installed? (0) | 2020.10.21 |
---|---|
How to make git log not prompt to continue? (0) | 2020.10.21 |
null vs empty string in Oracle (0) | 2020.10.21 |
Return from lambda forEach() in java (0) | 2020.10.21 |
Should I derive custom exceptions from Exception or ApplicationException in .NET? (0) | 2020.10.21 |