diff --git a/lib/components/Parallax.js b/lib/components/Parallax.js
new file mode 100644
index 000000000..07f9dec88
--- /dev/null
+++ b/lib/components/Parallax.js
@@ -0,0 +1,180 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+
+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; }; }();
+
+var _react = require('react');
+
+var _react2 = _interopRequireDefault(_react);
+
+var _propTypes = require('prop-types');
+
+var _propTypes2 = _interopRequireDefault(_propTypes);
+
+var _propValidation = require('../utils/propValidation');
+
+var _ParallaxController = require('../libs/ParallaxController');
+
+var _ParallaxController2 = _interopRequireDefault(_ParallaxController);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+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; }
+
+var Parallax = function (_Component) {
+ _inherits(Parallax, _Component);
+
+ function Parallax() {
+ var _ref;
+
+ var _temp, _this, _ret;
+
+ _classCallCheck(this, Parallax);
+
+ for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
+ args[_key] = arguments[_key];
+ }
+
+ return _ret = (_temp = (_this = _possibleConstructorReturn(this, (_ref = Parallax.__proto__ || Object.getPrototypeOf(Parallax)).call.apply(_ref, [this].concat(args))), _this), _this.mapRefOuter = function (ref) {
+ _this._outer = ref;
+ }, _this.mapRefInner = function (ref) {
+ _this._inner = ref;
+ }, _temp), _possibleConstructorReturn(_this, _ret);
+ }
+
+ _createClass(Parallax, [{
+ key: 'componentDidMount',
+ value: function componentDidMount() {
+ // Make sure the provided controller is an instance of the Parallax Controller
+ var isInstance = this.controller instanceof _ParallaxController2.default;
+
+ // Throw if neither context or global is available
+ if (!this.controller && !isInstance) {
+ throw new Error("Must wrap your application's components in a .");
+ }
+
+ // Deprecation warning for <=1.0.0
+ // If no context is available but the window global is then warn
+ if (!this.context.parallaxController && window.ParallaxController) {
+ console.log('Calling ParallaxController.init() has been deprecated in favor of using the component. For usage details see: https://github.com/jscottsmith/react-scroll-parallax/tree/v1.1.0#usage');
+ }
+
+ // create a new parallax element and save the reference
+ this.element = this.controller.createElement({
+ elInner: this._inner,
+ elOuter: this._outer,
+ props: {
+ disabled: this.props.disabled,
+ offsetXMax: this.props.offsetXMax,
+ offsetXMin: this.props.offsetXMin,
+ offsetYMax: this.props.offsetYMax,
+ offsetYMin: this.props.offsetYMin,
+ slowerScrollRate: this.props.slowerScrollRate
+ }
+ });
+ }
+ }, {
+ key: 'componentWillReceiveProps',
+ value: function componentWillReceiveProps(nextProps) {
+ // updates the elements props when relevant parallax props change
+ if (this.props.disabled !== nextProps.disabled || this.props.offsetXMax !== nextProps.offsetXMax || this.props.offsetXMin !== nextProps.offsetXMin || this.props.offsetYMax !== nextProps.offsetYMax || this.props.offsetYMin !== nextProps.offsetYMin || this.props.slowerScrollRate !== nextProps.slowerScrollRate) {
+ this.controller.updateElement(this.element, {
+ props: {
+ disabled: nextProps.disabled,
+ offsetXMax: nextProps.offsetXMax,
+ offsetXMin: nextProps.offsetXMin,
+ offsetYMax: nextProps.offsetYMax,
+ offsetYMin: nextProps.offsetYMin,
+ slowerScrollRate: nextProps.slowerScrollRate
+ }
+ });
+ }
+ // resets element styles when disabled
+ if (this.props.disabled !== nextProps.disabled && nextProps.disabled) {
+ this.controller.resetElementStyles(this.element);
+ }
+ }
+ }, {
+ key: 'componentWillUnmount',
+ value: function componentWillUnmount() {
+ this.controller.removeElement(this.element);
+ }
+ }, {
+ key: 'render',
+ value: function render() {
+ var _props = this.props,
+ children = _props.children,
+ className = _props.className,
+ Tag = _props.tag,
+ styleOuter = _props.styleOuter,
+ styleInner = _props.styleInner;
+
+
+ var rootClass = 'parallax-outer' + (className ? ' ' + className : '');
+
+ return _react2.default.createElement(
+ Tag,
+ {
+ className: rootClass,
+ ref: this.mapRefOuter,
+ style: styleOuter
+ },
+ _react2.default.createElement(
+ 'div',
+ {
+ className: 'parallax-inner',
+ ref: this.mapRefInner,
+ style: styleInner
+ },
+ children
+ )
+ );
+ }
+ }, {
+ key: 'controller',
+ get: function get() {
+ // Legacy versions may use the global, not context
+ return this.context.parallaxController || window.ParallaxController;
+ }
+
+ // refs
+
+ }]);
+
+ return Parallax;
+}(_react.Component);
+
+Parallax.defaultProps = {
+ disabled: false,
+ offsetYMax: 0,
+ offsetYMin: 0,
+ offsetXMax: 0,
+ offsetXMin: 0,
+ slowerScrollRate: false, // determines whether scroll rate is faster or slower than standard scroll
+ tag: 'div'
+};
+Parallax.propTypes = {
+ children: _propTypes2.default.node,
+ className: _propTypes2.default.string,
+ disabled: _propTypes2.default.bool.isRequired,
+ offsetXMax: _propTypes2.default.oneOfType([_propTypes2.default.string, _propTypes2.default.number]),
+ offsetXMin: _propTypes2.default.oneOfType([_propTypes2.default.string, _propTypes2.default.number]),
+ offsetYMax: _propValidation.offsetMax,
+ offsetYMin: _propValidation.offsetMin,
+ slowerScrollRate: _propTypes2.default.bool.isRequired,
+ styleOuter: _propTypes2.default.object,
+ styleInner: _propTypes2.default.object,
+ tag: _propTypes2.default.string.isRequired
+};
+Parallax.contextTypes = {
+ parallaxController: _propTypes2.default.object // not required because this could be rendered on the server.
+};
+exports.default = Parallax;
+module.exports = exports['default'];
\ No newline at end of file
diff --git a/lib/components/ParallaxBanner.js b/lib/components/ParallaxBanner.js
new file mode 100644
index 000000000..2977d5479
--- /dev/null
+++ b/lib/components/ParallaxBanner.js
@@ -0,0 +1,118 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+
+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 _react = require('react');
+
+var _react2 = _interopRequireDefault(_react);
+
+var _propTypes = require('prop-types');
+
+var _propTypes2 = _interopRequireDefault(_propTypes);
+
+var _Parallax = require('./Parallax');
+
+var _Parallax2 = _interopRequireDefault(_Parallax);
+
+var _propValidation = require('../utils/propValidation');
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+var constainerStyle = {
+ position: 'relative',
+ overflow: 'hidden',
+ width: '100%',
+ height: '50vh'
+};
+
+var absoluteStyle = {
+ position: 'absolute',
+ top: 0,
+ right: 0,
+ bottom: 0,
+ left: 0
+};
+
+var ParallaxBanner = function ParallaxBanner(_ref) {
+ var children = _ref.children,
+ className = _ref.className,
+ layers = _ref.layers,
+ style = _ref.style,
+ disabled = _ref.disabled;
+
+ return _react2.default.createElement(
+ 'div',
+ {
+ style: _extends({}, constainerStyle, style),
+ className: 'parallax-banner' + (className ? ' ' + className : '')
+ },
+ layers.map(function (_ref2, i) {
+ var image = _ref2.image,
+ amount = _ref2.amount,
+ slowerScrollRate = _ref2.slowerScrollRate,
+ children = _ref2.children,
+ _ref2$expanded = _ref2.expanded,
+ expanded = _ref2$expanded === undefined ? true : _ref2$expanded;
+
+ // if this is an expanded layer overwrite the top/bottom styles with negative margins
+ var expandedStyle = expanded ? {
+ top: amount * 100 * -1 + '%',
+ bottom: amount * 100 * -1 + '%'
+ } : {};
+
+ return _react2.default.createElement(
+ _Parallax2.default,
+ {
+ key: 'layer-' + i,
+ offsetYMax: amount * 100 + '%',
+ offsetYMin: amount * -1 * 100 + '%',
+ slowerScrollRate: slowerScrollRate,
+ styleInner: absoluteStyle,
+ styleOuter: absoluteStyle,
+ disabled: disabled
+ },
+ image ? _react2.default.createElement('div', {
+ className: 'parallax-banner-layer-' + i,
+ style: _extends({
+ backgroundImage: 'url(' + image + ')',
+ backgroundPosition: 'center',
+ backgroundSize: 'cover'
+ }, absoluteStyle, expandedStyle)
+ }) : _react2.default.createElement(
+ 'div',
+ {
+ className: 'parallax-banner-layer-' + i,
+ style: _extends({}, absoluteStyle, expandedStyle)
+ },
+ children
+ )
+ );
+ }),
+ children
+ );
+};
+
+ParallaxBanner.defaultProps = {
+ disabled: false
+};
+
+ParallaxBanner.propTypes = {
+ className: _propTypes2.default.string,
+ children: _propTypes2.default.node,
+ disabled: _propTypes2.default.bool.isRequired,
+ layers: _propTypes2.default.arrayOf(_propTypes2.default.shape({
+ amount: _propTypes2.default.number.isRequired,
+ children: _propTypes2.default.oneOfType([_propTypes2.default.node, _propTypes2.default.func]),
+ expanded: _propTypes2.default.bool,
+ image: _propTypes2.default.string,
+ slowerScrollRate: _propTypes2.default.bool
+ })),
+ style: _propTypes2.default.object
+};
+
+exports.default = ParallaxBanner;
+module.exports = exports['default'];
\ No newline at end of file
diff --git a/lib/components/ParallaxProvider.js b/lib/components/ParallaxProvider.js
new file mode 100644
index 000000000..1708e0774
--- /dev/null
+++ b/lib/components/ParallaxProvider.js
@@ -0,0 +1,82 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+
+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; }; }();
+
+var _react = require('react');
+
+var _react2 = _interopRequireDefault(_react);
+
+var _propTypes = require('prop-types');
+
+var _propTypes2 = _interopRequireDefault(_propTypes);
+
+var _ParallaxController = require('../libs/ParallaxController');
+
+var _ParallaxController2 = _interopRequireDefault(_ParallaxController);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+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; }
+
+var ParallaxProvider = function (_Component) {
+ _inherits(ParallaxProvider, _Component);
+
+ function ParallaxProvider() {
+ _classCallCheck(this, ParallaxProvider);
+
+ return _possibleConstructorReturn(this, (ParallaxProvider.__proto__ || Object.getPrototypeOf(ParallaxProvider)).apply(this, arguments));
+ }
+
+ _createClass(ParallaxProvider, [{
+ key: 'getChildContext',
+ value: function getChildContext() {
+ // Passes down the reference to the controller
+ var parallaxController = this.parallaxController;
+
+ return { parallaxController: parallaxController };
+ }
+ }, {
+ key: 'componentWillMount',
+ value: function componentWillMount() {
+ // Don't initialize on the server
+ var isServer = typeof window === 'undefined';
+
+ if (!isServer) {
+ // Must not be the server so kick it off...
+ this.parallaxController = _ParallaxController2.default.init();
+ }
+ }
+ }, {
+ key: 'componentWillUnmount',
+ value: function componentWillUnmount() {
+ this.parallaxController = this.parallaxController.destroy();
+ }
+ }, {
+ key: 'render',
+ value: function render() {
+ var children = this.props.children;
+
+
+ return children;
+ }
+ }]);
+
+ return ParallaxProvider;
+}(_react.Component);
+
+ParallaxProvider.propTypes = {
+ children: _propTypes2.default.node.isRequired
+};
+ParallaxProvider.childContextTypes = {
+ parallaxController: _propTypes2.default.object
+};
+exports.default = ParallaxProvider;
+module.exports = exports['default'];
\ No newline at end of file
diff --git a/lib/index.js b/lib/index.js
new file mode 100644
index 000000000..b6b357217
--- /dev/null
+++ b/lib/index.js
@@ -0,0 +1,29 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+exports.ParallaxController = exports.ParallaxBanner = exports.ParallaxProvider = exports.Parallax = undefined;
+
+var _Parallax2 = require('./components/Parallax');
+
+var _Parallax3 = _interopRequireDefault(_Parallax2);
+
+var _ParallaxProvider2 = require('./components/ParallaxProvider');
+
+var _ParallaxProvider3 = _interopRequireDefault(_ParallaxProvider2);
+
+var _ParallaxBanner2 = require('./components/ParallaxBanner');
+
+var _ParallaxBanner3 = _interopRequireDefault(_ParallaxBanner2);
+
+var _ParallaxController2 = require('./libs/ParallaxController');
+
+var _ParallaxController3 = _interopRequireDefault(_ParallaxController2);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+exports.Parallax = _Parallax3.default;
+exports.ParallaxProvider = _ParallaxProvider3.default;
+exports.ParallaxBanner = _ParallaxBanner3.default;
+exports.ParallaxController = _ParallaxController3.default;
\ No newline at end of file
diff --git a/lib/libs/ParallaxController.js b/lib/libs/ParallaxController.js
new file mode 100644
index 000000000..5e5c748fa
--- /dev/null
+++ b/lib/libs/ParallaxController.js
@@ -0,0 +1,425 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+
+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 _index = require('../utils/index');
+
+function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
+
+/**
+ * -------------------------------------------------------
+ * Parallax Controller
+ * -------------------------------------------------------
+ *
+ * The global controller for setting up window scroll/resize
+ * listeners, managing and caching parallax element positions,
+ * determining which elements are inside the viewport based on
+ * scroll position, and then updating parallax element styles
+ * based on min/max offsets and current scroll position.
+ *
+ */
+function ParallaxController() {
+ // All parallax elements to be updated
+ var elements = [];
+
+ // Tracks current scroll y distance
+ var scrollY = 0;
+
+ // Window inner height
+ var windowHeight = 0;
+
+ // ID to increment for elements
+ var id = 0;
+
+ // Ticking
+ var ticking = false;
+
+ // Scroll direction
+ // let scrollDown = null;
+
+ // Passive support
+ var supportsPassive = (0, _index.testForPassiveScroll)();
+
+ function _addListeners() {
+ window.addEventListener('scroll', _handleScroll, supportsPassive ? { passive: true } : false);
+ window.addEventListener('resize', _handleResize, false);
+ }
+
+ function _removeListeners() {
+ window.removeEventListener('scroll', _handleScroll, supportsPassive ? { passive: true } : false);
+ window.removeEventListener('resize', _handleResize, false);
+ }
+
+ _addListeners();
+
+ /**
+ * Window scroll handler. Sets the 'scrollY'
+ * and then calls '_updateElementPositions()'.
+ */
+ function _handleScroll() {
+ // reference to prev scroll y
+ // const prevScrollY = scrollY;
+
+ // Save current scroll
+ scrollY = window.pageYOffset; // Supports IE 9 and up.
+
+ // direction
+ // scrollDown = scrollY > prevScrollY;
+
+ // Only called if the last animation request has been
+ // completed and there are parallax elements to update
+ if (!ticking && elements.length > 0) {
+ ticking = true;
+ window.requestAnimationFrame(_updateElementPositions);
+ }
+ }
+
+ /**
+ * Window resize handler. Sets the new window inner height
+ * then updates parallax element attributes and positions.
+ */
+ function _handleResize() {
+ _setWindowHeight();
+ _updateElementAttributes();
+ _updateElementPositions();
+ }
+
+ /**
+ * Creates a unique id to distinguish parallax elements.
+ * @return {Number}
+ */
+ function _createID() {
+ ++id;
+ return id;
+ }
+
+ /**
+ * Update element positions.
+ * Determines if the element is in view based on the cached
+ * attributes, if so set the elements parallax styles.
+ */
+ function _updateElementPositions() {
+ elements.forEach(function (element) {
+ if (element.props.disabled) return;
+
+ // check if the element is in view then
+ var isInView = (0, _index.isElementInView)(element, windowHeight, scrollY);
+
+ // set styles if it is
+ if (isInView) _setParallaxStyles(element);
+
+ // reset ticking so more animations can be called
+ ticking = false;
+ });
+ }
+
+ /**
+ * Update element attributes.
+ * Sets up the elements offsets based on the props passed from
+ * the component then caches the elements current position and
+ * other important attributes.
+ */
+ function _updateElementAttributes() {
+ elements.forEach(function (element) {
+ if (element.props.disabled) return;
+
+ _setupOffsets(element);
+
+ _cacheAttributes(element);
+ });
+ }
+
+ /**
+ * Remove parallax styles from all elements.
+ */
+ function _removeParallaxStyles() {
+ elements.forEach(function (element) {
+ _resetStyles(element);
+ });
+ }
+
+ /**
+ * Cache the window height.
+ */
+ function _setWindowHeight() {
+ var html = document.documentElement;
+ windowHeight = window.innerHeight || html.clientHeight;
+ }
+
+ /**
+ * Takes a parallax element and caches important values that
+ * cause layout reflow and paints. Stores the values as an
+ * attribute object accesible on the parallax element.
+ * @param {object} element
+ */
+ function _cacheAttributes(element) {
+ var _element$offsets = element.offsets,
+ yMin = _element$offsets.yMin,
+ yMax = _element$offsets.yMax,
+ xMax = _element$offsets.xMax,
+ xMin = _element$offsets.xMin;
+ var slowerScrollRate = element.props.slowerScrollRate;
+
+ // NOTE: Many of these cause layout and reflow so we're not
+ // calculating them on every frame -- instead these values
+ // are cached on the element to access later when determining
+ // the element's position and offset.
+
+ var el = element.elOuter;
+ var rect = el.getBoundingClientRect();
+ var elHeight = el.offsetHeight;
+ var elWidth = el.offsetWidth;
+ var scrollY = window.pageYOffset;
+
+ // NOTE: offsetYMax and offsetYMin are percents
+ // based of the height of the element. They must be
+ // calculated as px to correctly determine whether
+ // the element is in the viewport.
+ var yPercent = yMax.unit === '%' || yMin.unit === '%';
+ var xPercent = xMax.unit === '%' || xMin.unit === '%';
+
+ // X offsets
+ var yMinPx = yMin.value;
+ var yMaxPx = yMax.value;
+
+ if (yPercent) {
+ var h100 = elHeight / 100;
+ yMaxPx = yMax.value * h100;
+ yMinPx = yMin.value * h100; // negative value
+ }
+
+ // Y offsets
+ var xMinPx = xMax.value;
+ var xMaxPx = xMin.value;
+
+ if (xPercent) {
+ var w100 = elWidth / 100;
+ xMaxPx = xMax.value * w100;
+ xMinPx = xMin.value * w100; // negative value
+ }
+
+ // NOTE: must add the current scroll position when the
+ // element is checked so that we get its absolute position
+ // relative to the document and not the viewport then
+ // add the min/max offsets calculated above.
+ var top = 0;
+ var bottom = 0;
+
+ if (slowerScrollRate) {
+ top = rect.top + scrollY + yMinPx;
+ bottom = rect.bottom + scrollY + yMaxPx;
+ } else {
+ top = rect.top + scrollY + yMinPx * -1;
+ bottom = rect.bottom + scrollY + yMaxPx * -1;
+ }
+
+ // NOTE: Total distance the element will move from when
+ // the top enters the view to the bottom leaving
+ // accounting for elements height and max/min offsets.
+ var totalDist = windowHeight + (elHeight + Math.abs(yMinPx) + yMaxPx);
+
+ element.attributes = {
+ top: top,
+ bottom: bottom,
+ elHeight: elHeight,
+ elWidth: elWidth,
+ yMaxPx: yMaxPx,
+ yMinPx: yMinPx,
+ xMaxPx: xMaxPx,
+ xMinPx: xMinPx,
+ totalDist: totalDist
+ };
+ }
+
+ /**
+ * Takes a parallax element and parses the offset props to get the value
+ * and unit. Sets these values as offset object accessible on the element.
+ * @param {object} element
+ */
+ function _setupOffsets(element) {
+ var _element$props = element.props,
+ offsetYMin = _element$props.offsetYMin,
+ offsetYMax = _element$props.offsetYMax,
+ offsetXMax = _element$props.offsetXMax,
+ offsetXMin = _element$props.offsetXMin;
+
+
+ var yMin = (0, _index.parseValueAndUnit)(offsetYMin);
+ var yMax = (0, _index.parseValueAndUnit)(offsetYMax);
+ var xMin = (0, _index.parseValueAndUnit)(offsetXMax);
+ var xMax = (0, _index.parseValueAndUnit)(offsetXMin);
+
+ if (xMin.unit !== xMax.unit || yMin.unit !== yMax.unit) {
+ throw new Error('Must provide matching units for the min and max offset values of each axis.');
+ }
+
+ var xUnit = xMin.unit || '%';
+ var yUnit = yMin.unit || '%';
+
+ element.offsets = {
+ xUnit: xUnit,
+ yUnit: yUnit,
+ yMin: yMin,
+ yMax: yMax,
+ xMin: xMin,
+ xMax: xMax
+ };
+ }
+
+ /**
+ * Takes a parallax element and set the styles based on the
+ * offsets and percent the element has moved though the viewport.
+ * @param {object} element
+ */
+ function _setParallaxStyles(element) {
+ var top = element.attributes.top - scrollY;
+ var totalDist = element.attributes.totalDist;
+
+ // Percent the element has moved based on current and total distance to move
+
+ var percentMoved = (top * -1 + windowHeight) / totalDist * 100;
+
+ // Scale percentMoved to min/max percent determined by offset props
+ var slowerScrollRate = element.props.slowerScrollRate;
+
+ // Get the parallax X and Y offsets
+
+ var offsets = (0, _index.getParallaxOffsets)(element.offsets, percentMoved, slowerScrollRate);
+
+ // Apply styles
+ var el = element.elInner;
+
+ // prettier-ignore
+ el.style.transform = 'translate3d(' + offsets.x.value + offsets.x.unit + ', ' + offsets.y.value + offsets.y.unit + ', 0)';
+ }
+
+ /**
+ * Takes a parallax element and removes parallax offset styles.
+ * @param {object} element
+ */
+ function _resetStyles(element) {
+ var el = element.elInner;
+ el.style.transform = '';
+ }
+
+ /**
+ * -------------------------------------------------------
+ * Public methods
+ * -------------------------------------------------------
+ */
+
+ /**
+ * Gets the parallax elements in the controller
+ * @return {array} parallax elements
+ */
+ this.getElements = function () {
+ return elements;
+ };
+
+ /**
+ * Creates a new parallax element object with new id
+ * and options to store in the 'elements' array.
+ * @param {object} options
+ * @return {object} element
+ */
+ this.createElement = function (options) {
+ var id = _createID();
+ var newElement = _extends({
+ id: id
+ }, options);
+
+ var updatedElements = [].concat(_toConsumableArray(elements), [newElement]);
+ elements = updatedElements;
+ this.update();
+
+ return newElement;
+ };
+
+ /**
+ * Creates a new parallax element object with new id
+ * and options to store in the 'elements' array.
+ * @param {object} element
+ */
+ this.removeElement = function (element) {
+ var updatedElements = elements.filter(function (el) {
+ return el.id !== element.id;
+ });
+ elements = updatedElements;
+ };
+
+ /**
+ * Updates an existing parallax element object with new options.
+ * @param {object} element
+ * @param {object} options
+ */
+ this.updateElement = function (element, options) {
+ var updatedElements = elements.map(function (el) {
+ // create element with new options and replaces the old
+ if (el.id === element.id) {
+ // update props
+ el.props = options.props;
+ }
+ return el;
+ });
+
+ elements = updatedElements;
+
+ // call update to set attributes and positions based on the new options
+ this.update();
+ };
+
+ /**
+ * Remove element styles.
+ * @param {object} element
+ */
+ this.resetElementStyles = function (element) {
+ _resetStyles(element);
+ };
+
+ /**
+ * Updates all parallax element attributes and postitions.
+ */
+ this.update = function () {
+ _setWindowHeight();
+ _updateElementAttributes();
+ _updateElementPositions();
+ };
+
+ /**
+ * Removes listeners, reset all styles then nullifies the global ParallaxController.
+ */
+ this.destroy = function () {
+ _removeListeners();
+ _removeParallaxStyles();
+ window.ParallaxController = null;
+ };
+}
+
+/**
+ * Static method to instantiate the ParallaxController.
+ * Returns a new or existing instance of the ParallaxController.
+ * @returns {Object} ParallaxController
+ */
+ParallaxController.init = function () {
+ var hasWindow = typeof window !== 'undefined';
+
+ if (!hasWindow) {
+ throw new Error('Looks like ParallaxController.init() was called on the server. This method must be called on the client.');
+ }
+
+ var controller = new ParallaxController();
+
+ // Keep global reference for legacy versions <= 1.1.0
+ if (hasWindow && !window.ParallaxController) {
+ window.ParallaxController = controller;
+ }
+
+ return controller;
+};
+
+exports.default = ParallaxController;
+module.exports = exports['default'];
\ No newline at end of file
diff --git a/lib/utils/clamp.js b/lib/utils/clamp.js
new file mode 100644
index 000000000..48fcf6c2c
--- /dev/null
+++ b/lib/utils/clamp.js
@@ -0,0 +1,12 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+exports.default = clamp;
+function clamp(number, lower, upper) {
+ number = number <= upper ? number : upper;
+ number = number >= lower ? number : lower;
+ return number;
+}
+module.exports = exports["default"];
\ No newline at end of file
diff --git a/lib/utils/getParallaxOffsets.js b/lib/utils/getParallaxOffsets.js
new file mode 100644
index 000000000..7a4a903b2
--- /dev/null
+++ b/lib/utils/getParallaxOffsets.js
@@ -0,0 +1,51 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+exports.default = getParallaxOffsets;
+
+var _index = require('./index');
+
+/**
+ * Gets the parallax X and Y offsets to be applied to an element
+ * based upon the percent the element has moved in the viewport
+ * and the min/max offsets
+ * @returns {Object}
+ */
+
+function getParallaxOffsets(offsets, percentMoved, slowerScrollRate) {
+ var yMin = offsets.yMin,
+ yMax = offsets.yMax,
+ xMin = offsets.xMin,
+ xMax = offsets.xMax;
+
+
+ var yUnit = yMax.unit;
+ var xUnit = xMax.unit;
+
+ // sets parallax to faster or slower than the rate of scroll
+ var x = 0;
+ var y = 0;
+
+ if (slowerScrollRate) {
+ x = (0, _index.scaleBetween)(percentMoved, xMin.value, xMax.value, 0, 100);
+ y = (0, _index.scaleBetween)(percentMoved, yMin.value, yMax.value, 0, 100);
+ } else {
+ // flipped max/min
+ x = (0, _index.scaleBetween)(percentMoved, xMax.value, xMin.value, 0, 100);
+ y = (0, _index.scaleBetween)(percentMoved, yMax.value, yMin.value, 0, 100);
+ }
+
+ return {
+ x: {
+ value: x,
+ unit: xUnit
+ },
+ y: {
+ value: y,
+ unit: yUnit
+ }
+ };
+}
+module.exports = exports['default'];
\ No newline at end of file
diff --git a/lib/utils/index.js b/lib/utils/index.js
new file mode 100644
index 000000000..931a4ca76
--- /dev/null
+++ b/lib/utils/index.js
@@ -0,0 +1,39 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+exports.testForPassiveScroll = exports.scaleBetween = exports.parseValueAndUnit = exports.isElementInView = exports.getParallaxOffsets = exports.clamp = undefined;
+
+var _clamp2 = require('./clamp');
+
+var _clamp3 = _interopRequireDefault(_clamp2);
+
+var _getParallaxOffsets2 = require('./getParallaxOffsets');
+
+var _getParallaxOffsets3 = _interopRequireDefault(_getParallaxOffsets2);
+
+var _isElementInView2 = require('./isElementInView');
+
+var _isElementInView3 = _interopRequireDefault(_isElementInView2);
+
+var _parseValueAndUnit2 = require('./parseValueAndUnit');
+
+var _parseValueAndUnit3 = _interopRequireDefault(_parseValueAndUnit2);
+
+var _scaleBetween2 = require('./scaleBetween');
+
+var _scaleBetween3 = _interopRequireDefault(_scaleBetween2);
+
+var _testForPassiveScroll2 = require('./testForPassiveScroll');
+
+var _testForPassiveScroll3 = _interopRequireDefault(_testForPassiveScroll2);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+exports.clamp = _clamp3.default;
+exports.getParallaxOffsets = _getParallaxOffsets3.default;
+exports.isElementInView = _isElementInView3.default;
+exports.parseValueAndUnit = _parseValueAndUnit3.default;
+exports.scaleBetween = _scaleBetween3.default;
+exports.testForPassiveScroll = _testForPassiveScroll3.default;
\ No newline at end of file
diff --git a/lib/utils/isElementInView.js b/lib/utils/isElementInView.js
new file mode 100644
index 000000000..1d9f4e55c
--- /dev/null
+++ b/lib/utils/isElementInView.js
@@ -0,0 +1,26 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+exports.default = isElementInView;
+/**
+ * Takes a parallax element and returns whether the element
+ * is in view based on the cached position of the element,
+ * current scroll position and the window height.
+ * @param {object} element
+ * @return {boolean} isInView
+ */
+function isElementInView(element, windowHeight, scrollY) {
+ var top = element.attributes.top - scrollY;
+ var bottom = element.attributes.bottom - scrollY;
+
+ var topInView = top >= 0 && top <= windowHeight;
+ var bottomInView = bottom >= 0 && bottom <= windowHeight;
+ var covering = top <= 0 && bottom >= windowHeight;
+
+ var isInView = topInView || bottomInView || covering;
+
+ return isInView;
+}
+module.exports = exports["default"];
\ No newline at end of file
diff --git a/lib/utils/parseValueAndUnit.js b/lib/utils/parseValueAndUnit.js
new file mode 100644
index 000000000..2b1e64b87
--- /dev/null
+++ b/lib/utils/parseValueAndUnit.js
@@ -0,0 +1,38 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+exports.default = parseValueAndUnit;
+/**
+ * Determines the unit of a string and parses the value
+ *
+ * @param {string} str
+ * @param {object} out
+ * @return {object} The parsed value and the unit if any
+ */
+function parseValueAndUnit(str) {
+ var out = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : { value: 0, unit: 'px' };
+
+ var isValid = typeof str === 'number' || typeof str === 'string';
+
+ if (!isValid) {
+ throw new Error('Invalid value provided. Must provide a value as a string or number');
+ }
+
+ str = String(str);
+ out.value = parseFloat(str, 10);
+ out.unit = str.match(/[\d.\-\+]*\s*(.*)/)[1] || '%'; // default to percent
+
+ var validUnits = ['px', '%'];
+ var isValidUnit = validUnits.find(function (unit) {
+ return unit === out.unit;
+ });
+
+ if (!isValidUnit) {
+ throw new Error('Invalid unit provided. Must provide a unit of px in or %');
+ }
+
+ return out;
+}
+module.exports = exports['default'];
\ No newline at end of file
diff --git a/lib/utils/propValidation.js b/lib/utils/propValidation.js
new file mode 100644
index 000000000..ab52f3151
--- /dev/null
+++ b/lib/utils/propValidation.js
@@ -0,0 +1,44 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+exports.offsetMin = offsetMin;
+exports.offsetMax = offsetMax;
+function offsetMin(props, propName) {
+ var componentName = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'ANONYMOUS';
+
+ var value = props[propName];
+ var isValid = typeof value === 'string' || typeof value === 'number';
+
+ if (!isValid) {
+ return new Error('[' + propName + '] in ' + componentName + ' must be a string with with "%"" or "px" units or number.');
+ }
+
+ if (props[propName]) {
+ if (typeof value === 'string') {
+ value = parseInt(value, 10);
+ }
+ return value <= 0 ? null : new Error('[' + propName + '] in ' + componentName + ' is greater than zero. [' + propName + '] must be less than or equal to zero.');
+ }
+ return null;
+}
+
+function offsetMax(props, propName) {
+ var componentName = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'ANONYMOUS';
+
+ var value = props[propName];
+ var isValid = typeof value === 'string' || typeof value === 'number';
+
+ if (!isValid) {
+ return new Error('[' + propName + '] in ' + componentName + ' must be a string with with "%"" or "px" units or number.');
+ }
+
+ if (props[propName]) {
+ if (typeof value === 'string') {
+ value = parseInt(value, 10);
+ }
+ return value >= 0 ? null : new Error('[' + propName + '] in ' + componentName + ' is less than zero. [' + propName + '] must be greater than or equal to zero.');
+ }
+ return null;
+}
\ No newline at end of file
diff --git a/lib/utils/scaleBetween.js b/lib/utils/scaleBetween.js
new file mode 100644
index 000000000..67e02dade
--- /dev/null
+++ b/lib/utils/scaleBetween.js
@@ -0,0 +1,11 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+exports.default = scaleBetween;
+// Scale between AKA normalize
+function scaleBetween(value, newMin, newMax, oldMin, oldMax) {
+ return (newMax - newMin) * (value - oldMin) / (oldMax - oldMin) + newMin;
+}
+module.exports = exports["default"];
\ No newline at end of file
diff --git a/lib/utils/testForPassiveScroll.js b/lib/utils/testForPassiveScroll.js
new file mode 100644
index 000000000..7a3dc96bf
--- /dev/null
+++ b/lib/utils/testForPassiveScroll.js
@@ -0,0 +1,20 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+exports.default = testForPassiveScroll;
+function testForPassiveScroll() {
+ var supportsPassiveOption = false;
+ try {
+ var opts = Object.defineProperty({}, 'passive', {
+ get: function get() {
+ supportsPassiveOption = true;
+ }
+ });
+ window.addEventListener('test', null, opts);
+ window.removeEventListener('test', null, opts);
+ } catch (e) {}
+ return supportsPassiveOption;
+}
+module.exports = exports['default'];
\ No newline at end of file
diff --git a/package.json b/package.json
index 841162966..50abf8eed 100644
--- a/package.json
+++ b/package.json
@@ -4,7 +4,7 @@
"description": "React components to create parallax scroll effects for banners, images or any other DOM elements.",
"repository": {
"type": "git",
- "url": "https://github.com/jscottsmith/react-scroll-parallax"
+ "url": "https://github.com/Bajtas/react-scroll-parallax"
},
"main": "lib/index.js",
"scripts": {
diff --git a/src/libs/ParallaxController.js b/src/libs/ParallaxController.js
index b47e1630f..2687cdcd5 100644
--- a/src/libs/ParallaxController.js
+++ b/src/libs/ParallaxController.js
@@ -212,8 +212,8 @@ function ParallaxController() {
top = rect.top + scrollY + yMinPx;
bottom = rect.bottom + scrollY + yMaxPx;
} else {
- top = rect.top + scrollY + yMaxPx * -1;
- bottom = rect.bottom + scrollY + yMinPx * -1;
+ top = rect.top + scrollY + yMinPx * -1;
+ bottom = rect.bottom + scrollY + yMaxPx * -1;
}
// NOTE: Total distance the element will move from when