sweet-scroll
    TypeScript icon, indicating that this package has built-in type declarations

    4.0.0 • Public • Published

    sweet-scroll

    CircleCI Status npm version David License

    ECMAScript2015+ & TypeScript Friendly, dependency-free smooth scroll library.

    🍭 See Demo

    Features

    • Dependecy-free!!
    • ECMAScript2015+ & TypeScript friendly
    • Use requestAnimationFrame API
    • Supports vertical and horizontal scroll
    • Supports dynamic trigger (event delegation)
    • Supports container for the scroll
    • Supports many easing types
    • Supports server-side rendering (Can load without putting out errors.)

    Migration Guide

    See the Migration Guide

    Table of Contents

    Usage

    1. Install

    via NPM

    $ npm install sweet-scroll
    use
    import SweetScroll from 'sweet-scroll';

    via MANUAL

    1. Download the sweet-scroll.min.js
    2. Load it in the script tag.
    <script src="sweet-scroll.min.js"></script>

    via CDN (UNPKG)

    <script src="https://unpkg.com/sweet-scroll/sweet-scroll.min.js"></script>

    2. Setup of HTML

    <a href="#intro" data-scroll>Go to Introduction</a>
    ...
    <div id="intro">Introduction</div>

    3. Initialize SweetScroll

    You need to initialize an instance after DOMContentLoaded.

    document.addEventListener(
      'DOMContentLoaded',
      () => {
        const scroller = new SweetScroll({
          /* some options */
        });
      },
      false,
    );

    Options

    The following options are applied by default. It can be customized as needed.

    {
      trigger'[data-scroll]',       // Selector for trigger (must be a valid css selector)
      header'[data-scroll-header]', // Selector or Element for fixed header (Selector of must be a valid css selector)
      duration1000,                 // Specifies animation duration in integer
      easing'easeOutQuint',         // Specifies the pattern of easing
      offset0,                      // Specifies the value to offset the scroll position in pixels
      verticaltrue,                 // Enable the vertical scroll
      horizontalfalse,              // Enable the horizontal scroll
      cancellabletrue,              // When fired wheel or touchstart events to stop scrolling
      updateURLfalse,               // Update the URL hash on after scroll (true | false | 'push' | 'replace')
      preventDefaulttrue,           // Cancels the container element click event
      stopPropagationtrue,          // Prevents further propagation of the container element click event in the bubbling phase
     
      // Callbacks
      beforenull,
      afternull,
      cancelnull,
      completenull,
      stepnull,
    }

    Easings

    Supports the following easing.

    Built-in (22)

    • Normal
      • linear
    • Quad
      • easeInQuad
      • easeOutQuad
      • easeInOutQuad
    • Cubic
      • easeInCubic
      • easeOutCubic
      • easeInOutCubic
    • Quart
      • easeInQuart
      • easeOutQuart
      • easeInOutQuart
    • Quint
      • easeInQuint
      • easeOutQuint (default)
      • easeInOutQuint
    • Sine
      • easeInSine
      • easeOutSine
      • easeInOutSine
    • Expo
      • easeInExpo
      • easeOutExpo
      • easeInOutExpo
    • Circ
      • easeInCirc
      • easeOutCirc
      • easeInOutCirc

    Advanced (9)

    Easing functions that are not built in can pass functions directly.

    const scroller = new SweetScroll({
      easing: advancedEasingFunction,
    });

    Elastic

    easeInElastic
    const easeInElastic = (_, t, b, c, d) => {
      let s = 1.70158;
      let p = 0;
      let a = c;
      if (t === 0) return b;
      if ((t /= d) === 1) return b + c;
      if (!p) p = d * 0.3;
      if (a < Math.abs(c)) {
        a = c;
        s = p / 4;
      } else {
        s = (p / (2 * Math.PI)) * asin(c / a);
      }
      return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin(((t * d - s) * (2 * Math.PI)) / p)) + b;
    };
    easeOutElastic
    const easeOutElastic = (_, t, b, c, d) => {
      let s = 1.70158;
      let p = 0;
      let a = c;
      if (t === 0) return b;
      if ((t /= d) === 1) return b + c;
      if (!p) p = d * 0.3;
      if (a < Math.abs(c)) {
        a = c;
        s = p / 4;
      } else {
        s = (p / (2 * Math.PI)) * asin(c / a);
      }
      return a * Math.pow(2, -10 * t) * Math.sin(((t * d - s) * (2 * Math.PI)) / p) + c + b;
    };
    easeInOutElastic
    const easeInOutElastic = (_, t, b, c, d) => {
      let s = 1.70158;
      let p = 0;
      let a = c;
      if (t === 0) return b;
      if ((t /= d / 2) === 2) return b + c;
      if (!p) p = d * (0.3 * 1.5);
      if (a < Math.abs(c)) {
        a = c;
        s = p / 4;
      } else {
        s = (p / (2 * Math.PI)) * Math.asin(c / a);
      }
      if (t < 1) {
        return (
          -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin(((t * d - s) * (2 * Math.PI)) / p)) + b
        );
      }
      return (
        a * Math.pow(2, -10 * (t -= 1)) * Math.sin(((t * d - s) * (2 * Math.PI)) / p) * 0.5 + c + b
      );
    };

    Back

    easeInBack
    const easeInBack = (_, t, b, c, d, s = 1.70158) => c * (t /= d) * t * ((s + 1) * t - s) + b;
    easeOutBack
    const easeOutBack = (_, t, b, c, d, s = 1.70158) =>
      c * ((t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b;
    easeInOutBack
    const easeInOutBack = (_, t, b, c, d, s = 1.70158) =>
      (t /= d / 2) < 1
        ? (c / 2) * (t * t * (((s *= 1.525) + 1) * t - s)) + b
        : (c / 2) * ((t -= 2) * t * (((s *= 1.525) + 1) * t + s) + 2) + b;

    Bounce

    easeOutBounce
    const easeOutBounce = (_, t, b, c, d) => {
      if ((t /= d) < 1 / 2.75) {
        return c * (7.5625 * t * t) + b;
      } else if (t < 2 / 2.75) {
        return c * (7.5625 * (t -= 1.5 / 2.75) * t + 0.75) + b;
      } else if (t < 2.5 / 2.75) {
        return c * (7.5625 * (t -= 2.25 / 2.75) * t + 0.9375) + b;
      }
      return c * (7.5625 * (t -= 2.625 / 2.75) * t + 0.984375) + b;
    };
    easeInBounce
    const easeOutBounce = (_, t, b, c, d) => {
      if ((t /= d) < 1 / 2.75) {
        return c * (7.5625 * t * t) + b;
      } else if (t < 2 / 2.75) {
        return c * (7.5625 * (t -= 1.5 / 2.75) * t + 0.75) + b;
      } else if (t < 2.5 / 2.75) {
        return c * (7.5625 * (t -= 2.25 / 2.75) * t + 0.9375) + b;
      }
      return c * (7.5625 * (t -= 2.625 / 2.75) * t + 0.984375) + b;
    };
     
    const easeInBounce = (x, t, b, c, d) => c - easeOutBounce(x, d - t, 0, c, d) + b;
    easeInOutBounce
    const easeOutBounce = (_, t, b, c, d) => {
      if ((t /= d) < 1 / 2.75) {
        return c * (7.5625 * t * t) + b;
      } else if (t < 2 / 2.75) {
        return c * (7.5625 * (t -= 1.5 / 2.75) * t + 0.75) + b;
      } else if (t < 2.5 / 2.75) {
        return c * (7.5625 * (t -= 2.25 / 2.75) * t + 0.9375) + b;
      }
      return c * (7.5625 * (t -= 2.625 / 2.75) * t + 0.984375) + b;
    };
     
    const easeInBounce = (x, t, b, c, d) => c - easeOutBounce(x, d - t, 0, c, d) + b;
     
    const easeInOutBounce = (x, t, b, c, d) =>
      t < d / 2
        ? easeInBounce(x, t * 2, 0, c, d) * 0.5 + b
        : easeOutBounce(x, t * 2 - d, 0, c, d) * 0.5 + c * 0.5 + b;

    Live demo

    Customizing Tips

    Specifying container elements

    In the following example we have specified in the container for scrolling the #container.

    <div id="container">
      <a href="#heading2" data-scroll>Go to Heading2</a>
      ...
      <h2 id="heading2">Heading2</h2>
    </div>
    // Specified in the CSS Selector
    const scroller = new SweetScroll(
      {
        /* some options */
      },
      '#container',
    );
     
    // Specified in the Element
    const scroller = new SweetScroll(
      {
        /* some options */
      },
      document.getElementById('container'),
    );

    Specify fixed header

    Add the data-scroll-header attribute in order to offset the height of the fixed header.

    <header data-scroll-header></header>

    Specify the CSS Selector in header option instead of the data-scroll-header attribute.

    const scroller = new SweetScroll({
      header: '#header',
    });

    Override of options for each element

    You can override the default options by passing the option in JSON format to the data-scroll-options.

    <a href="#target" data-scroll data-scroll-options='{"easing": "easeOutExpo"}'>Go to Target</a>

    If you want to use in non anchor element

    Will use the data-scroll attribute instead of href.

    <button type="button" data-scroll="+=500">Scroll under 500px</button>

    Scrolling animation in another page

    The following, Introduce one of the mounting method.

    document.addEventListener(
      'DOMContentLoaded',
      () => {
        const scroller = new SweetScroll();
        const hash = window.location.hash;
        const needsInitialScroll = document.getElementById(hash.substr(1)) != null;
     
        if (needsInitialScroll) {
          window.location.hash = '';
        }
     
        window.addEventListener(
          'load',
          () => {
            if (needsInitialScroll) {
              scroller.to(hash, { updateURL: 'replace' });
            }
          },
          false,
        );
      },
      false,
    );

    Live demo

    You can also achieve the same thing in other ways by using the provided API.

    API

    new SweetScroll(options?: PartialOptions, container?: string | Element | Window)

    Will generate a SweetScroll instance.

    Example:

    const scroller = new SweetScroll(
      {
        duration: 1200,
        easing: 'easeOutExpo',
      },
      '#container',
    );

    SweetScroll.create(options?: PartialOptions, container?: string | Element | Window)

    Will generate a SweetScroll instance. (factory method)

    Example:

    const scroller = SweetScroll.create(
      {
        duration: 1200,
        easing: 'easeOutExpo',
      },
      '#container',
    );

    to(distance: any, options?: PartialOptions)

    Scroll animation to the specified distance. distance to can specify the CSS Selector or scroll position.

    Example:

    const scroller = new SweetScroll();
     
    // CSS Selector of target element
    scroller.to('#footer');
     
    // Object
    scroller.to({ top: 1000, left: 20 });
     
    // Array (top:0, left:1000)
    scroller.to([0, 1000]);
     
    // Number (Priority to vertical scroll position. by default.)
    scroller.to(500);
     
    // String (Relative position)
    scroller.to('+=500');
    scroller.to('-=200');

    toTop(distance: any, options?: PartialOptions)

    Vertical scroll animation to the specified distance.

    Example:

    scroller.toTop(0);

    toLeft(distance: any, options?: PartialOptions)

    Horizontal scroll animation to the specified distance.

    Example:

    scroller.toLeft(1500);

    toElement($el: Element, options?: PartialOptions)

    Scroll animation to the specified Element.

    Example:

    scroller.toElement(document.getElementById('content'));

    update(options: PartialOptions)

    Will update the SweetScroll instance. Primarily used in the case of option update.

    Example:

    scroller.update({
      trigger: 'a[href^="#"]',
      duration: 3000,
    });

    stop(gotoEnd: boolean = true)

    gotoEnd: {Boolean}

    Will stop the current scroll animation.

    Example:

    scroller.stop(true);

    destroy()

    Will destroy the SweetScroll instance. Disable of the method and event handler.

    Example:

    scroller.destroy();

    Callbacks

    In before and after, you will pass the coordinates and the triggering element in the argument. In addition, you can stop the scrolling by return a before in false.

    Example:

    const scroller = new SweetScroll({
      // Stop scrolling case of trigger element that contains the `is-disabled` class.
      before: (offset: Offset, $trigger: Element | null, scroller: SweetScroll): boolean | void => {
        console.log('Before!!', offset, scroller);
        if ($trigger && $trigger.classList.contains('is-disabled')) {
          return false;
        }
      },
     
      // If the `wheel` or `touchstart` event is called
      cancel: (scroller: SweetScroll): void => {
        console.log('Cancel!!', scroller);
      },
     
      // Scroll animation is complete
      after: (offset: Offset, $trigger: Element | null, scroller: SweetScroll): void => {
        console.log('After!!', offset, $trigger, scroller);
      },
     
      // Scroll animation is complete (`after` or `cancel`)
      complete: (isCancel: boolean, scroller: SweetScroll): void => {
        console.log('Complete!!', isCancel, scroller);
      },
     
      // Each animation frame
      step: (time: number, scroller: SweetScroll): void => {
        console.log('step', time, scroller);
      },
    });

    Extends Class:

    The following is a pattern to override a method in the inheritance destination class.

    import SweetScroll, { Offset } from 'sweet-scroll';
     
    class MyScroll extends SweetScroll {
      protected onBefore(offset: Offset, $trigger: Element | null): boolean | void {
        // Stop scrolling case of trigger element that contains the `is-disabled` class.
        console.log('Before!!', offset);
        if ($trigger && $trigger.classList.contains('is-disabled')) {
          return false;
        }
      }
     
      protected onCancel(): void {
        console.log('Canell!!');
      }
     
      protected onAfter(offset: Offset, $trigger: Element | null): void {
        console.log('After!!', offset, $trigger);
      }
     
      protected onComplete(isCancel: boolean): void {
        console.log('Complete!!', isCancel);
      }
     
      protected onStep(time: number): void {
        console.log('step', time);
      }
    }

    Browser Support

    Works in IE10+, and all modern browsers.

    Scrolling with IE9

    It is necessary to use polyfill or ponyfill of requestAnimationFrame.

    Example ponyfill

    Using raf module.

    import raf from 'raf';
    import SweetScroll from 'sweet-scroll';
     
    SweetScroll.raf = raf;
    SweetScroll.caf = raf.cancel;

    CHANGELOG

    See the CHANGELOG.md

    Contibute

    1. Fork it!
    2. Create your feature branch: git checkout -b my-new-feature
    3. Commit your changes: git commit -am 'Add some feature'
    4. Push to the branch: git push origin my-new-feature
    5. Submit a pull request 💪

    Bugs, feature requests and comments are more than welcome in the issues.

    Development

    We will develop using the following npm scripts.

    yarn start

    Launch the local server and let the demo run. Opening http://localhost:3000 in your browser.

    yarn build

    Compile TypeScript and create type definitions.

    yarn test

    Run unit testing with Jest.

    License

    MIT © tsuyoshiwada

    Install

    npm i sweet-scroll

    DownloadsWeekly Downloads

    11,355

    Version

    4.0.0

    License

    MIT

    Unpacked Size

    79.6 kB

    Total Files

    25

    Last publish

    Collaborators

    • wadackel