最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Equivalent of document.querySelectorAll() in React, onScroll - Stack Overflow

programmeradmin3浏览0评论

What's the equivalent of document.querySelectorAll('.classname') in React? I understand I should use Refs, but how dow I observe multiple Refs onScroll?

I usually use a function like the one below to check the viewport position of multiple elements in the page, and trigger different css animation when each element enters the viewport:

HTML

<ul>
  <li data-position="below-viewport"></li>
  <li data-position="below-viewport"></li>
  <li data-position="below-viewport"></li>
  <li data-position="below-viewport"></li>
</ul>

Javascript

getPosition: function (element) {
  const rect = element.getBoundingClientRect();

  if ((rect.top > -1) && (rect.top < (window.innerHeight * 0.75))) {
    element.setAttribute('data-js-position','in-viewport');
  } else if ((rect.top > 0) && (rect.top < window.innerHeight)) {
    element.setAttribute('data-js-position','entering-viewport');
  } else if (rect.top > window.innerHeight) {
    element.setAttribute('data-js-position','below-viewport');
  } else if (rect.bottom < 0) {
    element.setAttribute('data-js-position','above-viewport');
  }
}

window.addEventListener('scroll', function() {
  [].forEach.call(document.querySelectorAll('[data-js-position]'), el => {
    positionChecker.getPosition(el);
  })
});

How would I implement something similar in React? Can you give me an example of a function that observes multiple divs in React?

Even better: how can I abstract this function in App.js, so that I can use it also in child Components?

What's the equivalent of document.querySelectorAll('.classname') in React? I understand I should use Refs, but how dow I observe multiple Refs onScroll?

I usually use a function like the one below to check the viewport position of multiple elements in the page, and trigger different css animation when each element enters the viewport:

HTML

<ul>
  <li data-position="below-viewport"></li>
  <li data-position="below-viewport"></li>
  <li data-position="below-viewport"></li>
  <li data-position="below-viewport"></li>
</ul>

Javascript

getPosition: function (element) {
  const rect = element.getBoundingClientRect();

  if ((rect.top > -1) && (rect.top < (window.innerHeight * 0.75))) {
    element.setAttribute('data-js-position','in-viewport');
  } else if ((rect.top > 0) && (rect.top < window.innerHeight)) {
    element.setAttribute('data-js-position','entering-viewport');
  } else if (rect.top > window.innerHeight) {
    element.setAttribute('data-js-position','below-viewport');
  } else if (rect.bottom < 0) {
    element.setAttribute('data-js-position','above-viewport');
  }
}

window.addEventListener('scroll', function() {
  [].forEach.call(document.querySelectorAll('[data-js-position]'), el => {
    positionChecker.getPosition(el);
  })
});

How would I implement something similar in React? Can you give me an example of a function that observes multiple divs in React?

Even better: how can I abstract this function in App.js, so that I can use it also in child Components?

Share Improve this question edited Mar 18, 2018 at 4:08 Giovanni asked Mar 18, 2018 at 3:58 GiovanniGiovanni 1861 gold badge2 silver badges9 bronze badges
Add a ment  | 

4 Answers 4

Reset to default 5

Make each li html element its own ponent and hold a ref reference to it in its own state.

class LiElement extends React.Component {
  ponentDidMount() {
    this.ref.getPosition()
  }

  render() {
    return (
      <li ref={(ctx) => this.ref = ctx}>
      </li>
    )
  }
}

Iterating through refs might get you something similar to what you're trying to achieve.

In this example I'm storing every node in a local Map so you can iterate through them and get their getBoundingClientRect.

Note, that there are several ways of doing this, you don't have to create a Map, you can just get each element's "ref", but you would have to assign a different "ref" value to each "li".

Also, it would be a good idea to throttle the handleScroll call..

class App extends React.Component {
  constructor(props) {
    super(props);
    this._nodes = new Map();
  }

  ponentDidMount() {
    window.addEventListener("scroll", this.handleScroll);
  }

  handleScroll = e => {
    Array.from(this._nodes.values())
      // make sure it exists
      .filter(node => node != null)
      .forEach(node => {
        this.getPosition(node);
      });
  };

  getPosition = node => {
    const rect = node.getBoundingClientRect();
    // do something cool here
    console.log("rect:", rect);
  };

  ponentWillUnmount() {
    window.removeEventListener("scroll", this.handleScroll);
  }

  render() {
    return (
      <div>
        <ul>
          <li key="1" ref={c => this._nodes.set(1, c)}>
            Your thing 1
          </li>
          <li key="2" ref={c => this._nodes.set(2, c)}>
            Your thing 2
          </li>
          <li key="3" ref={c => this._nodes.set(3, c)}>
            Your thing 3
          </li>
        </ul>
      </div>
    );
  }
}

Adding this other possible solution: creating a new react ponent VisibilitySensor, so to use only one ref and only one function

import React, { Component } from 'react';

class VisibilitySensor extends Component {

  ponentDidMount() {
    window.addEventListener('scroll', this.getElementPosition = this.getElementPosition.bind(this));
    this.getElementPosition();
  }

  ponentWillUnmount() {
    window.removeEventListener('scroll', this.getElementPosition);
  }

  getElementPosition() {
    const element = this.visibilitySensor;
    var rect = element.getBoundingClientRect();

    if ((rect.top > 0) && (rect.top < (window.innerHeight * 0.75))) {
      element.setAttribute("data-position","in-viewport");
    } else if (rect.top > window.innerHeight) {
      element.setAttribute("data-position","below-viewport");
    } else if (rect.bottom < 0) {
      element.setAttribute("data-position","above-viewport");
    }
  }

  render() {
    return (
      <div data-position="below-viewport" ref={(element) => { this.visibilitySensor = element; }}>
        {this.props.children}
      </div>
    );
  }
}

export default VisibilitySensor;

Then wrapping every li (or div) I need to watch with the above ponent. I personally ended up disliking the solution above, because the new added div would mess up with my css styling, particularly width of child related to width of parent, etc..

 <ul>
  <VisibilitySensor>
    <li></li>
  </VisibilitySensor>
  <VisibilitySensor>
    <li></li>
  </VisibilitySensor>
  <VisibilitySensor>
    <li></li>
  </VisibilitySensor>
  <VisibilitySensor>
    <li></li>
  </VisibilitySensor>
</ul>

I was able to work around this with the useRef hook by adding a ref on the parent element that holds all the children you wish to scroll to.

So in your case, you could do:

TOP OF YOUR REACT CODE

   const parentScrollRef = React.useRef();

HTML

<ul ref={parentScrollRef}>
  <li data-position="below-viewport"></li>
  <li data-position="below-viewport"></li>
  <li data-position="below-viewport"></li>
  <li data-position="below-viewport"></li>
</ul>

Then you'd use call the onQuerySelectorAll on the ref. With react v16, you will be able to call the querySelectorAll on the ref.current object. So the code should look like this:

 getPosition: function (element) {
    const rect = element.getBoundingClientRect();

    if ((rect.top > -1) && (rect.top < (window.innerHeight * 0.75))) {
    element.setAttribute('data-js-position','in-viewport');
     } else if ((rect.top > 0) && (rect.top < window.innerHeight)) {
    element.setAttribute('data-js-position','entering-viewport');
     } else if (rect.top > window.innerHeight) {
    element.setAttribute('data-js-position','below-viewport');
     } else if (rect.bottom < 0) {
    element.setAttribute('data-js-position','above-viewport');
     }
  }

// You would then target the ref, no need to hit the whole window.
parentScrollRef.current.addEventListener('scroll', function() {
const dataPositions = parentScrollRef.current.querySelectorAll(".data-js-position");
   dataPositions.forEach((position) => {
    positionChecker.getPosition(el);
  }
});

//Also remember your clean up xD
return (
    parentScrollRef.current.removeEventListener('scroll', function() {
   // cleanup function
});
)
发布评论

评论列表(0)

  1. 暂无评论