├── LICENSE ├── README.md ├── bower.json ├── naturalScroll.js └── package.json /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 asvd 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 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | natural scroll 3 | ============== 4 | 5 | Objects in real life never stop or start moving instantly. Having this 6 | in mind, `natural scroll` performs scrolling smoothly and naturally: 7 | it starts and finishes the movement with zero speed and 8 | acceleration. If another scrolling target is specified during an 9 | animation still running, `natural scroll` recalculates the remaining 10 | animation frames, so that the scrolling continues smoothly and reaches 11 | the new destination. The slowdowns and accelerations do not make the 12 | animation look slower. The scrolling just *feels better*. A user may 13 | not even notice the magic at all, but a good design should not be 14 | noticed. For him the scrolling becomes natural, comfortable and 15 | predictable. 16 | 17 | You can see how `natural scroll` works on the following web-pages 18 | (click the menu items there and carefully watch how the page is 19 | scrolled): 20 | 21 | - [Home page of the intence project](http://asvd.github.io/intence) 22 | 23 | - [Demo page](http://asvd.github.io/viewport/) for the 24 | [viewport.js](https://github.com/asvd/viewport) library 25 | 26 | 27 | `natural scroll` has flexible FPS. Which means if a system is too slow 28 | (or a web-page is too overdesigned), `natural scroll` skips some of 29 | the frames, preserving the total time of animation. Therefore the 30 | destination scrolling position is reached on time and users do not 31 | have to wait any longer. Of course on faster systems the animation is 32 | more fluent. 33 | 34 | `natural scroll` does not have any dependencies, it is written in 35 | vanilla javascript which means it works anywhere. And it **only costs 36 | 748 bytes** of minified code including the UMD-headers! 37 | 38 | 39 | ### Usage 40 | 41 | Using `natural scroll` is very simple. Download and unpack the 42 | [distribution](https://github.com/asvd/naturalScroll/releases/download/v0.2.2/naturalScroll-0.2.2.tar.gz), or install it using [Bower](http://bower.io/): 43 | 44 | ```sh 45 | $ bower install natural-scroll 46 | ``` 47 | 48 | Load the `naturalScroll.js` module in a preferable way: 49 | 50 | ```html 51 | 52 | ``` 53 | 54 | Invoke the following methods to scroll a viewport to the desired 55 | position: 56 | 57 | ```js 58 | // element to scroll, can be document.body 59 | var viewport = document.getElementById('myViewport'); 60 | var positionTop = 1000; 61 | var positionLeft = 500; 62 | 63 | naturalScroll.scrollTop(viewport, positionTop); 64 | naturalScroll.scrollLeft(viewport, positionLeft); 65 | ``` 66 | 67 | You can also provide the third argument which is an animation time (in 68 | msec, 600 by default), but I would not change it. 69 | 70 | Have fun! 71 | 72 | - 73 | 74 | Follow me on twitter: https://twitter.com/asvd0 75 | 76 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "natural-scroll", 3 | "version": "0.2.2", 4 | "homepage": "https://github.com/asvd/naturalScroll", 5 | "authors": [ 6 | "Dmitry Prokashev " 7 | ], 8 | "description": "Smoothly scroll to the desired position", 9 | "main": "naturalScroll.js", 10 | "moduleType": [ 11 | "amd", 12 | "globals" 13 | ], 14 | "keywords": [ 15 | "scroll", 16 | "scrolling", 17 | "programmatically", 18 | "menu", 19 | "navigation" 20 | ], 21 | "license": "MIT", 22 | "ignore": [ 23 | "**/.*", 24 | "node_modules", 25 | "bower_components", 26 | "test", 27 | "tests" 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /naturalScroll.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview naturalScroll - scrolls a viewport naturally 3 | * @version 0.2.2 4 | * 5 | * @license MIT, see http://github.com/asvd/naturalScroll 6 | * @copyright 2015 asvd 7 | */ 8 | 9 | 10 | (function (root, factory) { 11 | if (typeof define === 'function' && define.amd) { 12 | define(['exports'], factory); 13 | } else if (typeof exports !== 'undefined') { 14 | factory(exports); 15 | } else { 16 | factory((root.naturalScroll = {})); 17 | } 18 | }(this, function (exports) { 19 | var allAnimations = [ 20 | [], // vertical scrolling animations, one for a viewport 21 | [] // horizontal animations 22 | ]; 23 | 24 | // for better compression 25 | var scrollTop = 'scrollTop'; 26 | var scrollLeft = 'scrollLeft'; 27 | 28 | // returns scrollTop() if argument is given, scrollLeft() otherwise 29 | var genScroll = function(top) { 30 | 31 | // exported method 32 | return function(elem, target, time) { 33 | elem = elem.scroller || elem; // needed for intence 34 | time = time || 600; 35 | 36 | // all animations for the particular direction 37 | var dirAnimations = allAnimations[top ? 0 : 1]; 38 | var prop = top ? scrollTop : scrollLeft; 39 | 40 | var animation, 41 | tick, 42 | i = 0, 43 | f0 = elem[prop], // current coordinate 44 | f1 = 0, // current speed 45 | f2 = 0; // current acceleration 46 | 47 | // searching for the element's animation 48 | for (;i < dirAnimations.length; i++) { 49 | animation = (dirAnimations[i].e == elem) ? 50 | dirAnimations[i] : animation; 51 | } 52 | 53 | if (animation) { 54 | // taking speed and accel. from the running animation 55 | f1 = animation.f[1]; 56 | f2 = animation.f[2]; 57 | } else { 58 | // generating a new animation which contains: 59 | // .e - element on which the animation is performed 60 | // .f - current animation frame data 61 | // .n - remaining frames number 62 | // .t - animation end timestamp 63 | dirAnimations.push(animation = {e : elem}); 64 | } 65 | 66 | animation.t = (new Date).getTime() + time; 67 | 68 | // total number of frames (most will be dropped though) 69 | var fnum = animation.n = time; 70 | var fnum2 = fnum * fnum; 71 | var fnum3 = fnum2 * fnum; 72 | var f0_target = f0-target; 73 | 74 | // calculating initial frame 75 | animation.f = [ 76 | f0, // coordinate 77 | f1, // speed 78 | f2, // acceleration 79 | 80 | // these magic formulae came from outer space 81 | - ( 9 * f2 * fnum2 + 82 | (36 * f1 -9 * f2) * fnum - 83 | 36 * f1 + 84 | 60 * f0_target 85 | ) / (fnum3 - fnum), 86 | 87 | 6 * ( 6 * f2 * fnum2 + 88 | (32 * f1 -6 * f2) * fnum - 89 | 32 * f1 + 90 | 60 * f0_target 91 | ) / fnum / ( fnum3 + 2 * fnum2 - fnum - 2 ), 92 | 93 | - 60 * ( f2 * fnum2 + 94 | (6 * f1 - f2) * fnum - 95 | 6 * f1 + 96 | 12 * f0_target 97 | ) / fnum / ( 98 | fnum2*fnum2 + 5*(fnum3 + fnum2-fnum) - 6 99 | ) 100 | ]; 101 | 102 | // creating the timeout function 103 | // and invoking it to apply the first frame instantly 104 | // (if the animation is already running, another timeout 105 | // is launched along with the existing, which is not a 106 | // problem, since we are already spam with this function 107 | // as fast as possible) 108 | (tick = function(i) { 109 | while ( 110 | // frames are not over 111 | animation.n && 112 | // current frame is not yet reached 113 | animation.n > animation.t - (new Date).getTime() 114 | ) { 115 | // calculating the next frame (i+1 means i>=0) 116 | for (i = 4; i+1;) { 117 | animation.f[i] += animation.f[i--+1]; 118 | } 119 | 120 | animation.n--; 121 | } 122 | 123 | elem[prop] = animation.f[0]; 124 | 125 | if (animation.n) { 126 | // scheduling the next frame 127 | (window.requestAnimationFrame || setTimeout)(tick, 1); 128 | } else { 129 | // stopping animation 130 | animation.f[1] = animation.f[2] = 0; 131 | } 132 | })(); 133 | } 134 | } 135 | 136 | exports[scrollTop] = genScroll( 137 | exports[scrollLeft] = genScroll() 138 | ); 139 | })); 140 | 141 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Dmitry Prokashev ", 3 | "name": "natural-scroll", 4 | "description": "Smoothly scroll to the desired position", 5 | "version": "0.2.2", 6 | "keywords": [ 7 | "scroll", 8 | "scrolling", 9 | "programmatically", 10 | "menu", 11 | "navigation" 12 | ], 13 | "repository": { 14 | "type": "git", 15 | "url": "git://github.com/asvd/naturalScroll.git" 16 | }, 17 | "browser": 18 | { 19 | "fs": false, 20 | "child_process": false 21 | }, 22 | "main": "naturalScroll.js", 23 | "dependencies": {}, 24 | "devDependencies": {}, 25 | "optionalDependencies": {}, 26 | "engines": { 27 | "node": "*" 28 | } 29 | } 30 | --------------------------------------------------------------------------------