Complex Interactivity Example

When deploying ScrollBooster in a real-world application—like a horizontal carousel of cards—you will run into complex user interaction issues. Users will attempt to click buttons inside the carousel, or they will accidentally trigger links when they finish dragging.

This example demonstrates how to solve three major UX problems:

  1. Locking the Axis: Forcing the math to only calculate horizontal movement.
  2. Interactive Elements: Preventing the drag from initiating when the user clicks a <button>.
  3. Ghost Clicks: Preventing accidental link navigation (<a>) if the user meant to drag, not click.

The Code

<style>
    .carousel-viewport {
        width: 500px;
        height: 200px;
        overflow: hidden;
        border: 2px solid #333;
        white-space: nowrap;
        user-select: none;
    }
    .carousel-content {
        display: inline-block;
        height: 100%;
    }
    .card {
        display: inline-block;
        width: 250px;
        height: 100%;
        background: #f4f4f4;
        border-right: 1px solid #ccc;
        padding: 20px;
        box-sizing: border-box;
    }
</style>

<div class="carousel-viewport">
    <div class="carousel-content">
        <div class="card">
            <h2>Card 1</h2>
            <button class="action-btn">Click Me (No Drag)</button>
        </div>
        <div class="card">
            <h2>Card 2</h2>
            <!-- A link we don't want triggering if the user is dragging -->
            <a href="/link" class="nav-link">Don't click while dragging</a>
        </div>
        <div class="card">
            <h2>Card 3</h2>
        </div>
    </div>
</div>

<script type="module">
    import ScrollBooster from 'scrollbooster';

    const viewport = document.querySelector('.carousel-viewport');
    const content = document.querySelector('.carousel-content');

    const sb = new ScrollBooster({
        viewport,
        content,

        // 1. DIRECTION LOCK:
        // Force strictly horizontal calculations. 
        // Up/down mouse movements are entirely ignored.
        direction: 'horizontal', 
        scrollMode: 'transform',

        // 2. PREVENT DRAG ON SPECIFIC ELEMENTS:
        // Fires on mousedown. If we return false, ScrollBooster ignores the event entirely.
        shouldScroll: (state, event) => {
            // Disable drag scroll if the user clicks our action button
            const isButton = event.target.classList.contains('action-btn');
            return !isButton; 
        },

        // 3. PREVENT ACCIDENTAL CLICKS AFTER A DRAG:
        // Fires when a full 'click' event completes.
        onClick: (state, event, isTouchDevice) => {
            // Check if the element the user released their mouse over is a link
            const isLink = event.target.tagName.toLowerCase() === 'a';

            // Behind the scenes, ScrollBooster tracks how far the mouse moved 
            // between mousedown and mouseup. If it moved more than 5px, 
            // state.isDragging will be true. If so, we prevent the browser from 
            // following the link!
            if (isLink && state.isDragging) {
                event.preventDefault();
            }
        }
    });
</script>

Deep Dive: The 5px Threshold

In the onClick callback, you'll notice we leverage state.isDragging.

How does ScrollBooster know the difference between a "click" and a "drag"? Internally, ScrollBooster hardcodes a CLICK_EVENT_THRESHOLD_PX of 5.

If the user clicks down, moves their mouse by 4px, and lets go, ScrollBooster considers that a sloppy, but valid, click (isDragging: false). If they move 6px before letting go, it triggers the drag mechanics (isDragging: true). By checking state.isDragging in the onClick callback, you ensure that a user wildly swiping across your carousel doesn't accidentally trigger the <a href="..."> tags housed within the cards.