Skip to content
Closed
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
121 changes: 89 additions & 32 deletions dist/smoothscroll.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* smoothscroll polyfill - v0.3.3
* smoothscroll polyfill - v0.4.0
* https://iamdustan.github.io/smoothscroll
* 2016 (c) Dustan Kasten, Jeremias Menichelli - MIT License
*/
Expand All @@ -15,10 +15,12 @@
*/

// polyfill
function polyfill() {
function polyfill(overrideBrowserImplementation) {
// return when scrollBehavior interface is supported
if ('scrollBehavior' in d.documentElement.style) {
return;
if (!overrideBrowserImplementation) {
return;
}
}

/*
Expand Down Expand Up @@ -63,13 +65,61 @@
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 (
(x.behavior !== undefined) &&
(x.behavior !== 'auto') &&
(x.behavior !== 'instant') &&
(x.behavior !== 'smooth')
) {
throw new TypeError('behavior not valid');
}

if (
(x.block !== undefined) &&
(x.block !== 'start') &&
(x.block !== 'end')
) {
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
* @method scrollIsInstant
* @param {Number|Object} x
* @returns {Boolean}
*/
function shouldBailOut(x) {
function scrollIsInstant(x) {
if (typeof x !== 'object'
|| x.behavior === undefined
|| x.behavior === 'auto'
Expand Down Expand Up @@ -183,14 +233,37 @@
});
}

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
);
}

/*
* ORIGINAL METHODS OVERRIDES
*/

// w.scroll and w.scrollTo
w.scroll = w.scrollTo = function() {
// avoid smooth behavior if not required
if (shouldBailOut(arguments[0])) {
if (scrollIsInstant(arguments[0])) {
original.scroll.call(
w,
arguments[0].left || arguments[0],
Expand All @@ -211,7 +284,7 @@
// w.scrollBy
w.scrollBy = function() {
// avoid smooth behavior if not required
if (shouldBailOut(arguments[0])) {
if (scrollIsInstant(arguments[0])) {
original.scrollBy.call(
w,
arguments[0].left || arguments[0],
Expand All @@ -231,39 +304,23 @@

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

// avoid smooth behavior if not required
if (shouldBailOut(arguments[0])) {
if (scrollIsInstant(arguments[0]) && opts.block == "top") {
original.scrollIntoView.call(this, arguments[0] || true);
return;
}

// 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
w.scrollBy({
left: parentRects.left,
top: parentRects.top,
behavior: 'smooth'
});
} else {
// reveal element in viewport
w.scrollBy({
scrollWithinParentElem(this, opts);
w.scrollBy({
left: clientRects.left,
top: clientRects.top,
behavior: 'smooth'
});
}
top: opts.block !== 'end' ? clientRects.top : clientRects.bottom - w.innerHeight,
behavior: opts.behavior
});
};
}

Expand All @@ -272,6 +329,6 @@
module.exports = { polyfill: polyfill };
} else {
// global
polyfill();
polyfill(window.__smoothscrollForcePolyfill);
}
})(window, document);
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"title": "smoothscroll",
"name": "smoothscroll-polyfill",
"version": "0.3.3",
"version": "0.4.0",
"author": {
"name": "Dustan Kasten",
"email": "dustan.kasten@gmail.com",
Expand Down
119 changes: 88 additions & 31 deletions src/smoothscroll.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@
*/

// polyfill
function polyfill() {
function polyfill(overrideBrowserImplementation) {
// return when scrollBehavior interface is supported
if ('scrollBehavior' in d.documentElement.style) {
return;
if (!overrideBrowserImplementation) {
return;
}
}

/*
Expand Down Expand Up @@ -57,13 +59,61 @@
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 (
(x.behavior !== undefined) &&
(x.behavior !== 'auto') &&
(x.behavior !== 'instant') &&
(x.behavior !== 'smooth')
) {
throw new TypeError('behavior not valid');
}

if (
(x.block !== undefined) &&
(x.block !== 'start') &&
(x.block !== 'end')
) {
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
* @method scrollIsInstant
* @param {Number|Object} x
* @returns {Boolean}
*/
function shouldBailOut(x) {
function scrollIsInstant(x) {
if (typeof x !== 'object'
|| x.behavior === undefined
|| x.behavior === 'auto'
Expand Down Expand Up @@ -177,14 +227,37 @@
});
}

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
);
}

/*
* ORIGINAL METHODS OVERRIDES
*/

// w.scroll and w.scrollTo
w.scroll = w.scrollTo = function() {
// avoid smooth behavior if not required
if (shouldBailOut(arguments[0])) {
if (scrollIsInstant(arguments[0])) {
original.scroll.call(
w,
arguments[0].left || arguments[0],
Expand All @@ -205,7 +278,7 @@
// w.scrollBy
w.scrollBy = function() {
// avoid smooth behavior if not required
if (shouldBailOut(arguments[0])) {
if (scrollIsInstant(arguments[0])) {
original.scrollBy.call(
w,
arguments[0].left || arguments[0],
Expand All @@ -225,39 +298,23 @@

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

// avoid smooth behavior if not required
if (shouldBailOut(arguments[0])) {
if (scrollIsInstant(arguments[0]) && opts.block == "top") {
original.scrollIntoView.call(this, arguments[0] || true);
return;
}

// 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
w.scrollBy({
left: parentRects.left,
top: parentRects.top,
behavior: 'smooth'
});
} else {
// reveal element in viewport
w.scrollBy({
scrollWithinParentElem(this, opts);
w.scrollBy({
left: clientRects.left,
top: clientRects.top,
behavior: 'smooth'
});
}
top: opts.block !== 'end' ? clientRects.top : clientRects.bottom - w.innerHeight,
behavior: opts.behavior
});
};
}

Expand All @@ -266,6 +323,6 @@
module.exports = { polyfill: polyfill };
} else {
// global
polyfill();
polyfill(window.__smoothscrollForcePolyfill);
}
})(window, document);
10 changes: 10 additions & 0 deletions test/cases/smoothScrollIntoViewEnd.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

// $('.scrollable-parent p:last-child').scrollIntoView();
later(function() {
$('.hello').scrollIntoView({behavior: 'smooth', block: 'end'});
later(function() {
// assertScroll(window.scrollY, 80)
// assertScroll($('.scrollable-parent').scrollTop, 673)
done()
})
})
Loading