Skip to content

Instantly share code, notes, and snippets.

@mirinzhang
Created October 16, 2018 10:06
Show Gist options
  • Select an option

  • Save mirinzhang/89a6c548ed2f19c22c58a2b16d1e0bf0 to your computer and use it in GitHub Desktop.

Select an option

Save mirinzhang/89a6c548ed2f19c22c58a2b16d1e0bf0 to your computer and use it in GitHub Desktop.

Revisions

  1. 泡面君 created this gist Oct 16, 2018.
    209 changes: 209 additions & 0 deletions InViewPort.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,209 @@
    /**
    * @File 检查元素是否在视口中
    * @Author: yabingzyb.zhang
    * @Date: 2018-10-16 15:45:13
    * @Last Modified by: yabingzyb.zhang
    * @Last Modified time: 2018-10-16 17:43:12
    */
    import React, { Component, PropTypes } from 'react';
    import { View, Dimensions } from 'react-native';

    // 滑动方向
    const DIRECTS = {
    UP: 'UP',
    DOWN: 'DOWN'
    };

    export default class extends Component {
    static propTypes = {
    // 检查元素出现的方向:0: 所有方向,1:从下向上,2:从上向下,3:垂直方向
    type: PropTypes.number,
    // 检测元素位置间隔
    delay: PropTypes.number,
    // 状态发生变化时的回调
    onChange: PropTypes.func.isRequired,
    // 是否停止监听
    disabled: PropTypes.bool
    };

    static defaultProps = {
    type: 0,
    delay: 100,
    disabled: false,
    onChange: () => { }
    };

    constructor(props) {
    super(props);

    this.state = {
    rectTop: 0,
    rectBottom: 0,
    verticalDirect: ''
    };

    this.viewPort = null;
    }

    componentDidMount() {
    if (!this.props.disabled) {
    this.startWatching();
    }
    }

    componentWillUnmount() {
    this.stopWatching();
    }

    componentWillReceiveProps(nextProps) {
    if (nextProps.disabled) {
    this.stopWatching();
    } else {
    this.prevStatus = null;
    this.startWatching();
    }
    }

    /**
    * 开始监听元素
    */
    startWatching() {
    if (this.interval) {
    return;
    }

    this.interval = setInterval(() => {
    if (!this.viewPort) {
    return;
    }

    this.viewPort.measure((x, y, width, height, pageX, pageY) => {
    const { rectBottom, verticalDirect: prevVerticalDirect } = this.state,
    offsetY = rectBottom - (pageY + height),
    verticalDirect = offsetY === 0 ? prevVerticalDirect : (offsetY > 0 ? DIRECTS.DOWN : DIRECTS.UP);

    this.setState({
    rectTop: pageY,
    rectBottom: pageY + height,
    rectWidth: pageX + width,
    verticalDirect
    }, () => {
    this.isInViewPort();
    });
    });
    }, this.props.delay || 100);
    }

    /**
    * 停止监听
    */
    stopWatching() {
    this.interval = clearInterval(this.interval);
    }

    /**
    * 检查元素是否在视口中
    */
    isInViewPort() {
    const { onChange, type = 0 } = this.props,
    window = Dimensions.get('window'),
    { prevStatus, state } = this;

    let isVisible;

    switch (type) {
    case 0:
    isVisible = this.checkAll(state, window);
    break;
    case 1:
    isVisible = this.checkVerticalUp(state, window);
    break;
    case 2:
    isVisible = this.checkVerticalDown(state, window);
    break;
    case 3:
    isVisible = this.checkVertical(state, window);
    break;
    default:
    isVisible = false;
    }

    if (prevStatus !== isVisible) {
    this.prevStatus = isVisible;
    onChange && onChange(isVisible);
    }
    }

    /**
    * 检查元素在整个页面中是否出现
    * @param {object} state
    * @param {object} window
    */
    checkAll(state, window) {
    const { rectBottom, rectTop, rectWidth } = state,
    { height, width } = window;

    return rectBottom &&
    rectTop >= 0 &&
    rectBottom <= height &&
    rectWidth > 0 &&
    rectWidth <= width;
    }

    /**
    * 检查元素在垂直方向是否出现在视口中,从下向上滑动
    * @param {object} state
    * @param {object} window
    */
    checkVerticalUp(state, window) {
    const { rectBottom, rectTop, verticalDirect } = state,
    { height } = window;

    return rectBottom &&
    rectTop >= 0 &&
    verticalDirect === DIRECTS.DOWN &&
    rectBottom <= height;
    }

    /**
    * 检查元素在垂直方向是否出现在视口中,从上向下滑动
    * @param {object} state
    * @param {object} window
    */
    checkVerticalDown(state, window) {
    const { rectBottom, rectTop, verticalDirect } = state,
    { height } = window;

    return rectBottom &&
    rectTop >= 0 &&
    verticalDirect === DIRECTS.UP &&
    rectBottom <= height;
    }

    /**
    * 检查元素在垂直方向是否出现在视口中
    * @param {object} state
    * @param {object} window
    */
    checkVertical(state, window) {
    const { rectBottom, rectTop } = state,
    { height } = window;

    return rectBottom &&
    rectTop >= 0 &&
    rectBottom <= height;
    }

    render() {
    return (
    <View
    collapsable={false}
    ref={component => {
    this.viewPort = component;
    }}
    {...this.props}>
    {this.props.children}
    </View>
    );
    }
    }