Skip to content

Commit

Permalink
Merge branch 'release/0.1.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
ryanhefner committed Jul 9, 2017
2 parents d1518e7 + 0d873b6 commit 89d8512
Show file tree
Hide file tree
Showing 9 changed files with 2,273 additions and 0 deletions.
13 changes: 13 additions & 0 deletions .babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"presets": [
["latest", {
"es2015": {
"modules": false
}
}],
"react"
],
"plugins": [
"transform-object-rest-spread"
],
}
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.DS_Store
node_modules/
*.sublime-project
*.sublime-workspace
3 changes: 3 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
src/
*.sublime-project
*.sublime-workspace
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2017 Ryan Hefner <[email protected]> (https://www.ryanhefner.com)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
97 changes: 97 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# React Scroll Trigger

React component that monitors `scroll` events to trigger callbacks when it enters,
exits and progresses through the viewport. All callback include the `progress` and
`velocity` of the scrolling, in the event you want to manipulate stuff based on
those values.

## Install

Via [npm](https://npmjs.com/package/react-scroll-trigger)
```
npm install react-scroll-trigger
```

Via [Yarn](http://yarn.fyi/react-scroll-trigger)
```
yarn add react-scroll-trigger
```

### Requirements

* [react](https://npmjs.com/package/react)
* [react-dom](https://npmjs.com/package/react-dom)
* [prop-types](https://npmjs.com/package/prop-types)
* [lodash](https://npmjs.com/package/lodash)

## How to use

```
import ScrollTrigger from 'react-scroll-trigger';
...
onEnterViewport() {
this.setState({
visible: true,
});
}
onExitViewport() {
this.setState({
visible: false,
});
}
render() {
const {
visible,
} = this.state;
return (
<ScrollTrigger onEnter={this.onEnterViewport} onExit={this.onExitViewport}>
<div className={`container ${visible ? 'container-animate' : ''}`}
</ScrollTrigger>
);
}
```

The `ScrollTrigger` is intended to be used as a composable element, allowing you
to either use it standalone within a page (ie. no children).

```
<ScrollTrigger onEnter={this.onEnterViewport} onExit={this.onExitViewport} />
```

Or, pass in children to receive events and `progress` based on the dimensions of
those elements within the DOM.

```
<ScrollTrigger onEnter={this.onEnterViewport} onExit={this.onExitViewport}>
<List>
[...list items...]
</List>
</ScrollTrigger>
```

The beauty of this component is its flexibility. I’ve used it to trigger
AJAX requests based on either the `onEnter` or `progress` of the component within
the viewport. Or, as a way to control animations or other transitions that you
would like to either trigger when visible in the viewport or based on the exact
`progress` of that element as it transitions through the viewport.

### Properties

Below are the properties that can be defined on a `<ScrollTrigger />` instance.
In addition to these properties, all other standard React properites like `className`,
`key`, etc. can be passed in as well and will be applied to the `<div>` that will
be rendered by the `ScrollTrigger`.

* `triggerOnLoad` - Boolean (Default: `true`)
* `onEnter` - Callback `({progress, velocity}, ref) => {}`
* `onExit` - Callback `({progress, velocity}, ref) => {}`
* `onProgress` - Callback `({progress, velocity}, ref) => {}`

## License

[MIT](LICENSE)
175 changes: 175 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };

var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }

function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import ReactDOM from 'react-dom';
import omit from 'lodash/omit';
import throttle from 'lodash/throttle';

var ScrollTrigger = function (_Component) {
_inherits(ScrollTrigger, _Component);

function ScrollTrigger(props) {
_classCallCheck(this, ScrollTrigger);

var _this = _possibleConstructorReturn(this, (ScrollTrigger.__proto__ || Object.getPrototypeOf(ScrollTrigger)).call(this, props));

_this.onScroll = throttle(_this.onScroll.bind(_this), 100, {
trailing: false
});

_this.onResize = throttle(_this.onResize.bind(_this), 100, {
trailing: false
});

_this.state = {
inViewport: false,
progress: 0,
lastScrollPosition: null,
lastScrollTime: null
};
return _this;
}

_createClass(ScrollTrigger, [{
key: 'componentDidMount',
value: function componentDidMount() {
addEventListener('resize', this.onResize);
addEventListener('scroll', this.onScroll);

if (this.props.triggerOnLoad) {
this.checkStatus();
}
}
}, {
key: 'componentWillUnmount',
value: function componentWillUnmount() {
removeEventListener('resize', this.onResize);
removeEventListener('scroll', this.onScroll);
}
}, {
key: 'onResize',
value: function onResize() {
this.checkStatus();
}
}, {
key: 'onScroll',
value: function onScroll() {
this.checkStatus();
}
}, {
key: 'checkStatus',
value: function checkStatus() {
var _props = this.props,
onEnter = _props.onEnter,
onExit = _props.onExit,
onProgress = _props.onProgress;
var _state = this.state,
lastScrollPosition = _state.lastScrollPosition,
lastScrollTime = _state.lastScrollTime;


var element = ReactDOM.findDOMNode(this.element);
var elementRect = element.getBoundingClientRect();
var viewportStart = 0;
var viewportEnd = document.body.clientHeight;
var inViewport = elementRect.top < viewportEnd && elementRect.bottom > viewportStart;

var position = window.scrollY;
var velocity = lastScrollPosition && lastScrollTime ? Math.abs((lastScrollPosition - position) / (lastScrollTime - Date.now())) : null;

if (inViewport) {
var progress = Math.max(0, Math.min(1, 1 - elementRect.bottom / (viewportEnd + elementRect.height)));

if (!this.state.inViewPort) {
this.setState({
inViewport: inViewport
});

onEnter({
progress: progress,
velocity: velocity
}, this);
}

this.setState({
lastScrollPosition: position,
lastScrollTime: Date.now()
});

onProgress({
progress: progress,
velocity: velocity
}, this);
return;
}

if (this.state.inViewport) {
var _progress = elementRect.top <= viewportEnd ? 1 : 0;

this.setState({
lastScrollPosition: position,
lastScrollTime: Date.now(),
inViewport: inViewport,
progress: _progress
});

onProgress({
progress: _progress,
velocity: velocity
}, this);

onExit({
progress: _progress,
velocity: velocity
}, this);
}
}
}, {
key: 'render',
value: function render() {
var _this2 = this;

var children = this.props.children;


var props = omit(this.props, ['onEnter', 'onExit', 'onProgress', 'triggerOnLoad']);

return React.createElement(
'div',
_extends({}, props, {
ref: function ref(element) {
_this2.element = element;
}
}),
children
);
}
}]);

return ScrollTrigger;
}(Component);

ScrollTrigger.propTypes = {
triggerOnLoad: PropTypes.bool,
onEnter: PropTypes.func,
onExit: PropTypes.func,
onProgress: PropTypes.func
};

ScrollTrigger.defaultProps = {
triggerOnLoad: true,
onEnter: function onEnter() {},
onExit: function onExit() {},
onProgress: function onProgress() {}
};

export default ScrollTrigger;
32 changes: 32 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"name": "react-scroll-trigger",
"version": "0.1.0",
"description": "React component tied to scroll events with callbacks for enter, exit and progress while scrolling through the viewport.",
"keywords": [
"react",
"react-component",
"scroll",
"trigger"
],
"main": "index.js",
"scripts": {
"compile": "babel -d ./ src/",
"prepublish": "npm run compile",
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": "[email protected]:ryanhefner/react-scroll-trigger.git",
"author": "Ryan Hefner <[email protected]> (https://www.ryanhefner.com)",
"license": "MIT",
"devDependencies": {
"babel-cli": "^6.24.1",
"babel-plugin-transform-object-rest-spread": "^6.23.0",
"babel-preset-latest": "^6.24.1",
"babel-preset-react": "^6.24.1"
},
"dependencies": {
"lodash": "^4.17.4",
"prop-types": "^15.5.10",
"react": "^15.6.1",
"react-dom": "^15.6.1"
}
}
Loading

0 comments on commit 89d8512

Please sign in to comment.