HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavascriptMinor

Smooth page scrolling in JavaScript

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
javascriptsmoothpagescrolling

Problem

I wrote a script to scroll pages when menu buttons are clicked, similar effect like: http://css-tricks.com/examples/SmoothPageScroll

var m1 = document.getElementById('m1'),
    m2 = document.getElementById('m2'),
    m3 = document.getElementById('m3'),
    m4 = document.getElementById('m4'),
    m5 = document.getElementById('m5'),
    m2PositionY = document.getElementById('id2').offsetTop,
    m3PositionY = document.getElementById('id3').offsetTop,
    doneValue = true,
    windowPageYOffset = 0;

function Scroll(ScrollValue1) {
    windowPageYOffset = window.pageYOffset;
    if(windowPageYOffset == ScrollValue1) {
        doneValue = true;
    }
    // scroll up
    else if(windowPageYOffset >= ScrollValue1) {
        window.scrollBy(0, -(1 + ((windowPageYOffset - ScrollValue1) / 10)));
    }
    // scroll down
    else {
        window.scrollBy(0, 1 + ((ScrollValue1 - windowPageYOffset) / 10));
    }
    if(windowPageYOffset != ScrollValue1) {
        var setTimeoutScrollValue1 = setTimeout('Scroll(' + ScrollValue1 + ')', 20);
    }
}

function doneOrNot(doneOrNotValue1) {
    if(doneValue && doneOrNotValue1 != windowPageYOffset) {
        Scroll(doneOrNotValue1);
        doneValue = false;
    }
}
m1.onclick = function () {
    doneOrNot(0); // no need for a id since top is allways 0
}
m2.onclick = function () {
    doneOrNot(m2PositionY);
}
m3.onclick = function () {
    doneOrNot(m3PositionY);
}


I am using a "true or false variable" (doneValue) to determine if previous Scroll is finished before next can start. I wonder if there is any better method to do this in JavaScript?

Please give other feedback if you have any :)

Solution

There are a few things you can do differently

  • Animations are most often accomplished by getting the difference between start and end values, and animating a factor from zero to 1. Multiply that with the difference, and you get an offset.



  • Instead of relying on an interval (which may be slow, since JS is single-threaded), most animation libraries compare the start time to the current time, to see how far along the animation should be.



  • Instead of waiting for the animation to stop and a boolean to flip, you can clear the animation's timer, and forcibly stop the animation, before starting a new one. That way the user's clicks aren't ignored, but override previous clicks instead.



Here's an alternative implementation using that approach and other good stuff:

var smoothScrollTo = (function () {
  var timer, start, factor;

  return function (target, duration) {
    var offset = window.pageYOffset,
        delta  = target - window.pageYOffset; // Y-offset difference
    duration = duration || 1000;              // default 1 sec animation
    start = Date.now();                       // get start time
    factor = 0;

    if( timer ) {
      clearInterval(timer); // stop any running animation
    }

    function step() {
      var y;
      factor = (Date.now() - start) / duration; // get interpolation factor
      if( factor >= 1 ) {
        clearInterval(timer); // stop animation
        factor = 1;           // clip to max 1.0
      } 
      y = factor * delta + offset;
      window.scrollBy(0, y - window.pageYOffset);
    }

    timer = setInterval(step, 10);
    return timer; // return the interval timer, so you can clear it elsewhere
  };
}());


Here's a demo

Another advantage of always animating a factor between zero and 1 is that you can run it through other expressions, and (for instance) animated the scroll with a sine wave.

Code Snippets

var smoothScrollTo = (function () {
  var timer, start, factor;

  return function (target, duration) {
    var offset = window.pageYOffset,
        delta  = target - window.pageYOffset; // Y-offset difference
    duration = duration || 1000;              // default 1 sec animation
    start = Date.now();                       // get start time
    factor = 0;

    if( timer ) {
      clearInterval(timer); // stop any running animation
    }

    function step() {
      var y;
      factor = (Date.now() - start) / duration; // get interpolation factor
      if( factor >= 1 ) {
        clearInterval(timer); // stop animation
        factor = 1;           // clip to max 1.0
      } 
      y = factor * delta + offset;
      window.scrollBy(0, y - window.pageYOffset);
    }

    timer = setInterval(step, 10);
    return timer; // return the interval timer, so you can clear it elsewhere
  };
}());

Context

StackExchange Code Review Q#13111, answer score: 7

Revisions (0)

No revisions yet.