Leff Leff - 23 days ago 12
Javascript Question

Smooth scrolling not working on click

I am trying to implement smooth scrolling on my site. I have links that I want to use for scrolling to elements:

<nav id="page-nav" class="nav section-links">
<button class="active" data-target="#info">Info</button>
<button data-target="#videos">Videos</button>
<button data-target="#stats">Statistics</button>
</nav>


I am attaching click event listener on buttons like this:

$(document).ready(function () {
var navigationLinks = document.getElementsByClassName('section-links')[0].getElementsByTagName('button');
Array.prototype.forEach.call(navigationLinks, child => {
console.log(child);
child.addEventListener('click', doScrolling(child.getAttribute('data-target')));
});
});


This is the
doScrolling
function:

function getElementY(query) {
return window.pageYOffset + document.querySelector(query).getBoundingClientRect().top
}

function doScrolling(element, duration) {
var duration = duration || 1000;
var startingY = window.pageYOffset
var elementY = getElementY(element)

// If element is close to page's bottom then window will scroll only to some position above the element.
var targetY = document.body.scrollHeight - elementY < window.innerHeight ? document.body.scrollHeight - window.innerHeight : elementY
var diff = targetY - startingY
// Easing function: easeInOutCubic
// From: https://gist.github.com/gre/1650294
var easing = function (t) { return t<.5 ? 4*t*t*t : (t-1)*(2*t-2)*(2*t-2)+1 }
var start

if (!diff) return

// Bootstrap our animation - it will get called right before next frame shall be rendered.
window.requestAnimationFrame(function step(timestamp) {
if (!start) start = timestamp
// Elapsed miliseconds since start of scrolling.
var time = timestamp - start
// Get percent of completion in range [0, 1].
var percent = Math.min(time / duration, 1)
// Apply the easing.
// It can cause bad-looking slow frames in browser performance tool, so be careful.
percent = easing(percent)

window.scrollTo(0, startingY + diff * percent)

// Proceed with animation as long as we wanted it to.
if (time < duration) {
window.requestAnimationFrame(step)
}
})
}


But, when I click on buttons nothing happens, there are no errors in the console, and I am not sure what is going on. How can I fix this?

Answer Source

What you are actually doing you are attaching result of doScrolling function call as an event handler. That is why it is not working as expected.

You should wrap the doScrolling call into a function and store the target value in a closure.

$(document).ready(function () {
  var navigationLinks = document.getElementsByClassName('section-links')[0].getElementsByTagName('button');
  Array.prototype.forEach.call(navigationLinks, child => {
    var target = child.getAttribute('data-target');
    child.addEventListener('click', function () { doScrolling(target) });
  });
});

function getElementY(query) {
  return window.pageYOffset + document.querySelector(query).getBoundingClientRect().top
}

function doScrolling(element, duration) {
  var duration = duration || 1000;
    var startingY = window.pageYOffset
  var elementY = getElementY(element)

  // If element is close to page's bottom then window will scroll only to some position above the element.
  var targetY = document.body.scrollHeight - elementY < window.innerHeight ? document.body.scrollHeight - window.innerHeight : elementY
    var diff = targetY - startingY
  // Easing function: easeInOutCubic
  // From: https://gist.github.com/gre/1650294
  var easing = function (t) { return t<.5 ? 4*t*t*t : (t-1)*(2*t-2)*(2*t-2)+1 }
  var start

  if (!diff) return

    // Bootstrap our animation - it will get called right before next frame shall be rendered.
    window.requestAnimationFrame(function step(timestamp) {
    if (!start) start = timestamp
    // Elapsed miliseconds since start of scrolling.
    var time = timestamp - start
        // Get percent of completion in range [0, 1].
    var percent = Math.min(time / duration, 1)
    // Apply the easing.
    // It can cause bad-looking slow frames in browser performance tool, so be careful.
    percent = easing(percent)

    window.scrollTo(0, startingY + diff * percent)

        // Proceed with animation as long as we wanted it to.
    if (time < duration) {
      window.requestAnimationFrame(step)
    }
  })
}
.content {
  height: 100vh;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<nav id="page-nav" class="nav section-links">
   <button class="active" data-target="#info">Info</button>
   <button data-target="#videos">Videos</button>
   <button data-target="#stats">Statistics</button>
</nav>

<div class="content" id="info">Info</div>
<div class="content" id="videos">Videos</div>
<div class="content" id="stats">Stats</div>