Skip to content

Commit 53b6905

Browse files
committed
first release
1 parent fa7bca7 commit 53b6905

File tree

9 files changed

+157
-37
lines changed

9 files changed

+157
-37
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
dist
2+
node_modules
3+
4+
test/bundle.js

.npmignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
src
2+
test

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
This is a [ponyfill](https://ponyfoo.com/articles/polyfills-or-ponyfills) with the added ability of animating the scroll itself.
44

5-
Kudos to [@hsablonniere](https://github.com/hsablonniere) for sharing the [original polyfill](https://gist.github.com/hsablonniere/2581101)
5+
Kudos to [@hsablonniere](https://github.com/hsablonniere) for sharing the [original polyfill](https://gist.github.com/hsablonniere/2581101) and [@jocki84](https://github.com/jocki84) for [improving](https://gist.github.com/jocki84/6ffafd003387179a988e) it!
66

77
## Install
88

@@ -12,9 +12,10 @@ npm install scroll-into-view-if-needed
1212

1313
## API
1414

15-
### scrollIntoViewIfNeeded(node:Element, centerIfNeeded:boolean || options:object)
15+
### scrollIntoViewIfNeeded(node:Element, centerIfNeeded:boolean, options:object)
1616

1717
Returns a function that can be used to cancel a scroll animation.
18+
Inspired by [scroll-iv](https://www.npmjs.com/package/scroll-iv).
1819

1920
#### Options
2021

@@ -29,7 +30,7 @@ The duration of the animation in milliseconds, defaults to 0 for no animation.
2930

3031
##### easing
3132

32-
@TODO
33+
default is ease. Possible values: `ease|easeIn|easeOut|easeInOut|linear`
3334

3435
## Examples
3536

@@ -42,8 +43,7 @@ const activeNode = document.querySelector('li.active')
4243
scrollIntoViewIfNeeded(activeNode, false)
4344

4445
// Animates it with a tiny animation lib, no need for jQuery or Velocity
45-
scrollIntoViewIfNeeded(activeNode, {
46-
centerIfNeeded: false,
46+
scrollIntoViewIfNeeded(activeNode, false, {
4747
duration: 150
4848
})
4949

package.json

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22
"name": "scroll-into-view-if-needed",
33
"version": "1.0.0",
44
"description": "Element.scrollIntoViewIfNeeded ponyfill that can animate the scrolling",
5-
"main": "index.js",
5+
"main": "dist/bundle.js",
66
"scripts": {
7-
"test": "echo \"Error: no test specified\" && exit 1"
7+
"test": "echo \"Error: no test specified\" && exit 1",
8+
"build": "rollup src/index.js -c --output dist/bundle.js && rollup test/index.js -c -f umd --output test/bundle.js",
9+
"prepublish": "npm run build"
810
},
911
"repository": {
1012
"type": "git",
@@ -22,5 +24,16 @@
2224
"bugs": {
2325
"url": "https://github.com/stipsan/scroll-into-view-if-needed/issues"
2426
},
25-
"homepage": "https://github.com/stipsan/scroll-into-view-if-needed#readme"
27+
"homepage": "https://github.com/stipsan/scroll-into-view-if-needed#readme",
28+
"dependencies": {
29+
"amator": "^1.0.1"
30+
},
31+
"devDependencies": {
32+
"babel": "^6.5.2",
33+
"babel-preset-es2015-rollup": "^1.1.1",
34+
"rollup": "^0.25.8",
35+
"rollup-plugin-babel": "^2.4.0",
36+
"rollup-plugin-commonjs": "^2.2.1",
37+
"rollup-plugin-node-resolve": "^1.5.0"
38+
}
2639
}

rollup.config.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import commonjs from 'rollup-plugin-commonjs';
2+
import nodeResolve from 'rollup-plugin-node-resolve';
3+
4+
export default {
5+
plugins: [
6+
nodeResolve({
7+
jsnext: true,
8+
main: true
9+
}),
10+
11+
commonjs({
12+
// non-CommonJS modules will be ignored, but you can also
13+
// specifically include/exclude files
14+
include: 'node_modules/**', // Default: undefined
15+
16+
// if false then skip sourceMap generation for CommonJS modules
17+
sourceMap: false, // Default: true
18+
})
19+
]
20+
}

src/index.js

Lines changed: 73 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,78 @@
1-
if (!Element.prototype.scrollIntoViewIfNeeded) {
2-
Element.prototype.scrollIntoViewIfNeeded = function (centerIfNeeded) {
3-
centerIfNeeded = arguments.length === 0 ? true : !!centerIfNeeded;
1+
import animate from 'amator'
42

5-
var parent = this.parentNode,
6-
parentComputedStyle = window.getComputedStyle(parent, null),
7-
parentBorderTopWidth = parseInt(parentComputedStyle.getPropertyValue('border-top-width')),
8-
parentBorderLeftWidth = parseInt(parentComputedStyle.getPropertyValue('border-left-width')),
9-
overTop = this.offsetTop - parent.offsetTop < parent.scrollTop,
10-
overBottom = (this.offsetTop - parent.offsetTop + this.clientHeight - parentBorderTopWidth) > (parent.scrollTop + parent.clientHeight),
11-
overLeft = this.offsetLeft - parent.offsetLeft < parent.scrollLeft,
12-
overRight = (this.offsetLeft - parent.offsetLeft + this.clientWidth - parentBorderLeftWidth) > (parent.scrollLeft + parent.clientWidth),
13-
alignWithTop = overTop && !overBottom;
3+
export default function (elem, centerIfNeeded, options) {
4+
5+
if (!elem) throw new Error('Element is required in scrollIntoViewIfNeeded')
6+
7+
function withinBounds(value, min, max, extent) {
8+
if (false === centerIfNeeded || max <= value + extent && value <= min + extent) {
9+
return Math.min(max, Math.max(min, value));
10+
} else {
11+
return (min + max) / 2;
12+
}
13+
}
1414

15-
if ((overTop || overBottom) && centerIfNeeded) {
16-
parent.scrollTop = this.offsetTop - parent.offsetTop - parent.clientHeight / 2 - parentBorderTopWidth + this.clientHeight / 2;
17-
}
15+
function makeArea(left, top, width, height) {
16+
return { "left": left, "top": top, "width": width, "height": height
17+
, "right": left + width, "bottom": top + height
18+
, "translate":
19+
function (x, y) {
20+
return makeArea(x + left, y + top, width, height);
21+
}
22+
, "relativeFromTo":
23+
function (lhs, rhs) {
24+
var newLeft = left, newTop = top;
25+
lhs = lhs.offsetParent;
26+
rhs = rhs.offsetParent;
27+
if (lhs === rhs) {
28+
return area;
29+
}
30+
for (; lhs; lhs = lhs.offsetParent) {
31+
newLeft += lhs.offsetLeft + lhs.clientLeft;
32+
newTop += lhs.offsetTop + lhs.clientTop;
33+
}
34+
for (; rhs; rhs = rhs.offsetParent) {
35+
newLeft -= rhs.offsetLeft + rhs.clientLeft;
36+
newTop -= rhs.offsetTop + rhs.clientTop;
37+
}
38+
return makeArea(newLeft, newTop, width, height);
39+
}
40+
};
41+
}
1842

19-
if ((overLeft || overRight) && centerIfNeeded) {
20-
parent.scrollLeft = this.offsetLeft - parent.offsetLeft - parent.clientWidth / 2 - parentBorderLeftWidth + this.clientWidth / 2;
21-
}
43+
var parent, area = makeArea(
44+
elem.offsetLeft, elem.offsetTop,
45+
elem.offsetWidth, elem.offsetHeight);
46+
while ((parent = elem.parentNode) instanceof HTMLElement) {
47+
var clientLeft = parent.offsetLeft + parent.clientLeft;
48+
var clientTop = parent.offsetTop + parent.clientTop;
2249

23-
if ((overTop || overBottom || overLeft || overRight) && !centerIfNeeded) {
24-
this.scrollIntoView(alignWithTop);
25-
}
26-
};
50+
// Make area relative to parent's client area.
51+
area = area.
52+
relativeFromTo(elem, parent).
53+
translate(-clientLeft, -clientTop);
54+
55+
var scrollLeft = withinBounds(
56+
parent.scrollLeft,
57+
area.right - parent.clientWidth, area.left,
58+
parent.clientWidth)
59+
var scrollTop = withinBounds(
60+
parent.scrollTop,
61+
area.bottom - parent.clientHeight, area.top,
62+
parent.clientHeight)
63+
if(options) {
64+
animate(parent, {
65+
scrollLeft: scrollLeft,
66+
scrollTop: scrollTop
67+
}, options)
68+
} else {
69+
parent.scrollLeft = scrollLeft
70+
parent.scrollTop = scrollTop
71+
}
72+
73+
// Determine actual scroll amount by reading back scroll properties.
74+
area = area.translate(clientLeft - parent.scrollLeft,
75+
clientTop - parent.scrollTop);
76+
elem = parent;
77+
}
2778
}

test/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import scrollIntoViewIfNeeded from '../src/index'
2+
3+
window.scrollIntoViewIfNeeded = scrollIntoViewIfNeeded

test/test-horizontal.html

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -106,22 +106,41 @@ <h1>scrollIntoViewIfNeeded test page</h1>
106106
<button data-item-idx="99">#99</button>
107107
</div>
108108

109-
<script src="../src/index.js"></script>
109+
<div id="buttons-animate">
110+
<span><code>animate scrollIntoViewIfNeeded()</code> to item : </span>
111+
<button data-item-idx="0">#0</button>
112+
<button data-item-idx="11">#11</button>
113+
<button data-item-idx="22">#22</button>
114+
<button data-item-idx="24">#24</button>
115+
<button data-item-idx="26">#26</button>
116+
<button data-item-idx="33">#33</button>
117+
<button data-item-idx="44">#44</button>
118+
<button data-item-idx="55">#55</button>
119+
<button data-item-idx="66">#66</button>
120+
<button data-item-idx="77">#77</button>
121+
<button data-item-idx="82">#82</button>
122+
<button data-item-idx="84">#84</button>
123+
<button data-item-idx="86">#86</button>
124+
<button data-item-idx="99">#99</button>
125+
</div>
126+
127+
<script src="./bundle.js"></script>
110128
<script>
111129
(function () {
112130
var scrollArea = document.getElementById('scroll-area'),
113131
buttonsCenterFalse = document.getElementById('buttons-centerFalse'),
114132
buttonsCenterTrue = document.getElementById('buttons-centerTrue'),
115133
buttonsCenterUndefined = document.getElementById('buttons-centerUndefined'),
116134
buttonsCenterNoArgs = document.getElementById('buttons-centerNoArgs'),
135+
buttonsAnimate = document.getElementById('buttons-animate'),
117136
scrollIntoViewIfNeededToItemAndSelect;
118-
scrollIntoViewIfNeededToItemAndSelect = function (itemIdx, centerIfNeeded) {
137+
scrollIntoViewIfNeededToItemAndSelect = function (itemIdx, centerIfNeeded, options) {
119138
scrollArea.querySelector('.selected').className = '';
120139
// Allow us to really have difference bewteen scrollIntoViewIfNeeded() and scrollIntoViewIfNeeded(undefined)
121140
if (arguments.length === 1) {
122-
scrollArea.children[itemIdx].scrollIntoViewIfNeeded();
141+
scrollIntoViewIfNeeded(scrollArea.children[itemIdx]);
123142
} else {
124-
scrollArea.children[itemIdx].scrollIntoViewIfNeeded(centerIfNeeded);
143+
scrollIntoViewIfNeeded(scrollArea.children[itemIdx], centerIfNeeded, options);
125144
}
126145
scrollArea.children[itemIdx].className = 'selected';
127146
};
@@ -145,5 +164,13 @@ <h1>scrollIntoViewIfNeeded test page</h1>
145164
scrollIntoViewIfNeededToItemAndSelect(e.target.dataset.itemIdx);
146165
}
147166
}, false);
167+
buttonsAnimate.addEventListener('click', function (e) {
168+
if (e.target.nodeName === 'BUTTON') {
169+
scrollIntoViewIfNeededToItemAndSelect(e.target.dataset.itemIdx, false, {
170+
centerIfNeeded: false,
171+
duration: 150
172+
});
173+
}
174+
}, false);
148175
})();
149176
</script>

test/test-vertical.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ <h1>scrollIntoViewIfNeeded test page</h1>
102102
<button data-item-idx="99">#99</button>
103103
</div>
104104

105-
<script src="index.js"></script>
105+
<script src="./bundle.js"></script>
106106
<script>
107107
(function () {
108108
var scrollArea = document.getElementById('scroll-area'),
@@ -115,9 +115,9 @@ <h1>scrollIntoViewIfNeeded test page</h1>
115115
scrollArea.querySelector('.selected').className = '';
116116
// Allow us to really have difference bewteen scrollIntoViewIfNeeded() and scrollIntoViewIfNeeded(undefined)
117117
if (arguments.length === 1) {
118-
scrollArea.children[itemIdx].scrollIntoViewIfNeeded();
118+
scrollIntoViewIfNeeded(scrollArea.children[itemIdx]);
119119
} else {
120-
scrollArea.children[itemIdx].scrollIntoViewIfNeeded(centerIfNeeded);
120+
scrollIntoViewIfNeeded(scrollArea.children[itemIdx], centerIfNeeded);
121121
}
122122
scrollArea.children[itemIdx].className = 'selected';
123123
};

0 commit comments

Comments
 (0)