Giter Club home page Giter Club logo

scrollama's Introduction

scrollama.js

Scrollama is a modern & lightweight JavaScript library for scrollytelling using IntersectionObserver in favor of scroll events. Current version: 3.2.0

3.0

Why 3.0?

The core parts of the Scrollama code base are being refactored for 3.0 to simplfy and clarify the logic. The goal behind this to ease make future maintainance, bug fixing, and feature additions easier moving forward.

New Fetaures

  • Built-in resize using resize observers.
  • Custom offsets on steps with data attributes

Deprecated Features

  • the order option

Important Changes

  • Version 3.0.0+: order has been deprecated.
  • Version 2.0.0+: .onContainerEnter and .onContainerExit have been deprecated in favor of CSS property position: sticky;. How to use position sticky.
  • Version 1.4.0+: you must manually add the IntersectionObserver polyfill for cross-browser support. See installation for details.

Jump to examples.

Why?

Scrollytelling can be complicated to implement and difficult to make performant. The goal of this library is to provide a simple interface for creating scroll-driven interactives. Scrollama is focused on performance by using IntersectionObserver to handle element position detection.

scrollytelling pattern

Examples

Note: most of these examples use D3 to keep the code concise, but this can be used with any library, or with no library at all.

  • Basic - just step triggers
  • Progress - incremental step progress callback
  • Sticky Graphic (Side by Side) - using CSS position: sticky; to create a fixed graphic to the side of the text.
  • Sticky Graphic (Overlay) - using CSS position: sticky; to create a fixed graphic with fullscreen graphic with text overlayed.
  • Custom Offset - Adding a data attribute to an element to provide a unique offset for a step.
  • Mobile Pattern - using pixels instead of percent for offset value so it doesn't jump around on scroll direction change
  • iframe Embed - Embedding a Scrollama instance inside an iframe using root option

Installation

Note: As of version 1.4.0, the IntersectionObserver polyfill has been removed from the build. You must include it yourself for cross-browser support. Check here to see if you need to include the polyfill.

Old school (exposes the scrollama global):

<script src="https://unpkg.com/scrollama"></script>

New school:

npm install scrollama intersection-observer --save

And then import/require it:

import scrollama from "scrollama"; // or...
const scrollama = require("scrollama");

How to use

Basic

You can use this library to simply trigger steps, similar to something like Waypoints. This is useful if you need more control over your interactive, or you don't want to follow the sticky scrollytelling pattern.

You can use any id/class naming conventions you want. The HTML structure should look like:

<!--you don't need the "data-step" attr, but can be useful for storing instructions for JS -->
<div class="step" data-step="a"></div>
<div class="step" data-step="b"></div>
<div class="step" data-step="c"></div>
// instantiate the scrollama
const scroller = scrollama();

// setup the instance, pass callback functions
scroller
  .setup({
    step: ".step",
  })
  .onStepEnter((response) => {
    // { element, index, direction }
  })
  .onStepExit((response) => {
    // { element, index, direction }
  });

API

scrollama.setup([options])

options:

Option Type Description Default
step string or HTMLElement[] required Selector (or array of elements) for the step elements that will trigger changes.
offset number (0 - 1, or string with "px") How far from the top of the viewport to trigger a step. 0.5
progress boolean Whether to fire incremental step progress updates or not. false
threshold number (1 or higher) The granularity of the progress interval in pixels (smaller = more granular). 4
once boolean Only trigger the step to enter once then remove listener. false
debug boolean Whether to show visual debugging tools or not. false
parent HTMLElement[] Parent element for step selector (use if you steps are in shadow DOM). undefined
container HTMLElement Parent element for the scroll story (use if scrollama is nested in a HTML element with overflow set to scroll or auto) undefined
root HTMLElement The element that is used as the viewport for checking visibility of the target. Must be the ancestor of the target. Defaults to the browser viewport if not specified or if null. See more details about usage of root on MDN. undefined

scrollama.onStepEnter(callback)

Callback that fires when the top or bottom edge of a step element enters the offset threshold.

The argument of the callback is an object: { element: DOMElement, index: number, direction: string }

element: The step element that triggered

index: The index of the step of all steps

direction: 'up' or 'down'

scrollama.onStepExit(callback)

Callback that fires when the top or bottom edge of a step element exits the offset threshold.

The argument of the callback is an object: { element: DOMElement, index: number, direction: string }

element: The step element that triggered

index: The index of the step of all steps

direction: 'up' or 'down'

scrollama.onStepProgress(callback)

Callback that fires the progress (0 - 1) a step has made through the threshold.

The argument of the callback is an object: { element: DOMElement, index: number, progress: number }

element: The step element that triggered

index: The index of the step of all steps

progress: The percent of completion of the step (0 - 1)

direction: 'up' or 'down'

scrollama.offsetTrigger([number or string])

Get or set the offset percentage. Value must be between 0-1 (where 0 = top of viewport, 1 = bottom), or a string that includes "px" (e.g., "200px"). If set, returns the scrollama instance.

scrollama.resize()

This is no longer necessary with the addition of a built-in resize observer. Tell scrollama to get latest dimensions the browser/DOM. It is best practice to throttle resize in your code, update the DOM elements, then call this function at the end.

scrollama.enable()

Tell scrollama to resume observing for trigger changes. Only necessary to call if you have previously disabled.

scrollama.disable()

Tell scrollama to stop observing for trigger changes.

scrollama.destroy()

Removes all observers and callback functions.

custom offset

To override the offset passed in the options, set a custom offset for an individual element using data attributes. For example: <div class="step" data-offset="0.25"> or data-offset="100px".

Scrollama In The Wild

Tips

  • Avoid using viewport height (vh) in your CSS because scrolling up and down constantly triggers vh to change, which will also trigger a window resize.

Alternatives

Logo

Logo by the awesome Elaina Natario

License

MIT License

Copyright (c) 2022 Russell Samora

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

scrollama's People

Contributors

albinotonnina avatar andershagbard avatar andysongs avatar ddazal avatar eelsie avatar gabrielflorit avatar git-ashish avatar iainmcampbell avatar ivanbacher avatar jonnyscholes avatar jsonkao avatar pldg avatar ryshackleton avatar sidkwok avatar smnarnold avatar tbroadley avatar tuckergordon avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

scrollama's Issues

Wrong license in file

Just a trifle, still: in the Readme the license is MIT, while in the file is Apache v2.

cannot read property state of undefined

Hi there,

this code:

const scroller3 = scrollama();

scroller3
  .setup({
    step: 'section',
    offset: 0.05,
    progress: true,
    debug: false,
  })
  .onStepProgress(({ element }) => {
    if (element.classList.contains('is-white')) {
      this.isDark = false;
    } else {
      this.isDark = true;
    }
  });

causes a lot of these errors on my page:

image

Might have something todo with no section element being available on the page? I'm not sure though - any advice?

Scrollama fails silently if an element is not found

I was having an issue getting scrollama to recognize when a container entered and exited the viewport. It turns out that in the scroller.setup(), I had listed a graphic class that didn't exist. No errors appeared.

Keyboard listeners

Would be great to be able to navigate between steps using the keyboard arrows and the space bar.

graph-scroll has it, and it's quite handy.

onContainerProgress

Hey, I'd like to suggest an additional callback: onContainerProgress with the response.progress variable indicating the scrolling progress in relation to the total container height.

Since we know the container element, and the logic for onStepProgress is already there, I'm guessing this could be pretty straightforward to implement.

For now I've managed to work around it, using onStepProgress callbacks:
https://codepen.io/21sieben/pen/mXZJZJ (debug view only, see #38 )

Multiple listeners on non step events

I have a page where I want the header text to disappear after it's 50% gone, then 2 images I want to fade in on scrolling around their top section and a parallax text that moves on progress.

My idea was to create three instances and set them up but somehow one of them always doesn't work while the 2 others do. What am I doing wrong?

const header = scroller
          .setup({
            step: '.top-cta',
            offset: 0.5,
            debug: true
          })
          .onStepEnter(this.handleStepEnter)
          .onStepExit(this.handleStepExit)
          .onStepProgress(this.handleStepProgress);

          header.resize()
          header.enable()
        
const ImageTop = scroller
          .setup({
            step: '.text1',
            offset: 0.5,
            order: 1
          })
          .onStepEnter(this.loadTop)
          .onStepExit(this.loadLeft)
          ImageTop.resize()
          ImageTop.enable()

const parallaxText = scroller
          .setup({
            step: '.parallax-text',
            offset: 0.5,
            order: 2,
            threshold: 9,
            progress: true
          })
          .onStepProgress(this.handleStepProgress);
          parallaxText.resize()
          parallaxText.enable();

plan increment

  • Should it be entire for entire scroll or per/step?
  • What happens if the element is > vh?

New Firefox browser doesn't track progress under certain conditions

It appears that if any part of a step element is below the viewport at the point that it is triggered, the progress response goes from 0 to 1 immediately. Chrome, IE, Safari all handle this fine however.

Could be something I'm missing, but I created a quick codepen for it. (Check it out in Firefox Quantum) https://codepen.io/jakecrump/pen/wpdGXZ

(Also, the codepen preview in Firefox is a little finicky and you may have to refresh a couple times to get it to trigger at all)

How to create multiple offsets?

Is it possible to create different offsets for the steps of a Scrollama instance? This seems impossible with the way it is setup now.

Scroll bound animation?

Hi! Been playing around with scrollama and couldn't figure out if its possible to do a scroll bound animation with greensock?

I've managed to get it to work with the 'onStepEnter/onStepExit' but that plays the animation immediately. Also with this scenario, if i were to scroll up and down a couple of times, the animation will continue to replay the number of times it was scrolled.

Here's my codepen:
link

Not entirely sure if this is the best implementation of gsap with scrollama. Any insight will be much appreciated! Thank you

optimize progress detection

Right now it divides a step into 100 increments, regardless of height. Maybe find a "comfortable" threshold (eg. 5px) and create increments based on that, so small elements won't have excessive watchers? Could also make that threshold configurable.

debug step elements as well?

  • new color outlines for rootMargin of each step that matches step border
  • verbose option that console logs details

Manage Viewport Jumps

Is there a way to manage viewport jumps that were implemented by #30 ?

Take this scenario for example: If you load the page at step 5 (scrolled half way down a page), step 5's onStepEnter get's triggered, but then so do all of the other steps above. These don't fire in order, so we get step 5, then 4, 3, 2, 1. In my case, I have effects in step 5 that is undone by previous steps.

Some possible solutions:

  • Could we make a current step indicator available? You could use this in some of the logic that fires during the onStepEnter callback.
  • Could we apply these steps in order? Although then you'd be running through the whole app each time you reload it.
  • Can we have the ability to turn off "backfilling" the steps?

If there is already a way around this and I've missed it, could you please let me know?

Would love to hear your thoughts. Thanks!

onStepEnter and onStepExit fire in wrong order?

Let's say I got 5 divs of a height of 500px each on a page.
If I scroll through it and log the Enter and Exit events when they go passed the offset treshold, the events are fired like this:

Enter (1st element enters)
Enter (2nd element enters)
Exit (1st element exits)
Enter (3rd element enters)
Exit (2nd Element exits)
...

What I would've expected:

Enter (1st element enters)
Exit (1st element exits)
Enter (2nd element enters)
Exit (2nd Element exits)
Enter (3rd element enters)
...

is there a specific reason for this order?

screencasts of start to finish implementation

  • 0: step triggers event on static graphic, responsive on mobile - example
  • 1: fixed graphic with step triggers, responsive on mobile - example
  • 2: fixed graphic with step triggers, stacked on mobile - example
  • 3: fixed graphic with progress based triggers, responsive on mobile - example
  • 4: sticky graphic pattern with step triggers, responsive on mobile - example

Intersection Observer Polyfill?

Hi there! This is awesome!

Given the spotty browser support for Intersection Observer, I might recommend include a version with the polyfill baked in, or mentioning it in the docs and pointing people to it.

Would be nice to get some browser support info in the readme, too.

If you want my help in any way with this, please just let me know and I'll fork and PR.

IntersectionObserver polyfill causes errors with server-side rendering

Our project does server-side rendering, so when we import scrollama, we get errors about window being undefined. That's because the IntersectionObserver polyfill isn't meant to run on the server.

A lot of other projects have polyfills as a prerequisite, rather than having them as dependencies. This gives developers the option to exclude the polyfill if their browser support list doesn't require it (which will, eventually, happen with IntersectionObserver), or when not running in a browser environment (such as for SSR).

How to destroy an instance?

With HMR, a component is loaded twice and if that component includes scrollama, it'll be initiated twice.
How can I destroy an instance (so all events are cleaned up and everything)?
Looking for something like

scroller.destroy();

Breaks when used with Angular CLI on Safari

Developing a project using Angular 4 and we included the Scrollama initialization in the NgAfterViewInit lifecycle hook but the setup function is breaking the site in Safari.

Specifically, it's the lines:

var obs = new IntersectionObserver(intersectStepProgress, options);
obs.observe(el);

We believe some polyfills are being overwritten by the Angular CLI tool and so the IntersectionObserver doesn't work properly in Safari.

We tested the demo code from this tutorial in Angular and it breaks it.

Mobile vs desktop demo

It would be neat to create a demo that shows how to make a scrolly that:

  • advances by percentage on desktop
  • advances by slide on mobile

Option to only fire once

Hey thanks for the really good work, its a lightweight modern alternative to scrollmagic that works really well with gsap/TweenMax and i am digging it so far :-)

Is there an option to disable trigger on reverse, like reverse: false or loop: false only saw the option to destroy/disable the controller

possible to not trigger a step

This is due to how IntersectionObserver works - it detects when an element is in view. if the scroll is so fast and the element small enough it can not register an enter/exit (an edge case, but possible if a user is skimming fast or anchor links). This would only be problematic if your JS logic needed step A to occur before B, or if they are totally independent (eg. A simply fades an element in).

Possible solution: index-based checks. Keep a queue of triggered elements, and make sure we don't skip (if so, fire previous trigger). This would work assuming all steps positioned relatively. This isn't a perfect solution however.

onStepProgress seems to fire only on enter and exit

I'm wanting to use Scrollama to parallax some elements over the progress of a div.

I've used ScrollMagic and looking to use Scrollama. Right now I just want to simply transform:translate an element on the progress of a step. The trigger step is the header of the page.

Here is some code below:

  function handleStepProgress(response) {
      console.log(response.progress)
  }

  function init() {
	scroller.setup({
	  step: '.step-parent .step',
          progress: true,
          offset: 0,
	  debug: true,
	})
	.onStepProgress(handleStepProgress)
	  window.addEventListener('resize', scroller.resize)
}
<section id="container">
    <div class="step-parent">
      <section class="step hero team-hero is-large"></section>
    </div>
</section>
.step-parent{
  background-color: black;
  position: relative;
  overflow: hidden;
  min-height: 400px;
}
.step {
  background-size: cover;
  background-position: center;
}

Any help or ways to better understand would be awesome.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.