Coder Social home page Coder Social logo

jsonkao / react-scrollama Goto Github PK

View Code? Open in Web Editor NEW
375.0 5.0 29.0 9.78 MB

Simple scrollytelling with the IntersectionObserver in React.

Home Page: https://jsonkao.github.io/react-scrollama

License: MIT License

JavaScript 90.93% HTML 9.07%
scrollytelling react intersection-observer

react-scrollama's Introduction

React Scrollama 🦙

npm version

React Scrollama is a simple and silky library for scrollytelling. It relies on IntersectionObserver and sticky positioning over scroll listeners. It is originally adapted from Russel Samora's Scrollama.

A few examples of ambitious interactive stories that were built with React Scrollama…


17 interactive visualization
stories
using React Scrollama
for scrollytelling

Comment Tati a imprimé
sa marque à Barbès

by Fabien Casaleggio

The scramble to secure
America’s voting machines

by Beatrice Jin

Sex Diversity Among Grad
Students is Stagnating

by Jason Kao

Demo

A live demo lives here. It was debu'd at the August 2018 ReactNYC meetup.

Basic step triggers Sticky graphic on the side

Install

React Scrollama can be installed as an npm package:

$ npm install react-scrollama

Usage

A Scrollama component wraps a set of steps. Each Step component must wrap a DOM element (i.e. not just text).

<Scrollama onStepEnter={callback} offset={0.5}>
  <Step data={1}>
    <div>...</div>
  </Step>
  <Step data={2}>
    <div>...</div>
  </Step>
</Scrollama>

<Scrollama> provides an interface for listening in on scroll triggers like entering or exiting a step. (Here's a full list of available props.)

A basic example:

import React, { useState } from 'react';
import { Scrollama, Step } from 'react-scrollama';

const ScrollamaDemo = () => {
  const [currentStepIndex, setCurrentStepIndex] = useState(null);

  // This callback fires when a Step hits the offset threshold. It receives the
  // data prop of the step, which in this demo stores the index of the step.
  const onStepEnter = ({ data }) => {
    setCurrentStepIndex(data);
  };

  return (
    <div style={{ margin: '50vh 0', border: '2px dashed skyblue' }}>
      <div style={{ position: 'sticky', top: 0, border: '1px solid orchid' }}>
        I'm sticky. The current triggered step index is: {currentStepIndex}
      </div>
      <Scrollama offset={0.5} onStepEnter={onStepEnter} debug>
        {[1, 2, 3, 4].map((_, stepIndex) => (
          <Step data={stepIndex} key={stepIndex}>
            <div
              style={{
                margin: '50vh 0',
                border: '1px solid gray',
                opacity: currentStepIndex === stepIndex ? 1 : 0.2,
              }}
            >
              I'm a Scrollama Step of index {stepIndex}
            </div>
          </Step>
        ))}
      </Scrollama>
    </div>
  );
};

export default ScrollamaDemo;

API

React Scrollama components do not render into the DOM. They are meant to set up Intersection Observers on the elements inside the <Step> components. In the code above, only the <div> elements would show up in the DOM.

Scrollama

These are the props you can set on the Scrollama component itself:

Prop Type Default Description
offset number (from 0 to 1) or pixel value (e.g. "300px") 0.3 How far from the top of the viewport to trigger a step (as a proportion of view height)
threshold number (greater than 1) 4 Granularity of the progress interval in pixels (smaller = more granular)
onStepEnter function Callback that fires when the top or bottom edge of a step enters the offset threshold.
onStepExit function Callback that fires when the top or bottom edge of a step exits the offset threshold.
onStepProgress function Callback that fires the progress a step has made through the threshold.
debug boolean false Whether to show visual debugging tools.

The onStepEnter and onStepExit callbacks receive one argument, an object, with the following properties:

{
  element, // The DOM node of the step that was triggered
  data, // The data supplied to the step
  direction, // 'up' or 'down'
  entry, // the original `IntersectionObserver` entry
}

The onStepProgress callback receives one argument, an object, with the following properties:

{
  element, // The DOM node of the step that was triggered
  data, // The data supplied to the step
  progress, // The percent of completion of the step (0 to 1)
  direction, // 'up' or 'down'
  entry, // the original `IntersectionObserver` entry
}

To create a fixed graphic with text scrolling beside/over it, use position: sticky;. How to use position sticky.

Step

A Step element can contain one child, which must be a DOM element. To use a React component as the child node, it must be wrapped with a DOM element like <div>.

These are the props you can set on the Step component:

Prop Type Default Description
data any Data to be given to <Scrollama> callbacks when step triggered.

You will also probably want to set a key prop on each Step if you're transforming an array of data into a list of Step elements (see Lists and Keys).

Thank you to everyone who made this possible!

Features roadmap

  • Currently, there is no way to throttle/customize React Scrollama's resize listener 😢. We're working on this in #44.
  • Fire previous step triggers if they were jumped

Lmk if you need these features ASAP.

react-scrollama's People

Contributors

cedricdelpoux avatar danieleguido avatar davidnmora avatar dependabot[bot] avatar depfu[bot] avatar fschwander avatar jkjustjoshing avatar jonesbp avatar jsonkao avatar kennethormandy avatar lane avatar maerzhase avatar nicholaslyang avatar thisispaul avatar tucker-gordon-bah 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

react-scrollama's Issues

Call for maintainers

Hi, so I've gotten super slow at responding to issues and pull requests. I'm tagging all the beautiful people who have helped make React Scrollama possible today. Would anyone like to become a maintainer with me? It would involve responding to issues and PRs faster than my current average time (1 month lol.. sorry).

@maerzhase @fschwander @davidnmora @tuckergordon

Sorry if I forgot anyone!

Jest Snapshots

Howdy,

I've noticed that when I setup Jest Snapshot testing on a page that has Scrollama setup, it continually re-generating ids. E.g:

    -                 id="813dd771-d938-48ca-a2d5-0c7b82d84333"
    +                 id="588693b2-d464-4ada-8abf-b217a101d32f"

Therefore snapshot tests are always failing. Is it possible to avoid this?

Update peerDependencies - allow React 17 and 18

Right now, the versions of react and react-dom required are ^16.8.0. However, this library should work fine with React 17 and React 18 (both of which are allowed in the react-intersection-observer dependency.

NPM v8 is enforcing peerDependencies more strictly than previous versions, so trying to install this on a React 17 project errors. I don't think there's anything in the library that would preclude it from working on newer versions of React. Can we add those versions to this package's peer dependencies?

Resizing should be more exposed to the user

Right now, React Scrollama simply adds Scrollama.handleResize as a resize event to the window. Ideally, users should be able to throttle this resizing. It would also be great to just be able to call it somehow after updating DOM/Step elements.

Dynamic contents don't get updated

Relevalent snippet

let jsx=[];
for (let i=0;i<N;i++){
   jsx.push(<Step data={{step:i}}><div> {i}</div></Step>);
}
return(<Scrollama onStepEnter={onStepEnter}>{jsx}</Scrollama>);

This works well while N is the same (or smaller) than the original. But if you change N to a larger number afterwards, it will only work up to that number.

React.Children.only expected to receive a single React element child

If you try to recreate the example in the README, you get this error:

React.Children.only expected to receive a single React element child

The problem is that <Step> is taking text nodes in the example:

        <Scrollama onStepEnter={this.onStepEnter}>
          <Step data={1} key='1'>
            step 1
          </Step>
          <Step data={2} key='2'>
            step 2
          </Step>
        </Scrollama>

However, if you replace the text nodes any element, then everything works correctly:

        <Scrollama onStepEnter={this.onStepEnter}>
          <Step data={1} key='1'>
            <div>step 1</div>
          </Step>
          <Step data={2} key='2'>
            <div>step 2</div>
          </Step>
        </Scrollama>

React.Children.only(children) throws with a text node: https://github.com/jsonkao/react-scrollama/blob/master/src/Step.js#L25

I don't know if you want to allow text nodes, or update the README to wrap the text in elements.

2.0.3-beta breaks next.js (throws error under ssr, no workaround)

Version 2.3.0-beta.0 introduces an incompatibility with next.js SSR. As v2.3.0-beta.0 fixes other dealbreaker issues (#19 (comment)) this is a blocker problem for me.

Overview

  1. Under next.js, after upgrading to 2.3.0-beta.0, previously working code now throws the error
Server Error 
ReferenceError: window is not defined
  1. There's a standard workaround for this sort of problem - using dynamic imports. However if you attempt this (which btw also works as expected under 2.2.16 even though there's no need for it) with 2.3.0-beta.0 you get a Scrollama error
Unhandled Runtime Error 
SyntaxError: Failed to construct 'IntersectionObserver': rootMargin must be specified in pixels or percent
  1. Similar next.js workarounds (eg using process.browser) also generate the same error.

So at present it looks like using the library with next.js is not possible at all.

Steps to reproduce 1 (window error)

(the no-frills example from the Readme, unmodified):

pages/index.js

import React, { useState } from 'react';
import { Scrollama, Step } from 'react-scrollama';

const ScrollamaDemo = () => {
  const [currentStepIndex, setCurrentStepIndex] = useState(null);

  // This callback fires when a Step hits the offset threshold. It receives the
  // data prop of the step, which in this demo stores the index of the step.
  const onStepEnter = ({ data }) => {
    setCurrentStepIndex(data);
  };

  return (
    <div style={{ margin: '50vh 0', border: '2px dashed skyblue' }}>
      <div style={{ position: 'sticky', top: 0, border: '1px solid orchid' }}>
        I'm sticky. The current triggered step index is: {currentStepIndex}
      </div>
      <Scrollama onStepEnter={onStepEnter} debug>
        {[1, 2, 3, 4].map((_, stepIndex) => (
          <Step data={stepIndex} key={stepIndex}>
            <div
              style={{
                margin: '50vh 0',
                border: '1px solid gray',
                opacity: currentStepIndex === stepIndex ? 1 : 0.2,
              }}
            >
              I'm a Scrollama Step of index {stepIndex}
            </div>
          </Step>
        ))}
      </Scrollama>
    </div>
  );
};

export default ScrollamaDemo;

Steps to reproduce 2 (IntersectionObserver error)

pages/index.js

import React, {useState} from "react"
import dynamic from 'next/dynamic'

const ScrollamaDemo = dynamic(
	() => import('../components/ScrollamaDemo'),
	{ ssr: false }
)

const Page = () => 
	<div><ScrollamaDemo/></div>

export default Page

components/ScrollamaDemo.js

import React, {useState} from "react"
import { Scrollama, Step } from 'react-scrollama'

const ScrollamaDemo = () => {
  const [currentStepIndex, setCurrentStepIndex] = useState(null);
  const onStepEnter = ({ data }) => {
    setCurrentStepIndex(data);
  };

  return (
    <div style={{ margin: '50vh 0', border: '2px dashed skyblue' }}>
      <div style={{ position: 'sticky', top: 0, border: '1px solid orchid' }}>
        I'm sticky. The current triggered step index is: {currentStepIndex}
      </div>
      <Scrollama onStepEnter={onStepEnter} debug>
        {[1, 2, 3, 4].map((_, stepIndex) => (
          <Step data={stepIndex} key={stepIndex}>
            <div
              style={{
                margin: '50vh 0',
                border: '1px solid gray',
                opacity: currentStepIndex === stepIndex ? 1 : 0.2,
              }}
            >
              I'm a Scrollama Step of index {stepIndex}
            </div>
          </Step>
        ))}
      </Scrollama>
    </div>
  )
}

export default ScrollamaDemo

Rewrite using functional components and hooks

Hi there it's me again! 😸
I had some time to look into scrollama and react-scrollama. With the problems we previously found in e.g. #40 I started to be curious about a modern functional components and hooks approach for react-scrollama . After I did a short deep dive into both libs and the use of react-intersection-observer-hook lib, it was actually pretty easy to implement a working version.

The benefits I see in the rewrite is mainly in code maintenance (less code) and smaller bundle size. My build works as a drop-in replacement of the example and is only ~8kb instead of ~26kb.

Since i am not sure if you are at all interested in such a rewrite I wanted to open this issue before I open a PR. Just let me know if you want to integrate this as a release of this library. You can have a look at the code here:
https://github.com/maerzhase/react-scrollama/tree/feature/functional-rewrite

Since there are no tests written i am not 100% sure if I might have missed something. I tried my best to keep up with the existing API and from running the example I actually do not experience any problems.

Eexpects window to be defined, but now always the case

Webpack production build using Gatsby return WebpackError: ReferenceError: window is not defined.
Should make sure window is define before loading intersection-oberver. Will also make other window usage in scrollama.js works with that mechanic.

onStepEnter isn't being called in gatsby

I'm trying to get the example working in Gatsby. The component renders, but onStepEnter is never actually called:

import React, { Component } from "react"
import { Scrollama, Step } from 'react-scrollama';

import Layout from "../components/layout"
import Head from '../components/head';

class NarrativePage extends Component {
  state = {
    data: 0,
    steps: [10, 20, 30],
    progress: 0,
  };

  onStepEnter = ({ element, data }) => {
    console.log(data);
    element.style.backgroundColor = 'lightgoldenrodyellow';
    this.setState({ data });
  };

  onStepExit = ({ element }) => {
    element.style.backgroundColor = '#fff';
  };

  onStepProgress = ({ element, progress }) => {
    this.setState({ progress });
  };

  render() {
    const { data, steps, progress } = this.state;
    
    return (
      <Layout>
        <Head title="Narrative"/>
        <div className="graphicContainer">
          <div className="scroller">
            <Scrollama
              onStepEnter={this.onStepEnter}
              onStepExit={this.onStepExit}
              progress
              onStepProgress={this.onStepProgress}
              offset={0.4}
              debug
            >
              {steps.map(value => (
                <Step data={value} key={value}>
                  <div className="step">
                    <p>step value: {value}</p>
                    {value === data && <p>{Math.round(progress * 100)}%</p>}
                  </div>
                </Step>
              ))}
            </Scrollama>
          </div>
          <div className="graphic">
            <p>{data}</p>
          </div>
        </div>
      </Layout>
    )
  }
}

export default NarrativePage;

Forgot default Props for offset in Scrollama.js

Hey there!

First of all: thanks for the great npm package. <3

It seems that you forgot the default props of of offset equals 0.3 in Scrollama.js (as described in your documentation).

Best, Fabian

Background image resolution changes

Hi @jsonkao

I am using your awesome react-scrollama npm in my project. I made the background image fixed with the css property "background-attachment: fixed" and added a scrollama component with 3 Steps over the container. Then, when I scroll up or down, the background image gets blurry except for start and end of scrolling. How can I fix it? Thank you in advance!

`defaultProps` warning

As of React #25699, usage of defaultProps will result in a warning:

Support for defaultProps will be removed from function components in a future major release. Use JavaScript default parameters instead.

This impacts the Scrollama and Step component.

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.