Mousewheel scroll event fire only once per scroll-session [duplicate] - javascript

This question already has an answer here:
jQuery page scroll event logic -- how to throttle
(1 answer)
Closed 6 years ago.
I am trying to mimic the functionality of the following website: www.verbaasd.net. Each scrolling "session" will only trigger one action.
Each time a user scrolls down an action will happen depending on the status of variabel count. I only want this to happen ONCE per scroll. For example if a user has a Macbook with touchpad it will fire multiple times very vast. The count will go from 1 to 4 pretty much instantly. Is there a way to set a timeout or something so it stops for 0.5 sec when variabel count increases or decreases by 1?
Current code:
var count = 1;
$(window).on('mousewheel DOMMouseScroll', function(e) {
if (e.originalEvent.wheelDelta / 120 > 0) {
count -= 1;
} else {
count += 1;
}
if (count < 1) count = 1;
if (count > 4) count = 4;
switch (count) {
case 1:
// do something
break;
case 2:
// do something
break;
case 3:
// do something
break;
case 4:
// do something
break;
}
$(".cd-background-wrapper").attr("data-slide", count);
});

I recommend other way.
You should use 'preventDefault' and delay effect using setTimeout.
I wrote a simple prototype code below link.
(only tested on Chrome and safari)
http://codepen.io/nigayo/pen/PNEvmY
[HTML]
<body>
<div id="wrap">
<section>section A</section>
<section>section B</section>
<section>section C</section>
<section>section D</section>
</div>
</body>
[CSS]
body {
overflow: hidden;
height: 100%;
}
#wrap {
position: relative;
width: 100%;
height: 100%;
top: 0;
}
section {
width: 100%;
height: 600px;
}
section:nth-child(1) {
background: red;
}
section:nth-child(2) {
background: blue;
}
section:nth-child(3) {
background: green;
}
section:nth-child(4) {
background: magenta;
}
[JavaScript]
(function() {
var currentPanel = 1;
var wrap = $('#wrap');
var panelsize = 600;
var step = 10;
var interval = 1000;
var direction = 1;
var bAnimation = false;
function animation() {
setTimeout(function() {
var currentTop = parseInt(wrap.css("top"));
if (direction < 0) {
if (currentTop <= minValue) {
setTimeout(function() {
bAnimation = false;
}, interval);
return;
}
} else {
if (currentTop >= minValue) {
setTimeout(function() {
bAnimation = false;
}, interval);
return;
}
}
wrap.css({
"top": currentTop - step
});
animation();
}, 16);
}
$(window).bind('mousewheel DOMMouseScroll', function(event) {
event.preventDefault();
if (bAnimation) return;
var currentTop = parseInt(wrap.css("top"));
if (event.originalEvent.wheelDelta < 0) {
//down scroll
minValue = currentTop - panelsize;
step = 10;
direction = -1;
} else {
//up scroll
minValue = currentTop + panelsize;
step = -10;
direction = 1;
}
console.log(minValue, bAnimation);
bAnimation = true;
animation();
});
})();
If you refer to my codes, you should use 'jquery animate function' or 'requestAnimationframe' for animation logic.

Answer thanks to A. Wolff. Using _.throttle with lodash.js did the trick! You can find more info here: https://css-tricks.com/the-difference-between-throttling-and-debouncing/

Related

else if not working in KeyCode events javascript

I am trying to make me character moving left and up and I think jump() and slideLeft()
functions are working properly and the problem is in the controller(e) function (else if (e.KeyCode===37)) . The first function is avaible but it isn't able to acces the second conditon function. Also, I would want to make the grid solid after I will make an slideRight() similar function ,so if my character is jumping on it, the platform would sustain the square . Has anyone any ideea for either of my questions ?
Code snippet:
var square = document.querySelector('.square');
var grid = document.querySelector('.grid');
var bottom = 0;
let isJumping = false;
let isGoingLeft = false;
var newBottom;
let left = 0;
let leftTimerId;
function jump() {
if (isJumping) return
let timerUpId = setInterval(function() {
if (bottom > 250) {
clearInterval(timerUpId);
let timerDownId = setInterval(function() {
if (bottom < 0) {
clearInterval(timerDownId);
isJumping = false;
}
bottom -= 5;
square.style.bottom = bottom + 'px';
}, 20)
}
isJumping = true;
bottom += 30;
square.style.bottom = bottom + 'px';
}, 20)
}
function slideLeft() {
console.log('da');
isGoingLeft = true;
leftTimerId = setInterval(function() {
left -= 5;
square.style.left = left + 'px';
}, 20)
}
function controller(e) {
if (e.keyCode === 32)
jump();
else if (e.KeyCode === 37)
slideLeft();
}
document.addEventListener('keydown', controller);
.grid {
position: absolute;
background-color: chartreuse;
height: 20px;
width: 500px;
bottom: 100px;
left: 100px;
}
.square {
position: absolute;
background-color: darkblue;
height: 100px;
width: 100px;
bottom: 0px;
left: 150px;
}
`
<div class="grid"></div>
<div class="square"></div>
EDIT:
There is a typo:
The second time you've written KeyCode
function controller(e) {
if(e.keyCode===32) {
jump();
}
else if(e.keyCode===37) {
slideLeft();
}
}
I don't really understand what you mean by the second part of your question. If you want a character to have the ability to jump on a square, you'll have to implement a collision detection. Something like this:
if ( isNotOnGround() ) {
fall()
}

How to disable JavaScript Function when other function is running

Hi,
I'm learning/practicing to make my custom slider in JS/JQuery, and I've written below code. Its almost running well but little issues. What I'm doing is I'm running it two types,
Auto running after each 5 seconds with autoRun() Function
On every click to slider indicator run to relevant slide with click event.
In below code, I'm facing couple of issues, and will be very thankful to you if you help me.
Issues I'm facing are:
When I click to slider indicator, I want to disable auto Run function for a specific time like 5 second so my slider look more professional.
When it goes to last slide or come back to first slide, console is showing an error below, and it also take double time eg: 10 seconds to go next slide.
"Uncaught TypeError: Cannot read property 'left' of undefined"
$(function () {
var $mainSliderWrap = $('#slider_main_wrapper')
, $sliderMain = $mainSliderWrap.find('.main-slider')
, $sliderchildren = $sliderMain.children('li')
, $sliderIndicator = $mainSliderWrap.find('.slider-main-indicator');
// Slider Setup
window.addEventListener('resize', initMainSlider);
initMainSlider();
// Slider SetUp function
function initMainSlider() {
var wWidth = window.outerWidth
, sliderMainWidth = wWidth * $sliderchildren.length
$sliderMain.css('width', sliderMainWidth + 'px');
$sliderMain.children('li').first().addClass('visible');
$sliderIndicator.children('li').first().addClass('active');
}
// Want to Run Slider on Click event
$sliderIndicator.on('click', 'li', updateMainSlider);
// If Click Event Not happenening then I want to auto run Slider after 5 seconds
autoRun()
function autoRun() {
var mainSliderChildLenght = $sliderchildren.length;
var i = 0;
var next = true;
var dir;
setInterval(function () {
if (mainSliderChildLenght == i || i < 0) {
next = !next;
if (i < 0) {
i = 0;
}
}
if (next) {
dir = 'next';
i++;
}
else {
dir = 'prev';
i--;
if(i < 0) {
return
}
}
updateMainSlider(dir);
$('#result').text(i)
}, 5000);
}
// Here is the function for Updating the Slider
function updateMainSlider(a) {
var visibleSlide = $sliderchildren.filter('.visible')
, actualTranslate = getTranslateValue($sliderMain, 'X');
if (a == 'next' || a == 'prev') { // inside this if is running when function is called from autoRun()
console.log(a)
var newSlide = (a == 'next') ? visibleSlide.next() : visibleSlide.prev()
, newSlideOffsetLeft = newSlide.offset().left
, valueToTranslte = -newSlideOffsetLeft + actualTranslate;
setTranslateValue($sliderMain, 'translateX', valueToTranslte);
visibleSlide.removeClass('visible');
newSlide.addClass('visible');
$sliderIndicator.children('.active').removeClass('active');
$sliderIndicator.find('li').eq(newSlide.index()).addClass('active');
}
else { // inside this if is running when function is called from click event
console.log(a)
var newSlide = $(a.target)
, $newSlideIndicatorIndex = newSlide.index()
, $visibleSlideIndex = visibleSlide.index();
if ($newSlideIndicatorIndex !== $visibleSlideIndex && !$($sliderIndicator).hasClass('disable-click')) {
$($sliderIndicator).addClass('disable-click');
setTimeout(function () {
$($sliderIndicator).removeClass('disable-click');
}, 1000);
var diff = $newSlideIndicatorIndex - $visibleSlideIndex
, valueToTranslte = -(diff * window.outerWidth) + actualTranslate;
setTranslateValue($sliderMain, 'translateX', valueToTranslte);
$($sliderchildren[$visibleSlideIndex]).removeClass('visible');
$($sliderchildren[$newSlideIndicatorIndex]).addClass('visible');
$sliderIndicator.children('.active').removeClass('active');
$sliderIndicator.find('li').eq($newSlideIndicatorIndex).addClass('active');
} // end if
} // end else
} // end function
// SetTranslate Value Fucntion
function setTranslateValue(element, property, value) {
$(element).css({
'transform': property + '(' + value + 'px)'
});
}
// Get Translate Value function
function getTranslateValue(element, axis) {
var trValue = $(element).css('transform');
if (trValue !== 'none') {
trValue = trValue.split(')')[0];
trValue = trValue.split(',');
trValue = (axis == 'X') ? trValue[4] : trValue[5];
}
else {
trValue = 0;
}
return Number(trValue);
}
})
ol {
list-style: none;
margin: 0;
padding: 0;
}
body {
margin: 0;
padding: 0;
}
.slider-main-wrapper {
box-shadow: inset 0 0 20px orange;
min-height: 100vh;
}
ol.main-slider {
height: 85vh;
box-shadow: inset 0 0 20px green;
transition: transform 500ms ease;
}
ol.main-slider > li {
float: left;
}
ol.main-slider > li .silder-main-content {
width: 100vw;
height: 85vh;
display: flex;
justify-content: center;
align-items: center;
}
ol.main-slider > li.visible .silder-main-content {
box-shadow: inset 0 0 140px green;
}
ol.slider-main-indicator {
height: 15vh;
display: flex;
}
ol.slider-main-indicator li {
box-shadow: inset 0 0 2px green;
flex: 1;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
}
ol.slider-main-indicator li.active {
box-shadow: inset 0 0 80px green;
cursor: default;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="result" style="font-size: 30px; position: absolute;
top: 0; left: 0"></div>
<div class="slider-main-wrapper" id="slider_main_wrapper">
<ol class="main-slider">
<li>
<div class="silder-main-content">
<h1>First Slide</h1>
</div>
</li>
<li>
<div class="silder-main-content">
<h2>Second Slide</h2>
</div>
</li>
<li>
<div class="silder-main-content">
<h1>Third Slide</h1>
</div>
</li>
<li>
<div class="silder-main-content">
<h1>Fourth Slide</h1>
</div>
</li>
</ol>
<!--end slides-->
<ol class="slider-main-indicator">
<li> <span class="text">First Slide</span> </li>
<li> <span class="text">Second Slide</span> </li>
<li> <span class="text">Third Slide</span> </li>
<li> <span class="text">Fourth Slide</span> </li>
</ol>
<!--end slide indicator-->
</div>
you'll want to clearInterval when you click, the setInterval again once the processing due to the "click" event completes - so, for a start, you'll need to save the returned value of setInterval to use in clearInterval
autoRun in this code returns a function which starts the interval
this is just "part" of your code, not the whole thing - trying to keep it readable regarding the changes I have implemented
$sliderIndicator.on('click', 'li', updateMainSlider);
// save the function returned by autoRun
var go = autoRun();
// start autoRun
go();
// add a variable to store interval identifier
var interval;
function autoRun() {
var mainSliderChildLenght = $sliderchildren.length;
var i = 0;
var next = true;
var dir;
// return a function to begin autoRun for real
return function() {
// save interval identifier
interval = setInterval(function () {
// your code unchanged
}, 5000);
};
}
function updateMainSlider(a) {
var visibleSlide = $sliderchildren.filter('.visible')
, actualTranslate = getTranslateValue($sliderMain, 'X');
if (a == 'next' || a == 'prev') {
// your code - unchanged
} else {
// clear interval
clearInterval(interval);
// your code - unchanged
// now add this to restart the interval
go();
}
}
You may still need to tweak some things, I haven't gone through your code in depth
as requested
$(function () {
var $mainSliderWrap = $('#slider_main_wrapper')
, $sliderMain = $mainSliderWrap.find('.main-slider')
, $sliderchildren = $sliderMain.children('li')
, $sliderIndicator = $mainSliderWrap.find('.slider-main-indicator');
// Slider Setup
window.addEventListener('resize', initMainSlider);
initMainSlider();
// Slider SetUp function
function initMainSlider() {
var wWidth = window.outerWidth
, sliderMainWidth = wWidth * $sliderchildren.length
$sliderMain.css('width', sliderMainWidth + 'px');
$sliderMain.children('li').first().addClass('visible');
$sliderIndicator.children('li').first().addClass('active');
}
// Want to Run Slider on Click event
$sliderIndicator.on('click', 'li', updateMainSlider);
// If Click Event Not happenening then I want to auto run Slider after 5 seconds
var go = autoRun();
// start autoRun
go();
var interval;
function autoRun() {
var mainSliderChildLenght = $sliderchildren.length;
var i = 0;
var next = true;
var dir;
return function() {
setInterval(function () {
if (mainSliderChildLenght == i || i < 0) {
next = !next;
if (i < 0) {
i = 0;
}
}
if (next) {
dir = 'next';
i++;
}
else {
dir = 'prev';
i--;
if(i < 0) {
return
}
}
updateMainSlider(dir);
$('#result').text(i)
}, 5000);
});
}
// Here is the function for Updating the Slider
function updateMainSlider(a) {
var visibleSlide = $sliderchildren.filter('.visible')
, actualTranslate = getTranslateValue($sliderMain, 'X');
if (a == 'next' || a == 'prev') { // inside this if is running when function is called from autoRun()
console.log(a)
var newSlide = (a == 'next') ? visibleSlide.next() : visibleSlide.prev()
, newSlideOffsetLeft = newSlide.offset().left
, valueToTranslte = -newSlideOffsetLeft + actualTranslate;
setTranslateValue($sliderMain, 'translateX', valueToTranslte);
visibleSlide.removeClass('visible');
newSlide.addClass('visible');
$sliderIndicator.children('.active').removeClass('active');
$sliderIndicator.find('li').eq(newSlide.index()).addClass('active');
}
else { // inside this if is running when function is called from click event
clearInterval(interval);
console.log(a)
var newSlide = $(a.target)
, $newSlideIndicatorIndex = newSlide.index()
, $visibleSlideIndex = visibleSlide.index();
if ($newSlideIndicatorIndex !== $visibleSlideIndex && !$($sliderIndicator).hasClass('disable-click')) {
$($sliderIndicator).addClass('disable-click');
setTimeout(function () {
$($sliderIndicator).removeClass('disable-click');
}, 1000);
var diff = $newSlideIndicatorIndex - $visibleSlideIndex
, valueToTranslte = -(diff * window.outerWidth) + actualTranslate;
setTranslateValue($sliderMain, 'translateX', valueToTranslte);
$($sliderchildren[$visibleSlideIndex]).removeClass('visible');
$($sliderchildren[$newSlideIndicatorIndex]).addClass('visible');
$sliderIndicator.children('.active').removeClass('active');
$sliderIndicator.find('li').eq($newSlideIndicatorIndex).addClass('active');
} // end if
go();
} // end else
} // end function
// SetTranslate Value Fucntion
function setTranslateValue(element, property, value) {
$(element).css({
'transform': property + '(' + value + 'px)'
});
}
// Get Translate Value function
function getTranslateValue(element, axis) {
var trValue = $(element).css('transform');
if (trValue !== 'none') {
trValue = trValue.split(')')[0];
trValue = trValue.split(',');
trValue = (axis == 'X') ? trValue[4] : trValue[5];
}
else {
trValue = 0;
}
return Number(trValue);
}
})

Javascript only add a class to an element on an interval after it's come into viewport

I have a series of images I want to transition from 0 opacity to 1 opacity when they come into the view port. I have the viewport check part done and the adding classes, however I would like them to be on an interval, so once the first 3 images come into the view port they appear 1, 2, 3 every .5seconds or so. Instead of all 3 at the same time.
here's a JS fiddle of how it works currently
reveal();
function reveal() {
var reveal = document.querySelectorAll(".reveal");
window.onscroll = function() {
for(var i = 0; i < reveal.length; i++) {
if(checkVisible(reveal[i]) === true) {
reveal[i].classList.add("fade");
}
}
}
};
function checkVisible(elm) {
var rect = elm.getBoundingClientRect();
var viewHeight = Math.max(document.documentElement.clientHeight, window.innerHeight);
return !(rect.bottom < 0 || rect.top - viewHeight >= -200);
}
https://jsfiddle.net/u04sy7jb/
I've modified your code to add a transition-delay of an additional .5 seconds for each element after the first one, in each "group" that is revealed as you scroll. I left comments in the JavaScript so you can understand the changes.
Let me know if you have any questions!
Live demo:
reveal();
function reveal() {
var reveal = document.querySelectorAll(".reveal");
window.onscroll = function() {
// start a new count each time user scrolls
count = 0;
for (var i = 0; i < reveal.length; i++) {
// also check here if the element has already been faded in
if (checkVisible(reveal[i]) && !reveal[i].classList.contains("fade")) {
// add .5 seconds to the transition for each
// additional element currently being revealed
reveal[i].style.transitionDelay = count * 500 + "ms";
reveal[i].classList.add("fade");
// increment count
count++;
}
}
}
};
function checkVisible(elm) {
var rect = elm.getBoundingClientRect();
var viewHeight = Math.max(document.documentElement.clientHeight, window.innerHeight);
return !(rect.bottom < 0 || rect.top - viewHeight >= -200);
}
.container {
width: 100%;
height: 1200px;
background-color: orange;
}
.reveal {
display: inline-block;
width: 32%;
margin: 0 auto;
height: 400px;
background-color: pink;
border: 1px solid black;
opacity: 0;
}
.fade {
opacity: 1;
transition: 1s;
}
<div class="container">
<div class="reveal"></div>
<div class="reveal"></div>
<div class="reveal"></div>
<div class="reveal"></div>
<div class="reveal"></div>
<div class="reveal"></div>
<div class="reveal"></div>
<div class="reveal"></div>
<div class="reveal"></div>
</div>
You could be able to stick your reveal[i].classList.add("fade"); inside of a setTimeout that executes as a function of your ith element so they show up how you're describing. Here is an example of adding short function to add the class and using it in a setTimeout to make this happen, although you could change it up to meet any additional needs.
function reveal() {
var reveal = document.querySelectorAll(".reveal");
window.onscroll = function() {
for(var i = 0; i < reveal.length; i++) {
if(checkVisible(reveal[i]) === true) {
addMyFadeClass(reveal[i], i)
}
}
}
};
function addMyFadeClass(element, i) {
setTimeout(function() {
element.classList.add("fade");
}, i * 500)
}
You can also use :nth-child CSS selectors without the need to change the JS:
.reveal:nth-child(3n+1).fade {
opacity: 1;
transition: 1s;
}
.reveal:nth-child(3n+2).fade {
opacity: 1;
transition: 1.5s;
}
.reveal:nth-child(3n).fade {
opacity: 1;
transition: 2s;
}
JSFiddle: https://jsfiddle.net/u04sy7jb/8/

How to not increase setInterval then called several times

Is there is possibility to not increase set interval speed after calling it several times. I'm doing the auto scroll function. After you hit the selected speed button it calls the function setInterval. My problem that more I hit button page scrolls faster and faster. how to solve my logical mistake?
function scroll() {
var scrollspeed = document.getElementById("scrollspeedval").value;
if (scrollspeed == 1) {
window.scrollBy(0, 1);
} else if (scrollspeed == 2) {
window.scrollBy(0, 2);
} else if (scrollspeed == 3) {
window.scrollBy(0, 4);
} else if (scrollspeed == 4) {
window.scrollBy(0, 8);
} else if (scrollspeed == 5) {
window.scrollBy(0, 12);
} else if (scrollspeed == 0) {
};
}
$("document").ready(function() {
$(".scrollcl").click(function() {
var interval_for_autoscroll = setInterval(function() {
scroll();
}, 400);
});
});
You should stop the already running interval timer using clearInterval before starting the new one:
clearInterval(interval_for_autoscroll); // where interval_for_autoscroll is declared outside the scope of the callback.
Something like this:
function scroll() {
var $object = $('.object'),
angle = $object.data('angle') || 0;
$object
.css({ 'transform': 'rotateZ(' + angle + 'deg)' })
.data('angle', (angle + 10) % 360);
}
$("document").ready(function() {
var interval_for_autoscroll;
$('.slow').click(function() {
clearInterval(interval_for_autoscroll);
interval_for_autoscroll = setInterval(scroll.bind(window), 400);
});
$('.normal').click(function() {
clearInterval(interval_for_autoscroll);
interval_for_autoscroll = setInterval(scroll.bind(window), 100);
});
$('.fast').click(function() {
clearInterval(interval_for_autoscroll);
interval_for_autoscroll = setInterval(scroll.bind(window), 10);
});
});
.object {
display: inline-block;
width: 100px;
height: 50px;
background-color: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="object">
</div>
<button class="slow">slow</button>
<button class="normal">normal</button>
<button class="fast">fast</button>
Seeing as you're using JQuery, I suppose .one() might be a better option for you, if you only want the event to trigger once.
You could try something like this:
$(".scrollcl").one("click",function(){});

Smooth scroll to specific div on click

What I'm trying to do is make it so that if you click on a button, it scrolls down (smoothly) to a specific div on the page.
What I need is if you click on the button, it smooth scrolls to the div 'second'.
.first {
width: 100%;
height: 1000px;
background: #ccc;
}
.second {
width: 100%;
height: 1000px;
background: #999;
}
<div class="first"><button type="button">Click Me!</button></div>
<div class="second">Hi</div>
do:
$("button").click(function() {
$('html,body').animate({
scrollTop: $(".second").offset().top},
'slow');
});
Updated Jsfiddle
There are many examples of smooth scrolling using JS libraries like jQuery, Mootools, Prototype, etc.
The following example is on pure JavaScript. If you have no jQuery/Mootools/Prototype on page or you don't want to overload page with heavy JS libraries the example will be of help.
http://jsfiddle.net/rjSfP/
HTML Part:
<div class="first"><button type="button" onclick="smoothScroll(document.getElementById('second'))">Click Me!</button></div>
<div class="second" id="second">Hi</div>
CSS Part:
.first {
width: 100%;
height: 1000px;
background: #ccc;
}
.second {
width: 100%;
height: 1000px;
background: #999;
}
JS Part:
window.smoothScroll = function(target) {
var scrollContainer = target;
do { //find scroll container
scrollContainer = scrollContainer.parentNode;
if (!scrollContainer) return;
scrollContainer.scrollTop += 1;
} while (scrollContainer.scrollTop == 0);
var targetY = 0;
do { //find the top of target relatively to the container
if (target == scrollContainer) break;
targetY += target.offsetTop;
} while (target = target.offsetParent);
scroll = function(c, a, b, i) {
i++; if (i > 30) return;
c.scrollTop = a + (b - a) / 30 * i;
setTimeout(function(){ scroll(c, a, b, i); }, 20);
}
// start scrolling
scroll(scrollContainer, scrollContainer.scrollTop, targetY, 0);
}
What if u use scrollIntoView function?
var elmntToView = document.getElementById("sectionId");
elmntToView.scrollIntoView();
Has {behavior: "smooth"} too.... ;) https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView
I played around with nico's answer a little and it felt jumpy. Did a bit of investigation and found window.requestAnimationFrame which is a function that is called on each repaint cycle. This allows for a more clean-looking animation. Still trying to hone in on good default values for step size but for my example things look pretty good using this implementation.
var smoothScroll = function(elementId) {
var MIN_PIXELS_PER_STEP = 16;
var MAX_SCROLL_STEPS = 30;
var target = document.getElementById(elementId);
var scrollContainer = target;
do {
scrollContainer = scrollContainer.parentNode;
if (!scrollContainer) return;
scrollContainer.scrollTop += 1;
} while (scrollContainer.scrollTop == 0);
var targetY = 0;
do {
if (target == scrollContainer) break;
targetY += target.offsetTop;
} while (target = target.offsetParent);
var pixelsPerStep = Math.max(MIN_PIXELS_PER_STEP,
(targetY - scrollContainer.scrollTop) / MAX_SCROLL_STEPS);
var stepFunc = function() {
scrollContainer.scrollTop =
Math.min(targetY, pixelsPerStep + scrollContainer.scrollTop);
if (scrollContainer.scrollTop >= targetY) {
return;
}
window.requestAnimationFrame(stepFunc);
};
window.requestAnimationFrame(stepFunc);
}
The solution that worked for me:
var element = document.getElementById("box");
element.scrollIntoView({behavior: "smooth"});
You can explore more options here
You can use basic css to achieve smooth scroll
html {
scroll-behavior: smooth;
}
I took the Ned Rockson's version and adjusted it to allow upwards scrolls as well.
var smoothScroll = function(elementId) {
var MIN_PIXELS_PER_STEP = 16;
var MAX_SCROLL_STEPS = 30;
var target = document.getElementById(elementId);
var scrollContainer = target;
do {
scrollContainer = scrollContainer.parentNode;
if (!scrollContainer) return;
scrollContainer.scrollTop += 1;
} while (scrollContainer.scrollTop === 0);
var targetY = 0;
do {
if (target === scrollContainer) break;
targetY += target.offsetTop;
} while (target = target.offsetParent);
var pixelsPerStep = Math.max(MIN_PIXELS_PER_STEP,
Math.abs(targetY - scrollContainer.scrollTop) / MAX_SCROLL_STEPS);
var isUp = targetY < scrollContainer.scrollTop;
var stepFunc = function() {
if (isUp) {
scrollContainer.scrollTop = Math.max(targetY, scrollContainer.scrollTop - pixelsPerStep);
if (scrollContainer.scrollTop <= targetY) {
return;
}
} else {
scrollContainer.scrollTop = Math.min(targetY, scrollContainer.scrollTop + pixelsPerStep);
if (scrollContainer.scrollTop >= targetY) {
return;
}
}
window.requestAnimationFrame(stepFunc);
};
window.requestAnimationFrame(stepFunc);
};
Ned Rockson basically answers this question. However there is a fatal flaw within his solution. When the targeted element is closer to the bottom of the page than the viewport-height, the function doesn't reach its exit statement and traps the user on the bottom of the page. This is simply solved by limiting the iteration count.
var smoothScroll = function(elementId) {
var MIN_PIXELS_PER_STEP = 16;
var MAX_SCROLL_STEPS = 30;
var target = document.getElementById(elementId);
var scrollContainer = target;
do {
scrollContainer = scrollContainer.parentNode;
if (!scrollContainer) return;
scrollContainer.scrollTop += 1;
} while (scrollContainer.scrollTop == 0);
var targetY = 0;
do {
if (target == scrollContainer) break;
targetY += target.offsetTop;
} while (target = target.offsetParent);
var pixelsPerStep = Math.max(MIN_PIXELS_PER_STEP,
(targetY - scrollContainer.scrollTop) / MAX_SCROLL_STEPS);
var iterations = 0;
var stepFunc = function() {
if(iterations > MAX_SCROLL_STEPS){
return;
}
scrollContainer.scrollTop =
Math.min(targetY, pixelsPerStep + scrollContainer.scrollTop);
if (scrollContainer.scrollTop >= targetY) {
return;
}
window.requestAnimationFrame(stepFunc);
};
window.requestAnimationFrame(stepFunc);
}

Categories

Resources