snicks
snicks

Reputation: 77

reactjs handling scroll for an element to see if its visible

I don't know why the handleScroll only executes once, not when i keep scrolling up and down. And I need to somehow get the element's height but im not sure how other than documentElement. The function needs to turn a true/false so that I can do a setState and add/change a class in my heroGallery div for a css animation.

 import React, { Component } from "react";

 class Intro extends Component {
constructor(props) {
    super(props);
    this.heroRef = React.createRef();
    this.state = {};
}

componentDidMount = () => {
    this.heroRef.current.getBoundingClientRect();
    let hero2 = this.heroRef;
    console.log(this.heroRef.current);
    window.addEventListener("scroll", this.handleScroll(hero2));
};

componentWillUnmount = () => {
    window.removeEventListener("scroll", this.handleScroll);
};

handleScroll = elm => {
    var rect = elm.current.getBoundingClientRect();
    //var viewHeight = Math.max(document.documentElement.clientHeight, window.innerHeight);

    //return !(rect.bottom < 0 || rect.top - viewHeight >= 0);
};

render() {
    return (
        <div className="introCont">
            <div className="introFirstSection" />
            <div className="heroSection">
                <div className="heroGallery" ref={this.heroRef}>
                    <div className="slide-down">item 1</div>
                    <div>item 2</div>
                    <div>item 3</div>
                    <div>item 4</div>
                    <div>item 5</div>
                    <div>item 6</div>
                    <div>item 7</div>
                    <div>item 8</div>
                </div>
            </div>
        </div>
    );
  }
 }

 export default Intro;

EDIT: Taking out my intro component from Switch lets it work.

import React, { lazy, Suspense, Component } from "react";
import { BrowserRouter, Route, Switch } from "react-router-dom";
import { connect } from "react-redux";
import * as actions from "./actions";

//import asyncComponent from "./components/AsyncComponent";
//const Header = asyncComponent(() => import("./Header"));
import Intro from "./components/Intro";
const Header = lazy(() => import("./components/Header"));
const Footer = lazy(() => import("./components/Footer"));
const Landing = lazy(() => import("./components/Landing"));
const Profile = lazy(() => import("./components/Profile"));
const BookList = lazy(() => import("./components/books/BookList"));
const BookNote = lazy(() => import("./components/books/BookNote"));
const StatsChart = lazy(() => import("./components/StatsChart"));

class App extends Component {
  componentDidMount() {
    this.props.fetchUser();
  }

  render() {
    return (
      <BrowserRouter>
        <div className="rootdk">
          <Suspense fallback={<div />}>
            <Header />
          </Suspense>
          <div className="container">
            <Suspense fallback={<div className="loader" />}>
              <Switch>
                <Route exact path="/intro" component={() => <Intro />} />

                <Route
                  exact
                  path="/mybooks"
                  component={() =>
                    this.props.thisuser ? <BookList /> : <Landing />
                  }
                />
                <Route
                  exact
                  path="/booknotes"
                  component={() =>
                    this.props.thisuser ? <BookNote /> : <Landing />
                  }
                />
                <Route
                  exact
                  path="/statschart"
                  component={() =>
                    this.props.thisuser ? <StatsChart /> : <Landing />
                  }
                />
                <Route
                  exact
                  path="/profile"
                  component={() =>
                    this.props.thisuser ? <Profile /> : <Landing />
                  }
                />
                <Route exact path="/" component={Landing} />
              </Switch>
            </Suspense>

            <Suspense fallback={<div />}>
              <Footer />
            </Suspense>
          </div>
        </div>
      </BrowserRouter>
    );
  }
}

function mapStateToProps(props) {
  return { thisuser: props.auth };
}

export default connect(
  mapStateToProps,
  actions
)(App);

Upvotes: 0

Views: 1023

Answers (2)

snicks
snicks

Reputation: 77

Another answer would be to use the HTML5 API, which makes it much much easier: https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API

Also, the container obviously has to have a fixed height, and have the overflow property set to auto or scroll.

Example:

import React, { Component } from "react";
import ReactDOM from "react-dom";

class Intro extends Component {
  constructor(props) {
    super(props);
    this.heroRef = React.createRef();
    this.state = {
      heroanim: "no"
    };
  }

  componentDidMount = () => {
    setTimeout(() => {
      this.startup();
    }, 500);
    this.startup();
  };

  componentWillUnmount = () => {};

  startup = () => {
    //the class we want to observe for visibility changes, scrolling for animation.
    let adBox = document.querySelector(".heroSection");

    //checks if page was tabbed out or hidden. separate from intersectionobserver
    document.addEventListener(
      "visibilitychange",
      this.handleVisibilityChange,
      false
    );
    //intersectionobserveroptions. root is highest element you want to compare to. threshhold is % visibility of element you're comparing to root. 'callback activated when 50% visibility.'
    let observerOptions = {
      root: null,
      rootMargin: "0px",
      threshold: [0.7]
    };

    //create new intersection observer
    let adObserver = new IntersectionObserver(
      this.intersectionCallback,
      observerOptions
    );

    //call it.
    adObserver.observe(adBox);
  };

  handleVisibilityChange = () => {
    if (document.hidden) {
      //changeclass to no animate through setstate
    } else {
      //changeclass to animate
    }
  };

  intersectionCallback = entries => {
    entries.forEach(entry => {
      let adBox = entry.target;

      if (entry.isIntersecting) {
        this.setState({ heroanim: "yes" });
      } else {
        console.log("not");

        this.setState({ heroanim: "no" });
      }
    });
  };

  render() {
    return (
      <div className="introCont">
        <div className="introFirstSection" />
        <div
          className={
            this.state.heroanim === "yes"
              ? "heroSection heroAnim"
              : "heroSection"
          }
          ref={this.heroRef}
        >
          <div className="heroGallery">
            <div className="slide-down">item 1</div>
            <div>item 2</div>
            <div>item 3</div>
            <div>item 4</div>
            <div>item 5</div>
            <div>item 6</div>
            <div>item 7</div>
            <div>item 8</div>
          </div>
        </div>
      </div>
    );
  }
}

export default Intro;

Upvotes: 0

Kyaw Siesein
Kyaw Siesein

Reputation: 725

It is because you are invoking the method in your addEventListener.

Demo: https://codesandbox.io/s/nv16oq6j?fontsize=14

Change your componentDidMount function to this.

componentDidMount = () => {
    this.heroRef.current.getBoundingClientRect();
    let hero2 = this.heroRef;
    console.log(this.heroRef.current);
    window.addEventListener("scroll", () => this.handleScroll(hero2));
};

All I did is make the second argument of addEventListener as a function.

Upvotes: 3

Related Questions