Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Slowly scroll down a page permanently without heavy CPU usage or laggy scrolling

I want to make a page scroll down slowly and smoothly. Well, the speed should actually adjustable. The user should also be able to scroll up manually while the script is scrolling down. First I tried this:

var autoScrollDelay = 1
var autoScrollSpeed = 1
var autoScrollTimer
function setAutoScroll(newValue) {
    autoScrollSpeed = newValue ? newValue : autoScrollSpeed
    if (autoScrollTimer) {
        clearInterval(autoScrollTimer)
    }
    if (autoScrollDelay) {
        autoScrollTimer = setInterval(function(){
            window.scrollBy(0,autoScrollSpeed)
      },autoScrollDelay)
    }
}
setAutoScroll(1) // higher number =  faster scrolling

But it was causing a very heavy CPU load and the slowest speed was too fast. And in addition to that manually scrolling up did not work properly while the code was running.

Then I tried:

var autoScrollDelay = 1
var autoScrollSpeed = 1
var autoScrollTimer
function setAutoScroll(newValue) {
    autoScrollDelay = newValue ? newValue : autoScrollDelay //using autoScrollDelay instead of autoScrollSpeed
    if (autoScrollTimer) {
        clearInterval(autoScrollTimer)
    }
    if (autoScrollDelay) {
        autoScrollTimer = setInterval(function(){
            window.scrollBy(0,autoScrollSpeed)
      },autoScrollDelay)
    }
}
setAutoScroll(200) // higher number scrolls slower

But the scrolling was not smooth when setting it too slow (e.g. 200).

Then I tried:

$("html, body").animate({
    scrollTop: $('html, body').get(0).scrollHeight, 
}, 40000, "linear");

But again the CPU load was unreasonably high and scrolling up or down manually wasn't possible this way.

Is there a better way to do this?

like image 762
Forivin Avatar asked Jul 23 '17 22:07

Forivin


2 Answers

Here is one possible implementation. The refresh rate is fixed, and corresponds to fps in the code below. To make sure that the speed is constant, I consider the time elapsed since the previous scroll when calculating the new scroll position. Manual scrolling is allowed (with the scroll bar, with the mouse wheel, or with touch on mobile devices) and taken into account by processing scroll, wheel and touchmove events. You can see the code at work in this codepen.

var fps = 100;
var speedFactor = 0.001;
var minDelta = 0.5;
var autoScrollSpeed = 10;
var autoScrollTimer, restartTimer;
var isScrolling = false;
var prevPos = 0, currentPos = 0;
var currentTime, prevTime, timeDiff;

window.addEventListener("scroll", function (e) {
    // window.pageYOffset is the fallback value for IE
    currentPos = window.scrollY || window.pageYOffset;
});

window.addEventListener("wheel", handleManualScroll);
window.addEventListener("touchmove", handleManualScroll);

function handleManualScroll() {
    // window.pageYOffset is the fallback value for IE
    currentPos = window.scrollY || window.pageYOffset;
    clearInterval(autoScrollTimer);
    if (restartTimer) {
        clearTimeout(restartTimer);
    }
    restartTimer = setTimeout(() => {
        prevTime = null;
        setAutoScroll();
    }, 50);
}

function setAutoScroll(newValue) {
    if (newValue) {
        autoScrollSpeed = speedFactor * newValue;
    }
    if (autoScrollTimer) {
        clearInterval(autoScrollTimer);
    }
    autoScrollTimer = setInterval(function(){
        currentTime = Date.now();
        if (prevTime) {
            if (!isScrolling) {
                timeDiff = currentTime - prevTime;
                currentPos += autoScrollSpeed * timeDiff;
                if (Math.abs(currentPos - prevPos) >= minDelta) {
                    isScrolling = true;
                    window.scrollTo(0, currentPos);
                    isScrolling = false;
                    prevPos = currentPos;
                    prevTime = currentTime;
                }
            }
        } else {
            prevTime = currentTime;
        }
    }, 1000 / fps);
}

setAutoScroll(20);
like image 166
ConnorsFan Avatar answered Oct 31 '22 15:10

ConnorsFan


The function from this article uses vanilla JS to implement smooth scrolling at various speeds. Here is a demo:

document.getElementById("scrollBottomButton").onclick = function() {
  var duration = document.getElementById("bottomScrollDuration").value * 1000;
  scrollIt(document.querySelector("#bottom-row"), duration, "easeOutQuad");
};

document.getElementById("scrollTopButton").onclick = function() {
  var duration = document.getElementById("topScrollDuration").value * 1000;
  scrollIt(document.getElementById("top-row"), duration, "easeOutQuad");
};

// thanks to https://pawelgrzybek.com/page-scroll-in-vanilla-javascript/
function scrollIt(destination, duration = 200, easing = "linear", callback) {
  const easings = {
    linear(t) {
      return t;
    },
    easeOutQuad(t) {
      return t * (2 - t);
    }
  };

  const start = window.pageYOffset;
  const startTime = "now" in window.performance
  ? performance.now()
  : new Date().getTime();

  const documentHeight = Math.max(
    document.body.scrollHeight,
    document.body.offsetHeight,
    document.documentElement.clientHeight,
    document.documentElement.scrollHeight,
    document.documentElement.offsetHeight
  );
  const windowHeight =
        window.innerHeight ||
        document.documentElement.clientHeight ||
        document.getElementsByTagName("body")[0].clientHeight;
  const destinationOffset = typeof destination === "number"
  ? destination
  : destination.offsetTop;
  const destinationOffsetToScroll = Math.round(
    documentHeight - destinationOffset < windowHeight
    ? documentHeight - windowHeight
    : destinationOffset
  );

  if ("requestAnimationFrame" in window === false) {
    window.scroll(0, destinationOffsetToScroll);
    if (callback) {
      callback();
    }
    return;
  }

  function scroll() {
    const now = "now" in window.performance
    ? performance.now()
    : new Date().getTime();
    const time = Math.min(1, (now - startTime) / duration);
    const timeFunction = easings[easing](time);
    window.scroll(
      0,
      Math.ceil(timeFunction * (destinationOffsetToScroll - start) + start)
    );

    if (window.pageYOffset === destinationOffsetToScroll) {
      if (callback) {
        callback();
      }
      return;
    }

    requestAnimationFrame(scroll);
  }

  scroll();
}


// scroll testing    
var middleHtml = [];

const schiller = "Nur Beharrung führt zum Ziel, Nur die Fülle führt zur Klarheit, Und im Abgrund wohnt die Wahrheit.".split(' ')

for(var i=0; i<schiller.length;i+=1){
  middleHtml.push("<div class=' container row' id='scrolling'><h1 style='margin: 30rem 10rem 30rem 0;font-size: 3.5em;font-family: Helvetica, sans-serif;color: #fff;'>"+schiller[i]+"</h1></div>");
}


document.getElementById('middle').innerHTML = middleHtml.join('');
.container-fluid {
background: #e52d27;
background: -webkit-linear-gradient(to top, #b31217, #e52d27);
background: linear-gradient(to top, #b31217, #e52d27);
}

.container-fluid input, .container-fluid .btn {
  border-radius: 0;
}

.btn {
  background: rgba(210,200,200,0.95);
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha.6/css/bootstrap.min.css" rel="stylesheet"/>

<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha.6/css/bootstrap.min.css" rel="stylesheet"/>

<div class='container-fluid'>
  <div class='row' id='top-row'>
    <div class='col-sm-8'>
      <input class='form-control' id='bottomScrollDuration' placeholder='Enter duration in seconds (4, 25, 40, etc...)' />
    </div>
    <div class='col-sm-4'>
      <button class='btn' id='scrollBottomButton'>Scroll to bottom</button>
    </div>    
  </div>
  <div id='middle'>    
  </div>

  <div class='row' id='bottom-row'>
    <div class='col-sm-8'>
      <input class='form-control' id='topScrollDuration' placeholder='Enter duration in seconds (4, 25, 40, etc...)' />
    </div>
    <div class='col-sm-4'>
      <button class='btn' id='scrollTopButton'>Scroll to top</button>
    </div>
  </div>
</div>

See CodePen Demo

Update

You could try this if you just want to adjust the speed and keep a constant scrolling behavior:

function pageScroll(speed) {
    window.scrollBy(0,1);
    scrolldelay = setTimeout(pageScroll,speed);
}

And then call the function with a speed of your choice i.e.:

pageScroll(1);

I ran it in Chrome, and it didn't tax my CPU usage. The CPU does spike more when it's run in Firefox.

like image 21
Dan Kreiger Avatar answered Oct 31 '22 16:10

Dan Kreiger