<div class="container">
    <ul class="slider">
        <li class="slider__el"><span>1</span></li>
        <li class="slider__el"><span>2</span></li>
        <li class="slider__el"><span>3</span></li>
        <li class="slider__el"><span>4</span></li>
        <li class="slider__el"><span>5</span></li>
        <li class="slider__el" data-status="active"><span>6</span></li>
        <li class="slider__el"><span>7</span></li>
        <li class="slider__el"><span>8</span></li>
        <li class="slider__el"><span>9</span></li>
        <li class="slider__el"><span>10</span></li>
        <li class="slider__el"><span>11</span></li>
    </ul>
</div>
:root {
    // card margin saved on a css var so we can access it in JS
    --card-margin: 10%;
}

body {
    height: 100vh;
    margin: 0;

    overflow: hidden;
    color: white;
    background: #e76f51;

    font-family: sans-serif;
}

ul {
    margin: 0;
    padding: 0;
}

li {
    list-style: none;
}

.container {
    height: 100%;

    display: flex;
    justify-content: center;
    align-items: center;
}

.slider {
    transition: transform 0.54s ease-out;

    &__el {
        width: 25vh;
        height: 25vh;
        margin: var(--card-margin) 0;

        background: #f4a261;
        border-radius: 5%;

        font-size: 6em;
        font-weight: lighter;

        display: flex;
        align-items: center;
        justify-content: center;

        transition: background 0.2s ease;

        &[data-status="active"] {
            background: #2a9d8f;
        }
    }
}
View Compiled
const appController = (function () {
    const key = {
        up: 38,
        down: 40
    };

    // Our total transformY value will be stored here
    const data = {
        total: {
            Ytransform: 0
        }
    };

    return {
        debounce: (func, wait) => {
            let timeout;

            return function executedFunction(...args) {
                const later = () => {
                    timeout = null;
                    func(...args);
                };

                clearTimeout(timeout);
                timeout = setTimeout(later, wait);
            };
        },

        // Tells which direction it has to slide to
        getDir: function (e) {
            if (e <= -1 || e.keyCode === key.up || e.wich === key.up) {
                return "down";
            } else if (
                e >= 1 ||
                e.keyCode === key.down ||
                e.wich === key.down
            ) {
                return "up";
            }
        },

        // Updates our total transformY value
        updateTotal: function () {
            return function (status, value) {
                return status === "inc"
                    ? (data.total.Ytransform += value)
                    : status === "dec"
                    ? (data.total.Ytransform -= value)
                    : data.total.Ytransform;
            };
        },

        getData: function () {
            return data;
        }
    };
})();

const UIController = (function () {
    const DOMstrings = {
        slider: ".slider",
        card: ".slider__el"
    };

    const UIData = {
        index: 0
    };

    const getElements = function () {
        return {
            cards: document.querySelectorAll(DOMstrings.card)
        };
    };

    // Will give us the exact size of our element: offset height + it's margin
    // To get our margin, I saved this value on a css variable '--card-margin'
    const getCardSize = function () {
        let height, margin;

        height = document.querySelector(DOMstrings.card).offsetHeight;
        margin = getComputedStyle(document.documentElement).getPropertyValue(
            "--card-margin"
        );
        margin = margin.replace("%", "");

        function calcFh() {
            let fullHeight = height + (height / 100) * margin;
            return fullHeight;
        }

        return calcFh();
    };

    // Gets our current active element index
    const getIndex = function () {
        const els = getElements(),
            cards = els.cards,
            cardsArr = Array.from(cards);

        let currentIndex = cardsArr.findIndex(
            (card) => card.dataset.status === "active"
        );

        UIData.index = currentIndex;
        return currentIndex;
    };

    // Updates index
    const setIndex = function () {
        let index = getIndex();

        return function (decrement) {
            decrement ? index-- : index++;

            UIData.index = index;
            return index;
        };
    };
    const keepIndex = setIndex();

    // Applies style to our active element
    const setActive = function (index) {
        const els = getElements(),
            cards = els.cards,
            cardsArr = Array.from(cards);

        cardsArr.forEach((card) => (card.dataset.status = ""));
        cardsArr[index].dataset.status = "active";
    };

    // Limiting the slider to get passed the last element
    const limit = function (index) {
        const els = getElements(),
            cards = els.cards;

        if (index > cards.length - 2) return "maxReached";
        if (index <= 0) return "minReached";
    };

    return {
        // Main sliding function
        slide: function (dir, updateValue) {
            let cardSize, yValue, index;

            const stop = limit(UIData.index);

            cardSize = getCardSize();

            if (dir === "down") {
                if (stop !== "minReached") {
                    yValue = updateValue("inc", cardSize);
                    index = keepIndex(true);
                    setActive(index);
                }
            } else if (dir === "up") {
                if (stop !== "maxReached") {
                    yValue = updateValue("dec", cardSize);
                    index = keepIndex(false);
                    setActive(index);
                }
            }

            if ((yValue > 0 && yValue < 10) || (yValue < 0 && yValue > -10))
                yValue = 0;

            document.querySelector(DOMstrings.slider).style.transform =
                "translateY(" + yValue + "px)";
        }
    };
})();

// Controls user actions
const controller = (function (UICtrl, appCtrl) {
    const eventListeners = function () {
        let debounce,
            delay,
            scrollDir,
            dir,
            arr = [];

        delay = 1200;
        debounce = appCtrl.debounce;
        scrollDir = appCtrl.getDir;

        const onScroll = debounce((e) => {
            dir = scrollDir(e);
            ctrlSlide(dir);
            arr = [];
        }, delay);

        const onKey = debounce((e) => {
            dir = scrollDir(e);
            ctrlSlide(dir);
        }, delay / 3);

        //events
        window.addEventListener("wheel", (e) => {
            let yValue;

            // fix for touchpads our magic mouse deltaY value
            if (arr.length < 4) {
                arr.push(e.deltaY);
                if (arr.length > 3) yValue = arr[2];
            }

            if (yValue !== undefined) onScroll(yValue);
        });

        document.addEventListener("keydown", (e) => {
            onKey(e);
        });
    };

    const ctrlSlide = function (dir) {
        let total;

        total = appCtrl.updateTotal();

        UICtrl.slide(dir, total);
    };

    return {
        init: function () {
            eventListeners();
        }
    };
})(UIController, appController);

controller.init();

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.