Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@ytanruengsri
Last active March 17, 2018 13:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ytanruengsri/c71ff5acff4700af805d4dd64101cd56 to your computer and use it in GitHub Desktop.
Save ytanruengsri/c71ff5acff4700af805d4dd64101cd56 to your computer and use it in GitHub Desktop.
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
/**
* Based on Intersection Observer API
**/
const BASE_CLASS = 'tracking_view';
const INTERSECTION_TYPES = {
UPPER: 'UPPER',
HALF: 'HALF',
LOWER: 'LOWER',
};
class TrackingObserver extends PureComponent {
constructor(...props) {
super(...props);
this.initialiseObserver = this.initialiseObserver.bind(this);
this.destroyObserver = this.destroyObserver.bind(this);
this.handleObserverCallback = this.handleObserverCallback.bind(this);
this.buildThresholdList = this.buildThresholdList.bind(this);
this.thresholdSteps = 10;
this.prevRatio = 0;
this.defaultOptions = {
/**
* The element that is used as the viewport for checking visiblity of the target.
* Must be the ancestor of the target. Defaults to the browser viewport if not specified or if nul
**/
root: null,
/**
* Margin around the root. Can have values similar to the CSS margin property,
* e.g. "10px 20px 30px 40px" (top, right, bottom, left).
* If the root element is specified, the values can be percentages.
**/
rootMargin: '0px',
/**
* Either a single number or an array of numbers which indicate
* at what percentage of the target's visibility the observer's callback should be executed
**/
threshold: this.buildThresholdList(this.thresholdSteps),
};
}
componentDidMount() {
this.initialiseObserver(this.trackingChildContainer, this.props.options);
}
componentWillUnmount() {
this.destroyObserver(this.trackingChildContainer);
}
initialiseObserver(target, options) {
const observerOptions = Object.assign({}, options, this.defaultOptions);
this.observer = new IntersectionObserver(
this.handleObserverCallback,
observerOptions,
);
this.observer.observe(target);
}
destroyObserver(target) {
if (target) {
this.observer.unobserve(target);
} else {
this.observer.disconnect();
}
}
handleObserverCallback(entries) {
entries.forEach(entry => {
if (entry.intersectionRatio === 0) {
this.props.onLeaveIntersection();
} else {
let intersectionType;
if (entry.intersectionRatio > this.prevRatio) {
intersectionType = INTERSECTION_TYPES.UPPER;
} else {
intersectionType = INTERSECTION_TYPES.LOWER;
}
this.props.onIntersectChange({
target: entry.target,
currentRatio: this.intersectionRatio,
prevRatio: this.prevRatio,
intersectionType,
});
}
this.prevRatio = entry.intersectionRatio;
});
}
buildThresholdList(numSteps) {
const thresholds = [];
for (let i = 1.0; i <= numSteps; i++) {
const ratio = i / numSteps;
thresholds.push(ratio);
}
thresholds.push(0);
return thresholds;
}
render() {
const {children} = this.props;
if (!children) {
return null;
}
return (
<div className={`${BASE_CLASS}_container`}>
<div
ref={el => {
this.trackingChildContainer = el;
}}
className={`${BASE_CLASS}_childContainer`}>
{children}
</div>
</div>
);
}
}
TrackingObserver.propTypes = {
once: PropTypes.bool,
children: PropTypes.node,
options: PropTypes.object,
onIntersectChange: PropTypes.func,
onStartIntersection: PropTypes.func,
onLeaveIntersection: PropTypes.func,
};
TrackingObserver.defaultProps = {
once: false,
options: {},
onIntersectChange: () => {},
onStartIntersection: () => {},
onLeaveIntersection: () => {},
};
export default TrackingObserver;
@ytanruengsri
Copy link
Author

storiesOf('TrackingView', module).add('Viewport Observer', () => (
  <div style={{height: '2000px'}}>
    <h4>0%</h4>
    <div style={{marginTop: '1000px'}}>
      <TrackingView
        onLeaveIntersection={action('Intersection Leave')}
        onIntersectChange={action('Intersection Change')}>
        <div className="trackable">
          <h4>Headline</h4>
          <img
            height="300px"
            src="https://mosaic01.ztat.net/vgs/media/camp-s/7E2/8/34/a6aed052-395b-445c-a8bb-4f8831777fed.jpg"
            alt="camp"
          />
        </div>
      </TrackingView>
    </div>
  </div>
));

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment