1(function (root, factory) { 2 if ( typeof define === 'function' && define.amd ) { 3 define([], factory(root)); 4 } else if ( typeof exports === 'object' ) { 5 module.exports = 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 smoothScroll = {}; // Object for public APIs 18 var supports = !!document.querySelector && !!root.addEventListener; // Feature test 19 var settings, eventTimeout, fixedHeader, headerHeight; 20 21 // Default settings 22 var defaults = { 23 speed: 500, 24 easing: 'easeInOutCubic', 25 offset: 0, 26 updateURL: true, 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 * Get the closest matching element up the DOM tree 77 * @param {Element} elem Starting element 78 * @param {String} selector Selector to match against (class, ID, or data attribute) 79 * @return {Boolean|Element} Returns false if not match found 80 */ 81 var getClosest = function (elem, selector) { 82 var firstChar = selector.charAt(0); 83 for ( ; elem && elem !== document; elem = elem.parentNode ) { 84 if ( firstChar === '.' ) { 85 if ( elem.classList.contains( selector.substr(1) ) ) { 86 return elem; 87 } 88 } else if ( firstChar === '#' ) { 89 if ( elem.id === selector.substr(1) ) { 90 return elem; 91 } 92 } else if ( firstChar === '[' ) { 93 if ( elem.hasAttribute( selector.substr(1, selector.length - 2) ) ) { 94 return elem; 95 } 96 } 97 } 98 return false; 99 }; 100 101 /** 102 * Get the height of an element 103 * @private 104 * @param {Node]} elem The element 105 * @return {Number} The element's height 106 */ 107 var getHeight = function (elem) { 108 return Math.max( elem.scrollHeight, elem.offsetHeight, elem.clientHeight ); 109 }; 110 111 /** 112 * Escape special characters for use with querySelector 113 * @private 114 * @param {String} id The anchor ID to escape 115 * @author Mathias Bynens 116 * @link https://github.com/mathiasbynens/CSS.escape 117 */ 118 var escapeCharacters = function ( id ) { 119 var string = String(id); 120 var length = string.length; 121 var index = -1; 122 var codeUnit; 123 var result = ''; 124 var firstCodeUnit = string.charCodeAt(0); 125 while (++index < length) { 126 codeUnit = string.charCodeAt(index); 127 // Note: thereâs no need to special-case astral symbols, surrogate 128 // pairs, or lone surrogates. 129 130 // If the character is NULL (U+0000), then throw an 131 // `InvalidCharacterError` exception and terminate these steps. 132 if (codeUnit === 0x0000) { 133 throw new InvalidCharacterError( 134 'Invalid character: the input contains U+0000.' 135 ); 136 } 137 138 if ( 139 // If the character is in the range [\1-\1F] (U+0001 to U+001F) or is 140 // U+007F, [â¦] 141 (codeUnit >= 0x0001 && codeUnit <= 0x001F) || codeUnit == 0x007F || 142 // If the character is the first character and is in the range [0-9] 143 // (U+0030 to U+0039), [â¦] 144 (index === 0 && codeUnit >= 0x0030 && codeUnit <= 0x0039) || 145 // If the character is the second character and is in the range [0-9] 146 // (U+0030 to U+0039) and the first character is a `-` (U+002D), [â¦] 147 ( 148 index === 1 && 149 codeUnit >= 0x0030 && codeUnit <= 0x0039 && 150 firstCodeUnit === 0x002D 151 ) 152 ) { 153 // http://dev.w3.org/csswg/cssom/#escape-a-character-as-code-point 154 result += '\\' + codeUnit.toString(16) + ' '; 155 continue; 156 } 157 158 // If the character is not handled by one of the above rules and is 159 // greater than or equal to U+0080, is `-` (U+002D) or `_` (U+005F), or 160 // is in one of the ranges [0-9] (U+0030 to U+0039), [A-Z] (U+0041 to 161 // U+005A), or [a-z] (U+0061 to U+007A), [â¦] 162 if ( 163 codeUnit >= 0x0080 || 164 codeUnit === 0x002D || 165 codeUnit === 0x005F || 166 codeUnit >= 0x0030 && codeUnit <= 0x0039 || 167 codeUnit >= 0x0041 && codeUnit <= 0x005A || 168 codeUnit >= 0x0061 && codeUnit <= 0x007A 169 ) { 170 // the character itself 171 result += string.charAt(index); 172 continue; 173 } 174 175 // Otherwise, the escaped character. 176 // http://dev.w3.org/csswg/cssom/#escape-a-character 177 result += '\\' + string.charAt(index); 178 179 } 180 return result; 181 }; 182 183 /** 184 * Calculate the easing pattern 185 * @private 186 * @link https://gist.github.com/gre/1650294 187 * @param {String} type Easing pattern 188 * @param {Number} time Time animation should take to complete 189 * @returns {Number} 190 */ 191 var easingPattern = function ( type, time ) { 192 var pattern; 193 if ( type === 'easeInQuad' ) pattern = time * time; // accelerating from zero velocity 194 if ( type === 'easeOutQuad' ) pattern = time * (2 - time); // decelerating to zero velocity 195 if ( type === 'easeInOutQuad' ) pattern = time < 0.5 ? 2 * time * time : -1 + (4 - 2 * t
195ime) * time; // acceleration until halfway, then deceleration 196 if ( type === 'easeInCubic' ) pattern = time * time * time; // accelerating from zero velocity 197 if ( type === 'easeOutCubic' ) pattern = (--time) * time * time + 1; // decelerating to zero velocity 198 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 199 if ( type === 'easeInQuart' ) pattern = time * time * time * time; // accelerating from zero velocity 200 if ( type === 'easeOutQuart' ) pattern = 1 - (--time) * time * time * time; // decelerating to zero velocity 201 if ( type === 'easeInOutQuart' ) pattern = time < 0.5 ? 8 * time * time * time * time : 1 - 8 * (--time) * time * time * time; // acceleration until halfway, then deceleration 202 if ( type === 'easeInQuint' ) pattern = time * time * time * time * time; // accelerating from zero velocity 203 if ( type === 'easeOutQuint' ) pattern = 1 + (--time) * time * time * time * time; // decelerating to zero velocity 204 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 205 return pattern || time; // no easing, no acceleration 206 }; 207 208 /** 209 * Calculate how far to scroll 210 * @private 211 * @param {Element} anchor The anchor element to scroll to 212 * @param {Number} headerHeight Height of a fixed header, if any 213 * @param {Number} offset Number of pixels by which to offset scroll 214 * @returns {Number} 215 */ 216 var getEndLocation = function ( anchor, headerHeight, offset ) { 217 var location = 0; 218 if (anchor.offsetParent) { 219 do { 220 location += anchor.offsetTop; 221 anchor = anchor.offsetParent; 222 } while (anchor); 223 } 224 location = location - headerHeight - offset; 225 return location >= 0 ? location : 0; 226 }; 227 228 /** 229 * Determine the document's height 230 * @private 231 * @returns {Number} 232 */ 233 var getDocumentHeight = function () { 234 return Math.max( 235 document.body.scrollHeight, document.documentElement.scrollHeight, 236 document.body.offsetHeight, document.documentElement.offsetHeight, 237 document.body.clientHeight, document.documentElement.clientHeight 238 ); 239 }; 240 241 /** 242 * Convert data-options attribute into an object of key/value pairs 243 * @private 244 * @param {String} options Link-specific options as a data attribute string 245 * @returns {Object} 246 */ 247 var getDataOptions = function ( options ) { 248 return !options || !(typeof JSON === 'object' && typeof JSON.parse === 'function') ? {} : JSON.parse( options ); 249 }; 250 251 /** 252 * Update the URL 253 * @private 254 * @param {Element} anchor The element to scroll to 255 * @param {Boolean} url Whether or not to update the URL history 256 */ 257 var updateUrl = function ( anchor, url ) { 258 if ( history.pushState && (url || url === 'true') ) { 259 history.pushState( null, null, [root.location.protocol, '//', root.location.host, root.location.pathname, root.location.search, anchor].join('') ); 260 } 261 }; 262 263 var getHeaderHeight = function ( header ) { 264 return header === null ? 0 : ( getHeight( header ) + header.offsetTop ); 265 }; 266 267 /** 268 * Start/stop the scrolling animation 269 * @public 270 * @param {Element} toggle The element that toggled the scroll event 271 * @param {Element} anchor The element to scroll to 272 * @param {Object} options 273 */ 274 smoothScroll.animateScroll = function ( toggle, anchor, options ) { 275 276 // Options and overrides 277 var settings = extend( settings || defaults, options || {} ); // Merge user options with defaults 278 var overrides = getDataOptions( toggle ? toggle.getAttribute('data-options') : null ); 279 settings = extend( settings, overrides ); 280 anchor = '#' + escapeCharacters(anchor.substr(1)); // Escape special characters and leading numbers 281 282 // Selectors and variables 283 var anchorElem = anchor === '#' ? document.documentElement : document.querySelector(anchor); 284 var startLocation = root.pageYOffset; // Current location on the page 285 if ( !fixedHeader ) { fixedHeader = document.querySelector('[data-scroll-header]'); } // Get the fixed header if not already set 286 if ( !headerHeight ) { headerHeight = getHeaderHeight( fixedHeader ); } // Get the height of a fixed header if one exists and not already set 287 var endLocation = getEndLocation( anchorElem, headerHeight, parseInt(settings.offset, 10) ); // Scroll to location 288 var animationInterval; // interval timer 289 var distance = endLocation - startLocation; // distance to travel 290 var documentHeight = getDocumentHeight(); 291 var timeLapsed = 0; 292 var percentage, position; 293 294 // Update URL 295 updateUrl(anchor, settings.updateURL); 296 297 /** 298 * Stop the scroll animation when it reaches its target (or the bottom/top of page) 299 * @private 300 * @param {Number} position Current position on the page 301 * @param {Number} endLocation Scroll to location 302 * @param {Number} animationInterval How much to scroll on this loop 303 */ 304 var stopAnimateScroll = function (position, endLocation, animationInterval) { 305 var currentLocation = root.pageYOffset; 306 if ( position == endLocation || currentLocation == endLocation || ( (root.innerHe
306ight + currentLocation) >= documentHeight ) ) { 307 clearInterval(animationInterval); 308 anchorElem.focus(); 309 settings.callbackAfter( toggle, anchor ); // Run callbacks after animation complete 310 } 311 }; 312 313 /** 314 * Loop scrolling animation 315 * @private 316 */ 317 var loopAnimateScroll = function () { 318 timeLapsed += 16; 319 percentage = ( timeLapsed / parseInt(settings.speed, 10) ); 320 percentage = ( percentage > 1 ) ? 1 : percentage; 321 position = startLocation + ( distance * easingPattern(settings.easing, percentage) ); 322 root.scrollTo( 0, Math.floor(position) ); 323 stopAnimateScroll(position, endLocation, animationInterval); 324 }; 325 326 /** 327 * Set interval timer 328 * @private 329 */ 330 var startAnimateScroll = function () { 331 settings.callbackBefore( toggle, anchor ); // Run callbacks before animating scroll 332 animationInterval = setInterval(loopAnimateScroll, 16); 333 }; 334 335 /** 336 * Reset position to fix weird iOS bug 337 * @link https://github.com/cferdinandi/smooth-scroll/issues/45 338 */ 339 if ( root.pageYOffset === 0 ) { 340 root.scrollTo( 0, 0 ); 341 } 342 343 // Start scrolling animation 344 startAnimateScroll(); 345 346 }; 347 348 /** 349 * If smooth scroll element clicked, animate scroll 350 * @private 351 */ 352 var eventHandler = function (event) { 353 var toggle = getClosest(event.target, '[data-scroll]'); 354 if ( toggle && toggle.tagName.toLowerCase() === 'a' ) { 355 event.preventDefault(); // Prevent default click event 356 smoothScroll.animateScroll( toggle, toggle.hash, settings); // Animate scroll 357 } 358 }; 359 360 /** 361 * On window scroll and resize, only run events at a rate of 15fps for better performance 362 * @private 363 * @param {Function} eventTimeout Timeout function 364 * @param {Object} settings 365 */ 366 var eventThrottler = function (event) { 367 if ( !eventTimeout ) { 368 eventTimeout = setTimeout(function() { 369 eventTimeout = null; // Reset timeout 370 headerHeight = getHeaderHeight( fixedHeader ); // Get the height of a fixed header if one exists 371 }, 66); 372 } 373 }; 374 375 /** 376 * Destroy the current initialization. 377 * @public 378 */ 379 smoothScroll.destroy = function () { 380 381 // If plugin isn't already initialized, stop 382 if ( !settings ) return; 383 384 // Remove event listeners 385 document.removeEventListener( 'click', eventHandler, false ); 386 root.removeEventListener( 'resize', eventThrottler, false ); 387 388 // Reset varaibles 389 settings = null; 390 eventTimeout = null; 391 fixedHeader = null; 392 headerHeight = null; 393 }; 394 395 /** 396 * Initialize Smooth Scroll 397 * @public 398 * @param {Object} options User settings 399 */ 400 smoothScroll.init = function ( options ) { 401 402 // feature test 403 if ( !supports ) return; 404 405 // Destroy any existing initializations 406 smoothScroll.destroy(); 407 408 // Selectors and variables 409 settings = extend( defaults, options || {} ); // Merge user options with defaults 410 fixedHeader = document.querySelector('[data-scroll-header]'); // Get the fixed header 411 headerHeight = getHeaderHeight( fixedHeader ); 412 413 // When a toggle is clicked, run the click handler 414 document.addEventListener('click', eventHandler, false ); 415 if ( fixedHeader ) { root.addEventListener( 'resize', eventThrottler, false ); } 416 417 }; 418 419 420 // 421 // Public APIs 422 // 423 424 return smoothScroll; 425 426});
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.