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