smooothy

repository·master·Indexed 21 days ago

https://github.com/vallafederico/smooothy

A tiny, fast, and framework-agnostic slider/carousel implementation. It supports infinite scrolling, snapping, touch interactions, parallax effects, and both horizontal and vertical orientations. The library provides a base Core class that can be extended, along with specialized classes like KeyboardSlider, LinkSlider, and ControlSlider.

Tokens
10.4K
Snippets
39
Records
48
Agent score
75%

What's inside smooothy

  1. Understand built-in interactions and behaviors

    master

    The slider includes several built-in interaction models:

    Touch and Mouse

    • Mouse/Touch: Supports drag interactions and swipes. It automatically detects if the slider is horizontal or vertical based on the vertical configuration.
    • Momentum: Includes momentum-based sliding.
    • Bounce: Provides bounce effects when infinite: false.
    • Snap: Supports snap behavior when snap: true is configured.
    • Keyboard: Supports arrow keys (ArrowLeft/ArrowRight for horizontal, ArrowUp/ArrowDown for vertical).
  2. Extend the Core class

    master

    The Core class is designed to be extended. You can create custom slider classes by inheriting from Core and adding your own methods or UI logic in the constructor.

    import Core from "smooothy"
    
    export class Slider extends Core {
      constructor(wrapper, config) {
        super({ wrapper, config })
    
        // create your UI / do whatever
        // ...
      }
    
      doSomething() {
        // add your custom methods
      }
    }
    export class Slider extends Core {
      constructor(wrapper, config) {
        super({ wrapper, config })
      }
    
      doSomething() {}
    }
  3. Quickstart: Basic slider implementation

    master

    To create a basic slider, provide a wrapper element to the Core constructor and run an animation loop using requestAnimationFrame to call slider.update() on every frame.

    HTML Structure:

    <div class="slider-wrapper">
      <div class="slide">Slide 1</div>
      <div class="slide">Slide 2</div>
      <div class="slide">Slide 3</div>
    </div>

    CSS Requirements (Horizontal):

    [data-slider] {
      display: flex;
      overflow-x: hidden;
    }
    
    [data-slider] > * {
      flex-shrink: 0;
      width: <number [unit]>;
    }

    JavaScript Implementation:

    const slider = new Core(document.querySelector("[data-slider]"), {
      infinite: true,
      snap: true,
    });
    
    function animate() {
      slider.update();
      requestAnimationFrame(animate);
    }
    
    animate();
    
    // To clean up:
    // slider.destroy();
    const slider = new Core(document.querySelector("[data-slider]"), {
      infinite: true,
      snap: true,
    });
    
    function animate() {
      slider.update();
      requestAnimationFrame(animate);
    }
    
    animate();
  4. Integrate Smooothy with Vue using a composable

    master

    In Vue, you can abstract Smooothy logic into a composable. Use onMounted to instantiate the Core class using a ref to the DOM element and add the instance.update method to the gsap.ticker. Use onUnmounted to remove the ticker and call slider.value.destroy() to prevent memory leaks.

    Example configuration for an infinite slider:

    const { sliderElement, slider } = useSmooothy({
      infinite: true,
    })
    <script setup lang="ts">
    import { ref, onMounted, onUnmounted } from "vue"
    import Core, { CoreConfig } from "smooothy"
    import gsap from "gsap"
    
    /** composable */
    function useSmooothy(config: Partial<CoreConfig> = {}) {
      const sliderElement = ref<HTMLElement | null>(null)
      const slider = ref<Core | null>(null)
    
      onMounted(() => {
        if (sliderElement.value) {
          const instance = new Core(sliderElement.value, config)
          gsap.ticker.add(instance.update.bind(instance))
          slider.value = instance
        }
      })
    
      onUnmounted(() => {
        if (slider.value) {
          gsap.ticker.remove(slider.value.update.bind(slider.value))
          slider.value.destroy()
        }
      })
    
      return {
        sliderElement,
        slider,
      }
    }
    
    const slides = Array.from({ length: 10 }, (_, i) => i)
    const { sliderElement, slider } = useSmooothy({
      infinite: true,
    })
    </script>
    
    <template>
      <div ref="sliderElement" class="...">
        <div v-for="(slide, i) in slides" :key="i">
          <!-- slide content -->
        </div>
      </div>
    </template>
  5. Use Smooothy callbacks for UI synchronization

    master

    Smooothy provides three main lifecycle callbacks to sync your UI with the slider state:

    • onSlideChange(current, previous): Triggered when the active slide index changes. Useful for updating pagination dots or indicators.
    • onResize(core): Triggered when the slider dimensions change. Use the core.viewport property to get new dimensions.
    • onUpdate(core): Triggered on every animation frame. Use this to update progress bars or parallax effects using core.progress, core.speed, or core.parallaxValues.
    const slider = new Core(wrapper, {
      onSlideChange: (current, previous) => {
        console.log(`Slide changed from ${previous} to ${current}`)
      },
      onUpdate: core => {
        // Update progress bar
        progressBar.style.transform = `scaleX(${core.progress * 100}%)`
      },
    })
  6. Implement Smooothy in Webflow

    master

    To use Smooothy in a Webflow project, follow these steps:

    1. Import the library: Add the Smooothy script globally in your Webflow project settings or via a custom code embed.
    2. HTML Structure: Create a container element with display: flex and flex-direction: row (horizontal). Place your slides inside this container.
      • Note: Do not use the CSS gap property on the container. If you need spacing between slides, wrap the slides in a div and apply padding to that wrapper instead.
      • Tip: Use a data attribute like data-smooothy="1" on the container to easily select it in JavaScript.
    3. CSS Styling: Ensure the container has a max-width (e.g., 100vw) to prevent layout overflow.
    4. JavaScript Initialization: Select the container and initialize the Smooothy instance. You must call slider.update() within a requestAnimationFrame loop to ensure smooth animation.
    <!-- 1. Import Library -->
    <script src="https://unpkg.com/smooothy"></script>
    
    <!-- 2. HTML Structure -->
    <div data-smooothy="1" style="display: flex; flex-direction: row;">
      <div class="slide">Slide 1</div>
      <div class="slide">Slide 2</div>
      <div class="slide">Slide 3</div>
    </div>
    
    <!-- 3. JS Initialization -->
    <script>
    const sliderWrapper = document.querySelector('[data-smooothy="1"]')
    const slider = new Smooothy(sliderWrapper, {
      // options
    })
    
    function animate() {
      slider.update()
      requestAnimationFrame(animate)
    }
    
    animate()
    </script>
  7. Integrate Smooothy with React using a custom hook

    master

    To use Smooothy in a React application, it is recommended to abstract the initialization and cleanup logic into a custom hook. This hook should use a ref callback to instantiate the Core class when the DOM node is available and use gsap.ticker to drive the update method. Ensure you clean up by removing the ticker and calling slider.destroy() when the component unmounts.

    Note: If using Next.js, ensure the component is marked with 'use client'.

    import { useEffect, useRef, useState } from "react"
    import Core, { CoreConfig } from "smooothy"
    import gsap from "gsap"
    
    /** hook */
    export function useSmooothy(config: Partial<CoreConfig> = {}) {
      const sliderRef = useRef<HTMLElement | null>(null)
      const [slider, setSlider] = useState<Core | null>(null)
    
      const refCallback = (node: HTMLElement | null) => {
        if (node && !slider) {
          const instance = new Core(node, config)
          gsap.ticker.add(instance.update.bind(instance))
          setSlider(instance)
        }
        sliderRef.current = node
      }
    
      useEffect(() => {
        return () => {
          if (slider) {
            gsap.ticker.remove(slider.update.bind(slider))
            slider.destroy()
          }
        }
      }, [slider])
    
      return { ref: refCallback, slider }
    }
    
    /** component usage */
    export default function ReactSlider() {
      const { ref, slider } = useSmooothy()
    
      return (
        <div ref={ref} className="...">
          {/* slides content */}
        </div>
      )
    }
  8. Handle slider resizing

    master

    The Core class automatically observes the wrapper element using a ResizeObserver. When the wrapper's dimensions change, the slider recalculates its viewport and item offsets.

    If you are using variableWidth mode without infinite looping, the slider will attempt to re-center the current slide during a resize event.

  9. Configure responsive offset behavior

    master

    The slider automatically recalculates dimensions on window resize. You can customize how the active slide is positioned relative to the wrapper using the setOffset configuration option. This function receives an object containing itemWidth and wrapperWidth and should return the desired offset value.

    const slider = new Core(wrapper, {
      setOffset: ({ itemWidth, wrapperWidth }) => {
        return wrapperWidth / 2 // Center the active slide
      },
    })
  10. Configure virtualScroll behavior

    master

    When scrollInput is enabled, the virtualScroll object configures how mouse wheel, trackpad, and keyboard inputs are handled.

    OptionTypeDefaultDescription
    mouseMultipliernumber0.5Multiplier for mouse wheel sensitivity
    touchMultipliernumber2Multiplier for touch scroll sensitivity
    firefoxMultipliernumber30Firefox-specific scroll multiplier
    useKeyboardbooleanfalseEnable keyboard scroll input
    passivebooleantrueUse passive event listeners
    const slider = new Core(wrapper, {
      virtualScroll: {
        mouseMultiplier: 0.75,
        touchMultiplier: 1.5,
      },
    })
  11. Configure smooothy Core options

    master

    The Core constructor accepts a configuration object.

    OptionTypeDefaultDescription
    infinitebooleantrueEnables infinite looping of slides
    snapbooleantrueEnables snapping to slide positions
    variableWidthbooleanfalseAllows slides with different widths that snap to center
    verticalbooleanfalseEnables vertical scrolling instead of horizontal
    dragSensitivitynumber0.005Multiplier for drag movement sensitivity
    lerpFactornumber0.3Controls the smoothness of animations (lower = smoother)
    scrollSensitivitynumber1Multiplier for scroll wheel sensitivity
    snapStrengthnumber0.1How strongly the slider snaps to positions
    speedDecaynumber0.85How quickly the sliding speed decays
    bounceLimitnumber1Maximum overscroll amount when infinite is false
    scrollInputbooleanfalseEnables mouse wheel/trackpad scrolling
    setOffsetfunction({itemWidth, wrapperWidth, itemHeight, wrapperHeight, vertical}) => vertical ? itemHeight : itemWidthCustom function to set slide end offset
    virtualScrollobjectundefinedConfiguration for virtual scroll behavior
    onSlideChangefunctionnullCallback when active slide changes
    onResizefunctionnullCallback when slider is resized
    onUpdatefunctionnullCallback on each update frame