DEV Community

Cover image for I created a draggable carousel with momentum scrolling and mobile support using Vanilla JavaScript
Ciprian Popescu
Ciprian Popescu

Posted on

I created a draggable carousel with momentum scrolling and mobile support using Vanilla JavaScript

A few weeks ago I saw a really nice carousel on the Stripe blog. So I decided to replicate it and integrate it with WordPress. Here are my steps.

See a demo on my homepage here - https://getbutterfly.com/

The WordPress Loop

I am getting the last 10 posts in the JavaScript category, I am setting up a counter and changing background colours based on this counter.

<?php
function whiskey_carousel() {
    $args = [
        'post_status' => 'publish',
        'post_type' => 'post',
        'posts_per_page' => 10,
        'category_name' => 'javascript'
    ];

    $featuredQuery = new WP_Query($args);

    $colours = [
        '#02bcf5', '#0073e6', '#003ab9', '#635bff', '#002c59', '#09cbcb',
        '#02bcf5', '#0073e6', '#003ab9', '#635bff', '#002c59', '#09cbcb',
    ];
    $i = 0;

    $data = '<div class="whiskey-cards">';

        if ($featuredQuery->have_posts()) {
            while ($featuredQuery->have_posts()) {
                $featuredQuery->the_post();

                $postID = get_the_ID();
                $excerpt = html_entity_decode(wp_trim_words(get_the_excerpt(), 32));

                $data .= '<div class="whiskey-card" style="background-color: ' . $colours[$i] . ';">
                    <h3><a href="' . get_permalink($postID) . '">' . get_the_title($postID) . '</a></h3>
                    <p class="whiskey-card--content">' . $excerpt . '</p>
                    <p class="whiskey-card--link"><a href="' . get_permalink($postID) . '">Learn more <svg class="HoverArrow" width="10" height="10" viewBox="0 0 10 10" aria-hidden="true"><g fill-rule="evenodd"><path class="HoverArrow__linePath" d="M0 5h7"></path><path class="HoverArrow__tipPath" d="M1 1l4 4-4 4"></path></g></svg></a></p>
                </div>';

                $i++;
            }
        }

    $data .= '</div>';

    return $data;
}

add_shortcode('whiskey-carousel', 'whiskey_carousel');
Enter fullscreen mode Exit fullscreen mode

The JavaScript Code

The JavaScript features momentum scrolling (mouse wheel) and (almost) native HTML dragging behaviour. The dragging is also available to mobile devices, as the content is overflowing horizontally.

document.addEventListener('DOMContentLoaded', () => {
    if (document.querySelector('.whiskey-cards')) {
        // Slider dragging
        const slider = document.querySelector('.whiskey-cards');
        let isDown = false;
        let startX;
        let scrollLeft;

        slider.addEventListener('mousedown', (e) => {
            isDown = true;
            slider.classList.add('active');
            startX = e.pageX - slider.offsetLeft;
            scrollLeft = slider.scrollLeft;
            cancelMomentumTracking();
        });

        slider.addEventListener('mouseleave', () => {
            isDown = false;
            slider.classList.remove('active');
        });

        slider.addEventListener('mouseup', () => {
            isDown = false;
            slider.classList.remove('active');
            beginMomentumTracking();
        });

        slider.addEventListener('mousemove', (e) => {
            if (!isDown) return;
            e.preventDefault();
            const x = e.pageX - slider.offsetLeft;
            const walk = (x - startX); //scroll-fast
            var prevScrollLeft = slider.scrollLeft;
            slider.scrollLeft = scrollLeft - walk;
            velX = slider.scrollLeft - prevScrollLeft;
        });

        // Momentum 
        var velX = 0;
        var momentumID;

        slider.addEventListener('wheel', (e) => {
            cancelMomentumTracking();
        });

        function beginMomentumTracking() {
            cancelMomentumTracking();
            momentumID = requestAnimationFrame(momentumLoop);
        }

        function cancelMomentumTracking() {
            cancelAnimationFrame(momentumID);
        }

        function momentumLoop() {
            slider.scrollLeft += velX * 2;
            velX *= 0.95;
            if (Math.abs(velX) > 0.5) {
                momentumID = requestAnimationFrame(momentumLoop);
            }
        }

        // Scroll
        const scrollContainer = document.querySelector(".whiskey-cards");

        scrollContainer.addEventListener("wheel", (evt) => {
            evt.preventDefault();

            window.requestAnimationFrame(() => {
                scrollContainer.scrollTo({ top: 0, left: scrollContainer.scrollLeft + (evt.deltaY * 2), behavior: "smooth" });
            });
        });
    }
});
Enter fullscreen mode Exit fullscreen mode

The CSS Style

And finally, there are lots of opinionated styles below, so make sure you get what you need.

.whiskey-cards {
    display: flex;
    flex-wrap: nowrap;
    overflow-x: scroll;
    -webkit-overflow-scrolling: touch;
    -ms-overflow-style: none;
    scrollbar-width: none;

    padding: 48px 48px 0 48px;

}
.whiskey-cards::-webkit-scrollbar {
    -webkit-appearance: none;
    width: 5px;
    height: 5px;
}
.whiskey-cards::-webkit-scrollbar-thumb {
    border-radius: 0;
    background-color: rgba(0, 0, 0, .5);
    background: linear-gradient(90deg, #02bcf5, #0073e6, #003ab9, #635bff);
    box-shadow: 0 0 1px rgba(255, 255, 255, .5);
    border-radius: 16px;
    opacity: .5;
}
.whiskey-cards:hover::-webkit-scrollbar-thumb {
    opacity: 1;
}

.whiskey-card {
    display: flex;
    flex-direction: column;
    min-width: 244px;
    flex-basis: 244px;
    border-radius: 16px;
    margin: 8px;
    padding: 16px;
    box-shadow: 0 -16px 24px rgb(0 0 0 / 5%);
    color: #ffffff;

    transition: all 150ms cubic-bezier(0.215,0.61,0.355,1);
}
.whiskey-card:hover {
    background-color: #0a2540 !important;
    transform: scale(1.04) translateY(-16px);
    box-shadow: 0 -16px 24px rgb(0 0 0 / 10%);
}
.whiskey-card h3 {
    padding-top: 0;
    line-height: 1.35;
}
.whiskey-card .whiskey-card--content {
    line-height: 1.5;
    font-size: 15px;
    font-weight: 300;
}
.whiskey-card .whiskey-card--link {
    line-height: 1.5;
    font-size: 15px;
    font-weight: 700;
    opacity: .7;
    margin: auto 0 0 0;
}
.whiskey-card h3 a,
.whiskey-card .whiskey-card--link a {
    color: #ffffff;
}
.whiskey-card .whiskey-card--link a svg {
    --arrowSpacing: 5px;
    --arrowHoverTransition: 150ms cubic-bezier(0.215,0.61,0.355,1);
    --arrowHoverOffset: translateX(3px);
    --arrowTipTransform: none;
    --arrowLineOpacity: 0;
    position: relative;
    top: 1px;
    margin-left: var(--arrowSpacing);
    stroke-width: 2px;
    fill: none;
    stroke: currentColor;
}
.HoverArrow__linePath {
    opacity: var(--arrowLineOpacity);
    transition: opacity var(--hoverTransition,var(--arrowHoverTransition));
}
.HoverArrow__tipPath {
    transform: var(--arrowTipTransform);
    transition: transform var(--hoverTransition,var(--arrowHoverTransition));
}
.whiskey-card:hover .HoverArrow__linePath {
    --arrowLineOpacity: 1;
}
.whiskey-card:hover .HoverArrow__tipPath {
    --arrowTipTransform: var(--arrowHoverOffset);
}
Enter fullscreen mode Exit fullscreen mode

I wrote about this and other JavaScript carousels and sliders here.

Top comments (2)

Collapse
 
generally23 profile image
generally23

Why is velocity here not distance over time.
I’ve been really invested into making a carousel like this one so I’m wondering how you came about this solution

For exemple: where did that .95 coming from

Collapse
 
wolffe profile image
Ciprian Popescu

My maths are not what they used to be. That .95 came from trial and error, until it looked smooth to me :)