Javascript React - 检查元素是否在 DOM 中可见

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/45514676/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 03:04:44  来源:igfitidea点击:

React - check if element is visible in DOM

javascriptreactjs

提问by John

I'm building a form - series of questions (radio buttons) the user needs to answer before he can move on to the next screen. For fields validation I'm using yup (npm package) and redux as state management.

我正在构建一个表单 - 用户在进入下一个屏幕之前需要回答的一系列问题(单选按钮)。对于字段验证,我使用 yup(npm 包)和 redux 作为状态管理。

For one particular scenario/combination a new screen (div) is revealed asking for a confirmation (checkbox) before the user can proceed. I want to apply the validation for this checkbox only if displayed.

对于一个特定的场景/组合,在用户可以继续之前,会显示一个新屏幕 (div) 要求确认(复选框)。我想仅在显示时应用此复选框的验证。

How can I check if an element (div) is displayed in the DOM using React?

如何使用 React 检查元素 (div) 是否显示在 DOM 中?

The way I thought of doing it was to set a varibale 'isScreenVisible' to false and if the conditions are met I would change the state to 'true'.

我想这样做的方法是将变量“isScreenVisible”设置为 false,如果满足条件,我会将状态更改为“true”。

I'm doing that check and setting 'isScreenVisible' to true or false in _renderScreen() but for some reason it's going into an infinite loop.

我正在检查并在 _renderScreen() 中将“isScreenVisible”设置为 true 或 false,但由于某种原因,它进入了无限循环。

My code:

我的代码:

class Component extends React.Component {

  constructor(props) {
    super(props);

    this.state = {
      formisValid: true,
      errors: {},
      isScreenVisible: false
    }

    this.FormValidator = new Validate();
    this.FormValidator.setValidationSchema(this.getValidationSchema());
  }

  areThereErrors(errors) {
    var key, er = false;
    for(key in errors) {
      if(errors[key]) {er = true}
    }
    return er;
  }

  getValidationSchema() {
    return yup.object().shape({
      TravelInsurance: yup.string().min(1).required("Please select an option"),
      MobilePhoneInsurance: yup.string().min(1).required("Please select an option"),
      Confirmation: yup.string().min(1).required("Please confirm"),
    });
  }

  //values of form fields
  getValidationObject() {
    let openConfirmation = (this.props.store.Confirmation === true)? 'confirmed': ''

    return {
      TravelInsurance: this.props.store.TravelInsurance,
      MobilePhoneInsurance: this.props.store.MobilePhoneInsurance,
      Confirmation: openConfirmation,
    }
  }

  setSubmitErrors(errors) {
    this.setState({errors: errors});
  }

  submitForm() {
    var isErrored, prom, scope = this, obj = this.getValidationObject();
    prom = this.FormValidator.validateSubmit(obj);

    prom.then((errors) => {
      isErrored = this.FormValidator.isFormErrored();

      scope.setState({errors: errors}, () => {
        if (isErrored) {
        } else {
          this.context.router.push('/Confirm');
        }
      });
    });
  }

  saveData(e) {
    let data = {}
    data[e.target.name] = e.target.value

    this.props.addData(data)

    this.props.addData({
      Confirmation: e.target.checked
    })
  }

  _renderScreen = () => {
    const {
      Confirmation
    } = this.props.store

    if(typeof(this.props.store.TravelInsurance) !== 'undefined' && typeof(this.props.store.MobilePhoneInsurance) !== 'undefined') &&
    ((this.props.store.TravelInsurance === 'Yes' && this.props.store.MobilePhoneInsurance === 'No') ||
    (this.props.store.TravelInsurance === 'No' && this.props.store.MobilePhoneInsurance === 'Yes')){

        this.setState({
            isScreenVisible: true
        })

          return(
            <div>
                <p>Please confirm that you want to proceed</p>

                  <CheckboxField
                    id="Confirmation"
                    name="Confirmation"
                    value={Confirmation}
                    validationMessage={this.state.errors.Confirmation}
                    label="I confirm that I would like to continue"
                    defaultChecked={!!Confirmation}
                    onClick={(e)=> {this.saveData(e)} }
                  />
                </FormLabel>
            </div>
          )
      }
      else{
        this.setState({
            isScreenVisible: false
        })
      }
  }

  render(){
    const {
      TravelInsurance,
      MobilePhoneInsurance
    } = this.props.store

    return (
      <div>           
          <RadioButtonGroup
            id="TravelInsurance"
            name="TravelInsurance"
            checked={TravelInsurance}
            onClick={this.saveData.bind(this)}
            options={{
              'Yes': 'Yes',
              'No': 'No'
            }}
            validationMessage={(this.state.errors.TravelInsurance) ? this.state.errors.TravelInsurance : null }
          />

        <RadioButtonGroup
          id="MobilePhoneInsurance"
          name="MobilePhoneInsurance"
          checked={MobilePhoneInsurance}
          onClick={this.saveData.bind(this)}
          options={{
            'Yes': 'Yes',
            'No': 'No'
          }}
          validationMessage={(this.state.errors.MobilePhoneInsurance) ? this.state.errors.MobilePhoneInsurance : null }
        />

        this._renderScreen()

        <ButtonRow
            primaryProps={{
                children: 'Continue',
                onClick: e=>{
                this.submitForm();
            }
        }}
      </div>
    )
  }
}

const mapStateToProps = (state) => {
  return {
    store: state.Insurance,
  }
}

const Insurance = connect(mapStateToProps,{addData})(Component)

export default Insurance

回答by Avraam Mavridis

You can attach a ref to the element that you want to check if it is on the viewport and then have something like:

您可以将 ref 附加到要检查它是否在视口上的元素上,然后具有以下内容:

  /**
   * Check if an element is in viewport
   *
   * @param {number} [offset]
   * @returns {boolean}
   */
  isInViewport(offset = 0) {
    if (!this.yourElement) return false;
    const top = this.yourElement.getBoundingClientRect().top;
    return (top + offset) >= 0 && (top - offset) <= window.innerHeight;
  }


  render(){

     return(<div ref={(el) => this.yourElement = el}> ... </div>)

  }

You can attach listeners like onScrolland check when the element will be on the viewport.

您可以附加侦听器,onScroll并检查元素何时出现在视口上。

You can also use the Intersection Observer API with a polyfilor use a HoC component that does the job

您还可以使用带有 polyfilIntersection Observer API或使用执行此工作HoC 组件

回答by Alex Gusev

Based on Avraam's answer I wrote a Typescript-compatible small hook to satisfy the actual React code convention.

根据 Avraam 的回答,我编写了一个兼容 Typescript 的小钩子来满足实际的 React 代码约定。

import { createRef, useEffect, useState } from "react";
import throttle from "lodash.throttle";

/**
 * Check if an element is in viewport

 * @param {number} offset - Number of pixels up to the observable element from the top
 * @param {number} throttleMilliseconds - Throttle observable listener, in ms
 */
export default function useVisibility<Element extends HTMLElement>(
  offset = 0,
  throttleMilliseconds = 100
): [Boolean, React.RefObject<Element>] {
  const [isVisible, setIsVisible] = useState(false);
  const currentElement = createRef<Element>();

  const onScroll = throttle(() => {
    if (!currentElement.current) {
      setIsVisible(false);
      return;
    }
    const top = currentElement.current.getBoundingClientRect().top;
    setIsVisible(top + offset >= 0 && top - offset <= window.innerHeight);
  }, throttleMilliseconds);

  useEffect(() => {
    window.addEventListener("scroll", onScroll);
    return () => window.removeEventListener("scroll", onScroll);
  });

  return [isVisible, currentElement];
}

Usage example:

用法示例:

const Example: FC = () => {
  const [ isVisible, currentElement ] = useVisibility<HTMLDivElement>(100);

  return <Spinner ref={currentElement} isVisible={isVisible} />;
};

You can find the example on Codesandbox. I hope you will find it helpful!

您可以在 Codesandbox 上找到示例。我希望你会发现它有帮助!