maxkoshel
9/26/2017 - 2:51 PM

Animated scroll to target element inside window or relative scrollable element

Animated scroll to target element inside window or relative scrollable element

/**
 * Animated scroll to target element inside window 
 * or relative scrollable element
 *
 * @param {Object} options
 * @param {Element} options.targetElement
 * @param {Number} options.duration
 * @param {Element} [options.insideElement=window]
 * @param {Number} [options.offset=0]
 * @param {Function} [options.onStart]
 * @param {Function} [options.onEnd]
 */
function animatedScrollTo({targetElement, duration, insideElement = window, offset = 0, onStart, onEnd}) {
    const startPositionY = insideElement.scrollTop;
    const targetPositionY = startPositionY + targetElement.getBoundingClientRect().top + offset;

    if (targetPositionY === startPositionY) {
        return;
    }

    let currentPositionY = 0;
    let start;

    if (typeof onStart === 'function') {
        onStart();
    }

    window.requestAnimationFrame(animateScroll);

    function animateScroll(timestamp) {
        start = start || timestamp;

        const deltaTop = Math.round(targetPositionY - startPositionY);
        const progress = timestamp - start;
        const percent = (progress >= duration ? 1 : easeInOutQuad(progress / duration));
        currentPositionY = startPositionY + Math.ceil(deltaTop * percent);

        insideElement.scrollTop = currentPositionY;

        if (percent < 1) {
            window.requestAnimationFrame(animateScroll);
        } else if (typeof onStart === 'function') {
            onEnd();
        }
    }
}

function easeInOutQuad(x) {
    return x < 0.5 ? 8 * x * x * x * x : 1 - 8 * (--x) * x * x * x;
}

export default animatedScrollTo;