├── CHANGELOG.md ├── demo ├── default.gif ├── autoFrame.gif ├── autoScroll.gif ├── index.js ├── Demo.test.js ├── index.html ├── index.css └── Demo.js ├── .travis.yml ├── .gitignore ├── .babelrc ├── src ├── index.js ├── scrollInitalState.js ├── references.md ├── nodeToScrollState.js ├── nodeChildrenToScrollState.js └── Scroller.js ├── test └── test.js ├── LICENSE ├── webpack.config.js ├── package.json └── README.md /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## CHANGELOG 2 | 3 | ### 0.1.0 4 | First commit 5 | -------------------------------------------------------------------------------- /demo/default.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/du5rte/react-skroll/HEAD/demo/default.gif -------------------------------------------------------------------------------- /demo/autoFrame.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/du5rte/react-skroll/HEAD/demo/autoFrame.gif -------------------------------------------------------------------------------- /demo/autoScroll.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/du5rte/react-skroll/HEAD/demo/autoScroll.gif -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | sudo: false 4 | 5 | cache: 6 | directories: 7 | - node_modules 8 | 9 | node_js: 10 | - "6" 11 | 12 | script: 13 | - npm test 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Mac OSX Files 2 | .DS_Store 3 | .Trashes 4 | .Spotlight-V100 5 | .AppleDouble 6 | .LSOverride 7 | .icloud 8 | 9 | # NPM 10 | /node_modules/ 11 | npm-debug.log 12 | /dist/ 13 | /lib/ 14 | -------------------------------------------------------------------------------- /demo/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | 4 | import Demo from './Demo' 5 | 6 | ReactDOM.render( 7 | , 8 | document.getElementById('render') 9 | ) 10 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", 4 | "@babel/preset-react" 5 | ], 6 | "plugins": [ 7 | "@babel/plugin-proposal-export-default-from", 8 | "@babel/plugin-proposal-class-properties" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /demo/Demo.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import Demo from './Demo'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import 'core-js/fn/object/entries.js' 2 | 3 | export Scroller from './Scroller' 4 | 5 | export scrollInitalState from './scrollInitalState' 6 | export nodeToScrollState from './nodeToScrollState' 7 | export nodeChildrenToScrollState from './nodeChildrenToScrollState' 8 | 9 | export default module.exports 10 | -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | React Scroll 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert' 2 | 3 | import ReactSkroll, { Scroller, ScrollLink, ScrollProvider, } from '../src' 4 | 5 | describe('Libary', () => { 6 | describe('modules', () => { 7 | it('should export default module', () => { 8 | assert.ok(ReactSkroll) 9 | }) 10 | 11 | it('should export modules', () => { 12 | assert.ok(Scroller) 13 | }) 14 | }) 15 | }) 16 | -------------------------------------------------------------------------------- /src/scrollInitalState.js: -------------------------------------------------------------------------------- 1 | export default { 2 | position: 0, 3 | positionRatio: 0, 4 | start: 0, 5 | end: 0, 6 | viewHeight: 0, 7 | scrollHeight: 0, 8 | ready: false, 9 | onStart: true, 10 | onMiddle: false, 11 | onEnd: false, 12 | children: [], 13 | // autoFrame: props.autoFrame, 14 | // autoScroll: props.autoScroll, 15 | originalPosition: null, 16 | changedPosition: null, 17 | timeStamp: null, 18 | scrolling: false, 19 | wheeling: false, 20 | touching: false, 21 | moving: false, 22 | resting: true, 23 | touches: [], 24 | deltaY: 0, 25 | } 26 | -------------------------------------------------------------------------------- /src/references.md: -------------------------------------------------------------------------------- 1 | ## References: 2 | - https://medium.com/@franleplant/react-higher-order-components-in-depth-cf9032ee6c3e#.2cnfo15to 3 | - https://github.com/souporserious/react-measure/blob/master/src/Measure.jsx 4 | - https://github.com/ReactTraining/react-router/blob/master/modules/Link.js 5 | - https://facebook.github.io/react/blog/2016/07/13/mixins-considered-harmful.html#context 6 | - https://css-tricks.com/snippets/jquery/smooth-scrolling/ 7 | - https://github.com/callmecavs/jump.js 8 | - https://github.com/jlmakes/scrollreveal 9 | - https://www.youtube.com/watch?v=rNsC1VI9388 10 | 11 | ## Decay 12 | - https://stackoverflow.com/questions/35656031/react-native-continue-animation-with-last-velocity-of-touch-gesture 13 | - http://fooo.fr/~vjeux/fb/animated-docs/ 14 | -------------------------------------------------------------------------------- /src/nodeToScrollState.js: -------------------------------------------------------------------------------- 1 | export default function nodeToScrollState({ 2 | scrollTop, 3 | scrollHeight, 4 | offsetHeight, 5 | children 6 | }) { 7 | // Interpreting native values 8 | let start = 0 9 | let viewHeight = offsetHeight 10 | let end = scrollHeight - viewHeight 11 | 12 | // current position 13 | let position = scrollTop 14 | let positionRatio = scrollTop / end 15 | 16 | // Conditionals 17 | let onStart = position <= start 18 | let onEnd = position >= end 19 | let onMiddle = !onStart && !onEnd 20 | 21 | // let scrolling = true / false 22 | 23 | let positionRelativeRatio = Math.abs(start - scrollTop / offsetHeight) 24 | 25 | return { 26 | position, 27 | positionRatio, 28 | // positionIndex, 29 | positionRelativeRatio, 30 | start, 31 | end, 32 | viewHeight, 33 | scrollHeight, 34 | onStart, 35 | onMiddle, 36 | onEnd 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Duarte Monteiro 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. 22 | -------------------------------------------------------------------------------- /src/nodeChildrenToScrollState.js: -------------------------------------------------------------------------------- 1 | export default function nodeChildrenToScrollState({ children, scrollTop }) { 2 | let list = [] 3 | 4 | // used to increment children view heights 5 | let start = 0 6 | 7 | // Fix: default props 8 | // let { theshold } = this.props 9 | let theshold = 0.5 10 | 11 | // TODO: experiment a map 12 | for (let i = 0; i < children.length; i++) { 13 | let { offsetHeight, attributes } = children[i] 14 | 15 | // interpreting native values 16 | let viewHeight = offsetHeight 17 | let end = start + viewHeight 18 | 19 | // current position values 20 | let position = start - scrollTop 21 | let positionRatio = position / offsetHeight 22 | let positionRatioRemainer = positionRatio <= -1 ? 1 : positionRatio >= 1 ? 1 : Math.abs(positionRatio % 1) 23 | 24 | /* Used for creating navigations and to links to 25 | * 26 | */ 27 | 28 | // Conditionals 29 | // FIX: use exact values 30 | let onView = positionRatio <= theshold && positionRatio >= -theshold 31 | let onFrame = position === scrollTop 32 | // TODO: review active 33 | // TODO: addfunction to run on activate() 34 | let active = onView 35 | 36 | list.push({ position, positionRatio, positionRatioRemainer, start, end, viewHeight, onView, active, onFrame }) 37 | 38 | // increament based on stacked item's height 39 | start += offsetHeight 40 | } 41 | 42 | return { children: list } 43 | } 44 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var webpack = require('webpack') 3 | var UglifyJsPlugin = require('uglifyjs-webpack-plugin') 4 | var HtmlWebpackPlugin = require('html-webpack-plugin') 5 | 6 | var env = process.env.NODE_ENV 7 | 8 | var config = { 9 | output: { 10 | library: 'ReactSkroll', 11 | libraryTarget: 'umd', 12 | }, 13 | module: { 14 | rules: [ 15 | { 16 | test: /\.(js)$/, 17 | exclude: /node_modules/, 18 | use: { loader: 'babel-loader' } 19 | }, 20 | ] 21 | }, 22 | resolve: { 23 | extensions: ['.js'] 24 | }, 25 | plugins: [ 26 | new webpack.DefinePlugin({ 27 | 'process.env.NODE_ENV': JSON.stringify(env) 28 | }) 29 | ] 30 | } 31 | 32 | if (process.env.NODE_ENV !== 'production') { 33 | config.mode = 'development' 34 | config.devtool = 'inline-source-map' 35 | 36 | config.plugins.push( 37 | new HtmlWebpackPlugin({ 38 | template: 'demo/index.html' 39 | }) 40 | ) 41 | } 42 | 43 | if (process.env.NODE_ENV === 'production') { 44 | config.mode = 'production' 45 | 46 | config.externals = { 47 | 'react': 'React', 48 | 'react-dom': 'ReactDOM', 49 | 'react-spring': 'ReactSpring', 50 | 'prop-types': 'PropTypes', 51 | } 52 | 53 | if (process.env.TARGET === 'minify') { 54 | config.optimization = { 55 | minimizer: [ 56 | new UglifyJsPlugin() 57 | ] 58 | } 59 | } 60 | } 61 | 62 | module.exports = config 63 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-skroll", 3 | "description": "Reactive Scrolling", 4 | "version": "0.7.3", 5 | "main": "lib/index.js", 6 | "scripts": { 7 | "clean": "rm -rf dist lib && mkdir dist lib", 8 | "prebuild": "npm run clean", 9 | "build:lib": "NODE_ENV=production babel src --out-dir lib", 10 | "build:umd": "NODE_ENV=production webpack src/index.js -o dist/react-skroll.js", 11 | "build:umd:min": "NODE_ENV=production TARGET=minify webpack src/index.js -o dist/react-skroll.min.js", 12 | "build": "npm run build:lib && npm run build:umd && npm run build:umd:min", 13 | "build:dev": "npm run build:lib -- --watch & npm run test -- --watch", 14 | "dev": "webpack-dev-server demo/index.js --content-base demo/", 15 | "prepublish": "npm run clean && npm run build", 16 | "test": "jest" 17 | }, 18 | "license": "MIT", 19 | "homepage": "https://github.com/du5rte/react-skroll#readme", 20 | "repository": { 21 | "type": "git", 22 | "url": "git+https://github.com/du5rte/react-skroll.git" 23 | }, 24 | "author": "Duarte Monteiro (http://du5rte.com)", 25 | "bugs": { 26 | "url": "https://github.com/du5rte/react-skroll/issues" 27 | }, 28 | "keywords": [ 29 | "react", 30 | "react-spring", 31 | "scroll", 32 | "scroller", 33 | "skroll", 34 | "react-scroll", 35 | "react-skroll" 36 | ], 37 | "files": [ 38 | "dist", 39 | "lib" 40 | ], 41 | "dependencies": { 42 | "lodash": ">=4.0.0", 43 | "resize-observer-polyfill": "^1.3.2", 44 | "throttle-debounce": "^1.0.1" 45 | }, 46 | "peerDependencies": { 47 | "core-js": ">=2.6.0", 48 | "prop-types": ">=15.0.0", 49 | "react": ">=16.6.0", 50 | "react-dom": ">=16.6.0", 51 | "react-spring": ">=7.0.0" 52 | }, 53 | "devDependencies": { 54 | "@babel/cli": "^7.2.0", 55 | "@babel/core": "^7.2.2", 56 | "@babel/plugin-proposal-class-properties": "^7.2.1", 57 | "@babel/plugin-proposal-export-default-from": "^7.2.0", 58 | "@babel/preset-env": "^7.2.0", 59 | "@babel/preset-react": "^7.0.0", 60 | "babel-core": "^7.0.0-bridge.0", 61 | "babel-jest": "^23.6.0", 62 | "babel-loader": "^8.0.4", 63 | "html-webpack-plugin": "^3.2.0", 64 | "jest": "^23.6.0", 65 | "prop-types": "^15.6.2", 66 | "react": "^16.6.3", 67 | "react-dom": "^16.6.3", 68 | "react-spring": "^7.2.1", 69 | "uglifyjs-webpack-plugin": "^2.0.1", 70 | "webpack": "^4.28.0", 71 | "webpack-cli": "^3.1.2", 72 | "webpack-dev-server": "^3.1.10" 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /demo/index.css: -------------------------------------------------------------------------------- 1 | * { 2 | padding: 0; 3 | margin: 0; 4 | } 5 | 6 | html, 7 | body { 8 | position: relative; 9 | width: 100%; 10 | height: 100%; 11 | font: 1em Helvetica, sans-serif; 12 | lineHeight: 1; 13 | color: white; 14 | } 15 | 16 | h1 { 17 | font-size: 20vw; 18 | margin: 0; 19 | } 20 | 21 | nav { 22 | font-size: 1.5vw; 23 | position: absolute; 24 | display: -webkit-box; 25 | display: -webkit-flex; 26 | display: -ms-flexbox; 27 | display: flex; 28 | -webkit-flex-flow: row wrap; 29 | -ms-flex-flow: row wrap; 30 | flex-flow: row wrap; 31 | -webkit-box-align: center; 32 | -webkit-align-items: center; 33 | -ms-flex-align: center; 34 | align-items: center; 35 | -webkit-box-pack: center; 36 | -webkit-justify-content: center; 37 | -ms-flex-pack: center; 38 | justify-content: center; 39 | box-sizing: border-box; 40 | top: 0; 41 | left: 0; 42 | width: 100%; 43 | padding: 0.5em; 44 | } 45 | 46 | h1 { 47 | text-align: middle; 48 | } 49 | 50 | ul { 51 | font-size: 2vw; 52 | margin: 0; 53 | left: 0; 54 | list-style: none; 55 | } 56 | 57 | li { 58 | padding-left: 2vw; 59 | } 60 | 61 | span { 62 | opacity: 0.3; 63 | } 64 | span.inactive { 65 | opacity: 0.5; 66 | } 67 | span.active { 68 | opacity: 1; 69 | } 70 | 71 | section { 72 | display: -webkit-box; 73 | display: -webkit-flex; 74 | display: -ms-flexbox; 75 | display: flex; 76 | height: 100%; 77 | -webkit-box-orient: vertical; 78 | -webkit-box-direction: normal; 79 | -webkit-flex-direction: row; 80 | -ms-flex-direction: row; 81 | flex-direction: row; 82 | -webkit-box-align: center; 83 | -webkit-align-items: center; 84 | -ms-flex-align: center; 85 | align-items: center; 86 | -webkit-box-pack: center; 87 | -webkit-justify-content: center; 88 | -ms-flex-pack: center; 89 | justify-content: center; 90 | } 91 | 92 | .wrapper { 93 | position: fixed; 94 | height: 100%; 95 | width: 100%; 96 | } 97 | 98 | .flex { 99 | display: flex; 100 | flex-direction: column; 101 | } 102 | 103 | .half-width { 104 | width: 50%; 105 | } 106 | 107 | .center-center { 108 | justify-content: center; 109 | align-items: center; 110 | } 111 | 112 | .left-center { 113 | justify-content: center; 114 | align-items: flex-start; 115 | } 116 | 117 | a, button { 118 | font-size: 1em; 119 | color: rgba(0, 0, 0, 0.25); 120 | padding: 0.25em 0.75em; 121 | margin: 0.25em; 122 | background: rgba(0, 0, 0, 0.1); 123 | border-radius: 9999px; 124 | outline: none; 125 | border: none; 126 | cursor: pointer; 127 | user-select: none; 128 | } 129 | 130 | a.active, button.active { 131 | color: rgba(0, 0, 0, 0.75); 132 | background: rgba(0, 0, 0, 0.25); 133 | } 134 | -------------------------------------------------------------------------------- /demo/Demo.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | 3 | import { Scroller, scrollInitalState } from '../src' 4 | 5 | function round(val) { 6 | return (Math.round(val * 100) / 100).toFixed(2); 7 | } 8 | 9 | const colors = [ 10 | {name: "Blue", color: "#215cf4" }, 11 | {name: "Cyan", color: "#0ccabf" }, 12 | {name: "Green", color: "#4ac36c" }, 13 | {name: "Yellow", color: "#e0be18" }, 14 | {name: "Red", color: "#e91e4f" }, 15 | {name: "Magenta", color: "#ca28e4" }, 16 | ] 17 | 18 | export default class Demo extends Component { 19 | constructor() { 20 | super() 21 | 22 | this.state = { 23 | scroll: scrollInitalState 24 | } 25 | } 26 | 27 | render() { 28 | const { scroll } = this.state 29 | 30 | return ( 31 |
32 | 55 | 56 | this.scroll = ref} 58 | autoScroll={true} 59 | autoFrame={true} 60 | onScrollChange={(scroll) => this.setState({ scroll })} 61 | > 62 | { 63 | colors.map(({ name, color }, index) => 64 |
65 |
66 |

{round(scroll.positionRatio)}

67 |
68 | 69 |
70 |
    71 |

    {'{'}

    72 | 73 | { 74 | Object.entries(scroll) 75 | .filter(([key, value]) => typeof value !== 'function') 76 | .filter(([key, value]) => typeof value !== 'object') 77 | .map(([key, value]) => 78 |
  • 79 | {key}: 80 | {value.toString()} 81 |
  • 82 | ) 83 | } 84 | 85 |
  • ...
  • 86 |

    {'}'}

    87 |
88 |
89 | 90 | 91 |
92 | ) 93 | } 94 |
95 |
96 | ) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-skroll 2 | Uses `react-spring` for butter smooth enhanced scrolling experience 3 | 4 | [![Build Status](https://travis-ci.org/du5rte/react-skroll.svg?branch=master)](https://travis-ci.org/du5rte/react-skroll) 5 | [![David](https://img.shields.io/david/peer/du5rte/react-skroll.svg)](https://github.com/du5rte/react-skroll) 6 | [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-blue.svg)](CONTRIBUTING.md#pull-requests) 7 | [![PRs Welcome](https://img.shields.io/badge/stability-experimental-red.svg)](CONTRIBUTING.md#pull-requests) 8 | 9 | ## Install 10 | ``` 11 | npm install react-skroll --save 12 | ``` 13 | 14 | ## UMD 15 | ``` 16 | 17 | 18 | ``` 19 | (Module exposed as `ReactSkroll`) 20 | 21 | ## Demo 22 | [Codepen Demo](http://codepen.io/du5rte/pen/KrGjEm) 23 | 24 | ## Usage 25 | 26 | ### Functional Children Pattern 27 | Most useful for simple scenarios when you only need the `scroll` inside the `Scroller` scope. 28 | 29 | ```javascript 30 | import { Scroller } from 'react-skroll' 31 | 32 | const Demo = () => ( 33 | this.scroll = ref} 35 | autoScroll={true} 36 | autoFrame={true} 37 | > 38 | {scroll => 39 | 40 |