Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
dist/*
node_modules/*
171 changes: 116 additions & 55 deletions dist/smoothscroll.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
return new RegExp(userAgentPatterns.join('|')).test(userAgent);
}

var ALLOWED_BEHAVIOR_VALUES = [undefined, 'auto', 'instant', 'smooth'];
var ALLOWED_BLOCK_VALUES = [undefined, 'start', 'end'];

// polyfill
function polyfill() {
// return if scroll behavior is supported and polyfill is not forced
Expand Down Expand Up @@ -61,7 +64,7 @@
* @param {Number} y
* @returns {undefined}
*/
function scrollElement(x, y) {
function scrollElement (x, y) {
this.scrollLeft = x;
this.scrollTop = y;
}
Expand All @@ -72,17 +75,56 @@
* @param {Number} k
* @returns {Number}
*/
function ease(k) {
function ease (k) {
return 0.5 * (1 - Math.cos(Math.PI * k));
}

/**
* Normalizes valid scrollIntoView arguments into an arguments object
* @method normalizeArgs
* @param {Boolean|Object=} x
* @returns {Object}
*/
function normalizeArgs (x) {
if (typeof x === 'undefined') {
return {
block: 'start',
behavior: 'auto'
};
}

if (typeof x === 'boolean') {
return {
block: (x ? 'start' : 'end'),
behavior: 'auto'
};
}

if (typeof x === 'object') {
if (ALLOWED_BEHAVIOR_VALUES.indexOf(x.behavior) === -1) {
throw new TypeError('behavior not valid');
}

if (ALLOWED_BLOCK_VALUES.indexOf(x.block) === -1) {
throw new TypeError('block not valid');
}

return {
block: x.block === 'end' ? 'end' : 'start',
behavior: x.behavior === 'smooth' ? 'smooth' : 'auto'
};
}

throw new TypeError('scrollIntoView accepts undefined, boolean or object as its first argument');
}

/**
* indicates if a smooth behavior should be applied
* @method shouldBailOut
* @param {Number|Object} firstArg
* @returns {Boolean}
*/
function shouldBailOut(firstArg) {
function shouldBailOut (firstArg) {
if (firstArg === null
|| typeof firstArg !== 'object'
|| firstArg.behavior === undefined
Expand Down Expand Up @@ -113,7 +155,7 @@
* @param {String} axis
* @returns {Boolean}
*/
function hasScrollableSpace(el, axis) {
function hasScrollableSpace (el, axis) {
if (axis === 'Y') {
return (el.clientHeight + ROUNDING_TOLERANCE) < el.scrollHeight;
}
Expand All @@ -130,7 +172,7 @@
* @param {String} axis
* @returns {Boolean}
*/
function canOverflow(el, axis) {
function canOverflow (el, axis) {
var overflowValue = w.getComputedStyle(el, null)['overflow' + axis];

return overflowValue === 'auto' || overflowValue === 'scroll';
Expand All @@ -140,10 +182,9 @@
* indicates if an element can be scrolled in either axis
* @method isScrollable
* @param {Node} el
* @param {String} axis
* @returns {Boolean}
*/
function isScrollable(el) {
function isScrollable (el) {
var isScrollableY = hasScrollableSpace(el, 'Y') && canOverflow(el, 'Y');
var isScrollableX = hasScrollableSpace(el, 'X') && canOverflow(el, 'X');

Expand All @@ -156,7 +197,7 @@
* @param {Node} el
* @returns {Node} el
*/
function findScrollableParent(el) {
function findScrollableParent (el) {
var isBody;

do {
Expand All @@ -176,7 +217,7 @@
* @param {Object} context
* @returns {undefined}
*/
function step(context) {
function step (context) {
var time = now();
var value;
var currentX;
Expand Down Expand Up @@ -208,7 +249,7 @@
* @param {Number} y
* @returns {undefined}
*/
function smoothScroll(el, x, y) {
function smoothScroll (el, x, y) {
var scrollable;
var startX;
var startY;
Expand Down Expand Up @@ -240,9 +281,45 @@
});
}

function scrollWithinParentElem (el, opts) {
var scrollableParent = findScrollableParent(el);

if (scrollableParent === d.body) {
return;
}

var clientRects = el.getBoundingClientRect();
var parentRects = scrollableParent.getBoundingClientRect();
var clientAdj = clientRects.top;

if (opts.block === 'end') {
var scrollbarHeight = scrollableParent.offsetHeight
- scrollableParent.clientHeight;

clientAdj = clientRects.bottom - parentRects.height + scrollbarHeight;
}

// reveal element inside parent
smoothScroll.call(
this,
scrollableParent,
scrollableParent.scrollLeft + clientRects.left - parentRects.left,
scrollableParent.scrollTop + clientAdj - parentRects.top
);

// reveal parent in viewport unless is fixed
if (w.getComputedStyle(scrollableParent).position !== 'fixed') {
w.scrollBy({
left: parentRects.left,
top: parentRects.top,
behavior: 'smooth'
});
}
}

// ORIGINAL METHODS OVERRIDES
// w.scroll and w.scrollTo
w.scroll = w.scrollTo = function() {
w.scroll = w.scrollTo = function () {
// avoid action when no arguments are passed
if (arguments[0] === undefined) {
return;
Expand All @@ -255,14 +332,14 @@
arguments[0].left !== undefined
? arguments[0].left
: typeof arguments[0] !== 'object'
? arguments[0]
: (w.scrollX || w.pageXOffset),
? arguments[0]
: (w.scrollX || w.pageXOffset),
// use top prop, second argument if present or fallback to scrollY
arguments[0].top !== undefined
? arguments[0].top
: arguments[1] !== undefined
? arguments[1]
: (w.scrollY || w.pageYOffset)
? arguments[1]
: (w.scrollY || w.pageYOffset)
);

return;
Expand All @@ -281,8 +358,9 @@
);
};


// w.scrollBy
w.scrollBy = function() {
w.scrollBy = function () {
// avoid action when no arguments are passed
if (arguments[0] === undefined) {
return;
Expand All @@ -295,13 +373,13 @@
arguments[0].left !== undefined
? arguments[0].left
: typeof arguments[0] !== 'object'
? arguments[0]
: 0,
? arguments[0]
: 0,
arguments[0].top !== undefined
? arguments[0].top
: arguments[1] !== undefined
? arguments[1]
: 0
? arguments[1]
: 0
);

return;
Expand All @@ -317,7 +395,7 @@
};

// Element.prototype.scroll and Element.prototype.scrollTo
Element.prototype.scroll = Element.prototype.scrollTo = function() {
Element.prototype.scroll = Element.prototype.scrollTo = function () {
// avoid action when no arguments are passed
if (arguments[0] === undefined) {
return;
Expand All @@ -336,14 +414,14 @@
arguments[0].left !== undefined
? ~~arguments[0].left
: typeof arguments[0] !== 'object'
? ~~arguments[0]
: this.scrollLeft,
? ~~arguments[0]
: this.scrollLeft,
// use top prop, second argument or fallback to scrollTop
arguments[0].top !== undefined
? ~~arguments[0].top
: arguments[1] !== undefined
? ~~arguments[1]
: this.scrollTop
? ~~arguments[1]
: this.scrollTop
);

return;
Expand All @@ -362,7 +440,7 @@
};

// Element.prototype.scrollBy
Element.prototype.scrollBy = function() {
Element.prototype.scrollBy = function () {
// avoid action when no arguments are passed
if (arguments[0] === undefined) {
return;
Expand Down Expand Up @@ -390,10 +468,13 @@
});
};


// Element.prototype.scrollIntoView
Element.prototype.scrollIntoView = function() {
Element.prototype.scrollIntoView = function () {
var opts = normalizeArgs(arguments[0]);

// avoid smooth behavior if not required
if (shouldBailOut(arguments[0]) === true) {
if (shouldBailOut(arguments[0]) === true && opts.block === 'top') {
original.scrollIntoView.call(
this,
arguments[0] === undefined
Expand All @@ -405,38 +486,18 @@
}

// LET THE SMOOTHNESS BEGIN!
var scrollableParent = findScrollableParent(this);
var parentRects = scrollableParent.getBoundingClientRect();
var clientRects = this.getBoundingClientRect();

if (scrollableParent !== d.body) {
// reveal element inside parent
smoothScroll.call(
this,
scrollableParent,
scrollableParent.scrollLeft + clientRects.left - parentRects.left,
scrollableParent.scrollTop + clientRects.top - parentRects.top
);

// reveal parent in viewport unless is fixed
if (w.getComputedStyle(scrollableParent).position !== 'fixed') {
w.scrollBy({
left: parentRects.left,
top: parentRects.top,
behavior: 'smooth'
});
}
} else {
// reveal element in viewport
w.scrollBy({
left: clientRects.left,
top: clientRects.top,
behavior: 'smooth'
});
}
scrollWithinParentElem(this, opts);
w.scrollBy({
left: clientRects.left,
top: opts.block !== 'end' ? clientRects.top : clientRects.bottom - w.innerHeight,
behavior: opts.behavior
});
};
}


if (typeof exports === 'object') {
// commonjs
module.exports = { polyfill: polyfill };
Expand Down
1 change: 0 additions & 1 deletion dist/smoothscroll.min.js

This file was deleted.

Loading