简体   繁体   中英

How can i manipulate DOM inside the onScroll event in ReactJS?

I am trying to creating an arrow-up button, which will be set to display: "none" if I am at a top of my page, and when I scroll down further, I want to set it to display: "block". How can i achieve this? I am trying to manipulate the DOM inside my handleScroll function but it doesn't work.

My TopButton.js component

import React from "react";
import "../assets/arrow-up.png";

class TopButton extends React.Component {
  constructor(props) {
    super(props);
    this.handleScroll = this.handleScroll.bind(this);
  }

  componentDidMount = () => {
    window.addEventListener("scroll", this.handleScroll, true);
  };

  handleClick = () => {
    document.body.scrollTop = 0;
    document.documentElement.scrollTop = 0;
  };

  handleScroll = () => {
    console.log("scroll");

    let x = document.getElementsByClassName("topbutton_button");
    var x = document.getElementsByClassName("topbutton_button");
    // x.style.display = "none";

    // console.log(event.target.className);

    // if (
    //   document.body.scrollTop > 40 ||
    //   document.documentElement.scrollTop > 40
    // ) {
    //   style.display = "block";
    // } else {
    //   display = "none";
    // }
  };

  render() {
    return (
      <div className="topbutton_container">
        <button
          style={{ display: "block" }}
          onClick={this.handleClick}
          onScroll={this.handleScroll}
          className="topbutton_button"
        >
          <img src={require("../assets/arrow-up.png")} />
        </button>
      </div>
    );
  }
}

export default TopButton;

There are at least two reasons it didn't work:

  • See this question's answers ; basically, getElementsByClassName returns an HTMLCollection , not a single element, but your commented-out code was treating it as though it were a single element.

  • If your component was ever re-rendered, it would be rendered in its default state, not the updated state you changed via the DOM

But that's not how you'd do it with React. Instead, you'd:

  1. have the button state (whether it should be block or not) held as state in your component;

  2. use that state when rendering the topbutton_button , setting its style or class accordingly; and

  3. update that state in your handleScroll handler

A couple of others notes:

  • You also need to remove your handler when the component is unmounting

  • You shouldn't use arrow functions for component lifecycle functions

  • You don't need to use bind on an arrow function ( handleScroll for instance). Either make it an arrow function or use bind in the constructor to bind it.

Something along these lines, see the *** comments

import React from "react";
import "../assets/arrow-up.png";

// *** Reusable function to decide whether we're "at the top" or not
function bodyIsAtTop() {
  return (
    document.body.scrollTop <= 40 &&
    document.documentElement.scrollTop <= 40
  );
}

class TopButton extends React.Component {

  constructor(props) {
    super(props);
    // *** Initial state
    this.state = {
      atTop: bodyIsAtTop()
    };
    // *** No need for the following if you use an arrow function
    // this.handleScroll = this.handleScroll.bind(this);
  }

  // *** Don't make this an arrow, make it a method
  componentDidMount() {
    window.addEventListener("scroll", this.handleScroll, true);
  };

  // *** Need to unbind when unmounted
  componentWillUnmount = () => {
    window.removeEventListener("scroll", this.handleScroll, true);
  };

  handleClick = () => {
    document.body.scrollTop = 0;
    document.documentElement.scrollTop = 0;
  };

  handleScroll = () => {
    // *** Update state (possibly; if the flag isn't different, this doesn't do anything)
    this.setState({atTop: bodyIsAtTop()});
  };

  render() {
    // *** Get the flag from state, use it below in style
    const {atTop} = this.state;
    return (
      <div className="topbutton_container">
        <button
          style={{ display: atTop ? "none" : "block" }}
          onClick={this.handleClick}
          onScroll={this.handleScroll}
          className="topbutton_button"
        >
          <img src={require("../assets/arrow-up.png")} />
        </button>
      </div>
    );
  }
}

export default TopButton;

There I've kept your arrow functions for handleScroll and handleClick . There's an argument for making them methods and using bind in the constructor instead, but it's mostly a style thing. (Well...style and it's easier to mock prototype methods for testing, which is a non-style reason for using prototype methods and bind .)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM