1(function (root, factory) { 2 if ( typeof define === 'function' && define.amd ) { 3 define('smoothScroll', factory(root)); 4 } else if ( typeof exports === 'object' ) { 5 module.smoothScroll = factory(root); 6 } else { 7 root.smoothScroll = factory(root); 8 } 9})(this, function (root) { 10 11 'use strict'; 12 13 // 14 // Variables 15 // 16 17 var exports = {}; // Object for public APIs 18 var supports = !!document.querySelector && !!root.addEventListener; // Feature test 19 var settings; 20 21 // Default settings 22 var defaults = { 23 speed: 500, 24 easing: 'easeInOutCubic', 25 offset: 0, 26 updateURL: false, 27 callbackBefore: function () {}, 28 callbackAfter: function () {} 29 }; 30 31 32 // 33 // Methods 34 // 35 36 /** 37 * A simple forEach() implementation for Arrays, Objects and NodeLists 38 * @private 39 * @param {Array|Object|NodeList} collection Collection of items to iterate 40 * @param {Function} callback Callback function for each iteration 41 * @param {Array|Object|NodeList} scope Object/NodeList/Array that forEach is iterating over (aka `this`) 42 */ 43 var forEach = function (collection, callback, scope) { 44 if (Object.prototype.toString.call(collection) === '[object Object]') { 45 for (var prop in collection) {
46 if (Object.prototype.hasOwnProperty.call(collection, prop)) { 47 callback.call(scope, collection[prop], prop, collection); 48 } 49 } 50 } else { 51 for (var i = 0, len = collection.length; i < len; i++) { 52 callback.call(scope, collection[i], i, collection); 53 } 54 } 55 }; 56 57 /** 58 * Merge defaults with user options 59 * @private 60 * @param {Object} defaults Default settings 61 * @param {Object} options User options 62 * @returns {Object} Merged values of defaults and options 63 */ 64 var extend = function ( defaults, options ) { 65 var extended = {}; 66 forEach(defaults, function (value, prop) { 67 extended[prop] = defaults[prop]; 68 }); 69 forEach(options, function (value, prop) { 70 extended[prop] = options[prop]; 71 }); 72 return extended; 73 }; 74 75 /** 76 * Calculate the easing pattern 77 * @private 78 * @param {String} type Easing pattern 79 * @param {Number} time Time animation should take to complete 80 * @returns {Number} 81 */ 82 var easingPattern = function ( type, time ) { 83 var pattern; 84 if ( type === 'easeInQuad' ) pattern = time * time; // accelerating from zero velocity 85 if ( type === 'easeOutQuad' ) pattern = time * (2 - time); // decelerating to zero velocity 86 if ( type === 'easeInOutQuad' ) pattern = time < 0.5 ? 2 * time * time : -1 + (4 - 2 * time) * time; // acceleration until halfway, then deceleration 87 if ( type === 'easeInCubic' ) pattern = time * time * time; // accelerating from zero velocity 88 if ( type === 'easeOutCubic' ) pattern = (--time) * time * time + 1; // decelerating to zero velocity 89 if ( type === 'easeInOutCubic' ) pattern = time < 0.5 ? 4 * time * time * time : (time - 1) * (2 * time - 2) * (2 * time - 2) + 1; // acceleration until halfway, then deceleration 90 if ( type === 'easeInQuart' ) pattern = time * time * time * time; // accelerating from zero velocity 91 if ( type === 'easeOutQuart' ) pattern = 1 - (--time) * time * time * time; // decelerating to zero velocity 92 if ( type === 'easeInOutQuart' ) pattern = time < 0.5 ? 8 * time * time * time * time : 1 - 8 * (--time) * time * time * time; // acceleration until halfway, then deceleration 93 if ( type === 'easeInQuint' ) pattern = time * time * time * time * time; // accelerating from zero velocity 94 if ( type === 'easeOutQuint' ) pattern = 1 + (--time) * time * time * time * time; // decelerating to zero velocity 95 if ( type === 'easeInOutQuint' ) pattern = time < 0.5 ? 16 * time * time * time * time * time : 1 + 16 * (--time) * time * time * time * time; // acceleration until halfway, then deceleration 96 return pattern || time; // no easing, no acceleration 97 }; 98 99 /** 100 * Calculate how far to scroll 101 * @private 102 * @param {Element} anchor The anchor element to scroll to 103 * @param {Number} headerHeight Height of a fixed header, if any 104 * @param {Number} offset Number of pixels by which to offset scroll 105 * @returns {Number} 106 */ 107 var getEndLocation = function ( anchor, headerHeight, offset ) { 108 var location = 0; 109 if (anchor.offsetParent) { 110 do { 111 location += anchor.offsetTop; 112 anchor = anchor.offsetParent; 113 } while (anchor); 114 } 115 location = location - headerHeight - offset; 116 return location >= 0 ? location : 0; 117 }; 118 119 /** 120 * Determine the document's height 121 * @private 122 * @returns {Number} 123 */ 124 var getDocumentHeight = function () { 125 return Math.max( 126 document.body.scrollHeight, document.documentElement.scrollHeight, 127 document.body.offsetHeight, document.documentElement.offsetHeight, 128 document.body.clientHeight, document.documentElement.clientHeight 129 ); 130 }; 131 132 /** 133 * Remove whitespace from a string 134 * @private 135 * @param {String} string 136 * @returns {String} 137 */ 138 var trim = function ( string ) { 139 return string.replace(/^\s+|\s+$/g, ''); 140 }; 141 142 /** 143 * Convert data-options attribute into an object of key/value pairs 144 * @private 145 * @param {String} options Link-specific options as a data attribute string 146 * @returns {Object} 147 */ 148 var getDataOptions = function ( options ) { 149 var settings = {}; 150 // Create a key/value pair for each setting 151 if ( options ) { 152 options = options.split(';'); 153 options.forEach( function(option) { 154 option = trim(option); 155 if ( option !== '' ) { 156 option = option.split(':'); 157 settings[option[0]] = trim(option[1]); 158 } 159 }); 160 } 161 return settings; 162 }; 163 164 /** 165 * Update the URL 166 * @private 167 * @param {Element} anchor The element to scroll to 168 * @param {Boolean} url Whether or not to update the URL history 169 */ 170 var updateUrl = function ( anchor, url ) { 171 if ( history.pushState && (url || url === 'true') ) { 172 history.pushState( { 173 pos: anchor.id 174 }, '', anchor ); 175 } 176 }; 177 178 /** 179 * Start/stop the scrolling animation 180 * @public 181 * @param {Element} toggle The element that toggled the scroll event 182 * @param {Element} anchor The element to scroll to 183 * @param {Object} settings 184 * @param {Event} event 185 */ 186 exports.animateScroll = function ( toggle, anchor, options, event ) { 187 188 // Options and overrides 189 var settings = extend( settings || defaults, options || {} ); // Merge user options with defaults 190 var overrides = getDataOptions( toggle ? toggle.getAttribute('data-options') : null ); 191 settings = extend( settings, overrides ); 192 193 // Selectors and variables 194 var fixedHeader = document.querySelector('[data-scroll-header]'); // Get the fixed header 195 var headerHeight = fixedHeader === null ? 0 : (fixedHeader.offsetHeight + fixedHeader.offsetTop); // Get the height of a fixed header if one exists 196 var startLocation = root.pageYOffset; // Current location on the page 197 var endLocation = getEndLocation( document.querySelector(anchor), headerHeight, parseInt(settings.offset, 10) ); // Scroll to location 198 var animationInterval; // interval timer 199 var distance = endLocation - startLocation; // distance to travel 200 var documentHeight = getDocumentHeight(); 201 var timeLapsed = 0; 202 var percentage, position; 203 204 // Prevent default click event 205 if ( toggle && toggle.tagName.toLowerCase() === 'a' && event ) { 206 event.preventDefault(); 207 } 208 209 // Update URL 210 updateUrl(anchor, settings.updateURL); 211 212 /** 213 * Stop the scroll animation when it reaches its target (or the bottom/top of page) 214 * @private 215 * @param {Number} position Current position on the page 216 * @param {Number} endLocation Scroll to location 217 * @param {Number} animationInterval How much to scroll on this loop 218 */ 219 var stopAnimateScroll = function (position, endLocation, animationInterval) { 220 var currentLocation = root.pageYOffset; 221 if ( position == endLocation || currentLocation == endLocation || ( (root.innerHe
221ight + currentLocation) >= documentHeight ) ) { 222 clearInterval(animationInterval); 223 settings.callbackAfter( toggle, anchor ); // Run callbacks after animation complete 224 } 225 }; 226 227 /** 228 * Loop scrolling animation 229 * @private 230 */ 231 var loopAnimateScroll = function () { 232 timeLapsed += 16; 233 percentage = ( timeLapsed / parseInt(settings.speed, 10) ); 234 percentage = ( percentage > 1 ) ? 1 : percentage; 235 position = startLocation + ( distance * easingPattern(settings.easing, percentage) ); 236 root.scrollTo( 0, Math.floor(position) ); 237 stopAnimateScroll(position, endLocation, animationInterval); 238 }; 239 240 /** 241 * Set interval timer 242 * @private 243 */ 244 var startAnimateScroll = function () { 245 settings.callbackBefore( toggle, anchor ); // Run callbacks before animating scroll 246 animationInterval = setInterval(loopAnimateScroll, 16); 247 }; 248 249 /** 250 * Reset position to fix weird iOS bug 251 * @link https://github.com/cferdinandi/smooth-scroll/issues/45 252 */ 253 if ( root.pageYOffset === 0 ) { 254 root.scrollTo( 0, 0 ); 255 } 256 257 // Start scrolling animation 258 startAnimateScroll(); 259 260 }; 261 262 /** 263 * Initialize Smooth Scroll 264 * @public 265 * @param {Object} options User settings 266 */ 267 exports.init = function ( options ) { 268 269 // feature test 270 if ( !supports ) return; 271 272 // Selectors and variables 273 settings = extend( defaults, options || {} ); // Merge user options with defaults 274 var toggles = document.querySelectorAll('[data-scroll]'); // Get smooth scroll toggles 275 276 // When a toggle is clicked, run the click handler 277 forEach(toggles, function (toggle) { 278 toggle.addEventListener('click', exports.animateScroll.bind( null, toggle, toggle.hash, settings ), false); 279 }); 280 281 }; 282 283 284 // 285 // Public APIs 286 // 287 288 return exports; 289 290});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.