Smooth horizontal wheel only scrolling - javascript

It is very annoing to hold shift when wheeling for horizontal scrolling.
There are some ways to horizontally scrool with wheel only but they directly copy wheel deltaY into scrollLeft which results in uncomfortable jump-scrolling.
Is there a mobile-friendly way to perform smooth horizontal scrolling on elements using wheel only? By "smooth" I mean something like default browser vertical wheel scroll behaviour.

Smooth scrolling can be achieved by using requestAnimationFrame.
You might want to play with denominator in getScrollStep function to adjust the smoothness of scrolling.
function horizontalWheel(container) {
/** Max `scrollLeft` value */
let scrollWidth;
/** Desired scroll distance per animation frame */
let getScrollStep = () => scrollWidth / 50 /* ADJUST TO YOUR WISH */ ;
/** Target value for `scrollLeft` */
let targetLeft;
function scrollLeft() {
let beforeLeft = container.scrollLeft;
let wantDx = getScrollStep();
let diff = targetLeft - container.scrollLeft;
let dX = wantDx >= Math.abs(diff) ? diff : Math.sign(diff) * wantDx;
// Performing horizontal scroll
container.scrollBy(dX, 0);
// Break if smaller `diff` instead of `wantDx` was used
if (dX === diff)
return;
// Break if can't scroll anymore or target reached
if (beforeLeft === container.scrollLeft || container.scrollLeft === targetLeft)
return;
requestAnimationFrame(scrollLeft);
}
container.addEventListener('wheel', e => {
e.preventDefault();
scrollWidth = container.scrollWidth - container.clientWidth;
targetLeft = Math.min(scrollWidth, Math.max(0, container.scrollLeft + e.deltaY));
requestAnimationFrame(scrollLeft);
});
}
Just pass elements you want to horizontally scroll with wheel in this function:
let list = document.querySelector('.hList');
horizontalWheel(list);
Example:
function horizontalWheel(container) {
/** Max `scrollLeft` value */
let scrollWidth;
/** Desired scroll distance per animation frame */
let getScrollStep = () => scrollWidth / 50 /* ADJUST TO YOUR WISH */ ;
/** Target value for `scrollLeft` */
let targetLeft;
function scrollLeft() {
let beforeLeft = container.scrollLeft;
let wantDx = getScrollStep();
let diff = targetLeft - container.scrollLeft;
let dX = wantDx >= Math.abs(diff) ? diff : Math.sign(diff) * wantDx;
// Performing horizontal scroll
container.scrollBy(dX, 0);
// Break if smaller `diff` instead of `wantDx` was used
if (dX === diff)
return;
// Break if can't scroll anymore or target reached
if (beforeLeft === container.scrollLeft || container.scrollLeft === targetLeft)
return;
requestAnimationFrame(scrollLeft);
}
container.addEventListener('wheel', e => {
e.preventDefault();
scrollWidth = container.scrollWidth - container.clientWidth;
targetLeft = Math.min(scrollWidth, Math.max(0, container.scrollLeft + e.deltaY));
requestAnimationFrame(scrollLeft);
});
}
//
// Usage
//
window.addEventListener('load', () => {
let list = document.querySelector('.hList');
horizontalWheel(list);
});
.hList {
width: 300px;
border: 2px solid red;
display: flex;
overflow: auto;
}
.element {
display: flex;
align-items: center;
justify-content: center;
color: white;
font-family: sans-serif;
flex-shrink: 0;
width: 150px;
height: 75px;
background: green;
border-right: 2px solid yellow;
}
.element:last-of-type {
border: none;
}
<div class="hList">
<div class="element">Element 1</div>
<div class="element">Element 2</div>
<div class="element">Element 3</div>
<div class="element">Element 4</div>
<div class="element">Element 5</div>
</div>

Related

Fade in opacity on fixed div during scroll up

I'm trying to make a fixed div fade in when the page scrolls. Using the code bellow for another div class and it works perfectly, however this fades out and I need to reverse it so the div fades in.
var turn = document.getElementById('turn');
window.addEventListener('scroll', function(e) {
// http://stackoverflow.com/a/28633515/962603
var scroll = window.pageYOffset || document.documentElement.scrollTop ||
document.body.scrollTop || 0;
turn.style.opacity = Math.max(0.1, Math.min(1, -scroll / 400 + 2));
});
I have tried swapping the min and max value but doesn't seem to work. Thanks!
Example with fixed div and fade in opacity during scroll:
var turn = document.getElementById('turn');
updateOpacity(turn, 0);
window.addEventListener('scroll', function(e) {
var limit = document.body.scrollHeight - window.innerHeight;
var scroll = window.pageYOffset ||
document.documentElement.scrollTop ||
document.body.scrollTop || 0;
updateOpacity(turn, scroll / limit);
randomFontColor(turn); // optional
});
function updateOpacity(element, opacity) {
element.style.opacity = opacity;
}
function randomFontColor(element) {
var randColor = Math.floor(Math.random() * 9999).toString();
var hexColor = "#" + randColor.padStart(6, 'FF');
element.style.color = hexColor;
}
body {
height: 2600px;
}
#turn {
left: 6%;
width: 88%;
position: fixed;
}
.highlight {
color: yellow;
line-height: 42px;
background: black;
font-weight: bold;
text-align: center;
}
<div id="turn" class="highlight">
Fixed top div with Fade in Effect on Scroll
</div>

How to console log when offsetLeft reach a number of pixels

I'm creating a simple game: drag en element far enough (200px) The console logs fine, when I drag the element 200px the console reads 200px, but I still get the wrong message.(not there yet)..
function far_enough() {
console.log('You have moved the box ' + el.offsetLeft + 'pixels');
if(el.offsetleft == 200){
console.log('200px great!');
} else {
console.log('not there yet!');
}
}
}
To answer your question, element.offsetLeft can return a decimal value. This means that it may return 200.01px instead of 200px exactly. This is what's causing your code to not work. A simple work-around is simply to use Math.round(element.offsetLeft) to return an integer value. However, even when using this, there's a chance that the offsetLeft does not return 200px exactly, especially when you drag the element too fast (the browser does not repaint for each pixel moved). Another solution is to use a range like from 200px to 250px.
I suppose that you're using position: absolute and manipulating left CSS property to cause the element to be draggable. However, moving elements with transform is better than using position. I highly suggest reading this article on why.
As you're moving your element using transform now, the offsetLeft value of the element never changes. Therefore, you can alternatively get the rendered box position using getBoundingClientRect(). Here's an example using a getBoundingClientRect(), which returns a left value relative to the viewport.
const draggable = document.querySelector('#draggable')
function farEnough() {
const box = draggable.getBoundingClientRect()
// Get center of box; move center of box by 200px to return true
let center = box.left + (box.right - box.left) / 2
if (center >= 200 && center <= 300) return true
else return false
}
let dragging = false
let dragStartX = null
let dragStartY = null
let draggableMovedX = null
let draggableMovedY = null
function dragHandler(e) {
dragging = true
dragStartX = e.clientX
dragStartY = e.clientY
}
function moveHandler(e) {
if (dragging) {
let moveX = e.clientX - dragStartX + draggableMovedX
let moveY = e.clientY - dragStartY + draggableMovedY
draggable.style.transform = `translate(${moveX}px, ${moveY}px)`
if (farEnough()) {
draggable.removeEventListener('mousedown', dragHandler)
window.removeEventListener('mousemove', moveHandler)
window.removeEventListener('mouseup', leaveHandler)
dragging = false
dragStartX = null
dragStartY = null
// 175px so that the center of the box is exactly at 200px from the left
draggable.style.transform = `translate(175px, ${moveY}px)`
console.log('You did it! You moved it 200px to the right!')
}
}
}
function leaveHandler(e) {
dragging = false
dragStartX = null
dragStartY = null
const box = draggable.getBoundingClientRect()
draggableMovedX = box.left
draggableMovedY = box.top
}
draggable.addEventListener('mousedown', dragHandler)
window.addEventListener('mousemove', moveHandler)
window.addEventListener('mouseup', leaveHandler)
body,
html {
width: 100%;
height: 100%;
position: relative;
margin: 0;
}
#draggable {
width: 50px;
height: 50px;
border-radius: 5px;
background: #121212;
cursor: grab;
}
#draggable:active {
cursor: grabbing;
}
#line {
position: absolute;
top: 0;
left: 200px;
width: 2px;
height: 100%;
background: red;
transform: translateX(-50%);
}
<div id="draggable"></div>
<div id="line"></div>
try
function far_enough()
{
let leftOffset = el.offsetLeft;
console.log('You have moved the box ' + leftOffset + 'pixels');
if (leftOffset == 200)
{
console.log('200px great!');
}
else
{
console.log('not there yet!');
}
}

Javascript Track page scroll depth on webpage load after 25%, 50%, 75% and 100% only if a user waits for 2seconds

To fire a console.log if a user scrolls on a webpage 25% and waits for 2 seconds or more. Similarly fire 50% scroll after a user scrolls a webpage 50% and waits for 2 seconds and similarly for 75% and 100%.
For example a user scrolls directly to 50% and waits there for 2 seconds then fire only 50% and not 25%. And also when the user scrolls to 100% quickly without 2 secs halt and then goes back to top do not fire any console.log as user has'nt waited for 2 secs. Pls help in adding this 2 seconds halt and then firing the console.log,
I did this using Javascript it is working but now how to add 2 seconds ?
var maxScrollHeight = document.documentElement.scrollHeight - document.documentElement.clientHeight;
var percentageObj = {};
var percentagesArr = [25,50,75,100];
window.addEventListener("scroll", function (event) {
var scrollVal = this.scrollY;
for(var i =0; i<percentagesArr.length;i++){
var currentPercentage = percentagesArr[i];
var scrollPercentage = parseInt((maxScrollHeight/100) * currentPercentage);
if(scrollVal >= scrollPercentage && !window.percentageObj[scrollPercentage.toString()]){
console.log("scrolled past - " + currentPercentage.toString() + "%");
window.percentageObj[scrollPercentage.toString()] = true;
}
}
});
Use setTimeout to delay logging and clearTimeout whenever page percentage changes:
var maxScrollHeight = document.documentElement.scrollHeight - document.documentElement.clientHeight;
var percentagesArr = [25,50,75,100];
const showed = {};
let timeout;
let previousPercentage;
window.addEventListener("scroll", function (event) {
var scrollVal = this.scrollY;
var scrollPercentage = Math.round(scrollVal / maxScrollHeight * 100);
let currentPercentage = 0;
let i = 0;
while(percentagesArr[i] <= scrollPercentage) {
currentPercentage = percentagesArr[i++];
}
if (previousPercentage !== currentPercentage) {
clearTimeout(timeout);
timeout = currentPercentage !== 0 && !showed[currentPercentage]
? setTimeout(() => {
console.log(currentPercentage);
showed[currentPercentage] = true;
}, 2000)
: null;
previousPercentage = currentPercentage;
}
});
window.addEventListener("resize", () => {
maxScrollHeight = document.documentElement.scrollHeight - document.documentElement.clientHeight;
})
body {
height: 500vh;
position: relative;
margin: 0;
}
#container div {
height: 30px;
background-color: red;
position: absolute;
width: 100%;
font-family: sans-serif;
justify-content: center;
display: flex;
align-items: center;
}
#p25 {
top: calc(25% * 4/5);
}
#p50 {
top: calc(50% * 4/5);
}
#p75 {
top: calc(75% * 4/5);
}
<div id="container">
<div id="p25">25%</div>
<div id="p50">50%</div>
<div id="p75">75%</div>
</div>
var maxScrollHeight = document.documentElement.scrollHeight - document.documentElement.clientHeight;
var percentageObj = {};
var percentagesArr = [25,50,75,100];
window.addEventListener("scroll", function (event) {
var scrollVal = this.scrollY;
for(var i =0; i<percentagesArr.length;i++){
var currentPercentage = percentagesArr[i];
var scrollPercentage = parseInt((maxScrollHeight/100) * currentPercentage);
let currentTop = window.scrollY;
if(scrollVal >= scrollPercentage && !window.percentageObj[scrollPercentage.toString()]){
setTimeout((currentTop)=>{
if(currentTop === window.scrollY){
console.log("scrolled past - " + currentPercentage.toString() + "%");
window.percentageObj[scrollPercentage.toString()] = true;
}
},2000)
}
}
});
try this.

Recalculate scrolling div position when used in a clipping path

I am using clipping paths to change my logo colour base on the background colour.
In addition to this the logo scrolls from top to bottom based on the users vertical position on the page. Top of page = logo at top, bottom of page = logo at bottom etc.
Unfortunately when I added the clipping paths the logos lost their scroll position and after the first one, do not work at all.
Is there a way around this? Also, the logo position was a little off to start with so if there is any way of addressing this at the same time.
You can see the original question here:
div position based on scroll position
I have tried this, but I can't seem to get it to work.
Scroll position lost when hiding div
I am using Advanced Custom Fields and each sections PHP file has this in the header as part of the clipping path using either the white or dark version of the logo accordingly. Its parent is positioned relatively and its child absolutely.
div class="logo-scroll">
<div class="scroll-text">
<img width="53px" height="260px" src="/wp-content/uploads/2019/07/sheree-walker-web-design-edinburgh-vertical-01.svg"/>
</div>
</div>
The Javascript
const docHeight = Math.max(document.documentElement.scrollHeight, document.body.scrollHeight);
const logo = document.querySelector('.scroll-text');
const logoHeight = logo.offsetHeight;
// to get the pseudoelement's '#page::before' top we use getComputedStyle method
const barTopMargin = parseInt(getComputedStyle(document.querySelector('#page'), '::before').top);
let viewportHeight, barHeight, maxScrollDist, currentScrollPos, scrollFraction;
logo.style.top = barTopMargin + 'px';
window.addEventListener('load', update);
window.addEventListener('resize', setSizes);
document.addEventListener('scroll', update);
setSizes();
function update() {
currentScrollPos = Math.max(document.documentElement.scrollTop, document.body.scrollTop);
scrollFraction = currentScrollPos / (docHeight - viewportHeight);
logo.style.top = barTopMargin + (scrollFraction * maxScrollDist) + 'px';
}
function setSizes() {
viewportHeight = window.innerHeight;
// to get the pseudoelement's '#page::before' height we use getComputedStyle method
barHeight = parseInt(getComputedStyle(document.querySelector('#page'), '::before').height);
maxScrollDist = barHeight - logoHeight;
update();
}
The CSS
.logo-scroll .scroll-text img {
padding: 0 6px 0 17px;
}
#page::before {
content: "";
position: fixed;
top: 30px;
bottom: 30px;
left: 30px;
right: 30px;
border: 2px solid white;
pointer-events: none;
-webkit-transition: all 2s; /* Safari prior 6.1 */
transition: all 2s;
}
.logo-scroll {
position: fixed;
left: 30px;
top: 30px;
bottom: 30px;
border-right: 2px solid white;
width: 75px;
z-index: 10;
}
.scroll-text {
position: fixed;
}
let logos, logoHeight, barTopMargin;
let viewportHeight;
window.addEventListener('load', init);
window.addEventListener('resize', setSizes);
document.addEventListener('scroll', update);
function init(lockUpdate) {
logos = document.querySelectorAll('.scroll-text');
setSizes(lockUpdate);
}
function update() {
// ensure initialization and prevent recursive call
if (!logos) init(true);
//*************************************************
/**************************************************
THIS LINE MUST BE HERE.
**************************************************/
let maxScrollDist = document.documentElement.scrollHeight - viewportHeight;
//*************************************************
let currentScrollPos = document.documentElement.scrollTop;
let newTop;
let middle = currentScrollPos + viewportHeight/2;
let middleY = maxScrollDist/2;
if (middle >= (maxScrollDist+viewportHeight)/2) {
let p = (middleY - Math.floor(middle - (maxScrollDist+viewportHeight)/2))*100/middleY;
newTop = viewportHeight/2 - logoHeight/2;
newTop += (100-p)*(viewportHeight/2)/100;
newTop -= (100-p)*(barTopMargin +logoHeight/2)/100;
newTop = Math.max(newTop, viewportHeight/2 - logoHeight/2); /*fix*/
} else {
let p = (middleY - Math.floor(-middle + (maxScrollDist+viewportHeight)/2))*100/middleY;
newTop = barTopMargin*(100-p)/100+(viewportHeight/2 - (logoHeight/2)*p/100 )*p/100;
newTop = Math.min(newTop, viewportHeight/2 - logoHeight/2); /*fix*/
}
logos.forEach(function(el) {
el.style.top = newTop + "px";
});
}
function setSizes(lockUpdate) {
logoHeight = logos[0].offsetHeight;
barTopMargin = parseInt(getComputedStyle(document.querySelector('#page'), '::before').top);
viewportHeight = window.innerHeight;
if (lockUpdate === true) return;
update();
}
updated and tested.
to check it put this code in your console:
document.removeEventListener('scroll', update);
document.onscroll = function() {
let _logoHeight = logos[0].offsetHeight;
let _barTopMargin = parseInt(getComputedStyle(document.querySelector('#page'), '::before').top);
let _viewportHeight = window.innerHeight;
let _maxScrollDist = document.documentElement.scrollHeight - _viewportHeight;
let currentScrollPos = document.documentElement.scrollTop;
let percent100 = currentScrollPos + _viewportHeight;
let scrolledPercent = currentScrollPos * 100/_maxScrollDist;
let newTop = ((_viewportHeight - _logoHeight/2)*scrolledPercent/100);
let middle = currentScrollPos + _viewportHeight/2;
let middleY = _maxScrollDist/2; // 100
if (middle >= (_maxScrollDist+_viewportHeight)/2) {
let y1 = middleY - Math.floor(middle - (_maxScrollDist+_viewportHeight)/2);
let p = y1*100/middleY;
newTop = _viewportHeight/2 - _logoHeight/2;
newTop += (100-p)*(_viewportHeight/2)/100;
newTop -= (100-p)*(30 +_logoHeight/2)/100;
newTop = Math.max(newTop, _viewportHeight/2 - _logoHeight/2); /*fix*/
} else {
let y2 = middleY - Math.floor(-middle + (_maxScrollDist+_viewportHeight)/2);
let p = y2*100/middleY;
newTop = 30*(100-p)/100+(_viewportHeight/2 - (_logoHeight/2)*p/100 )*p/100;
newTop = Math.min(newTop, _viewportHeight/2 - _logoHeight/2); /*fix*/
}
logos.forEach(function(el) {
el.style.top = newTop + "px";
});
}
CSS fix:
custom.css :: line 767
#media (max-width: 1000px)...
.scroll-text {
padding-left: 13px;
/*width: 27px;*/
}
.scroll-text img {
/* remove it. but if necessary move it to .scroll-text rule above
width: 27px; */
}
custom.css :: line 839
#media (max-width: 599px)...
.logo-scroll {
/*display: none; why! remove it*/
}
custom.css :: line 268
.scroll-text {
position: fixed;
/* height: 280px; remove it*/
padding-left: 20px;
}
see this capture
finally, have a nice day and goodby.
You are selecting only the first 'logo-text'. Instead of:
const logo = document.querySelector('.scroll-text');
You should use querySelectorAll:
const logos = document.querySelectorAll('.scroll-text');
Then, in your scroll handler, you should move them all.
So, you would then substitute each instance of usage of logo with a loop through the all the logo elements:
logos.forEach(logo => logo.style.top = ...);
Please be aware that you are doing quite expensive stuff in a scroll handler, which is not great for rendering performance. You might also want to use requestAnimationFrame to improve the rendering performance. Check out the reference page on MDN. Actually, I quickly whipped a version using requestAnimationFrame but there was no sensible performance improvement. This is probably due to the fact that apparently rAF fires roughly at the same rate than the scroll event. Anyway, I removed it to avoid confusion. If you detect performance issues, let me know.
I suggest, though, that you move the logo using transform: translate() rather than top. Here you have a complete solution. I tried it in Chrome.
const docHeight = Math.max(document.documentElement.scrollHeight, document.body.scrollHeight);
const logos = document.querySelectorAll('.scroll-text');
const logoHeight = logos[0].offsetHeight;
// to get the pseudoelement's '#page::before' top we use getComputedStyle method
const barTopMargin = parseInt(getComputedStyle(document.querySelector('#page'), '::before').top);
let viewportHeight, barHeight, maxScrollDist, currentScrollPos, scrollFraction;
window.addEventListener('load', update);
window.addEventListener('resize', setSizes);
document.addEventListener('scroll', update);
setSizes();
function update() {
currentScrollPos = Math.max(document.documentElement.scrollTop, document.body.scrollTop);
scrollFraction = currentScrollPos / (docHeight - viewportHeight);
const translateDelta = barTopMargin + (scrollFraction * maxScrollDist);
logos.forEach(logo => logo.style.transform = `translateY(${translateDelta}px)`);
}
function setSizes() {
viewportHeight = window.innerHeight;
// to get the pseudoelement's '#page::before' height we use getComputedStyle method
barHeight = parseInt(getComputedStyle(document.querySelector('#page'), '::before').height);
maxScrollDist = barHeight - logoHeight;
update();
}

Smooth scroll anchor links WITHOUT jQuery

Is it possible to use smooth scroll to anchor links but without jQuery? I am creating a new site and I don't want to use jQuery.
Extending this answer: https://stackoverflow.com/a/8918062/3851798
After defining your function of scrollTo, you can pass the element you want to scrollTo in the function.
function scrollTo(element, to, duration) {
if (duration <= 0) return;
var difference = to - element.scrollTop;
var perTick = difference / duration * 10;
setTimeout(function() {
element.scrollTop = element.scrollTop + perTick;
if (element.scrollTop === to) return;
scrollTo(element, to, duration - 10);
}, 10);
}
If you have a div with an id="footer"
<div id="footer" class="categories">…</div>
In the script that you run to scroll you can run this,
elmnt = document.getElementById("footer");
scrollTo(document.body, elmnt.offsetTop, 600);
And there you have it. Smooth scrolling without jQuery. You can actually play around with that code on your browser's console and fine tune it to your liking.
Using the function from here: JavaScript animation and modifying it to modify a property (not only a style's property), you can try something like this:
DEMO: http://jsfiddle.net/7TAa2/1/
Just saying...
function animate(elem, style, unit, from, to, time, prop) {
if (!elem) {
return;
}
var start = new Date().getTime(),
timer = setInterval(function() {
var step = Math.min(1, (new Date().getTime() - start) / time);
if (prop) {
elem[style] = (from + step * (to - from)) + unit;
} else {
elem.style[style] = (from + step * (to - from)) + unit;
}
if (step === 1) {
clearInterval(timer);
}
}, 25);
if (prop) {
elem[style] = from + unit;
} else {
elem.style[style] = from + unit;
}
}
window.onload = function() {
var target = document.getElementById("div5");
animate(document.scrollingElement || document.documentElement, "scrollTop", "", 0, target.offsetTop, 2000, true);
};
div {
height: 50px;
}
<div id="div1">asdf1</div>
<div id="div2">asdf2</div>
<div id="div3">asdf3</div>
<div id="div4">asdf4</div>
<div id="div5">asdf5</div>
<div id="div6">asdf6</div>
<div id="div7">asdf7</div>
<div id="div8">asdf8</div>
<div id="div9">asdf9</div>
<div id="div10">asdf10</div>
<div id="div10">asdf11</div>
<div id="div10">asdf12</div>
<div id="div10">asdf13</div>
<div id="div10">asdf14</div>
<div id="div10">asdf15</div>
<div id="div10">asdf16</div>
<div id="div10">asdf17</div>
<div id="div10">asdf18</div>
<div id="div10">asdf19</div>
<div id="div10">asdf20</div>
Actually, there is more lightweight and simple way to do that:
https://codepen.io/ugg0t/pen/mqBBBY
function scrollTo(element) {
window.scroll({
behavior: 'smooth',
left: 0,
top: element.offsetTop
});
}
document.getElementById("button").addEventListener('click', () => {
scrollTo(document.getElementById("8"));
});
div {
width: 100%;
height: 200px;
background-color: black;
}
div:nth-child(odd) {
background-color: white;
}
button {
position: absolute;
left: 10px;
top: 10px;
}
<div id="1"></div>
<div id="2"></div>
<div id="3"></div>
<div id="4"></div>
<div id="5"></div>
<div id="6"></div>
<div id="7"></div>
<div id="8"></div>
<div id="9"></div>
<div id="10"></div>
<button id="button">Button</button>
This is a pretty old question, but it's important to say that nowadays smooth scrolling is supported in CSS, so there's no need for any scripts:
html {
scroll-behavior: smooth;
}
As noted by #Andiih, as of late 2022 there is full browser support for this.
Use this:
let element = document.getElementById("box");
element.scrollIntoView();
element.scrollIntoView(false);
element.scrollIntoView({block: "end"});
element.scrollIntoView({behavior: "instant", block: "end", inline: "nearest"});
DEMO: https://jsfiddle.net/anderpo/x8ucc5ak/1/
Vanilla js variant using requestAnimationFrame with easings and all browsers supported:
const requestAnimationFrame = window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame;
function scrollTo(to) {
const start = window.scrollY || window.pageYOffset
const time = Date.now()
const duration = Math.abs(start - to) / 3;
(function step() {
var dx = Math.min(1, (Date.now() - time) / duration)
var pos = start + (to - start) * easeOutQuart(dx)
window.scrollTo(0, pos)
if (dx < 1) {
requestAnimationFrame(step)
}
})()
}
Any easing supported!
CSS3 transitions with a :target selector can give a nice result without any JS hacking. I was just contemplating whether to imlement this but without Jquery it does get a bit messy. See this question for details.
Try this code here:
window.scrollTo({
top: 0,
left: 0,
behavior: 'smooth'
});
Smooth Scroll behavior with polyfill...
Example:
document.querySelectorAll('a[href^="#"]').addEventListener("click", function(event) {
event.preventDefault();
document.querySelector(this.getAttribute("href")).scrollIntoView({ behavior: "smooth" });
});
Repository: https://github.com/iamdustan/smoothscroll
My favorite scroll-to library currently is Zenscroll because of the wide range of features and small size (currently only 3.17kb).
In the future it may make more sense to use the native scrollIntoView functionality, but since it'd have to be polyfilled in most production sites today due to the lack of IE support, I recommend using Zenscroll instead in all cases.
March 2022
I know this is an old question but wanted to put forward an answer that has simpler ways of doing it in modern days. As of today, almost all the major browsers are compatible with scroll-behavior including Safari with its latest release. Still, you might want to employ fallback methods or just use the javascript approach described in method 2 for compatibility in older browsers.
Method 1: HTML and CSS
You can just do this with
Click
.
.
.
<h2 id="target">Target</h2>
and CSS
html {
scroll-behavior: smooth
}
Method 2: JavaScript
Or if you have a unique case that needs javascript, go on elaborate with this method.
const scrollTrigger = document.getElementById('scroll-trigger');
const target = document.getElementById('target');
scrollTrigger.addEventListener('click', function (e) {
window.scroll({
top: target.offsetTop,
left:0,
behavior: 'smooth' });
}, false)
It's upgraded version from #Ian
// Animated scroll with pure JS
// duration constant in ms
const animationDuration = 600;
// scrollable layout
const layout = document.querySelector('main');
const fps = 12; // in ms per scroll step, less value - smoother animation
function scrollAnimate(elem, style, unit, from, to, time, prop) {
if (!elem) {
return;
}
var start = new Date().getTime(),
timer = setInterval(function () {
var step = Math.min(1, (new Date().getTime() - start) / time);
var value = (from + step * (to - from)) + unit;
if (prop) {
elem[style] = value;
} else {
elem.style[style] = value;
}
if (step === 1) {
clearInterval(timer);
}
}, fps);
if (prop) {
elem[style] = from + unit;
} else {
elem.style[style] = from + unit;
}
}
function scrollTo(hash) {
const target = document.getElementById(hash);
const from = window.location.hash.substring(1) || 'start';
const offsetFrom = document.getElementById(from).offsetTop;
const offsetTo = target.offsetTop;
scrollAnimate(layout,
"scrollTop", "", offsetFrom, offsetTo, animationDuration, true);
setTimeout(function () {
window.location.hash = hash;
}, animationDuration+25)
};
// add scroll when click on menu items
var menu_items = document.querySelectorAll('a.mdl-navigation__link');
menu_items.forEach(function (elem) {
elem.addEventListener("click",
function (e) {
e.preventDefault();
scrollTo(elem.getAttribute('href').substring(1));
});
});
// scroll when open link with anchor
window.onload = function () {
if (window.location.hash) {
var target = document.getElementById(window.location.hash.substring(1));
scrollAnimate(layout, "scrollTop", "", 0, target.offsetTop, animationDuration, true);
}
}
For anyone in 2019,
first, you add an event listener
document.getElementById('id').addEventListener('click', () => scrollTo())
then you target the element and go smoothly to it
function scrollTo() {
let target = document.getElementById('target');
target.scrollIntoView({
behavior: "smooth",
block: "end",
inline: "nearest"
})
}
Based on MDN docs for scroll options we can use the following code:
element.scrollTo({
top: 100,
left: 100,
behavior: 'smooth'
});
In fact, the behavior key can accept smooth and auto variables. first for smooth motion and second for a single jump.
‍‍
Here is a simple solution in pure JavaScript. It takes advantage of CSS property scroll-behavior: smooth
function scroll_to(id) {
document.documentElement.style.scrollBehavior = 'smooth'
element = document.createElement('a');
element.setAttribute('href', id)
element.click();
}
Usage:
Say we have 10 divs:
<div id='df7ds89' class='my_div'>ONE</div>
<div id='sdofo8f' class='my_div'>TWO</div>
<div id='34kj434' class='my_div'>THREE</div>
<div id='gbgfh98' class='my_div'>FOUR</div>
<div id='df89sdd' class='my_div'>FIVE</div>
<div id='34l3j3r' class='my_div'>SIX</div>
<div id='56j5453' class='my_div'>SEVEN</div>
<div id='75j6h4r' class='my_div'>EIGHT</div>
<div id='657kh54' class='my_div'>NINE</div>
<div id='43kjhjh' class='my_div'>TEN</div>
We can scroll to the ID of choice:
scroll_to('#657kh54')
You simply call this function on your click event (e.g. click button then scroll to div #9).
Result:
Of course it looks much smoother in real life.
FIDDLE
Unfortunately, IE and Safari don't support scrollBehavior = 'smooth' as of 2019
MDN Web Docs
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. setTimeout can be used to a similar effect when requestAnimationFrame is not supported.
/*
#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) {
var elem = document.querySelector("div");
scrollToSmoothly(elem.offsetTop);
});
document.getElementById("toTop").addEventListener('click', function(e){
scrollToSmoothly(0, 700);
});
<button id="toElement">Scroll To Element</button>
<div style="margin: 1000px 0px; text-align: center;">Div element
<button id="toTop">Scroll back to top</button>
</div>
For more complex cases, the SmoothScroll.js library can be used, which handles 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("toTop").addEventListener('click', function(e){
smoothScroll({yPos: 0, duration: 700});
});
<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="toTop">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'
});
Demo:
<button onClick="scrollToDiv()">Scroll To Element</button>
<div style="margin: 500px 0px;">Div</div>
<script>
function scrollToDiv(){
var elem = document.querySelector("div");
window.scroll({
top: elem.offsetTop,
left: 0,
behavior: 'smooth'
});
}
</script>
Modern browsers support the scroll-behavior CSS property, which can be used to make scrolling in the document smooth (without the need for JavaScript). Anchor tags can be used for this by giving the anchor tag a href of # plus the id of the element to scroll to). You can also set the scroll-behavior property for a specific container like a div to make its contents scroll smoothly.
html, body{
scroll-behavior: smooth;
}
Scroll To Element
<div id="elem" style="margin: 500px 0px;">Div</div>
Without jQuery
const links = document.querySelectorAll('header nav ul a')
for (const link of links) {
link.onclick = function clickHandler(e) {
e.preventDefault()
const href = this.getAttribute('href')
document.querySelector(href).scrollIntoView({ behavior: 'smooth' })
}
}
body {
background-color: black;
height:7000px
}
header {
margin-top: 1.3rem;
margin-bottom: 25rem;
display: flex;
justify-content: center;
align-items: center;
}
nav ul {
display: flex;
}
nav ul li {
all: unset;
margin: 2rem;
cursor: pointer;
}
nav ul li a {
all: unset;
font: bold 1.8rem robto;
color: white;
letter-spacing: 1px;
cursor: pointer;
padding-top: 3rem;
padding-bottom: 2rem;
}
#team,
#contact,
#about {
background-color: #e2df0d;
width: 100%;
height: 35rem;
display: flex;
justify-content: center;
align-items: center;
color: black;
font: bold 4rem roboto;
letter-spacing: 6.2px;
margin-top: 70rem;
}
<header>
<!-- NavBar -->
<nav>
<ul>
<li>Team</li>
<li>Contact</li>
<li>About</li>
</ul>
</nav>
</header>
<!-- ----------- Team ----------------------- -->
<div id="team">
<h2>Team</h2>
</div>
<!-- ----------- Contact ----------------------- -->
<div id="contact">
<h2>Contact</h2>
</div>
<!-- ----------- About ----------------------- -->
<div id="about">
<h2>About</h2>
</div>
Or with just CSS, but it's not supported in all browsers yet
html {scroll-behavior: smooth}
If you want to set all of your deep links # to scroll smoothly you can do this:
const allLinks = document.querySelectorAll('a[href^="#"]')
allLinks.forEach(link => {
const
targetSelector = link.getAttribute('href'),
target = document.querySelector(targetSelector)
if (target) {
link.addEventListener('click', function(e) {
e.preventDefault()
const top = target.offsetTop // consider decreasing your main nav's height from this number
window.scroll({
behavior: 'smooth',
left: 0,
top: top
});
})
}
})
An example code to consider also your main nav's height (this code goes where top const is declared):
const
mainHeader = document.querySelector('header#masthead'), //change to your correct main nav selector
mainHeaderHeight = mainHeader.offsetHeight,
// now calculate top like this:
top = target.offsetTop - mainHeaderHeight
Here is the most elegant and concise solution.
Links:
CSS:
html {
scroll-behavior: smooth;
}
Remember to add a unique id="elementIDtoScrollTo" to each HTML element.

Categories