JSFiddle - React, Tailwind, and code Playground

by Paul Leech

HTML

<div class="controls">
    <button onclick="scroll_down()">scroll down</button>
    <button onclick="scroll_up()">scroll up</button>
</div>
<div class="container">
<p>
    Dolor lacus. Lacus metus. Metus lorem litora. Fames purus felis eni parturient. Risus lacus a cras in eros fermentum facilisis netus et. Curae neque sed quis non nulla. Nulla magna sit massa neque dictum mi arcu litora fringilla integer proin. Nulla justo porta volutpat donec pellentesque hymenaeos etiam id mattis class. Curae vitae. Massa class per faucibus sodales nisl ac ultricies at mollis aliquam quisque magnis varius. Porta purus tempus rhoncus lacinia aenean ut.

Fames nulla tellus class facilisis blandit consectetuer sodales accumsan erat. Netus morbi pharetra fringilla nunc. Felis augue scelerisque ut turpis ultricies egestas semper. Fames purus convallis lorem diam penatibus. Porta augue est ipsum dolor fames erat nec. Felis neque volutpat justo lorem commodo non adipiscing. Proin augue diam habitant viverra ad dignissim condimentum. Porta massa fermentum cras. Neque nulla curae nullam morbi suspendisse ve varius. Proin lorem senectus felis a tellus venenatis non purus ridiculus vestibulum pretium cras. Dolor donec tempor parturient. Metus lacus. Fusce fames senectus vel amet. Dolor neque mollis netus eleifend vestibulum platea ante. Ipsum morbi congue molestie fames a quis adipiscing. Purus lacus congue purus aliquet interdum fames sagittis. Nulla velit. Lacus justo dictumst curabitur felis tincidunt lectus. Donec fames etiam semper sollicitudin facilisis montes. Etiam nulla vehicula massa auctor parturient dictumst. Neque curae quam suscipit nisl. Neque fames elit dapibus at orci netus.

Purus massa euismod mauris sem ut consectetuer. Fames ipsum ante tellus eu dictumst aliquam consectetuer mattis fames condimentum ultrices arcu ve. Felis augue volutpat ut felis nibh duis fermentum. Morbi purus. Justo porta mus duis vel rutrum ad torquent conubia neque ante sociis mi convallis ante...

CSS

div.controls {
    padding: 10px 30px;
    margin-bottom: 10px;
}

div.container {
    height: 400px;
    width: 300px;
    overflow: scroll;
    border: 1px solid blue;
}

div.container p {
    font-size: 20px;
    line-height: 2em;
    margin-top: 0px;
}

JavaScript

function scroll_down() {
    var el = document.querySelector('.container');
    smooth_scroll_to(el, el.scrollTop + 200, 600);
}

function scroll_up() {
    var el = document.querySelector('.container');
    smooth_scroll_to(el, el.scrollTop - 200, 600);
}


/**
    Smoothly scroll element to the given target (element.scrollTop)
    for the given duration

    Returns a promise that's fulfilled when done, or rejected if
    interrupted
 */
var smooth_scroll_to = function(element, target, duration) {
    target = Math.round(target);
    duration = Math.round(duration);
    if (duration < 0) {
        return Promise.reject("bad duration");
    }
    if (duration === 0) {
        element.scrollTop = target;
        return Promise.resolve();
    }

    var start_time = Date.now();
    var end_time = start_time + duration;

    var start_top = element.scrollTop;
    var distance = target - start_top;

    // based on http://en.wikipedia.org/wiki/Smoothstep
    var smooth_step = function(start, end, point) {
        if(point <= start) { return 0; }
        if(point >= end) { return 1; }
        var x = (point - start) / (end - start); // interpolation
        return x*x*(3 - 2*x);
    }

    return new Promise(function(resolve, reject) {
        // This is to keep track of where the element's scrollTop is
        // supposed to be, based on what we're doing
        var previous_top = element.scrollTop;

        // This is like a think function from a game loop
        var scroll_frame = function() {
            if(element.scrollTop != previous_top) {
                reject("interrupted");
                return;
            }

            // set the scrollTop for this frame
            var now = Date.now();
            var point = smooth_step(start_time, end_time, now);
            var frameTop = Math.round(start_top + (distance * point));
            element.scrollTop = frameTop;

            // check if we're done!
            if(now >= end_time) {
               ...