Smooth scrolling not working on click - javascript

I am trying to implement smooth scrolling on my site. I have links that I want to use for scrolling to elements:
<nav id="page-nav" class="nav section-links">
<button class="active" data-target="#info">Info</button>
<button data-target="#videos">Videos</button>
<button data-target="#stats">Statistics</button>
</nav>
I am attaching click event listener on buttons like this:
$(document).ready(function () {
var navigationLinks = document.getElementsByClassName('section-links')[0].getElementsByTagName('button');
Array.prototype.forEach.call(navigationLinks, child => {
console.log(child);
child.addEventListener('click', doScrolling(child.getAttribute('data-target')));
});
});
This is the doScrolling function:
function getElementY(query) {
return window.pageYOffset + document.querySelector(query).getBoundingClientRect().top
}
function doScrolling(element, duration) {
var duration = duration || 1000;
var startingY = window.pageYOffset
var elementY = getElementY(element)
// If element is close to page's bottom then window will scroll only to some position above the element.
var targetY = document.body.scrollHeight - elementY < window.innerHeight ? document.body.scrollHeight - window.innerHeight : elementY
var diff = targetY - startingY
// Easing function: easeInOutCubic
// From: https://gist.github.com/gre/1650294
var easing = function (t) { return t<.5 ? 4*t*t*t : (t-1)*(2*t-2)*(2*t-2)+1 }
var start
if (!diff) return
// Bootstrap our animation - it will get called right before next frame shall be rendered.
window.requestAnimationFrame(function step(timestamp) {
if (!start) start = timestamp
// Elapsed miliseconds since start of scrolling.
var time = timestamp - start
// Get percent of completion in range [0, 1].
var percent = Math.min(time / duration, 1)
// Apply the easing.
// It can cause bad-looking slow frames in browser performance tool, so be careful.
percent = easing(percent)
window.scrollTo(0, startingY + diff * percent)
// Proceed with animation as long as we wanted it to.
if (time < duration) {
window.requestAnimationFrame(step)
}
})
}
But, when I click on buttons nothing happens, there are no errors in the console, and I am not sure what is going on. How can I fix this?

What you are actually doing you are attaching result of doScrolling function call as an event handler. That is why it is not working as expected.
You should wrap the doScrolling call into a function and store the target value in a closure.
$(document).ready(function () {
var navigationLinks = document.getElementsByClassName('section-links')[0].getElementsByTagName('button');
Array.prototype.forEach.call(navigationLinks, child => {
var target = child.getAttribute('data-target');
child.addEventListener('click', function () { doScrolling(target) });
});
});
function getElementY(query) {
return window.pageYOffset + document.querySelector(query).getBoundingClientRect().top
}
function doScrolling(element, duration) {
var duration = duration || 1000;
var startingY = window.pageYOffset
var elementY = getElementY(element)
// If element is close to page's bottom then window will scroll only to some position above the element.
var targetY = document.body.scrollHeight - elementY < window.innerHeight ? document.body.scrollHeight - window.innerHeight : elementY
var diff = targetY - startingY
// Easing function: easeInOutCubic
// From: https://gist.github.com/gre/1650294
var easing = function (t) { return t<.5 ? 4*t*t*t : (t-1)*(2*t-2)*(2*t-2)+1 }
var start
if (!diff) return
// Bootstrap our animation - it will get called right before next frame shall be rendered.
window.requestAnimationFrame(function step(timestamp) {
if (!start) start = timestamp
// Elapsed miliseconds since start of scrolling.
var time = timestamp - start
// Get percent of completion in range [0, 1].
var percent = Math.min(time / duration, 1)
// Apply the easing.
// It can cause bad-looking slow frames in browser performance tool, so be careful.
percent = easing(percent)
window.scrollTo(0, startingY + diff * percent)
// Proceed with animation as long as we wanted it to.
if (time < duration) {
window.requestAnimationFrame(step)
}
})
}
.content {
height: 100vh;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<nav id="page-nav" class="nav section-links">
<button class="active" data-target="#info">Info</button>
<button data-target="#videos">Videos</button>
<button data-target="#stats">Statistics</button>
</nav>
<div class="content" id="info">Info</div>
<div class="content" id="videos">Videos</div>
<div class="content" id="stats">Stats</div>

"On click add event listener not working."
Your event listener is OK, see:
$(document).ready(function () {
var navigationLinks = document.getElementsByClassName('section-links')[0].getElementsByTagName('button');
Array.prototype.forEach.call(navigationLinks, child => {
console.log(child);
child.addEventListener('click', function() { console.log("click") });
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<nav id="page-nav" class="nav section-links">
<button class="active" data-target="#info">Info</button>
<button data-target="#videos">Videos</button>
<button data-target="#stats">Statistics</button>
</nav>

Related

Restart counter animation when element in viewport

This code make the counter/animation start when in view, but I would like for it to restart when scrolling out of view and then in view again. Can't seem to solve it.
If you would like to see it live link here - scroll down to the bottom just before the footer.
https://easyrecycle.dk/Serviceomraader.html
var a = 0;
$(window).scroll(function() {
var oTop = $('#counter').offset().top - window.innerHeight;
if (a == 0 && $(window).scrollTop() > oTop) {
$('.counter-value').each(function() {
var $this = $(this),
countTo = $this.attr('data-count');
$({
countNum: $this.text()
}).animate({
countNum: countTo
},
{
duration: 3000,
easing: 'swing',
step: function() {
$this.text(Math.floor(this.countNum));
},
complete: function() {
$this.text(this.countNum);
//alert('finished');
}
});
});
a = 1;
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="counter">
<div class="counter-container">
<div class="counter-box">
<div class="counter-value" data-count="30">0</div>
<span></span>
<p>Antal medarbejdere</p>
</div>
<div class="counter-box">
<div class="counter-value" data-count="51000">0</div>
<span></span>
<p>Processeret udstyr i KG pr. md.</p>
</div>
<div class="counter-box">
<div class="counter-value" data-count="51">0</div>
<span></span>
<p>Antal afhentninger pr. md.</p>
</div>
</div>
</div>
Don't use the .scroll() Event listener. It's a performance killer. Same goes for JavaScript's "scroll" EventListener - if used to perform heavy tasks. Actually you don't need jQuery at all.
Your task basically involves the use of:
the Intersection Observer API to watch for elements entering / exiting the viewport — instead of the pretty expensive scroll listener you used
a counterStart() function (triggered on viewport enter) that uses requestAnimationFrame — the most performant way to handle animations on the web.
a counterStop() function (triggered on viewport exit) that cancels the AnimationFrame and resets the element counter back to its start value.
an easing function in which you'll pass the linearly mapped time fraction (float 0.0 ... 1.0) in order to retrieve the easing value to be used to derive your integers/counters.
//gist.github.com/gre/1650294
const easeInOutQuad = (t) => t < .5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
const inViewportCounter = (el) => {
const duration = +el.dataset.duration || 3000;
const start = +el.textContent || 0;
const end = +el.dataset.count || 100;
let raf;
//stackoverflow.com/a/70746179/383904
const counterStart = () => {
if (start === end) return; // If equal values, stop here.
const range = end - start;
let curr = start; // Set current to start
const timeStart = Date.now();
const loop = () => {
let elaps = Date.now() - timeStart;
if (elaps > duration) elaps = duration;
const frac = easeInOutQuad(elaps / duration); // Get the time fraction with easing
const step = frac * range; // Calculate the value step
curr = start + step; // Increment or Decrement current value
el.textContent = Math.trunc(curr); // Apply to UI as integer
if (elaps < duration) raf = requestAnimationFrame(loop); // Loop
};
raf = requestAnimationFrame(loop); // Start the loop!
};
const counterStop = (el) => {
cancelAnimationFrame(raf);
el.textContent = start;
};
//stackoverflow.com/a/70746179/383904
const inViewport = (entries, observer) => {
entries.forEach(entry => {
// Enters viewport:
if (entry.isIntersecting) counterStart(entry.target);
// Exits viewport:
else counterStop(entry.target);
});
};
const Obs = new IntersectionObserver(inViewport);
const obsOptions = {}; //developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API#Intersection_observer_options
// Attach observer to element:
Obs.observe(el, obsOptions);
};
document.querySelectorAll('[data-count]').forEach(inViewportCounter);
[data-count] { margin: 55vh 0; font: 3rem/1.4 sans-serif; }
Scroll down...
<div data-count="300">0</div>
<div data-count="51000">0</div>
<div data-count="1000">0</div>
For a better understanding of the above main two chunks of code head to their original base answers which I basically reused and adapted:
Animate counter from start to end value
CSS3-Animate elements if visible in viewport (Page Scroll)
with the basic differences being:
in the counter function I passed the linear fraction of time into the easing function
and for the Intersection Observer one, I used an if .. else block; the else used to reset the element's textContent back to "0" (or any start value).
Sorry for not using jQuery as I probably would've years ago, but hopefully you learned something exciting and new. If some parts are not completely clear, feel always free to ask.
As you can see from my example below, i add a simple if (if is not in view) and reset the text/variable.
var a = 0;
$(window).scroll(function() {
var oTop = $('#counter').offset().top - window.innerHeight;
if (a == 0 && $(window).scrollTop() > oTop) {
$('.counter-value').each(function() {
var $this = $(this),
countTo = $this.attr('data-count');
$({
countNum: $this.text()
}).animate({
countNum: countTo
},
{
duration: 3000,
easing: 'swing',
step: function() {
$this.text(Math.floor(this.countNum));
},
complete: function() {
$this.text(this.countNum);
//alert('finished');
}
});
});
a = 1;
} else {
$('.counter-value').text('0');
a = 0;
}
});
.fakespace{
height:500px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class='fakespace'></div>
<div id="counter">
<div class="counter-container">
<div class="counter-box">
<div class="counter-value" data-count="30">0</div>
<span></span>
<p>Antal medarbejdere</p>
</div>
<div class="counter-box">
<div class="counter-value" data-count="51000">0</div>
<span></span>
<p>Processeret udstyr i KG pr. md.</p>
</div>
<div class="counter-box">
<div class="counter-value" data-count="51">0</div>
<span></span>
<p>Antal afhentninger pr. md.</p>
</div>
</div>
</div>

Scroll down a bit, wait, and repeat to bottom then wait a bit and reload page in Javascript

I need a javascript that scrolls down a bit and wait and repeat this until it arrives at the end of the web page. then wait and reload the page. Here is my script so far
<script>
onbeforeunload = function () {
window.scrollTo(0, 0);
}
onload = e=>{
var d = document.documentElement;
var offset = d.scrollTop + window.innerHeight;
var height = d.offsetHeight;
if (offset >= height) {
setTimeout(function () {
location.reload(1);
}, 5000);
}
setInterval(function(){
scrollBy(0,100);
}, 5000);
};
onscroll = function() {
var d = document.documentElement;
var offset = d.scrollTop + window.innerHeight;
var height = d.offsetHeight;
if (offset >= height) {
setTimeout(function () {
location.reload(1);
}, 5000);
}
};
</script>
my problem is that my scrypt doesnt work really good. Sometimes it doesnt reload the page when it is at the end of the page.
Thanks for your help!
This should do the trick:
function autoScrollandReload(scrollDistance,pause){
/// start the interval with the pause in between executions
var interval = setInterval(function(){
/// denife values, not necessary but easyier to read
var scrolled = window.pageYOffset;
var scroll_size = document.body.scrollHeight;
var scroll_remaining = scroll_size-scrolled;;
/// check if scrolling is necessary
if(scroll_remaining <= window.innerHeight){
clearInterval(interval);
/// scroll back to top
window.scrollTo(0,0);
/// do here what you want
setTimeout(location.reload(),500);
}else{
window.scrollBy(0,scrollDistance);
};
},pause);
};
Just call the function like this:
autoScrollandReload(100,5000);
This scrolls the page by 100px each 5 seconds

scroll to anchor without jquery or smoothscroll [duplicate]

I want to have 4 buttons/links on the beginning of the page, and under them the content.
On the buttons I put this code:
Scroll to element 1
Scroll to element 2
Scroll to element 3
Scroll to element 4
And under links there will be content:
<h2 id="idElement1">Element1</h2>
content....
<h2 id="idElement2">Element2</h2>
content....
<h2 id="idElement3">Element3</h2>
content....
<h2 id="idElement4">Element4</h2>
content....
It is working now, but cannot make it look more smooth.
I used this code, but cannot get it to work.
$('html, body').animate({
scrollTop: $("#elementID").offset().top
}, 2000);
Any suggestions? Thank you.
Edit: and the fiddle: http://jsfiddle.net/WxJLx/2/
Super smoothly with requestAnimationFrame
For smoothly rendered scrolling animation one could use window.requestAnimationFrame() which performs better with rendering than regular setTimeout() solutions.
A basic example looks like this. Function step is called for browser's every animation frame and allows for better time management of repaints, and thus increasing performance.
function doScrolling(elementY, duration) {
var startingY = window.pageYOffset;
var diff = elementY - startingY;
var start;
// Bootstrap our animation - it will get called right before next frame shall be rendered.
window.requestAnimationFrame(function step(timestamp) {
if (!start) start = timestamp;
// Elapsed milliseconds since start of scrolling.
var time = timestamp - start;
// Get percent of completion in range [0, 1].
var percent = Math.min(time / duration, 1);
window.scrollTo(0, startingY + diff * percent);
// Proceed with animation as long as we wanted it to.
if (time < duration) {
window.requestAnimationFrame(step);
}
})
}
For element's Y position use functions in other answers or the one in my below-mentioned fiddle.
I set up a bit more sophisticated function with easing support and proper scrolling to bottom-most elements:
https://jsfiddle.net/s61x7c4e/
Question was asked 5 years ago and I was dealing with smooth scroll and felt giving a simple solution is worth it to those who are looking for. All the answers are good but here you go a simple one.
function smoothScroll(){
document.querySelector('.your_class or #id here').scrollIntoView({
behavior: 'smooth'
});
}
just call the smoothScroll function on onClick event on your source element.
DOCS: https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView
Note: Please check compatibility here
3rd Party edit
Support for Element.scrollIntoView() in 2020 is this:
Region full + partial = sum full+partial Support
Asia 73.24% + 22.75% = 95.98%
North America 56.15% + 42.09% = 98.25%
India 71.01% + 20.13% = 91.14%
Europe 68.58% + 27.76% = 96.35%
Just made this javascript only solution below.
Simple usage:
EPPZScrollTo.scrollVerticalToElementById('signup_form', 20);
Engine object (you can fiddle with filter, fps values):
/**
*
* Created by Borbás Geri on 12/17/13
* Copyright (c) 2013 eppz! development, LLC.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*/
var EPPZScrollTo =
{
/**
* Helpers.
*/
documentVerticalScrollPosition: function()
{
if (self.pageYOffset) return self.pageYOffset; // Firefox, Chrome, Opera, Safari.
if (document.documentElement && document.documentElement.scrollTop) return document.documentElement.scrollTop; // Internet Explorer 6 (standards mode).
if (document.body.scrollTop) return document.body.scrollTop; // Internet Explorer 6, 7 and 8.
return 0; // None of the above.
},
viewportHeight: function()
{ return (document.compatMode === "CSS1Compat") ? document.documentElement.clientHeight : document.body.clientHeight; },
documentHeight: function()
{ return (document.height !== undefined) ? document.height : document.body.offsetHeight; },
documentMaximumScrollPosition: function()
{ return this.documentHeight() - this.viewportHeight(); },
elementVerticalClientPositionById: function(id)
{
var element = document.getElementById(id);
var rectangle = element.getBoundingClientRect();
return rectangle.top;
},
/**
* Animation tick.
*/
scrollVerticalTickToPosition: function(currentPosition, targetPosition)
{
var filter = 0.2;
var fps = 60;
var difference = parseFloat(targetPosition) - parseFloat(currentPosition);
// Snap, then stop if arrived.
var arrived = (Math.abs(difference) <= 0.5);
if (arrived)
{
// Apply target.
scrollTo(0.0, targetPosition);
return;
}
// Filtered position.
currentPosition = (parseFloat(currentPosition) * (1.0 - filter)) + (parseFloat(targetPosition) * filter);
// Apply target.
scrollTo(0.0, Math.round(currentPosition));
// Schedule next tick.
setTimeout("EPPZScrollTo.scrollVerticalTickToPosition("+currentPosition+", "+targetPosition+")", (1000 / fps));
},
/**
* For public use.
*
* #param id The id of the element to scroll to.
* #param padding Top padding to apply above element.
*/
scrollVerticalToElementById: function(id, padding)
{
var element = document.getElementById(id);
if (element == null)
{
console.warn('Cannot find element with id \''+id+'\'.');
return;
}
var targetPosition = this.documentVerticalScrollPosition() + this.elementVerticalClientPositionById(id) - padding;
var currentPosition = this.documentVerticalScrollPosition();
// Clamp.
var maximumScrollPosition = this.documentMaximumScrollPosition();
if (targetPosition > maximumScrollPosition) targetPosition = maximumScrollPosition;
// Start animation.
this.scrollVerticalTickToPosition(currentPosition, targetPosition);
}
};
Smooth scrolling - look ma no jQuery
Based on an article on itnewb.com i made a demo plunk to smoothly scroll without external libraries.
The javascript is quite simple. First a helper function to improve cross browser support to determine the current position.
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;
}
Then a function to determine the position of the destination element - the one where we would like to scroll to.
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;
}
And the core function to do the scrolling
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++;
}
return false;
}
To call it you just do the following. You create a link which points to another element by using the id as a reference for a destination anchor.
<a href="#anchor-2"
onclick="smoothScroll('anchor-2');">smooth scroll to the headline with id anchor-2<a/>
...
... some content
...
<h2 id="anchor-2">Anchor 2</h2>
Copyright
In the footer of itnewb.com the following is written: The techniques, effects and code demonstrated in ITNewb articles may be used for any purpose without attribution (although we recommend it) (2014-01-12)
You could also check this great Blog - with some very simple ways to achieve this :)
https://css-tricks.com/snippets/jquery/smooth-scrolling/
Like (from the blog)
// Scroll to specific values
// scrollTo is the same
window.scroll({
top: 2500,
left: 0,
behavior: 'smooth'
});
// Scroll certain amounts from current position
window.scrollBy({
top: 100, // could be negative value
left: 0,
behavior: 'smooth'
});
// Scroll to a certain element
document.querySelector('.hello').scrollIntoView({
behavior: 'smooth'
});
and you can also get the element "top" position like below (or some other way)
var e = document.getElementById(element);
var top = 0;
do {
top += e.offsetTop;
} while (e = e.offsetParent);
return top;
Why not use CSS scroll-behavior property
html {
scroll-behavior: smooth;
}
The browser support is also good
https://caniuse.com/#feat=css-scroll-behavior
For a more comprehensive list of methods for smooth scrolling, see my answer here.
To scroll to a certain position in an exact amount of time, window.requestAnimationFrame can be put to use, calculating the appropriate current position each time. To scroll to an element, just set the y-position to element.offsetTop.
/*
#param pos: the y-position to scroll to (in pixels)
#param time: the exact amount of time the scrolling will take (in milliseconds)
*/
function scrollToSmoothly(pos, time) {
var currentPos = window.pageYOffset;
var start = null;
if(time == null) time = 500;
pos = +pos, time = +time;
window.requestAnimationFrame(function step(currentTime) {
start = !start ? currentTime : start;
var progress = currentTime - start;
if (currentPos < pos) {
window.scrollTo(0, ((pos - currentPos) * progress / time) + currentPos);
} else {
window.scrollTo(0, currentPos - ((currentPos - pos) * progress / time));
}
if (progress < time) {
window.requestAnimationFrame(step);
} else {
window.scrollTo(0, pos);
}
});
}
Demo:
function scrollToSmoothly(pos, time) {
var currentPos = window.pageYOffset;
var start = null;
if(time == null) time = 500;
pos = +pos, time = +time;
window.requestAnimationFrame(function step(currentTime) {
start = !start ? currentTime : start;
var progress = currentTime - start;
if (currentPos < pos) {
window.scrollTo(0, ((pos - currentPos) * progress / time) + currentPos);
} else {
window.scrollTo(0, currentPos - ((currentPos - pos) * progress / time));
}
if (progress < time) {
window.requestAnimationFrame(step);
} else {
window.scrollTo(0, pos);
}
});
}
document.getElementById("toElement").addEventListener("click", function(e){
scrollToSmoothly(document.querySelector('div').offsetTop, 500 /* milliseconds */);
});
document.getElementById("backToTop").addEventListener("click", function(e){
scrollToSmoothly(0, 500);
});
<button id="toElement">Scroll To Element</button>
<div style="margin: 1000px 0px; text-align: center;">Div element
<button id="backToTop">Scroll back to top</button>
</div>
The SmoothScroll.js library can also be used, which supports scrolling to an element on the page in addition to more complex features such as smooth scrolling both vertically and horizontally, scrolling inside other container elements, different easing behaviors, scrolling relatively from the current position, and more.
document.getElementById("toElement").addEventListener("click", function(e){
smoothScroll({toElement: document.querySelector('div'), duration: 500});
});
document.getElementById("backToTop").addEventListener("click", function(e){
smoothScroll({yPos: 'start', duration: 500});
});
<script src="https://cdn.jsdelivr.net/gh/LieutenantPeacock/SmoothScroll#1.2.0/src/smoothscroll.min.js" integrity="sha384-UdJHYJK9eDBy7vML0TvJGlCpvrJhCuOPGTc7tHbA+jHEgCgjWpPbmMvmd/2bzdXU" crossorigin="anonymous"></script>
<button id="toElement">Scroll To Element</button>
<div style="margin: 1000px 0px; text-align: center;">Div element
<button id="backToTop">Scroll back to top</button>
</div>
Alternatively, you can pass an options object to window.scroll which scrolls to a specific x and y position and window.scrollBy which scrolls a certain amount from the current position:
// Scroll to specific values
// scrollTo is the same
window.scroll({
top: 2500,
left: 0,
behavior: 'smooth'
});
// Scroll certain amounts from current position
window.scrollBy({
top: 100, // could be negative value
left: 0,
behavior: 'smooth'
});
If you only need to scroll to an element, not a specific position in the document, you can use Element.scrollIntoView with behavior set to smooth.
document.getElementById("elemID").scrollIntoView({
behavior: 'smooth'
});
I've been using this for a long time:
function scrollToItem(item) {
var diff=(item.offsetTop-window.scrollY)/8
if (Math.abs(diff)>1) {
window.scrollTo(0, (window.scrollY+diff))
clearTimeout(window._TO)
window._TO=setTimeout(scrollToItem, 30, item)
} else {
window.scrollTo(0, item.offsetTop)
}
}
usage:
scrollToItem(element) where element is document.getElementById('elementid') for example.
Variation of #tominko answer.
A little smoother animation and resolved problem with infinite invoked setTimeout(), when some elements can't allign to top of viewport.
function scrollToItem(item) {
var diff=(item.offsetTop-window.scrollY)/20;
if(!window._lastDiff){
window._lastDiff = 0;
}
console.log('test')
if (Math.abs(diff)>2) {
window.scrollTo(0, (window.scrollY+diff))
clearTimeout(window._TO)
if(diff !== window._lastDiff){
window._lastDiff = diff;
window._TO=setTimeout(scrollToItem, 15, item);
}
} else {
console.timeEnd('test');
window.scrollTo(0, item.offsetTop)
}
}
you can use this plugin. Does exactly what you want.
http://flesler.blogspot.com/2007/10/jqueryscrollto.html
If one need to scroll to an element inside a div there is my solution based on Andrzej Sala's answer:
function scroolTo(element, duration) {
if (!duration) {
duration = 700;
}
if (!element.offsetParent) {
element.scrollTo();
}
var startingTop = element.offsetParent.scrollTop;
var elementTop = element.offsetTop;
var dist = elementTop - startingTop;
var start;
window.requestAnimationFrame(function step(timestamp) {
if (!start)
start = timestamp;
var time = timestamp - start;
var percent = Math.min(time / duration, 1);
element.offsetParent.scrollTo(0, startingTop + dist * percent);
// Proceed with animation as long as we wanted it to.
if (time < duration) {
window.requestAnimationFrame(step);
}
})
}
Why not use this easy way
Native JS
document.querySelector(".layout").scrollIntoView({
behavior: "smooth",
});
Smooth scrolling with jQuery.ScrollTo
To use the jQuery ScrollTo plugin you have to do the following
Create links where href points to another elements.id
create the elements you want to scroll to
reference jQuery and the scrollTo Plugin
Make sure to add a click event handler for each link that should do smooth scrolling
Creating the links
<h1>Smooth Scrolling with the jQuery Plugin .scrollTo</h1>
<div id="nav-list">
Scroll to element 1
Scroll to element 2
Scroll to element 3
Scroll to element 4
</div>
Creating the target elements here only the first two are displayed the other headings are set up the same way. To see another example i added a link back to the navigation a.toNav
<h2 id="idElement1">Element1</h2>
....
<h2 id="idElement1">Element1</h2>
...
<a class="toNav" href="#nav-list">Scroll to Nav-List</a>
Setting the references to the scripts. Your path to the files may be different.
<script src="./jquery-1.8.3.min.js"></script>
<script src="./jquery.scrollTo-1.4.3.1-min.js"></script>
Wiring it all up
The code below is borrowed from jQuery easing plugin
jQuery(function ($) {
$.easing.elasout = function (x, t, b, c, d) {
var s = 1.70158; var p = 0; var a = c;
if (t == 0) return b;
if ((t /= d) == 1) return b + c;
if (!p) p = d * .3;
if (a < Math.abs(c)) {
a = c; var s = p / 4;
} else var s = p / (2 * Math.PI) * Math.asin(c / a);
// line breaks added to avoid scroll bar
return a * Math.pow(2, -10 * t) * Math.sin((t * d - s)
* (2 * Math.PI) / p) + c + b;
};
// important reset all scrollable panes to (0,0)
$('div.pane').scrollTo(0);
$.scrollTo(0); // Reset the screen to (0,0)
// adding a click handler for each link
// within the div with the id nav-list
$('#nav-list a').click(function () {
$.scrollTo(this.hash, 1500, {
easing: 'elasout'
});
return false;
});
// adding a click handler for the link at the bottom
$('a.toNav').click(function () {
var scrollTargetId = this.hash;
$.scrollTo(scrollTargetId, 1500, {
easing: 'elasout'
});
return false;
});
});
Fully working demo on plnkr.co
You may take a look at the soucre code for the demo.
Update May 2014
Based on another question i came across another solution from kadaj. Here jQuery animate is used to scroll to an element inside a <div style=overflow-y: scroll>
$(document).ready(function () {
$('.navSection').on('click', function (e) {
debugger;
var elemId = ""; //eg: #nav2
switch (e.target.id) {
case "nav1":
elemId = "#s1";
break;
case "nav2":
elemId = "#s2";
break;
case "nav3":
elemId = "#s3";
break;
case "nav4":
elemId = "#s4";
break;
}
$('.content').animate({
scrollTop: $(elemId).parent().scrollTop()
+ $(elemId).offset().top
- $(elemId).parent().offset().top
}, {
duration: 1000,
specialEasing: { width: 'linear'
, height: 'easeOutBounce' },
complete: function (e) {
//console.log("animation completed");
}
});
e.preventDefault();
});
});

Is it possible to determine where a scroll will end up using javascript? If so, how?

I have a situation where, for example, if a user's scroll will result in a 1000 px change in scrollTop I'd like to know ahead of time.
The perfect example is iCalendar's control over a user's scroll. No matter how hard you scroll in the iCalendar application, the farthest you can scroll is to the next or previous month.
I currently have a very hackish solution to limit scroll behavior, which only takes into account where the user's scroll currently is.
MyConstructor.prototype._stopScroll = function(){
//Cache the previous scroll position and set a flag that will control
//whether or not we stop the scroll
var previous = this._container.scrollTop;
var flag = true;
//Add an event listener that stops the scroll if the flag is set to true
this._container.addEventListener('scroll', function stop(){
if(flag) {
this._container.scrollTop = previous;
}
}.bind(this), false);
//Return a function that has access to the stop function and can remove it
//as an event listener
return function(){
setTimeout(function(){
flag = false;
this._container.removeEventListener('scroll', stop, false);
}.bind(this), 0);
}.bind(this);
};
This approach works, and will stop a scroll in progress, but it is not smooth and I'd love to know if there's a better way to accomplish this.
The key to this question is can I know ahead of time where a scroll will end up. Thanks!!!
Edit: Just found the following project on github:
https://github.com/jquery/jquery-mousewheel
I tried the demo and it's able to report my touchpad and mouse scroll speed. Also it able to stop scrolling without any position fixed hacks :D
I'll have a look in the next few days and see if I can write anything that reports scroll speed, direction, velocity, device etc. Hopefully I'm able to make some jquery plugin that can override all scrolling interaction.
I'll update this post when I've got more info on this subject.
It's impossible to predict where a mouse scroll will end up.
A touchscreen/touchpad swipe on the other hand has a certain speed that will slow down after the user stopped swiping, like a car that got a push and starts slowing down afterwards.
Sadly every browser/os/driver/touchscreen/touchpad/etc has it's own implementation for that slowing down part so we can't predict that.
But we can of course write our own implementation.
We got 3 implementations that could be made:
A. Direction
B. Direction and speed
C. Direction, speed and velocity
iCalender probably uses implementation A.
Implementation A:
Outputs scroll direction to console, user is able to scroll +/- 1px
before the direction is detected.
Demo on JSFiddle
Demo with animation on JSFiddle
(function iDirection() {
var preventLoop = true;
var currentScroll = scrollTop();
function scroll() {
if(preventLoop) {
//Get new scroll position
var newScroll = scrollTop();
//Stop scrolling
preventLoop = false;
freeze(newScroll);
//Check direction
if(newScroll > currentScroll) {
console.log("scrolling down");
//scroll down animation here
} else {
console.log("scrolling up");
//scroll up animation here
}
/*
Time in milliseconds the scrolling is disabled,
in most cases this is equal to the time the animation takes
*/
setTimeout(function() {
//Update scroll position
currentScroll = newScroll;
//Enable scrolling
unfreeze();
/*
Wait 100ms before enabling the direction function again
(to prevent a loop from occuring).
*/
setTimeout(function() {
preventLoop = true;
}, 100);
}, 1000);
}
}
$(window).on("scroll", scroll);
})();
Implementation B:
Outputs scroll direction, distance and average speed to console, user is able to scroll the amount of pixels set in the distance variable.
If the user scrolls fast they might scroll a few more pixels though.
Demo on JSFiddle
(function iDirectionSpeed() {
var distance = 50; //pixels to scroll to determine speed
var preventLoop = true;
var currentScroll = scrollTop();
var currentDate = false;
function scroll() {
if(preventLoop) {
//Set date on scroll
if(!currentDate) {
currentDate = new Date();
}
//Get new scroll position
var newScroll = scrollTop();
var scrolledDistance = Math.abs(currentScroll - newScroll);
//User scrolled `distance` px or scrolled to the top/bottom
if(scrolledDistance >= distance || !newScroll || newScroll == scrollHeight()) {
//Stop scrolling
preventLoop = false;
freeze(newScroll);
//Get new date
var newDate = new Date();
//Calculate time
var time = newDate.getTime() - currentDate.getTime();
//Output speed
console.log("average speed: "+scrolledDistance+"px in "+time+"ms");
/*
To calculate the animation duration in ms:
x: time
y: scrolledDistance
z: distance you're going to animate
animation duration = z / y * x
*/
//Check direction
if(newScroll > currentScroll) {
console.log("scrolling down");
//scroll down animation here
} else {
console.log("scrolling up");
//scroll up animation here
}
/*
Time in milliseconds the scrolling is disabled,
in most cases this is equal to the time the animation takes
*/
setTimeout(function() {
//Update scroll position
currentScroll = newScroll;
//Unset date
currentDate = false;
//Enable scrolling
unfreeze();
/*
Wait 100ms before enabling the direction function again
(to prevent a loop from occuring).
*/
setTimeout(function() {
preventLoop = true;
}, 100);
}, 1000);
}
}
}
$(window).on("scroll", scroll);
})();
Implementation C:
Outputs scroll direction, distance and speeds to console, user is able to scroll the amount of pixels set in the distance variable.
If the user scrolls fast they might scroll a few more pixels though.
Demo on JSFiddle
(function iDirectionSpeedVelocity() {
var distance = 100; //pixels to scroll to determine speed
var preventLoop = true;
var currentScroll = [];
var currentDate = [];
function scroll() {
if(preventLoop) {
//Set date on scroll
currentDate.push(new Date());
//Set scrollTop on scroll
currentScroll.push(scrollTop());
var lastDate = currentDate[currentDate.length - 1];
var lastScroll = currentScroll[currentScroll.length - 1];
//User scrolled `distance` px or scrolled to the top/bottom
if(Math.abs(currentScroll[0] - lastScroll) >= distance || !lastScroll || lastScroll == scrollHeight()) {
//Stop scrolling
preventLoop = false;
freeze(currentScroll[currentScroll.length - 1]);
//Total time
console.log("Time: "+(lastDate.getTime() - currentDate[0].getTime())+"ms");
//Total distance
console.log("Distance: "+Math.abs(lastScroll - currentScroll[0])+"px");
/*
Calculate speeds between every registered scroll
(speed is described in milliseconds per pixel)
*/
var speeds = [];
for(var x = 0; x < currentScroll.length - 1; x++) {
var time = currentDate[x + 1].getTime() - currentDate[x].getTime();
var offset = Math.abs(currentScroll[x - 1] - currentScroll[x]);
if(offset) {
var speed = time / offset;
speeds.push(speed);
}
}
//Output array of registered speeds (milliseconds per pixel)
console.log("speeds (milliseconds per pixel):");
console.log(speeds);
/*
We can use the array of speeds to check if the speed is increasing
or decreasing between the first and last half as example
*/
var half = Math.round(speeds.length / 2);
var equal = half == speeds.length ? 0 : 1;
var firstHalfSpeed = 0;
for(var x = 0; x < half; x++ ) {
firstHalfSpeed += speeds[x];
}
firstHalfSpeed /= half;
var secondHalfSpeed = 0;
for(var x = half - equal; x < speeds.length; x++ ) {
secondHalfSpeed += speeds[x];
}
secondHalfSpeed /= half;
console.log("average first half speed: "+firstHalfSpeed+"ms per px");
console.log("average second half speed: "+secondHalfSpeed+"ms per px");
if(firstHalfSpeed < secondHalfSpeed) {
console.log("conclusion: speed is decreasing");
} else {
console.log("conclusion: speed is increasing");
}
//Check direction
if(lastScroll > currentScroll[0]) {
console.log("scrolling down");
//scroll down animation here
} else {
console.log("scrolling up");
//scroll up animation here
}
/*
Time in milliseconds the scrolling is disabled,
in most cases this is equal to the time the animation takes
*/
setTimeout(function() {
//Unset scroll positions
currentScroll = [];
//Unset dates
currentDate = [];
//Enable scrolling
unfreeze();
/*
Wait 100ms before enabling the direction function again
(to prevent a loop from occuring).
*/
setTimeout(function() {
preventLoop = true;
}, 100);
}, 2000);
}
}
}
$(window).on("scroll", scroll);
})();
Helper functions used in above implementations:
//Source: https://github.com/seahorsepip/jPopup
function freeze(top) {
if(window.innerWidth > document.documentElement.clientWidth) {
$("html").css("overflow-y", "scroll");
}
$("html").css({"width": "100%", "height": "100%", "position": "fixed", "top": -top});
}
function unfreeze() {
$("html").css("position", "static");
$("html, body").scrollTop(-parseInt($("html").css("top")));
$("html").css({"position": "", "width": "", "height": "", "top": "", "overflow-y": ""});
}
function scrollTop() {
return $("html").scrollTop() ? $("html").scrollTop() : $("body").scrollTop();
}
function scrollHeight() {
return $("html")[0].scrollHeight ? $("html")[0].scrollHeight : $("body")[0].scrollHeight;
}
Just had a look at scrollify mentioned in the comments, it's 10kb and needs to hook at every simple event: touch, mouse scroll, keyboard buttons etc.
That doesn't seem very future proof, who know what possible user interaction can cause a scroll in the future?
The onscroll event on the other hand will always be triggered when the page scrolls, so let's just hook the animation code on that without worrying about any input device interaction.
As #seahorsepip states, it is not generally possible to know where a scroll will end up without adding custom behavior with JavaScript. The MDN docs do not list any way to access queued scroll events: https://developer.mozilla.org/en-US/docs/Web/Events/scroll
I found this information helpful:
Normalizing mousewheel speed across browsers
It highlights the difficulty of knowing where the page will go based on user input. My suggestion is to trigger a scroll to Y event when the code predicts the threshold is reached. In your example, if the scroll has moved the page 800 of 1000 pixels in a time window of 250ms, then set the scroll to that 1000 pixel mark and cut off the scroll for 500ms.
https://developer.mozilla.org/en-US/docs/Web/API/window/scrollTo
i'm not pretty sure if i've got what you're looking for. I've had project once, where i had to control the scrolling. Back then i've overwritten the default scroll event, after that you can set a custom distance for "one" scroll. Additionally added jQuery animations to scroll to a specific position.
Here you can take a look: http://c-k.co/zw1/
If that's what you're looking for you can contact me, and i'll see how much i still understand of my own thingy there
is easy to use event listener to do it. Here is a React example:
/**
* scroll promise
*/
const scrollPromiseCallback = useCallback((func:Function) => {
return new Promise((resolve, reject) => {
func(resolve, reject)
})
}, [])
/**
* scroll callback
*/
const scrollCallback = useCallback((scrollContainer, onScrollEnd, resolve) => {
/** 防抖时间 */
const debounceTime = 200
/** 防抖计时器 */
let timer = null
const listener = () => {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
scrollContainer.removeEventListener('scroll', listener)
resolve(true)
onScrollEnd?.()
}, debounceTime)
}
scrollContainer.addEventListener('scroll', listener)
}, [])
const scrollTo = useCallback((props:IUseScrollToProps) => {
return scrollPromiseCallback((resolve, reject) => {
const {
scrollContainer = window, top = 0, left = 0, behavior = 'auto',
} = props
scrollCallback(scrollContainer, props?.onScrollEnd, resolve)
scrollContainer.scrollTo({
top,
left,
behavior,
})
})
}, [scrollCallback, scrollPromiseCallback])

Javascript scroll to div

I have two issues I'm wrestling with at the moment. I'm trying to use javascript to jump to an ID on the page (I like the slow animation JS gives). The javascript I am using though isn't working though. Here's a JS fiddle with what I'm working on:
http://jsfiddle.net/n6H8v/5/
Here is the code(sorry for the long HTML, but had to use it for testing scrolling):
HTML:
<section id="thumbs-work">
<img id="top-cat" src="images/cat.png">
<p> /* Large amount of text to force scrolling */ </p>
</section>
<div class="back-to-top back-to-top-left">
<p><a>
<span class="top-jump">BACK TO TOP</span><img src="images/cat.png" alt="back to top"></a></p>
</div>
</div>
Javascript:
$( document ).ready(function() {
$(".top-jump").click(function() {
$("html, body").animate({ scrollTop: $("#top-cat").offset().top }, 1000);
return true;
});
});
Any help would be greatly appreciated
It's because you didn't load jQuery in JSFiddle. It's in the upper left, under Frameworks & Extensions.
I use this script to accomplish what your doing, now the code doesnt match your class names so you would have to change them, but this will scroll to the bottom then back to the top smoothly.
See Jsfiddle for more info
$('a').click(function(){
$('html, body').animate({
scrollTop: $( $(this).attr('href') ).offset().top
}, 1500);
return false;
});
Here´s a javascript function that might be helpful:
window.smoothScrollTo = (function () {
var timer, start, factor;
return function (target, duration) {
var offset = window.pageYOffset,
delta = target - window.pageYOffset; // Y-offset difference
duration = duration || 500; // default 1 sec animation
start = Date.now(); // get start time
factor = 0;
if( timer ) {
clearInterval(timer); // stop any running animations
}
function step() {
var y;
factor = (Date.now() - start) / duration; // get interpolation factor
if( factor >= 1 ) {
clearInterval(timer); // stop animation
factor = 1; // clip to max 1.0
}
y = factor * delta + offset;
window.scrollBy(0, y - window.pageYOffset);
}
timer = setInterval(step, 10);
//document.getElementById("but").click();
return timer;
};
}());

Categories

Resources