diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..3dddf3f --- /dev/null +++ b/.eslintignore @@ -0,0 +1,2 @@ +dist/* +node_modules/* diff --git a/dist/smoothscroll.js b/dist/smoothscroll.js index 6fdae4f..a6c06fa 100644 --- a/dist/smoothscroll.js +++ b/dist/smoothscroll.js @@ -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 @@ -61,7 +64,7 @@ * @param {Number} y * @returns {undefined} */ - function scrollElement(x, y) { + function scrollElement (x, y) { this.scrollLeft = x; this.scrollTop = y; } @@ -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 @@ -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; } @@ -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'; @@ -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'); @@ -156,7 +197,7 @@ * @param {Node} el * @returns {Node} el */ - function findScrollableParent(el) { + function findScrollableParent (el) { var isBody; do { @@ -176,7 +217,7 @@ * @param {Object} context * @returns {undefined} */ - function step(context) { + function step (context) { var time = now(); var value; var currentX; @@ -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; @@ -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; @@ -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; @@ -281,8 +358,9 @@ ); }; + // w.scrollBy - w.scrollBy = function() { + w.scrollBy = function () { // avoid action when no arguments are passed if (arguments[0] === undefined) { return; @@ -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; @@ -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; @@ -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; @@ -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; @@ -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 @@ -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 }; diff --git a/dist/smoothscroll.min.js b/dist/smoothscroll.min.js deleted file mode 100644 index 73d778a..0000000 --- a/dist/smoothscroll.min.js +++ /dev/null @@ -1 +0,0 @@ -!function(){"use strict";function o(o){var t=["MSIE ","Trident/","Edge/"];return new RegExp(t.join("|")).test(o)}function t(){function t(o,t){this.scrollLeft=o,this.scrollTop=t}function r(o){return.5*(1-Math.cos(Math.PI*o))}function i(o){if(null===o||"object"!=typeof o||void 0===o.behavior||"auto"===o.behavior||"instant"===o.behavior)return!0;if("object"==typeof o&&"smooth"===o.behavior)return!1;throw new TypeError("behavior member of ScrollOptions "+o.behavior+" is not a valid value for enumeration ScrollBehavior.")}function s(o,t){return"Y"===t?o.clientHeight+h1?1:s),e=o.startX+(o.x-o.startX)*t,i=o.startY+(o.y-o.startY)*t,o.method.call(o.scrollable,e,i),e===o.x&&i===o.y||l.requestAnimationFrame(a.bind(l,o))}function p(o,r,i){var s,c,n,f,p=y();o===e.body?(s=l,c=l.scrollX||l.pageXOffset,n=l.scrollY||l.pageYOffset,f=u.scroll):(s=o,c=o.scrollLeft,n=o.scrollTop,f=t),a({scrollable:s,method:f,startTime:p,startX:c,startY:n,x:r,y:i})}if(!("scrollBehavior"in e.documentElement.style&&!0!==l.__forceSmoothScrollPolyfill__)){var d=l.HTMLElement||l.Element,v=468,h=o(l.navigator.userAgent)?1:0,u={scroll:l.scroll||l.scrollTo,scrollBy:l.scrollBy,elementScroll:d.prototype.scroll||t,scrollIntoView:d.prototype.scrollIntoView},y=l.performance&&l.performance.now?l.performance.now.bind(l.performance):Date.now;l.scroll=l.scrollTo=function(){void 0!==arguments[0]&&(!0!==i(arguments[0])?p.call(l,e.body,void 0!==arguments[0].left?~~arguments[0].left:l.scrollX||l.pageXOffset,void 0!==arguments[0].top?~~arguments[0].top:l.scrollY||l.pageYOffset):u.scroll.call(l,void 0!==arguments[0].left?arguments[0].left:"object"!=typeof arguments[0]?arguments[0]:l.scrollX||l.pageXOffset,void 0!==arguments[0].top?arguments[0].top:void 0!==arguments[1]?arguments[1]:l.scrollY||l.pageYOffset))},l.scrollBy=function(){void 0!==arguments[0]&&(i(arguments[0])?u.scrollBy.call(l,void 0!==arguments[0].left?arguments[0].left:"object"!=typeof arguments[0]?arguments[0]:0,void 0!==arguments[0].top?arguments[0].top:void 0!==arguments[1]?arguments[1]:0):p.call(l,e.body,~~arguments[0].left+(l.scrollX||l.pageXOffset),~~arguments[0].top+(l.scrollY||l.pageYOffset)))},d.prototype.scroll=d.prototype.scrollTo=function(){if(void 0!==arguments[0])if(!0!==i(arguments[0])){var o=arguments[0].left,t=arguments[0].top;p.call(this,this,void 0===o?this.scrollLeft:~~o,void 0===t?this.scrollTop:~~t)}else{if("number"==typeof arguments[0]&&void 0===arguments[1])throw new SyntaxError("Value couldn't be converted");u.elementScroll.call(this,void 0!==arguments[0].left?~~arguments[0].left:"object"!=typeof arguments[0]?~~arguments[0]:this.scrollLeft,void 0!==arguments[0].top?~~arguments[0].top:void 0!==arguments[1]?~~arguments[1]:this.scrollTop)}},d.prototype.scrollBy=function(){void 0!==arguments[0]&&(!0!==i(arguments[0])?this.scroll({left:~~arguments[0].left+this.scrollLeft,top:~~arguments[0].top+this.scrollTop,behavior:arguments[0].behavior}):u.elementScroll.call(this,void 0!==arguments[0].left?~~arguments[0].left+this.scrollLeft:~~arguments[0]+this.scrollLeft,void 0!==arguments[0].top?~~arguments[0].top+this.scrollTop:~~arguments[1]+this.scrollTop))},d.prototype.scrollIntoView=function(){if(!0!==i(arguments[0])){var o=f(this),t=o.getBoundingClientRect(),r=this.getBoundingClientRect();o!==e.body?(p.call(this,o,o.scrollLeft+r.left-t.left,o.scrollTop+r.top-t.top),"fixed"!==l.getComputedStyle(o).position&&l.scrollBy({left:t.left,top:t.top,behavior:"smooth"})):l.scrollBy({left:r.left,top:r.top,behavior:"smooth"})}else u.scrollIntoView.call(this,void 0===arguments[0]||arguments[0])}}}var l=window,e=document;"object"==typeof exports?module.exports={polyfill:t}:t()}(); \ No newline at end of file diff --git a/src/smoothscroll.js b/src/smoothscroll.js index ac8a324..51a61b0 100644 --- a/src/smoothscroll.js +++ b/src/smoothscroll.js @@ -20,6 +20,9 @@ function isMicrosoftBrowser(userAgent) { 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 @@ -59,7 +62,7 @@ function polyfill() { * @param {Number} y * @returns {undefined} */ - function scrollElement(x, y) { + function scrollElement (x, y) { this.scrollLeft = x; this.scrollTop = y; } @@ -70,17 +73,56 @@ function polyfill() { * @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 @@ -111,7 +153,7 @@ function polyfill() { * @param {String} axis * @returns {Boolean} */ - function hasScrollableSpace(el, axis) { + function hasScrollableSpace (el, axis) { if (axis === 'Y') { return (el.clientHeight + ROUNDING_TOLERANCE) < el.scrollHeight; } @@ -128,7 +170,7 @@ function polyfill() { * @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'; @@ -138,10 +180,9 @@ function polyfill() { * 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'); @@ -154,7 +195,7 @@ function polyfill() { * @param {Node} el * @returns {Node} el */ - function findScrollableParent(el) { + function findScrollableParent (el) { var isBody; do { @@ -174,7 +215,7 @@ function polyfill() { * @param {Object} context * @returns {undefined} */ - function step(context) { + function step (context) { var time = now(); var value; var currentX; @@ -206,7 +247,7 @@ function polyfill() { * @param {Number} y * @returns {undefined} */ - function smoothScroll(el, x, y) { + function smoothScroll (el, x, y) { var scrollable; var startX; var startY; @@ -238,9 +279,45 @@ function polyfill() { }); } + 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; @@ -253,14 +330,14 @@ function polyfill() { 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; @@ -279,8 +356,9 @@ function polyfill() { ); }; + // w.scrollBy - w.scrollBy = function() { + w.scrollBy = function () { // avoid action when no arguments are passed if (arguments[0] === undefined) { return; @@ -293,13 +371,13 @@ function polyfill() { 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; @@ -315,7 +393,7 @@ function polyfill() { }; // 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; @@ -334,14 +412,14 @@ function polyfill() { 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; @@ -360,7 +438,7 @@ function polyfill() { }; // Element.prototype.scrollBy - Element.prototype.scrollBy = function() { + Element.prototype.scrollBy = function () { // avoid action when no arguments are passed if (arguments[0] === undefined) { return; @@ -388,10 +466,13 @@ function polyfill() { }); }; + // 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 @@ -403,38 +484,18 @@ function polyfill() { } // 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 }; diff --git a/test/cases/smoothScrollIntoViewEnd.js b/test/cases/smoothScrollIntoViewEnd.js new file mode 100644 index 0000000..0c347cd --- /dev/null +++ b/test/cases/smoothScrollIntoViewEnd.js @@ -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() + }) + }) diff --git a/test/cases/smoothScrollIntoViewStart.js b/test/cases/smoothScrollIntoViewStart.js new file mode 100644 index 0000000..b68de6e --- /dev/null +++ b/test/cases/smoothScrollIntoViewStart.js @@ -0,0 +1,9 @@ +// $('.scrollable-parent p:last-child').scrollIntoView(); +later(function() { + $('.hello').scrollIntoView({behavior: 'smooth', block: 'start'}); + later(function() { + // assertScroll(window.scrollY, 80) + // assertScroll($('.scrollable-parent').scrollTop, 905) + done() + }) +}) diff --git a/test/harness.html b/test/harness.html new file mode 100644 index 0000000..d9a0455 --- /dev/null +++ b/test/harness.html @@ -0,0 +1,61 @@ + + + + + smoothscroll tests - basic + + + + + + + + + +
+ +
+

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Qui iure obcaecati, repudiandae aspernatur cumque recusandae + adipisci consequuntur maiores, quo in nulla ratione facere distinctio beatae, quae consequatur ab labore dolorum.

+

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Qui iure obcaecati, repudiandae aspernatur cumque recusandae + adipisci consequuntur maiores, quo in nulla ratione facere distinctio beatae, quae consequatur ab labore dolorum.

+

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Qui iure obcaecati, repudiandae aspernatur cumque recusandae + adipisci consequuntur maiores, quo in nulla ratione facere distinctio beatae, quae consequatur ab labore dolorum.

+

hello!

+

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Qui iure obcaecati, repudiandae aspernatur cumque recusandae + adipisci consequuntur maiores, quo in nulla ratione facere distinctio beatae, quae consequatur ab labore dolorum.

+

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Qui iure obcaecati, repudiandae aspernatur cumque recusandae + adipisci consequuntur maiores, quo in nulla ratione facere distinctio beatae, quae consequatur ab labore dolorum.

+

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Qui iure obcaecati, repudiandae aspernatur cumque recusandae + adipisci consequuntur maiores, quo in nulla ratione facere distinctio beatae, quae consequatur ab labore dolorum.

+
+
+ + + + + + + + + diff --git a/test/index.html b/test/index.html new file mode 100644 index 0000000..1654fc3 --- /dev/null +++ b/test/index.html @@ -0,0 +1,74 @@ + + + + + smooth scroll polyfill tests + + + + + + + + + + + + diff --git a/test/support/raf.js b/test/support/raf.js new file mode 100644 index 0000000..5f4a187 --- /dev/null +++ b/test/support/raf.js @@ -0,0 +1,32 @@ + +// http://paulirish.com/2011/requestanimationframe-for-smart-animating/ +// http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating + +// requestAnimationFrame polyfill by Erik Möller. fixes from Paul Irish and Tino Zijdel + +// MIT license + +(function() { +var lastTime = 0; +var vendors = ['ms', 'moz', 'webkit', 'o']; +for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { + window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame']; + window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] + || window[vendors[x]+'CancelRequestAnimationFrame']; +} + +if (!window.requestAnimationFrame) + window.requestAnimationFrame = function(callback, element) { + var currTime = new Date().getTime(); + var timeToCall = Math.max(0, 16 - (currTime - lastTime)); + var id = window.setTimeout(function() { callback(currTime + timeToCall); }, + timeToCall); + lastTime = currTime + timeToCall; + return id; + }; + +if (!window.cancelAnimationFrame) + window.cancelAnimationFrame = function(id) { + clearTimeout(id); + }; +}()); diff --git a/test/support/test-core.js b/test/support/test-core.js new file mode 100644 index 0000000..b616760 --- /dev/null +++ b/test/support/test-core.js @@ -0,0 +1,36 @@ +function $(selector) { + return document.querySelector(selector); +} + +function later(cb) { + setTimeout(cb, 600) +} + +function fail(msg) { + document.querySelector('.test-result').textContent += msg +} + +function done(msg) { + var resultCtr = document.querySelector('.test-result') + if (resultCtr.textContent === "") { + resultCtr.classList.add('success') + } + resultCtr.textContent += "complete" +} + +function assertScroll(actual, expected) { + // Allow a little bit of leeway + if (Math.abs(actual - expected) > 3) { + fail(":failure: Expected '" + actual + "' to be '" + expected + "':") + } +} + +function parseQuery(qstr) { + var query = {}; + var a = qstr.substr(1).split('&'); + for (var i = 0; i < a.length; i++) { + var b = a[i].split('='); + query[decodeURIComponent(b[0])] = decodeURIComponent(b[1] || ''); + } + return query; +} diff --git a/test/support/test.css b/test/support/test.css new file mode 100644 index 0000000..e87988e --- /dev/null +++ b/test/support/test.css @@ -0,0 +1,41 @@ + +body { + background-color: #fefefe; + color: #212121; + font-family: 'Roboto Condensed', Arial, sans-serif; + font-size: 20px; + + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.top-padder { + width: 100%; + height: 400px; +} + +.scrollable-parent { + background-color: #efefef; + border-radius: 4px; + margin: 20px 0 0; + max-height: 200px; + overflow: scroll; + padding: 30px 50px; +} +.hello { + text-align: center; +} + +.test-result { + position: fixed; + top: 50%; + left: 0; + right: 0; + text-align: center; + color: red; +} + +.test-result.success { + color: green; +}