UNPKG

react-inview-js

Version:

React Inview checker with only javascript

159 lines (135 loc) 4.27 kB
import React, { Component } from 'react'; import debounce from "lodash/debounce"; // Returns dimensions of container function getViewPortBox(offsetY, boundingBox) { let vTop = 0, vLeft = 0, vWidth = window.innerWidth || document.documentElement.clientWidth, vHeight = window.innerHeight || document.documentElement.clientHeight; if (offsetY >= 0 && offsetY <= 1) { const newHeight = vHeight * (1- offsetY); const newTop = (vHeight - newHeight) /2; vTop = newTop; vHeight = newHeight; } return { top: vTop, height: vHeight, width: vWidth, left: vLeft }; } // Returns dimensions of element/target function getBoundingBox(el) { let rect = el.getBoundingClientRect(); return rect; } // Checks to see if element is visisble function isElementFullyVisible (el, rect, viewport) { return ( rect.top >= viewport.top && rect.left >= 0 && rect.bottom <= viewport.top + viewport.height && rect.right <= (window.innerWidth || document.documentElement.clientWidth) ); } function isElementTopVisible (el, rect, viewport) { const topIsInView = !(rect.top >= (viewport.top + viewport.height)); const topIsAboveView = !((rect.top + rect.height - viewport.height) <= viewport.top); return ( topIsInView && topIsAboveView ); } let ReactInviewWrapper = function ReactInviewWrapper ({ offsetY = 0, showGuides = false, fullElementInView = true, debounceTime = 100 }={}) { return (ComposedComponent) => { return class ReactInview extends Component { constructor() { super(); this.state = { elementIsInView: false, elementIsHasBeenInView: false, boundingBox: {}, viewPortBox: {} }; this.scrollListener = this.scrollListener.bind(this); this.handleScroll = debounce(this.handleScroll.bind(this), debounceTime); } componentDidMount() { if (!this.refs.container) { throw new Error('Cannot find container'); } if (typeof(window) !== 'undefined') { window.addEventListener('scroll', this.scrollListener); this.handleScroll(); } } componentWillUnmount() { if (typeof(window) !== 'undefined') { window.removeEventListener('scroll', this.scrollListener); } } scrollListener() { window.requestAnimationFrame(() => { this.handleScroll(); }); } handleScroll() { if (typeof this.refs.container === 'undefined') { return; } const element = this.refs.container.children[0]; const boundingBox = getBoundingBox(element); const viewPortBox = getViewPortBox(offsetY, boundingBox); let elementIsInView = false; if(fullElementInView) { elementIsInView = isElementFullyVisible(element, boundingBox, viewPortBox); } else { elementIsInView = isElementTopVisible(element, boundingBox, viewPortBox); } const newState = { elementIsInView: elementIsInView, boundingBox: boundingBox, viewPortBox: viewPortBox }; if (elementIsInView) { newState.elementHasBeenInView = elementIsInView; } this.setState(newState); } _showGuides() { if (showGuides && typeof this.state.viewPortBox.top !== 'undefined') { const {top, left, height, width} = this.state.viewPortBox; let styles = { 'backgroundColor': '#ccc', 'position': 'fixed', 'opacity': '.5', 'top': top, 'left': left, 'height': height, 'width': width, 'zIndex': 99999999999 }; return <div style={styles}></div>; } } render() { let styles = {}; if (showGuides) { styles = { backgroundColor: 'gray' } } return ( <div style={styles} ref="container"> <ComposedComponent update={this.handleScroll} {...this.state} {...this.props} /> { this._showGuides() } </div> ); } }; }; }; export default ReactInviewWrapper;