Skip to content

Commit 5c3aa29

Browse files
committed
Merge branch 'release/0.1.0' into develop
2 parents d1518e7 + 0d873b6 commit 5c3aa29

File tree

9 files changed

+2273
-0
lines changed

9 files changed

+2273
-0
lines changed

.babelrc

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"presets": [
3+
["latest", {
4+
"es2015": {
5+
"modules": false
6+
}
7+
}],
8+
"react"
9+
],
10+
"plugins": [
11+
"transform-object-rest-spread"
12+
],
13+
}

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.DS_Store
2+
node_modules/
3+
*.sublime-project
4+
*.sublime-workspace

.npmignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
src/
2+
*.sublime-project
3+
*.sublime-workspace

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2017 Ryan Hefner <[email protected]> (https://www.ryanhefner.com)
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
# React Scroll Trigger
2+
3+
React component that monitors `scroll` events to trigger callbacks when it enters,
4+
exits and progresses through the viewport. All callback include the `progress` and
5+
`velocity` of the scrolling, in the event you want to manipulate stuff based on
6+
those values.
7+
8+
## Install
9+
10+
Via [npm](https://npmjs.com/package/react-scroll-trigger)
11+
```
12+
npm install react-scroll-trigger
13+
```
14+
15+
Via [Yarn](http://yarn.fyi/react-scroll-trigger)
16+
```
17+
yarn add react-scroll-trigger
18+
```
19+
20+
### Requirements
21+
22+
* [react](https://npmjs.com/package/react)
23+
* [react-dom](https://npmjs.com/package/react-dom)
24+
* [prop-types](https://npmjs.com/package/prop-types)
25+
* [lodash](https://npmjs.com/package/lodash)
26+
27+
## How to use
28+
29+
```
30+
import ScrollTrigger from 'react-scroll-trigger';
31+
32+
...
33+
34+
onEnterViewport() {
35+
this.setState({
36+
visible: true,
37+
});
38+
}
39+
40+
onExitViewport() {
41+
this.setState({
42+
visible: false,
43+
});
44+
}
45+
46+
render() {
47+
const {
48+
visible,
49+
} = this.state;
50+
51+
return (
52+
<ScrollTrigger onEnter={this.onEnterViewport} onExit={this.onExitViewport}>
53+
<div className={`container ${visible ? 'container-animate' : ''}`}
54+
</ScrollTrigger>
55+
);
56+
}
57+
```
58+
59+
The `ScrollTrigger` is intended to be used as a composable element, allowing you
60+
to either use it standalone within a page (ie. no children).
61+
62+
```
63+
<ScrollTrigger onEnter={this.onEnterViewport} onExit={this.onExitViewport} />
64+
```
65+
66+
Or, pass in children to receive events and `progress` based on the dimensions of
67+
those elements within the DOM.
68+
69+
```
70+
<ScrollTrigger onEnter={this.onEnterViewport} onExit={this.onExitViewport}>
71+
<List>
72+
[...list items...]
73+
</List>
74+
</ScrollTrigger>
75+
```
76+
77+
The beauty of this component is its flexibility. I’ve used it to trigger
78+
AJAX requests based on either the `onEnter` or `progress` of the component within
79+
the viewport. Or, as a way to control animations or other transitions that you
80+
would like to either trigger when visible in the viewport or based on the exact
81+
`progress` of that element as it transitions through the viewport.
82+
83+
### Properties
84+
85+
Below are the properties that can be defined on a `<ScrollTrigger />` instance.
86+
In addition to these properties, all other standard React properites like `className`,
87+
`key`, etc. can be passed in as well and will be applied to the `<div>` that will
88+
be rendered by the `ScrollTrigger`.
89+
90+
* `triggerOnLoad` - Boolean (Default: `true`)
91+
* `onEnter` - Callback `({progress, velocity}, ref) => {}`
92+
* `onExit` - Callback `({progress, velocity}, ref) => {}`
93+
* `onProgress` - Callback `({progress, velocity}, ref) => {}`
94+
95+
## License
96+
97+
[MIT](LICENSE)

index.js

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
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; };
2+
3+
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; }; }();
4+
5+
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
6+
7+
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; }
8+
9+
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; }
10+
11+
import React, { Component } from 'react';
12+
import PropTypes from 'prop-types';
13+
import ReactDOM from 'react-dom';
14+
import omit from 'lodash/omit';
15+
import throttle from 'lodash/throttle';
16+
17+
var ScrollTrigger = function (_Component) {
18+
_inherits(ScrollTrigger, _Component);
19+
20+
function ScrollTrigger(props) {
21+
_classCallCheck(this, ScrollTrigger);
22+
23+
var _this = _possibleConstructorReturn(this, (ScrollTrigger.__proto__ || Object.getPrototypeOf(ScrollTrigger)).call(this, props));
24+
25+
_this.onScroll = throttle(_this.onScroll.bind(_this), 100, {
26+
trailing: false
27+
});
28+
29+
_this.onResize = throttle(_this.onResize.bind(_this), 100, {
30+
trailing: false
31+
});
32+
33+
_this.state = {
34+
inViewport: false,
35+
progress: 0,
36+
lastScrollPosition: null,
37+
lastScrollTime: null
38+
};
39+
return _this;
40+
}
41+
42+
_createClass(ScrollTrigger, [{
43+
key: 'componentDidMount',
44+
value: function componentDidMount() {
45+
addEventListener('resize', this.onResize);
46+
addEventListener('scroll', this.onScroll);
47+
48+
if (this.props.triggerOnLoad) {
49+
this.checkStatus();
50+
}
51+
}
52+
}, {
53+
key: 'componentWillUnmount',
54+
value: function componentWillUnmount() {
55+
removeEventListener('resize', this.onResize);
56+
removeEventListener('scroll', this.onScroll);
57+
}
58+
}, {
59+
key: 'onResize',
60+
value: function onResize() {
61+
this.checkStatus();
62+
}
63+
}, {
64+
key: 'onScroll',
65+
value: function onScroll() {
66+
this.checkStatus();
67+
}
68+
}, {
69+
key: 'checkStatus',
70+
value: function checkStatus() {
71+
var _props = this.props,
72+
onEnter = _props.onEnter,
73+
onExit = _props.onExit,
74+
onProgress = _props.onProgress;
75+
var _state = this.state,
76+
lastScrollPosition = _state.lastScrollPosition,
77+
lastScrollTime = _state.lastScrollTime;
78+
79+
80+
var element = ReactDOM.findDOMNode(this.element);
81+
var elementRect = element.getBoundingClientRect();
82+
var viewportStart = 0;
83+
var viewportEnd = document.body.clientHeight;
84+
var inViewport = elementRect.top < viewportEnd && elementRect.bottom > viewportStart;
85+
86+
var position = window.scrollY;
87+
var velocity = lastScrollPosition && lastScrollTime ? Math.abs((lastScrollPosition - position) / (lastScrollTime - Date.now())) : null;
88+
89+
if (inViewport) {
90+
var progress = Math.max(0, Math.min(1, 1 - elementRect.bottom / (viewportEnd + elementRect.height)));
91+
92+
if (!this.state.inViewPort) {
93+
this.setState({
94+
inViewport: inViewport
95+
});
96+
97+
onEnter({
98+
progress: progress,
99+
velocity: velocity
100+
}, this);
101+
}
102+
103+
this.setState({
104+
lastScrollPosition: position,
105+
lastScrollTime: Date.now()
106+
});
107+
108+
onProgress({
109+
progress: progress,
110+
velocity: velocity
111+
}, this);
112+
return;
113+
}
114+
115+
if (this.state.inViewport) {
116+
var _progress = elementRect.top <= viewportEnd ? 1 : 0;
117+
118+
this.setState({
119+
lastScrollPosition: position,
120+
lastScrollTime: Date.now(),
121+
inViewport: inViewport,
122+
progress: _progress
123+
});
124+
125+
onProgress({
126+
progress: _progress,
127+
velocity: velocity
128+
}, this);
129+
130+
onExit({
131+
progress: _progress,
132+
velocity: velocity
133+
}, this);
134+
}
135+
}
136+
}, {
137+
key: 'render',
138+
value: function render() {
139+
var _this2 = this;
140+
141+
var children = this.props.children;
142+
143+
144+
var props = omit(this.props, ['onEnter', 'onExit', 'onProgress', 'triggerOnLoad']);
145+
146+
return React.createElement(
147+
'div',
148+
_extends({}, props, {
149+
ref: function ref(element) {
150+
_this2.element = element;
151+
}
152+
}),
153+
children
154+
);
155+
}
156+
}]);
157+
158+
return ScrollTrigger;
159+
}(Component);
160+
161+
ScrollTrigger.propTypes = {
162+
triggerOnLoad: PropTypes.bool,
163+
onEnter: PropTypes.func,
164+
onExit: PropTypes.func,
165+
onProgress: PropTypes.func
166+
};
167+
168+
ScrollTrigger.defaultProps = {
169+
triggerOnLoad: true,
170+
onEnter: function onEnter() {},
171+
onExit: function onExit() {},
172+
onProgress: function onProgress() {}
173+
};
174+
175+
export default ScrollTrigger;

package.json

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{
2+
"name": "react-scroll-trigger",
3+
"version": "0.1.0",
4+
"description": "React component tied to scroll events with callbacks for enter, exit and progress while scrolling through the viewport.",
5+
"keywords": [
6+
"react",
7+
"react-component",
8+
"scroll",
9+
"trigger"
10+
],
11+
"main": "index.js",
12+
"scripts": {
13+
"compile": "babel -d ./ src/",
14+
"prepublish": "npm run compile",
15+
"test": "echo \"Error: no test specified\" && exit 1"
16+
},
17+
"repository": "[email protected]:ryanhefner/react-scroll-trigger.git",
18+
"author": "Ryan Hefner <[email protected]> (https://www.ryanhefner.com)",
19+
"license": "MIT",
20+
"devDependencies": {
21+
"babel-cli": "^6.24.1",
22+
"babel-plugin-transform-object-rest-spread": "^6.23.0",
23+
"babel-preset-latest": "^6.24.1",
24+
"babel-preset-react": "^6.24.1"
25+
},
26+
"dependencies": {
27+
"lodash": "^4.17.4",
28+
"prop-types": "^15.5.10",
29+
"react": "^15.6.1",
30+
"react-dom": "^15.6.1"
31+
}
32+
}

0 commit comments

Comments
 (0)