Related
I have a strange issue which I can only replicate on Microsoft browsers (Edge and IE11 tested).
<style>
body {
height: 5000px;
width: 5000px;
}
</style>
<p>Click the button to scroll the document window to 1000 pixels.</p>
<button onclick="scrollWin()">Click me to scroll!</button>
<script>
function scrollWin() {
window.scrollTo({
left: 1000,
top: 1000,
behavior:"smooth"
});
}
</script>
This code correctly scrolls the window 1000px to the left and down, with a smooth behaviour in Chrome and Firefox. However, on Edge and IE, it does not move at all.
As mentioned before, the Scroll Behavior specification has only been implemented in Chrome, Firefox and Opera.
Here's a one-liner to detect support for the behavior property in ScrollOptions:
const supportsNativeSmoothScroll = 'scrollBehavior' in document.documentElement.style;
And here's a simple implementation for cross-browser smooth scrolling: https://gist.github.com/eyecatchup/d210786daa23fd57db59634dd231f341
Maybe not a true answer in the sense of the word, but I have solved this problem by using this helpful polyfill: https://github.com/iamdustan/smoothscroll which works really well across all browsers.
Example page for pollyfill: http://iamdustan.com/smoothscroll/
Many thanks to the author.
You can detect support for the behavior option in scrollTo using this snippet:
function testSupportsSmoothScroll () {
var supports = false
try {
var div = document.createElement('div')
div.scrollTo({
top: 0,
get behavior () {
supports = true
return 'smooth'
}
})
} catch (err) {}
return supports
}
Tested in Chrome, Firefox, Safari, and Edge, and seems to work correctly. If supports is false, you fall back to a polyfill.
Indeed, they don't support this variant, MDN articles should be updated.
One way to polyfill this method is to run the scroll method in a requestAnimationFrame powered loop. Nothing too fancy here.
The main problem that arises is how to detect when this variant is not supported. actually #nlawson's answer tackles this problem perfectly...
For this, we can use the fact that a call to Window#scroll will fire a ScrollEvent if the viewPort actually did scroll.
This means we can set up an asynchronous test that will:
Attach an event handler to the ScrollEvent,
Call a first time scroll(left , top) variant to be sure the Event will fire,
Overwrite this call with a second one using the options variant.
In the event handler, if we aren't at the correct scroll position, this means we need to attach our polyfill.
So the caveat of this test is that it is an asynchronous test. But since you need to actually wait for the document has loaded before calling this method, I guess in 99% of cases it will be ok.
Now to less burden the main doc, and since it is already an asynchronous test, we can even wrap this test inside an iframe, which gives us something like:
/* Polyfills the Window#scroll(options) & Window#scrollTo(options) */
(function ScrollPolyfill() {
// The asynchronous tester
// wrapped in an iframe (will not work in SO's StackSnippet®)
var iframe = document.createElement('iframe');
iframe.onload = function() {
var win = iframe.contentWindow;
// listen for a scroll event
win.addEventListener('scroll', function handler(e){
// when the scroll event fires, check that we did move
if(win.pageXOffset < 99) { // !== 0 should be enough, but better be safe
attachPolyfill();
}
// cleanup
document.body.removeChild(iframe);
});
// set up our document so we can scroll
var body = win.document.body;
body.style.width = body.style.height = '1000px';
win.scrollTo(10, 0); // force the event
win.scrollTo({left:100, behavior:'instant'}); // the one we actually test
};
// prepare our frame
iframe.src = "about:blank";
iframe.setAttribute('width', 1);
iframe.setAttribute('height', 1);
iframe.setAttribute('style', 'position:absolute;z-index:-1');
iframe.onerror = function() {
console.error('failed to load the frame, try in jsfiddle');
};
document.body.appendChild(iframe);
// The Polyfill
function attachPolyfill() {
var original = window.scroll, // keep the original method around
animating = false, // will keep our timer's id
dx = 0,
dy = 0,
target = null;
// override our methods
window.scrollTo = window.scroll = function polyfilledScroll(user_opts) {
// if we are already smooth scrolling, we need to stop the previous one
// whatever the current arguments are
if(animating) {
clearAnimationFrame(animating);
}
// not the object syntax, use the default
if(arguments.length === 2) {
return original.apply(this, arguments);
}
if(!user_opts || typeof user_opts !== 'object') {
throw new TypeError("value can't be converted to a dictionnary");
}
// create a clone to not mess the passed object
// and set missing entries
var opts = {
left: ('left' in user_opts) ? user_opts.left : window.pageXOffset,
top: ('top' in user_opts) ? user_opts.top : window.pageYOffset,
behavior: ('behavior' in user_opts) ? user_opts.behavior : 'auto',
};
if(opts.behavior !== 'instant' && opts.behavior !== 'smooth') {
// parse 'auto' based on CSS computed value of 'smooth-behavior' property
// But note that if the browser doesn't support this variant
// There are good chances it doesn't support the CSS property either...
opts.behavior = window.getComputedStyle(document.scrollingElement || document.body)
.getPropertyValue('scroll-behavior') === 'smooth' ?
'smooth' : 'instant';
}
if(opts.behavior === 'instant') {
// not smooth, just default to the original after parsing the oject
return original.call(this, opts.left, opts.top);
}
// update our direction
dx = (opts.left - window.pageXOffset) || 0;
dy = (opts.top - window.pageYOffset) || 0;
// going nowhere
if(!dx && !dy) {
return;
}
// save passed arguments
target = opts;
// save the rAF id
animating = anim();
};
// the animation loop
function anim() {
var freq = 16 / 300, // whole anim duration is approximately 300ms #60fps
posX, poxY;
if( // we already reached our goal on this axis ?
(dx <= 0 && window.pageXOffset <= +target.left) ||
(dx >= 0 && window.pageXOffset >= +target.left)
){
posX = +target.left;
}
else {
posX = window.pageXOffset + (dx * freq);
}
if(
(dy <= 0 && window.pageYOffset <= +target.top) ||
(dy >= 0 && window.pageYOffset >= +target.top)
){
posY = +target.top;
}
else {
posY = window.pageYOffset + (dx * freq);
}
// move to the new position
original.call(window, posX, posY);
// while we are not ok on both axis
if(posX !== +target.left || posY !== +target.top) {
requestAnimationFrame(anim);
}
else {
animating = false;
}
}
}
})();
Sorry for not providing a runable demo inside the answer directly, but StackSnippet®'s over-protected iframes don't allow us to access the content of an inner iframe on IE...
So instead, here is a link to a jsfiddle.
Post-scriptum:
Now comes to my mind that it might actually be possible to check for support in a synchronous way by checking for the CSS scroll-behavior support, but I'm not sure it really covers all UAs in the history...
Post-Post-scriptum:
Using #nlawson's detection we can now have a working snippet ;-)
/* Polyfills the Window#scroll(options) & Window#scrollTo(options) */
(function ScrollPolyfill() {
// The synchronous tester from #nlawson's answer
var supports = false
test_el = document.createElement('div'),
test_opts = {top:0};
// ES5 style for IE
Object.defineProperty(test_opts, 'behavior', {
get: function() {
supports = true;
}
});
try {
test_el.scrollTo(test_opts);
}catch(e){};
if(!supports) {
attachPolyfill();
}
function attachPolyfill() {
var original = window.scroll, // keep the original method around
animating = false, // will keep our timer's id
dx = 0,
dy = 0,
target = null;
// override our methods
window.scrollTo = window.scroll = function polyfilledScroll(user_opts) {
// if we are already smooth scrolling, we need to stop the previous one
// whatever the current arguments are
if(animating) {
clearAnimationFrame(animating);
}
// not the object syntax, use the default
if(arguments.length === 2) {
return original.apply(this, arguments);
}
if(!user_opts || typeof user_opts !== 'object') {
throw new TypeError("value can't be converted to a dictionnary");
}
// create a clone to not mess the passed object
// and set missing entries
var opts = {
left: ('left' in user_opts) ? user_opts.left : window.pageXOffset,
top: ('top' in user_opts) ? user_opts.top : window.pageYOffset,
behavior: ('behavior' in user_opts) ? user_opts.behavior : 'auto',
};
if(opts.behavior !== 'instant' && opts.behavior !== 'smooth') {
// parse 'auto' based on CSS computed value of 'smooth-behavior' property
// But note that if the browser doesn't support this variant
// There are good chances it doesn't support the CSS property either...
opts.behavior = window.getComputedStyle(document.scrollingElement || document.body)
.getPropertyValue('scroll-behavior') === 'smooth' ?
'smooth' : 'instant';
}
if(opts.behavior === 'instant') {
// not smooth, just default to the original after parsing the oject
return original.call(this, opts.left, opts.top);
}
// update our direction
dx = (opts.left - window.pageXOffset) || 0;
dy = (opts.top - window.pageYOffset) || 0;
// going nowhere
if(!dx && !dy) {
return;
}
// save passed arguments
target = opts;
// save the rAF id
animating = anim();
};
// the animation loop
function anim() {
var freq = 16 / 300, // whole anim duration is approximately 300ms #60fps
posX, poxY;
if( // we already reached our goal on this axis ?
(dx <= 0 && window.pageXOffset <= +target.left) ||
(dx >= 0 && window.pageXOffset >= +target.left)
){
posX = +target.left;
}
else {
posX = window.pageXOffset + (dx * freq);
}
if(
(dy <= 0 && window.pageYOffset <= +target.top) ||
(dy >= 0 && window.pageYOffset >= +target.top)
){
posY = +target.top;
}
else {
posY = window.pageYOffset + (dx * freq);
}
// move to the new position
original.call(window, posX, posY);
// while we are not ok on both axis
if(posX !== +target.left || posY !== +target.top) {
requestAnimationFrame(anim);
}
else {
animating = false;
}
}
}
})();
// OP's code,
// by the time you click the button, the polyfill should already be set up if needed
function scrollWin() {
window.scrollTo({
left: 1000,
top: 1000,
behavior: 'smooth'
});
}
body {
height: 5000px;
width: 5000px;
}
<p>Click the button to scroll the document window to 1000 pixels.</p>
<button onclick="scrollWin()">Click me to scroll!</button>
Unfortunately, there is no way for that method to work in these two browsers.
You can check open issues here and see that they have done nothing on this issue.
https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/15534521/
You can try to use Element.ScrollLeft and Element.ScrollTop property with Window.scrollTo().
Below is the example which is working with Edge and other browsers.
<html>
<style>
body {
height: 5000px;
width: 5000px;
}
</style>
<p>Click the button to scroll the document window to 1000 pixels.</p>
<button onclick="scrollWin(this)">Click me to scroll!</button>
<script>
function scrollWin(pos) {
window.scrollTo(pos.offsetTop+1000,pos.offsetLeft+1000);
}
</script>
</html>
Smooth behavior is not working with this code.
Reference:
Element.scrollLeft
Element.scrollTop
Regards
Deepak
The "smoothscroll" polyfill supports only "smooth" option. To support all options in scrollIntoViewOptions it's better to use seamless-scroll-polyfill (https://www.npmjs.com/package/seamless-scroll-polyfill)
Worked for me.
Here is a link with explanation https://github.com/Financial-Times/polyfill-library/issues/657
I have a strange issue which I can only replicate on Microsoft browsers (Edge and IE11 tested).
<style>
body {
height: 5000px;
width: 5000px;
}
</style>
<p>Click the button to scroll the document window to 1000 pixels.</p>
<button onclick="scrollWin()">Click me to scroll!</button>
<script>
function scrollWin() {
window.scrollTo({
left: 1000,
top: 1000,
behavior:"smooth"
});
}
</script>
This code correctly scrolls the window 1000px to the left and down, with a smooth behaviour in Chrome and Firefox. However, on Edge and IE, it does not move at all.
As mentioned before, the Scroll Behavior specification has only been implemented in Chrome, Firefox and Opera.
Here's a one-liner to detect support for the behavior property in ScrollOptions:
const supportsNativeSmoothScroll = 'scrollBehavior' in document.documentElement.style;
And here's a simple implementation for cross-browser smooth scrolling: https://gist.github.com/eyecatchup/d210786daa23fd57db59634dd231f341
Maybe not a true answer in the sense of the word, but I have solved this problem by using this helpful polyfill: https://github.com/iamdustan/smoothscroll which works really well across all browsers.
Example page for pollyfill: http://iamdustan.com/smoothscroll/
Many thanks to the author.
You can detect support for the behavior option in scrollTo using this snippet:
function testSupportsSmoothScroll () {
var supports = false
try {
var div = document.createElement('div')
div.scrollTo({
top: 0,
get behavior () {
supports = true
return 'smooth'
}
})
} catch (err) {}
return supports
}
Tested in Chrome, Firefox, Safari, and Edge, and seems to work correctly. If supports is false, you fall back to a polyfill.
Indeed, they don't support this variant, MDN articles should be updated.
One way to polyfill this method is to run the scroll method in a requestAnimationFrame powered loop. Nothing too fancy here.
The main problem that arises is how to detect when this variant is not supported. actually #nlawson's answer tackles this problem perfectly...
For this, we can use the fact that a call to Window#scroll will fire a ScrollEvent if the viewPort actually did scroll.
This means we can set up an asynchronous test that will:
Attach an event handler to the ScrollEvent,
Call a first time scroll(left , top) variant to be sure the Event will fire,
Overwrite this call with a second one using the options variant.
In the event handler, if we aren't at the correct scroll position, this means we need to attach our polyfill.
So the caveat of this test is that it is an asynchronous test. But since you need to actually wait for the document has loaded before calling this method, I guess in 99% of cases it will be ok.
Now to less burden the main doc, and since it is already an asynchronous test, we can even wrap this test inside an iframe, which gives us something like:
/* Polyfills the Window#scroll(options) & Window#scrollTo(options) */
(function ScrollPolyfill() {
// The asynchronous tester
// wrapped in an iframe (will not work in SO's StackSnippet®)
var iframe = document.createElement('iframe');
iframe.onload = function() {
var win = iframe.contentWindow;
// listen for a scroll event
win.addEventListener('scroll', function handler(e){
// when the scroll event fires, check that we did move
if(win.pageXOffset < 99) { // !== 0 should be enough, but better be safe
attachPolyfill();
}
// cleanup
document.body.removeChild(iframe);
});
// set up our document so we can scroll
var body = win.document.body;
body.style.width = body.style.height = '1000px';
win.scrollTo(10, 0); // force the event
win.scrollTo({left:100, behavior:'instant'}); // the one we actually test
};
// prepare our frame
iframe.src = "about:blank";
iframe.setAttribute('width', 1);
iframe.setAttribute('height', 1);
iframe.setAttribute('style', 'position:absolute;z-index:-1');
iframe.onerror = function() {
console.error('failed to load the frame, try in jsfiddle');
};
document.body.appendChild(iframe);
// The Polyfill
function attachPolyfill() {
var original = window.scroll, // keep the original method around
animating = false, // will keep our timer's id
dx = 0,
dy = 0,
target = null;
// override our methods
window.scrollTo = window.scroll = function polyfilledScroll(user_opts) {
// if we are already smooth scrolling, we need to stop the previous one
// whatever the current arguments are
if(animating) {
clearAnimationFrame(animating);
}
// not the object syntax, use the default
if(arguments.length === 2) {
return original.apply(this, arguments);
}
if(!user_opts || typeof user_opts !== 'object') {
throw new TypeError("value can't be converted to a dictionnary");
}
// create a clone to not mess the passed object
// and set missing entries
var opts = {
left: ('left' in user_opts) ? user_opts.left : window.pageXOffset,
top: ('top' in user_opts) ? user_opts.top : window.pageYOffset,
behavior: ('behavior' in user_opts) ? user_opts.behavior : 'auto',
};
if(opts.behavior !== 'instant' && opts.behavior !== 'smooth') {
// parse 'auto' based on CSS computed value of 'smooth-behavior' property
// But note that if the browser doesn't support this variant
// There are good chances it doesn't support the CSS property either...
opts.behavior = window.getComputedStyle(document.scrollingElement || document.body)
.getPropertyValue('scroll-behavior') === 'smooth' ?
'smooth' : 'instant';
}
if(opts.behavior === 'instant') {
// not smooth, just default to the original after parsing the oject
return original.call(this, opts.left, opts.top);
}
// update our direction
dx = (opts.left - window.pageXOffset) || 0;
dy = (opts.top - window.pageYOffset) || 0;
// going nowhere
if(!dx && !dy) {
return;
}
// save passed arguments
target = opts;
// save the rAF id
animating = anim();
};
// the animation loop
function anim() {
var freq = 16 / 300, // whole anim duration is approximately 300ms #60fps
posX, poxY;
if( // we already reached our goal on this axis ?
(dx <= 0 && window.pageXOffset <= +target.left) ||
(dx >= 0 && window.pageXOffset >= +target.left)
){
posX = +target.left;
}
else {
posX = window.pageXOffset + (dx * freq);
}
if(
(dy <= 0 && window.pageYOffset <= +target.top) ||
(dy >= 0 && window.pageYOffset >= +target.top)
){
posY = +target.top;
}
else {
posY = window.pageYOffset + (dx * freq);
}
// move to the new position
original.call(window, posX, posY);
// while we are not ok on both axis
if(posX !== +target.left || posY !== +target.top) {
requestAnimationFrame(anim);
}
else {
animating = false;
}
}
}
})();
Sorry for not providing a runable demo inside the answer directly, but StackSnippet®'s over-protected iframes don't allow us to access the content of an inner iframe on IE...
So instead, here is a link to a jsfiddle.
Post-scriptum:
Now comes to my mind that it might actually be possible to check for support in a synchronous way by checking for the CSS scroll-behavior support, but I'm not sure it really covers all UAs in the history...
Post-Post-scriptum:
Using #nlawson's detection we can now have a working snippet ;-)
/* Polyfills the Window#scroll(options) & Window#scrollTo(options) */
(function ScrollPolyfill() {
// The synchronous tester from #nlawson's answer
var supports = false
test_el = document.createElement('div'),
test_opts = {top:0};
// ES5 style for IE
Object.defineProperty(test_opts, 'behavior', {
get: function() {
supports = true;
}
});
try {
test_el.scrollTo(test_opts);
}catch(e){};
if(!supports) {
attachPolyfill();
}
function attachPolyfill() {
var original = window.scroll, // keep the original method around
animating = false, // will keep our timer's id
dx = 0,
dy = 0,
target = null;
// override our methods
window.scrollTo = window.scroll = function polyfilledScroll(user_opts) {
// if we are already smooth scrolling, we need to stop the previous one
// whatever the current arguments are
if(animating) {
clearAnimationFrame(animating);
}
// not the object syntax, use the default
if(arguments.length === 2) {
return original.apply(this, arguments);
}
if(!user_opts || typeof user_opts !== 'object') {
throw new TypeError("value can't be converted to a dictionnary");
}
// create a clone to not mess the passed object
// and set missing entries
var opts = {
left: ('left' in user_opts) ? user_opts.left : window.pageXOffset,
top: ('top' in user_opts) ? user_opts.top : window.pageYOffset,
behavior: ('behavior' in user_opts) ? user_opts.behavior : 'auto',
};
if(opts.behavior !== 'instant' && opts.behavior !== 'smooth') {
// parse 'auto' based on CSS computed value of 'smooth-behavior' property
// But note that if the browser doesn't support this variant
// There are good chances it doesn't support the CSS property either...
opts.behavior = window.getComputedStyle(document.scrollingElement || document.body)
.getPropertyValue('scroll-behavior') === 'smooth' ?
'smooth' : 'instant';
}
if(opts.behavior === 'instant') {
// not smooth, just default to the original after parsing the oject
return original.call(this, opts.left, opts.top);
}
// update our direction
dx = (opts.left - window.pageXOffset) || 0;
dy = (opts.top - window.pageYOffset) || 0;
// going nowhere
if(!dx && !dy) {
return;
}
// save passed arguments
target = opts;
// save the rAF id
animating = anim();
};
// the animation loop
function anim() {
var freq = 16 / 300, // whole anim duration is approximately 300ms #60fps
posX, poxY;
if( // we already reached our goal on this axis ?
(dx <= 0 && window.pageXOffset <= +target.left) ||
(dx >= 0 && window.pageXOffset >= +target.left)
){
posX = +target.left;
}
else {
posX = window.pageXOffset + (dx * freq);
}
if(
(dy <= 0 && window.pageYOffset <= +target.top) ||
(dy >= 0 && window.pageYOffset >= +target.top)
){
posY = +target.top;
}
else {
posY = window.pageYOffset + (dx * freq);
}
// move to the new position
original.call(window, posX, posY);
// while we are not ok on both axis
if(posX !== +target.left || posY !== +target.top) {
requestAnimationFrame(anim);
}
else {
animating = false;
}
}
}
})();
// OP's code,
// by the time you click the button, the polyfill should already be set up if needed
function scrollWin() {
window.scrollTo({
left: 1000,
top: 1000,
behavior: 'smooth'
});
}
body {
height: 5000px;
width: 5000px;
}
<p>Click the button to scroll the document window to 1000 pixels.</p>
<button onclick="scrollWin()">Click me to scroll!</button>
Unfortunately, there is no way for that method to work in these two browsers.
You can check open issues here and see that they have done nothing on this issue.
https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/15534521/
You can try to use Element.ScrollLeft and Element.ScrollTop property with Window.scrollTo().
Below is the example which is working with Edge and other browsers.
<html>
<style>
body {
height: 5000px;
width: 5000px;
}
</style>
<p>Click the button to scroll the document window to 1000 pixels.</p>
<button onclick="scrollWin(this)">Click me to scroll!</button>
<script>
function scrollWin(pos) {
window.scrollTo(pos.offsetTop+1000,pos.offsetLeft+1000);
}
</script>
</html>
Smooth behavior is not working with this code.
Reference:
Element.scrollLeft
Element.scrollTop
Regards
Deepak
The "smoothscroll" polyfill supports only "smooth" option. To support all options in scrollIntoViewOptions it's better to use seamless-scroll-polyfill (https://www.npmjs.com/package/seamless-scroll-polyfill)
Worked for me.
Here is a link with explanation https://github.com/Financial-Times/polyfill-library/issues/657
Reading the AngularJS docs I haven't figured out if $anchorScroll can have a duration/easing option to smooth scroll to elements.
It only says:
$location.hash('bottom');
// call $anchorScroll()
$anchorScroll();
I do not use jquery and don't want to; is there still a clever yet simple way to make or extend $anchorScroll in order to make scrolling more smooth?
Unfortunately this is not possible using $anchorScroll. As you discovered $anchorScroll doesn't have any options and doesn't work with $ngAnimate. In order to animate the scroll you would need to use your own service/factory or just straight javascript.
For the sake of self-learning I put together an example with a smooth scrolling service. There are probably better ways to do this so any feedback is encouraged.
To scroll to an element you attach a ng-click="gotoElement(ID)" to any element. I think an even better route would be to make this a directive.
Here's the working example on jsFiddle.
Update
There are now a number of third-party directives for accomplishing this.
https://github.com/oblador/angular-scroll.
https://github.com/d-oliveros/ngSmoothScroll
https://github.com/arnaudbreton/angular-smoothscroll
https://gist.github.com/justinmc/d72f38339e0c654437a2
You can also use the angular-scroll, link "https://github.com/durated/angular-scroll/". It is smooth scrolling also few easing functions to get a professional look.
The answer from Brett worked great for me. I did some small changes on his solution in terms of modularization and testability.
Here's is yet another working example on JsFiddle that includes the other version with testing included.
For testing, I'm using Karma and Jasmine. Signature has been slightly modified as follows:
anchorSmoothScroll.scrollTo(elementId, speed);
Where element is a mandatory attribute to scroll to and speed is optional where the default is 20 (as it was before).
You can also use ngSmoothScroll, link: https://github.com/d-oliveros/ngSmoothScroll.
Just include the smoothScroll module as a dependency and use it like this:
Click me!
None of the solutions here actually does what OP originally asked, that is, make $anchorScroll scrolling smoothly.
Difference between smooth scrolling directives and $anchroScroll is that it uses/modifies $location.hash(), which may be desirable in some cases.
Here is gist for simple module that replaces $anchorScroll scrolling with smooth scrolling. It uses https://github.com/oblador/angular-scroll library for the scrolling itself (replace it with something else if you want, it should be easy).
https://gist.github.com/mdvorak/fc8b531d3e082f3fdaa9
Note: It actually does not get $anchorScroll to scroll smoothly, but it replaces its handler for scrolling.
Enable it simply by referencing mdvorakSmoothScroll module in your application.
Alan, thank you. If anyone interested, I formatted it based on John Pappa standards.
(function() {
'use strict';
var moduleId = 'common';
var serviceId = 'anchorSmoothScroll';
angular
.module(moduleId)
.service(serviceId, anchorSmoothScroll);
anchorSmoothScroll.$inject = ['$document', '$window'];
function anchorSmoothScroll($document, $window) {
var document = $document[0];
var window = $window;
var service = {
scrollDown: scrollDown,
scrollUp: scrollUp,
scrollTo: scrollTo,
scrollToTop: scrollToTop
};
return service;
function getCurrentPagePosition(currentWindow, doc) {
// Firefox, Chrome, Opera, Safari
if (currentWindow.pageYOffset) return currentWindow.pageYOffset;
// Internet Explorer 6 - standards mode
if (doc.documentElement && doc.documentElement.scrollTop)
return doc.documentElement.scrollTop;
// Internet Explorer 6, 7 and 8
if (doc.body.scrollTop) return doc.body.scrollTop;
return 0;
}
function getElementY(doc, element) {
var y = element.offsetTop;
var node = element;
while (node.offsetParent && node.offsetParent !== doc.body) {
node = node.offsetParent;
y += node.offsetTop;
}
return y;
}
function scrollDown(startY, stopY, speed, distance) {
var timer = 0;
var step = Math.round(distance / 25);
var leapY = startY + step;
for (var i = startY; i < stopY; i += step) {
setTimeout('window.scrollTo(0, ' + leapY + ')', timer * speed);
leapY += step;
if (leapY > stopY) leapY = stopY;
timer++;
}
};
function scrollUp(startY, stopY, speed, distance) {
var timer = 0;
var step = Math.round(distance / 25);
var leapY = startY - step;
for (var i = startY; i > stopY; i -= step) {
setTimeout('window.scrollTo(0, ' + leapY + ')', timer * speed);
leapY -= step;
if (leapY < stopY) leapY = stopY;
timer++;
}
};
function scrollToTop(stopY) {
scrollTo(0, stopY);
};
function scrollTo(elementId, speed) {
var element = document.getElementById(elementId);
if (element) {
var startY = getCurrentPagePosition(window, document);
var stopY = getElementY(document, element);
var distance = stopY > startY ? stopY - startY : startY - stopY;
if (distance < 100) {
this.scrollToTop(stopY);
} else {
var defaultSpeed = Math.round(distance / 100);
speed = speed || (defaultSpeed > 20 ? 20 : defaultSpeed);
if (stopY > startY) {
this.scrollDown(startY, stopY, speed, distance);
} else {
this.scrollUp(startY, stopY, speed, distance);
}
}
}
};
};
})();
I am not aware of how to animate $anchorScroll . Here's how I do it in my projects:
/* Scroll to top on each ui-router state change */
$rootScope.$on('$stateChangeStart', function() {
scrollToTop();
});
And the JS function:
function scrollToTop() {
if (typeof jQuery == 'undefined') {
return window.scrollTo(0,0);
} else {
var body = $('html, body');
body.animate({scrollTop:0}, '600', 'swing');
}
log("scrollToTop");
return true;
}
We can use JQuery and Javascript with Directive to perform the scrolling to a specific div on anchor tag click.
Please check the working example on the below link -
https://stackoverflow.com/a/67513880/6656918
I have developed a Windows application using TideSDK with HTML , CSS and Javascript.To give it a widget look I disappered its title bar.So I can't drag it.The TideSDK documentation says "after disappearing title bars, you can drag window using its contents with javascript".
How can I drag the window with javascript selecting its components(tiles in my case)?Any tutorial or code as I found nothing on google with my search.
My App looks like below:
I've written the following generic function for dragging using JS:
function setDragOfElementOnAnother ($draggableElement, $draggedElememnt, allowDragPredicate) {
var stopDragFunction = function() {
$draggedElememnt.data("drag", false);
$draggedElememnt.removeData("startPoint");
$("html, body").unbind("mouseup.drag");
$("html, body").unbind("mousemove.drag");
};
var dragFunction = function(e) {
if (!parseBoolean($draggedElememnt.data("drag")))
return;
var begin = $draggedElememnt.data("startPoint");
$draggedElememnt.css({ left: e.clientX - begin.x, top: e.clientY - begin.y });
};
$draggableElement.mousedown(function(e) {
if ((e.clientX - $(this).offset().left < 0 || $(this).offset().left + $(this).width() < e.clientX) ||
e.clientY - $(this).offset().top < 0)
return;
if (allowDragPredicate && !allowDragPredicate(e))
return;
$draggedElememnt.data("drag", true);
$draggedElememnt.data("startPoint", Point(e.clientX - $(this).offset().left, e.clientY - $(this).offset().top));
$("html, body").bind("mouseup.drag", stopDragFunction);
$("html, body").bind("mousemove.drag", dragFunction);
});
$draggableElement.mouseup(stopDragFunction);
$draggableElement.mousemove(dragFunction);
}
This function is used to declare dragging of one element using another.
Prerequisites:
(1) The dragged element's position has to be either fixed or absolute (relative should also work). (2) jQuery.
By specifying both dragged and draggable elements as the same one, pressing on the element and moving the mouse will cause the element to drag.
The 'allowDragPredicate' variable is optional, and is useful to create boundaries.
EDIT: Forgot. Also add the code:
function Point(xVal, yVal) {
if (xVal === undefined) {
xVal = 0;
}
if (yVal === undefined) {
yVal = 0;
}
return {
x: xVal,
y: yVal
};
}
EDIT 2: And:
function parseBoolean(str) {
if (str === true)
return true;
if (str)
return /^true$/i.test(str);
return false;
}
EDIT 3: And, added a simple jsFiddle example.
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();
});
});