jlmakes / scrollreveal

Animate elements as they scroll into view.
https://scrollrevealjs.org/
22.38k stars 2.26k forks source link

Using Scroll Reveal in a React Component #218

Closed joshleong closed 8 years ago

joshleong commented 8 years ago

Been attempting to use ScrollReveal in a react component, everything seems to be working except ScrollReveal isn't being triggered on scrolling, but re-sizing the window in order to hide the elements.

jlmakes commented 8 years ago

Hey @joshleong

I’ve spent a little time with React, but not enough to offer a solution off the top. You’re saying scroll events do nothing, but resize events work as expected?

I’m really swinging blind here as I’m new to React (let alone tried to use ScrollReveal with it), but my first guess would be to make sure to call reveal() in the componentDidMount() event handler. (Source)

Mounting: componentDidMount

void componentDidMount()

Invoked once, only on the client (not on the server), immediately after the initial rendering occurs. At this point in the lifecycle, you can access any refs to your children (e.g., to access the underlying DOM representation). The componentDidMount() method of child components is invoked before that of parent components.

If you want to integrate with other JavaScript frameworks, set timers using setTimeout or setInterval, or send AJAX requests, perform those operations in this method.

If possible, throw together a JSbin or share a screen recording of the problem.

joshleong commented 8 years ago

Ah, cool let me fiddle with that for a bit, and I'll put together a screencast with more details together.

joshleong commented 8 years ago

Hey Julian, I put together a screencast with a little more details https://youtu.be/LpoesOwcTw8.

jlmakes commented 8 years ago

Thanks for the video @joshleong :bow:

When you scroll, I see onScroll-SiteLayout output a number of times in your console…

onScroll = (e) => {
  console.log('onScroll-SiteLayout'); // this is firing when you scroll…

  const srcElement = e.nativeEvent.srcElement;

  const scrollTop = srcElement.scrollTop;
  const innerHeight = window.innerHeight;
  const threshold = innerHeight * 0.4;
}

It’s also very clear in your JSX you’re styling top level element to be a scrollable container:

<div style={{height: '100%', overflowY: 'auto', position: 'relative'}} onScroll={this.onScroll}>

Plus I see here in your Journal Component you have an un-used container variable (declared after you instantiate ScrollReveal)??

afterMount = () => {

  const sr = ScrollReveal();
  const container = document.querySelector('.grid-journal'); // un-used??

  // etc...

Looks ScrollReveal isn’t configured to use the container you’ve setup...

What I would suggest trying is adding a .someClass to your top level SiteLayout component div:

- <div style={{height: '100%', overflowY: 'auto', position: 'relative'}} onScroll={this.onScroll}>
+ <div className="someClass" style={{height: '100%', overflowY: 'auto', position: 'relative'}} onScroll={this.onScroll}>

And, assigning it as the container for your ScrollReveal instance:

afterMount = () => {

- const sr = ScrollReveal();
- const container = document.querySelector('.grid-journal');
+ const container = document.querySelector('.someClass');
+ const sr = ScrollReveal({ container: container });

Let me know if this gets you closer!

joshleong commented 8 years ago

Yes that worked! Oh, be happy to chat about the project, send me a message on skype josh.leong.

oyeanuj commented 8 years ago

@joshleong What was your final solution of integrating ScrollReveal in React? Possible to provide a gist/example?

joshleong commented 8 years ago

Actually Julian's answer made my code work, it's up there in the conversation!

qknow-w commented 8 years ago

i want to read success code. thanks

ArnaudValensi commented 8 years ago

Here is a full example:

npm install --save scrollreveal or install like you're used to doing it.

It doesn't work well if there are multiple instances of ScrollReveal, so we have to create a module returning an instance:

Create new file: scrollReveal.js:

import ScrollReveal from 'scrollreveal'

export default ScrollReveal()

Then in a component:

import React from 'react'
import sr from './scrollReveal.js'

export class Testimonial extends React.Component {
  props: Props;

  componentDidMount = () => {
    const config = {
      origin: 'right',
      duration: 1000,
      delay: 150,
      distance: '500px',
      scale: 1,
      easing: 'ease',
    }

    sr.reveal(this.refs.box1, config)
  }

  render () {
    return (
      <section className='testimonial' id='testimonials'>
        <div className='row' ref='box1'>
          ...
        </div>
      </section>
    )
  }
}

export default Testimonial

We configure scrollreveal in componentDidMount. We access the DOM element, setting a ref='name' and accessing using this.refs.name.

Last thing, the entire page must scroll, il you have a container element which does an overflow: hidden for example, the animation may not trigger. I think you can check it doing this: document.onscroll = function() { console.log('Scroll ok); }; Maybe in this case we can set the scrolling container, I don't know.

That's it, it works very well this way.

jlmakes commented 7 years ago

ScrollReveal 4

As of version 4.0.0-beta.10, the ScrollReveal constructor returns a singleton—which means the extra file used in the example above is no longer necessary:

~Create new file: scrollReveal.js:~

- import ScrollReveal from 'scrollreveal'

- export default ScrollReveal()

Then in a component:

  import React from 'react'
- import sr from './scrollReveal.js'
+ import ScrollReveal from 'scrollreveal'

  export class Testimonial extends React.Component {
    props: Props;

    componentDidMount = () => {
      const config = {
        origin: 'right',
        duration: 1000,
        delay: 150,
        distance: '500px',
        scale: 1,
        easing: 'ease',
      }

-     sr.reveal(this.refs.box1, config)
+     ScrollReveal().reveal(this.refs.box1, config)
    }

    render () {
      return (
        <section className='testimonial' id='testimonials'>
          <div className='row' ref='box1'>
            ...
          </div>
        </section>
      )
    }
  }

  export default Testimonial

ScrollReveal v4 official release coming soon! ScrollReveal + React official docs coming soon!

sniebauer commented 7 years ago

Any news on Scroll Reveal React? Stoked to try it out!

jlmakes commented 7 years ago

@sniebauer I appreciate the sentiment!

The latest v4.0.0-beta.14 has been a stable release candidate for a couple weeks now, so I will launch as soon as I finish the new website/docs. (It’s all blood sweat and tears trying to get this thing out the door this month!)

This late in the game, I don’t plan on holding up launch for React-specific integration/documentation, but it is a priority for me: I want to serve more intermediate/advanced developers, and (to be blunt), piggy back off the React hype train.

The idea of a more complicated integration—such as a separate package that provides a higher-order component—is interesting to me, but to be honest, I’m not yet sure what constraints I’ll run into trying to express the ScrollReveal 4 API while Thinking In React™ (or if a separate package/higher-order component is even necessary/valuable, and maintainable).

Realistically, I think simple live integration examples (similar to what’s shown above) will be included at (or shortly after) launch. The rest, depends on what I uncover.

jelenajjo commented 6 years ago

@jlmakes Any updates? :) It is confusing and hard to use this library in React apps at this point, documentation and a bit better support would really mean a lot.

Especially with Sequenced Animations, because in React we have just one component that we pass multiple times, so we have same ref on all those elements. And then only the last elements gets to be animated.

I love this library, I use it for all my projects, like:

I saw few other similar libraries/packages made for React but I would really like to stick to this one since I use it for so long and it provides the best experience. I get positive feedback on daily basis cause of those animations.

If you can make this in top few priority things on list that would be awesome. :+1:

jlmakes commented 6 years ago

I appreciate the support @jelenajjo

Nothing new in regards to React to report yet, still finishing up the new docs. 😅

The official React docs detail Integrating with DOM Manipulation Plugins, which will likely be my guide when I have the time; have you gone through that yet?

jelenajjo commented 6 years ago

I will take a look again! :) Okay, looking forward to finished docs. :+1:

jlmakes commented 6 years ago

Since you brought this up @jelenajjo again, (and it’s obviously a popular question,) I thought I'd at least take a stab at a React ScrollReveal integration; this is what I’ve come up with so far:

// WithScrollReveal.js

import React, { Component } from 'react'
import { findDOMNode } from 'react-dom'
import ScrollReveal from 'scrollreveal'

export const WithScrollReveal = Original =>
  class extends Component {
    constructor(props) {
      super(props)
      this.target = []
    }
    componentDidMount() {
      ScrollReveal().reveal(
        this.target,
        this.props.options,
        this.props.interval
      )
    }
    componentWillUnmount() {
      ScrollReveal().clean(this.target)
    }
    render() {
      const children = React.Children.map(this.props.children, child =>
        React.cloneElement(child, {
          ref: c => this.target.push(findDOMNode(c))
        })
      )
      return <Original {...this.props}>{children}</Original>
    }
  }

export default WithScrollReveal

It’s a pretty small higher order component, that captures the DOM nodes of the Original component’s children—and then passes them to ScrollReveal via lifecycle methods.

I’d like to mention that this relies on ReactDOM.findDOMNode() which may be deprecated in the future, so I’m open to other solutions if anyone has them.

Imagine we have a simple component like this:

// List.js

import React from 'react'

const List = ({ children }) => (
  <div>
    <h1>List:</h1>
    {children}
  </div>
)

export default List

Then in our app, we might use it like this:

// App.js

import React from 'react'
import { render } from 'react-dom'

import List from './List'
import WithScrollReveal from './WithScrollReveal'

const RevealedList = WithScrollReveal(List)

const App = () => (
  <RevealedList options={{ duration: 2000 }} interval={200}>
    <div>foo</div>
    <div>bar</div>
    <div>jam</div>
  </RevealedList>
)

render(<App />, document.getElementById('root'))

I’m certain there’s room for improvement. One idea is that the options and interval props may be too generic, causing naming collisions. Hopefully though, this at least helps anyone trying to work with React and ScrollReveal, for the time being.

Check out the Live Example on CodeSandbox

jelenajjo commented 6 years ago

@jlmakes Thanks! I will test this out now, and if I come up with improvements I will share def.

jlmakes commented 6 years ago

Also, I forgot to mention—since we're relying on references, stateless functional components will not work as reveal targets:

You may not use the ref attribute on functional components because they don’t have instances. — Refs and the DOM - React

It’s worth mentioning that the next section in the docs also details a solution, but I wasn't able to figure out how a solution that went with the grain that way.

Take this example:

// Item.js (incompatible)

import React, { Component } from 'react'

const Item = ({ content }) => <li>{content}</li>

export default Item

That means if we tried to use them like this:

const App = () =>
  <RevealedList options={{ distance: '50px', origin: 'right' }} interval={500}>
    <Item content="foo" />
    <Item content="bar" />
    <Item content="jam" />
  </RevealedList>

We won't get any action cause React just doesn't expose references for stateless functional components. Even though it's a crazy simple, we still would need to use a the component class:

// Item.js

import React, { Component } from 'react'

class Item extends Component {
  render() {
    return <li>{this.props.content}</li>
  }
}

export default Item

I updated the previous live example to reflect this.

ryanturnbullroam commented 6 years ago

any update on the react version @jlmakes ?

jlmakes commented 6 years ago

As it stands @ryanturnbull, the higher-order component I outlined above works well—I'm mainly just looking for more feedback on the implementation. It's only about 30 lines of code, so I'm not sure it warrants a separate package for now. (Plus this way, you can manage prop naming conflicts.)

Also, another developer has created react-scrollreveal but @RusinovAnton has a different opinion about what the integration API should look like. It's possible he and I could work together to make that an official package (Hi Anton!), but for now I ask that you use the HOC detailed above and share your feedback.

RusinovAnton commented 6 years ago

@jlmakes Hey, Julian! I would love to help with official react-scrollreveal component. Currently I don't have much time for that though, but I believe I may get to it in two weeks or so.

I want to share some of my findings while developing react-scrollreveal. That component initially was made for certain project where I had to use ScrollReveal with react. At first it was separate components for each animated element but I've faced performance issues so after that I made HOC that intended to wrap whole page on the highest level in components tree and then I declared all animated elements on that page by passing needed selectors, then on component's mount single ScrollReveal instance was created and managed needed animations.

When I'll have time I would look closely at HOC that you've suggested here and open issue with implementation draft in react-scrollreveal repo. Feel free to open it yourself if you want to, I think that would be a good start of work and we could move discussion there.

Ayush-v commented 2 years ago

Any update on the react version ?