├── 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 |
--------------------------------------------------------------------------------