Slidy Slidy

slidy/core

Simple, configurable, nested & reusable sliding action script.

Сompletely mimics the behavior of a native scroll with mouse drag, index navigation, acceleration, gravity, easings, custom animations & infinite loop mode.

Try the DEMO.

Getting started

The package is available via NPM:

npm i -D @slidy/core

or via CDN:

<script src="https://unpkg.com/@slidy/core"></script>

Playground is available in svelte REPL.

Usage Examples

There are several options how to get Slidy into your project.

ES Module Import

<head>
   <script type="module">
        import { slidy } from "https://unpkg.com/@slidy/core/dist/index.mjs";

        slidy(document.querySelector("#node"))
    </script>
</head>

<section>
    <ul id="node">
        <!-- slide items -->
    </ul>
</section>

CommonJS Require

<head>
   <script type="module">
        import { slidy } from "https://unpkg.com/@slidy/core/dist/index.cjs";

        slidy(document.querySelector("#node"))
    </script>
</head>

<section>
    <ul id="node">
        <!-- slidy items -->
    </ul>
</section>

IIFE as Window Object

<head>
    <script src="https://unpkg.com/@slidy/core"></script>
</head>

<script>
    let slidy = null;
    let node = document.querySelector("#slidy"),

    window.onload = () => {
        slidy = Slidy.core(node);
    }
</script>

<section>
    <ul id="slidy">
        <!-- slidy items -->
    </ul>
</section>

SvelteJS

To use slidy with SvelteJS Framework, pass slidy as action into the parent DOM node:

<script>
    import { slidy } from "@slidy/core";
</script>

<section>
    <ul use:slidy>
        <!-- slidy items -->
    </ul>
</section>

API

slidy have two arguments: required node and optional options.

Options

KeyDefaultTypeDescription
index0numberThe starting index on render.
clamp0numberDefines the step in number of slides to slide on.
indent1numberCreates an indent at the edges. The value is calculated from the gap: gap * indent
sensity5numberDefines sensite as the pixels required to drag in order to start move, 0 when sliding.
gravity1.2numberControls the gravity value 0 (space) : 1 (earth) : 2 (underground).
duration375numberSliding duration in ms.
animationundefinedAnimationFuncCustom sliding animation. Predefined sets are available via @slidy/animation.
easingundefined(t: number) => numberSliding easing behaviour. Predefined sets are available vie @slidy/easing.
snapundefinedstringDefines an area to “snap” the slide: "start", "center", "end", "deck", undefined. By default the behaviour is clamp sliding by edges.
axisundefined"x" or "y"Defines the flow direction.
loopfalsebooleanActivated the infinite sliding mode.

Readonly properties:

KeyTypeDescription
positionnumberCurrent position
directionnumberChildren move direction
verticalnumberChildren axis flow: 0 or any Number as true
reversenumberChildren reverse flow: -1 or 1

Flow

To control the flow direction use one of the options on the parent node:

To use deck flow use the snap: "deck" option, it may be required for some animations.

Usage example

<head>
    <script type="module">
        import { slidy } from '@slidy/core';;
        import { linear } from '@slidy/easing';
        import { fade } from '@slidy/animation';

        const options = {
            index: 0,
            clamp: 0,
            indent: 1,
            sensity: 5,
            gravity: 1.2,
            duration: 375,
            animation: fade,
            easing: linear,
            snap: 'center',
            axis: 'x',
            loop: false,
        };
        
        const node = document.querySelector("#node");
        slidy(node, options);
    </script>
</head>

<section>
    <ul id="node">
        <!-- items -->
    </ul>
</section>

Events

NameDetailDescription
mount{options}node.children.length and node.children are connected.
resize{ROE}The target node changed it’s size: ROE: ResizeObserverEntry[]
move{index,position}The sliding occured.
index{index}The index was changed.
keys{e.key}The key is pressed during the target node focus. Arrow keys behaviour is predefined for navigation with preventDefault used on them. To get the focus use tabIndex=0 attribute on the target node.
update{updated.options}Options were changed.
destroy{node}Targed node is unmounted from the DOM or destroy() method was used.

Events Usage Example

<head>
    <script type="module">
        import { slidy } from "@slidy/core";

        const node = document.querySelector("#node");
        slidy(document.querySelector("#node"));

        node.addEventListener("mount", e => console.log(e));
        node.onupdate = e => console.log(e.detail);

        function onMove(e) {
            const { index, position } = e.detail
            console.log(index, position)
        }
    </script>
</head>

<section>
    <ul id="node" onmove="onMove" tabindex="0">
        <!-- items -->
    </ul>
</section>

Methods

NameDescription
to(index: number)Scrolls to index.
update({ option: value })Update property in options.
destroy()Remove event listners, observers and default properties on slidy instance.

Methods Usage Example

<head>
    <script type="module">
        import { slidy } from "https://unpkg.com/@slidy/core/dist/index.mjs";

        const node = document.querySelector("#node");
        const prev = document.querySelector("#prev");
        const next = document.querySelector("#next");

        slidy(node);
        slidy.update({ snap: "center" });

        prev.onclick = () => slidy.to(index - 1);
        next.onclick = () => slidy.to(index + 1);
    </script>
</head>

<section>
    <ul id="node">
        <!-- items -->
    </ul>
</section>

<nav>
    <button id="prev"></button>
    <button id="next"></button>
</nav>

<!--
    If `slidy()` is defined in global scope, use global event handlers as attribures:

    <nav>
        <button onclick="slidy.to(index - 1)">←</button>
        <button onclick="slidy.to(index + 1)">→</button>
    </nav>
-->

Easing

Easing is an animation function, also known as timing function. It allows to create smooth sliding transitions.

/** Both input and output are numbers in range [0, 1].
type Easing = (t: number) => number;

By default an easing function is linear. Examples of easing function can be seen here.

The set of predefined easing functions is available as [@slidy/easing][separate package].

Animation

animation function allows to define a custom sliding animation.

interface AnimationArgs {
	node: HTMLElement;
  child: Child;
  options: Options;
  translate: string;
}

type AnimationFunc = (args: AnimationArgs) => CSSStyleDeclaration;

Functions receives 4 arguments:

NameTypeDescription
nodeHTMLElementslidy instance root DOM node.
childChildExtended childNode object.
optionsOptions@slidy/core options subset.
translatestringBasic translate required for any function { transform: translate }

child

NameTypeDescription
inumberChild index in array.
indexnumberChild index in core script.
activenumberCalculated as options.loop ? cix : options.index.
sizenumberCalculated as size + gap by options.vertical.
distnumberSnap position distance.
tracknumberMove by slide size from its snap point in specified direction.
turnnumberCalculated as -1 <- child.track / child.size -> 1
expnumberInterpolated child.track as 0 <- exp -> 1.

options

NameTypeDescription
indexnumberActive slide index.
positionnumberCurrent position value.
verticalnumberChildren flow, calculated as 0 or any Number as true.
reversenumberThe children reverse flow state: `-1
snapSnapThe snap position value: "start", "center", "end", "deck", undefined.

Animation presets

Predefined custom animations are available as separate package.

Limitations

  1. Children (slide items) absolute positioning should not be applied. While the script uses coordinates from the childNode, it breaks the script functionality completely;

  2. @slidy/core does not style the DOM, but keep in mind that there are some inline properties applied to the target node to provide functionality:

{
    outline: none;
    overflow: hidden;
    user-select: none;
}