ScrollTrigger

Let your page react to scroll changes.

README

ScrollTrigger Build Deploy License Issues GitHub release npm (scoped)


Let your page react to scroll changes.

The most basic usage of ScrollTrigger is to trigger classes based on the current scroll position. E.g. when an element enters the viewport, fade it in. You can add custom offsets per element, or set offsets on the viewport (e.g. always trigger after the element reaches 20% of the viewport)

When using the callbacks ScrollTrigger becomes really powerfull. You can run custom code when an element enters / becomes visible, and even return Promises to halt the trigger if the callback fails. This makes lazy loading images very easy.

Installation

npm install @terwanerik/scrolltrigger or just add the dist/ScrollTrigger.min.js file to your project and import that.

How to use?

The easiest way to start is to create a new instance and add some triggers to it, with all default values. This will toggle the 'visible' class when the element comes into the viewport, and toggles the 'invisible' class when it scrolls out of the viewport.

  1. ``` js
  2. // when using ES6 import / npm
  3. import ScrollTrigger from '@terwanerik/scrolltrigger'
  4. // Create a new ScrollTrigger instance with default options
  5. const trigger = new ScrollTrigger() // When not using npm, create a new instance with 'new ScrollTrigger.default()'
  6. // Add all html elements with attribute data-trigger
  7. trigger.add('[data-trigger]')
  8. ```

Now in your CSS add the following classes, this fades the [data-trigger] elements in and out.

  1. ```css
  2. .visible, .invisible {
  3.   opacity: 0.0;
  4.   transition: opacity 0.5s ease;
  5. }
  6. .visible {
  7.   opacity: 1.0;
  8. }
  9. ```

⚠️ Attention

Are you migrating from 0.x to 1.x? Checkout the migration guide!


Some more demo's


A more detailed example

Adding callbacks / different classes can be done globally, this becomes the default for all triggers you add, or you can specify custom configuration when adding a trigger.

  1. ``` js
  2. // Create a new ScrollTrigger instance with some custom options
  3. const trigger = new ScrollTrigger({
  4.   trigger: {
  5.     once: true
  6.   }
  7. })
  8. // Add all html elements with attribute data-trigger, these elements will only be triggered once
  9. trigger.add('[data-trigger]')
  10. // Add all html elements with attribute data-triggerAlways, these elements will always be triggered
  11. trigger.add('[data-triggerAlways]', { once: false })
  12. ```

Options

The full set of options is taken from the demo directory, for more info, check that out.

  1. ``` js
  2. const trigger = new ScrollTrigger({
  3.     // Set custom (default) options for the triggers, these can be overwritten
  4.     // when adding new triggers to the ScrollTrigger instance. If you pass
  5.     // options when adding new triggers, you'll only need to pass the object
  6.     // `trigger`, e.g. { once: false }
  7.     trigger: {
  8.         // If the trigger should just work one time
  9.         once: false,
  10.         offset: {
  11.             // Set an offset based on the elements position, returning an
  12.             // integer = offset in px, float = offset in percentage of either
  13.             // width (when setting the x offset) or height (when setting y)
  14.             //
  15.             // So setting an yOffset of 0.2 means 20% of the elements height,
  16.             // the callback / class will be toggled when the element is 20%
  17.             // in the viewport.
  18.             element: {
  19.                 x: 0,
  20.                 y: (trigger, rect, direction) => {
  21.                     // You can add custom offsets according to callbacks, you
  22.                     // get passed the trigger, rect (DOMRect) and the scroll
  23.                     // direction, a string of either top, left, right or
  24.                     // bottom.
  25.                     return 0.2
  26.                 }
  27.             },
  28.             // Setting an offset of 0.2 on the viewport means the trigger
  29.             // will be called when the element is 20% in the viewport. So if
  30.             // your screen is 1200x600px, the trigger will be called when the
  31.             // user has scrolled for 120px.
  32.             viewport: {
  33.                 x: 0,
  34.                 y: (trigger, frame, direction) => {
  35.                     // We check if the trigger is visible, if so, the offset
  36.                     // on the viewport is 0, otherwise it's 20% of the height
  37.                     // of the viewport. This causes the triggers to animate
  38.                     // 'on screen' when the element is in the viewport, but
  39.                     // don't trigger the 'out' class until the element is out
  40.                     // of the viewport.

  41.                     // This is the same as returning Math.ceil(0.2 * frame.h)
  42.                     return trigger.visible ? 0 : 0.2
  43.                 }
  44.             }
  45.         },
  46.         toggle: {
  47.             // The class(es) that should be toggled
  48.             class: {
  49.                 in: 'visible', // Either a string, or an array of strings
  50.                 out: ['invisible', 'extraClassToToggleWhenHidden']
  51.             },
  52.             callback: {
  53.                 // A callback when the element is going in the viewport, you can
  54.                 // return a Promise here, the trigger will not be called until
  55.                 // the promise resolves.
  56.                 in: null,
  57.                 // A callback when the element is visible on screen, keeps
  58.                 // on triggering for as long as 'sustain' is set
  59.                 visible: null,
  60.                 // A callback when the element is going out of the viewport.
  61.                 // You can also return a promise here, like in the 'in' callback.
  62.                 //
  63.                 // Here an example where all triggers take 10ms to trigger
  64.                 // the 'out' class.
  65.                 out: (trigger) => {
  66.                     // `trigger` contains the Trigger object that goes out
  67.                     // of the viewport
  68.                     return new Promise((resolve, reject) => {
  69.                         setTimeout(resolve, 10)
  70.                     })
  71.                 }
  72.             }
  73.         },
  74.     },
  75.     // Set custom options and callbacks for the ScrollAnimationLoop
  76.     scroll: {
  77.         // The amount of ms the scroll loop should keep triggering after the
  78.         // scrolling has stopped. This is sometimes nice for canvas
  79.         // animations.
  80.         sustain: 200,
  81.         // Window|HTMLDocument|HTMLElement to check for scroll events
  82.         element: window,
  83.         // Add a callback when the user has scrolled, keeps on triggering for
  84.         // as long as the sustain is set to do
  85.         callback: didScroll,
  86.         // Callback when the user started scrolling
  87.         start: () => {},
  88.         // Callback when the user stopped scrolling
  89.         stop: () => {},
  90.         // Callback when the user changes direction in scrolling
  91.         directionChange: () => {}
  92.     }
  93. })

  94. /***
  95. ** Methods on the ScrollTrigger instance
  96. ***/

  97. /**
  98. * Creates a Trigger object from a given element and optional option set
  99. * @param {HTMLElement} element
  100. * @param {DefaultOptions.trigger} [options=DefaultOptions.trigger] options
  101. * @returns Trigger
  102. */
  103. trigger.createTrigger(element, options)

  104. /**
  105. * Creates an array of triggers
  106. * @param {HTMLElement[]|NodeList} elements
  107. * @param {Object} [options=null] options
  108. * @returns {Trigger[]} Array of triggers
  109. */
  110. trigger.createTriggers(elements, options)

  111. /**
  112. * Adds triggers
  113. * @param {string|HTMLElement|NodeList|Trigger|Trigger[]} objects A list of objects or a query
  114. * @param {Object} [options=null] options
  115. * @returns {ScrollTrigger}
  116. */
  117. trigger.add(objects, options)

  118. /**
  119. * Removes triggers
  120. * @param {string|HTMLElement|NodeList|Trigger|Trigger[]} objects A list of objects or a query
  121. * @returns {ScrollTrigger}
  122. */
  123. trigger.remove(objects)

  124. /**
  125. * Lookup one or multiple triggers by a query string
  126. * @param {string} selector
  127. * @returns {Trigger[]}
  128. */
  129. trigger.query(selector)

  130. /**
  131. * Lookup one or multiple triggers by a certain HTMLElement or NodeList
  132. * @param {HTMLElement|HTMLElement[]|NodeList} element
  133. * @returns {Trigger|Trigger[]|null}
  134. */
  135. trigger.search(element)

  136. /**
  137. * Reattaches the scroll listener
  138. */
  139. trigger.listen()

  140. /**
  141. * Kills the scroll listener
  142. */
  143. trigger.kill()


  144. /***
  145. ** Methods on a Trigger instance, e.g. when receiving from a callback or from a query
  146. ***/
  147. const receivedTrigger = new Trigger()

  148. /**
  149. * The HTML element
  150. */
  151. receivedTrigger.element

  152. /**
  153. * The offset settings
  154. */
  155. receivedTrigger.offset

  156. /**
  157. * The toggle settings
  158. */
  159. receivedTrigger.toggle

  160. /**
  161. * If the trigger should fire once, boolean
  162. */
  163. receivedTrigger.once

  164. /**
  165. * If the trigger is visible, boolean
  166. */
  167. receivedTrigger.visible
  168. ```

Migrating from 0.x to 1.x

The main differences between 0.x and 1.x are the way you add and configure your
triggers. 0.x added all HTMLElement's with the data-scroll attribute by default,
1.x doesn't do that, this requires you to add the triggers yourself. This improves
the configuration of the triggers.

Also, please note that when not using a package manager / webpack, and you're
just importing the minified version, you'll have to always use new ScrollTrigger.default().

  1. ``` html
  2. <script src="dist/ScrollTrigger.min.js"></script>
  3. <script>
  4. var trigger = new ScrollTrigger.default()
  5. </script>
  6. ```

Take for example the following element in ScrollTrigger 0.x:

  1. ``` html
  2. <div data-scroll="once addHeight" data-scroll-showCallback="alert('Visible')" data-scroll-hideCallback="alert('Invisible')"></div>
  3. ```

In ScrollTrigger 1.x you would write this mostly in JavaScript:

  1. ``` js
  2. // Say you have some divs with class 'animateMe'
  3. const scrollTrigger = new ScrollTrigger()
  4. scrollTrigger.add('.animateMe', {
  5.     once: true, // same functionality as the `once` flag in v0.x
  6.     offset: {
  7.         element: {
  8.             y: 1.0 // note that we pass a float instead of an integer, when the
  9.                    // offset is a float, it takes it as a percentage, in this
  10.                    // case, add 100% of the height of the element, the same
  11.                    // functionality as the `addHeight` flag in v0.x
  12.         }
  13.     },
  14.     toggle: {
  15.         callback: {
  16.             in: () => { // same as the data-scroll-showCallback, no need to set a
  17.                         // custom callScope when calling custom functions and
  18.                         // the possibility to return a Promise
  19.                 alert('Visible')
  20.             },
  21.             out: () => { // same as the data-scroll-hideCallback
  22.                 alert('Invisible')
  23.             }
  24.         }
  25.     }
  26. })
  27. ```

The advantage of writing all this in javascript is the configuration possible, say
i want to change the offset of the element after the first time it's been visible
(e.g. remove the addHeight flag after it's been shown):

  1. ``` js
  2. scrollTrigger.add('.animateMe', {
  3.     offset: {
  4.         element: {
  5.             y: 1.0
  6.         }
  7.     },
  8.     toggle: {
  9.         callback: {
  10.             in: (trigger) => {
  11.                 // remove the element offset
  12.                 trigger.offset.element.y = 0
  13.             }
  14.         }
  15.     }
  16. })
  17. ```

Another example for setting custom classes per toggle;

  1. ``` html
  2. <div data-scroll="toggle(animateIn, animateOut)"></div>
  3. ```

Becomes

  1. ``` js
  2. const scrollTrigger = new ScrollTrigger()

  3. scrollTrigger.add('[data-scroll]', {
  4.     toggle: {
  5.         class: {
  6.             in: 'animateIn',
  7.             out: 'animateOut'
  8.         }
  9.     }
  10. })
  11. ```

If you have any questions on migrating to v1.x feel free to create a new issue.

Contributing

Fork, have a look in the src/ directory and enjoy! If you have improvements, start a new branch & create a pull request when you're all done :)

Troubleshooting

You can see really quickly if the Trigger is working by hitting 'inspect element'. Here you can see if the visible/invisble class is toggled when you scroll past the element.

If the classes don't toggle, check the JavaScript console. There might be some handy info in there.

Found an issue?

Ooh snap, well, bugs happen. Please create a new issue and mention the OS and browser (including version) that the issue is occurring on. If you are really kind, make a minimal, complete and verifiable example and upload that to codepen.

Legacy

Looking for the old ScrollTrigger? Check out the legacy branch!