1/** 2 * smooth-scroll v5.3.6 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([], factory(root)); 13 } else if ( typeof exports === 'object' ) { 14 module.exports = factory(root); 15 } else { 16 root.smoothScroll = factory(root); 17 } 18})(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, headerHeight; 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 var getHeaderHeight = function ( header ) { 273 return header === null ? 0 : ( getHeight( header ) + header.offsetTop ); 274 }; 275 276 /** 277 * Start/stop the scrolling animation 278 * @public 279 * @param {Element} toggle The element that toggled the scroll event 280 * @param {Element} anchor The element to scroll to 281 * @param {Object} options 282 */ 283 smoothScroll.animateScroll = function ( toggle, anchor, options ) { 284 285 // Options and overrides 286 var settings = extend( settings || defaults, options || {} ); // Merge user options with defaults 287 var overrides = getDataOptions( toggle ? toggle.getAttribute('data-options') : null ); 288 settings = extend( settings, overrides ); 289 anchor = '#' + escapeCharacters(anchor.substr(1)); // Escape special characters and leading numbers 290 291 // Selectors and variables 292 var anchorElem = anchor === '#' ? document.documentElement : document.querySelector(anchor); 293 var startLocation = root.pageYOffset; // Current location on the page 294 if ( !fixedHeader ) { fixedHeader = document.querySelector('[data-scroll-header]'); } // Get the fixed header if not already set 295 if ( !headerHeight ) { headerHeight = getHeaderHeight( fixedHeader ); } // Get the height of a fixed header if one exists and not already set 296 var endLocation = getEndLocation( anchorElem, headerHeight, parseInt(settings.offset, 10) ); // Scroll to location 297 var animationInterval; // interval timer 298 var distance = endLocation - startLocation; // distance to travel 299 var documentHeight = getDocumentHeight(); 300 var timeLapsed = 0; 301 var percentage, position; 302 303 // Update URL 304 updateUrl(anchor, settings.updateURL); 305 306 /** 307 * Stop the scroll animation when it reaches its target (or the bottom/top of page) 308 * @private 309 * @param {Number} position Current position on the page 310 * @param {Number} endLocation Scroll to location 311 * @param {Number} animationInterval How much to scroll on this loop 312 */ 313 var stopAnimateScroll = function (position, endLocation, animationInterval) { 314 var currentLocation = root.pageYOffset; 315 if ( position == endLocation || currentLocation == endLocation || ( (root.innerHe
315ight + currentLocation) >= documentHeight ) ) { 316 clearInterval(animationInterval); 317 anchorElem.focus(); 318 settings.callbackAfter( toggle, anchor ); // Run callbacks after animation complete 319 } 320 }; 321 322 /** 323 * Loop scrolling animation 324 * @private 325 */ 326 var loopAnimateScroll = function () { 327 timeLapsed += 16; 328 percentage = ( timeLapsed / parseInt(settings.speed, 10) ); 329 percentage = ( percentage > 1 ) ? 1 : percentage; 330 position = startLocation + ( distance * easingPattern(settings.easing, percentage) ); 331 root.scrollTo( 0, Math.floor(position) ); 332 stopAnimateScroll(position, endLocation, animationInterval); 333 }; 334 335 /** 336 * Set interval timer 337 * @private 338 */ 339 var startAnimateScroll = function () { 340 settings.callbackBefore( toggle, anchor ); // Run callbacks before animating scroll 341 animationInterval = setInterval(loopAnimateScroll, 16); 342 }; 343 344 /** 345 * Reset position to fix weird iOS bug 346 * @link https://github.com/cferdinandi/smooth-scroll/issues/45 347 */ 348 if ( root.pageYOffset === 0 ) { 349 root.scrollTo( 0, 0 ); 350 } 351 352 // Start scrolling animation 353 startAnimateScroll(); 354 355 }; 356 357 /** 358 * If smooth scroll element clicked, animate scroll 359 * @private 360 */ 361 var eventHandler = function (event) { 362 var toggle = getClosest(event.target, '[data-scroll]'); 363 if ( toggle && toggle.tagName.toLowerCase() === 'a' ) { 364 event.preventDefault(); // Prevent default click event 365 smoothScroll.animateScroll( toggle, toggle.hash, settings); // Animate scroll 366 } 367 }; 368 369 /** 370 * On window scroll and resize, only run events at a rate of 15fps for better performance 371 * @private 372 * @param {Function} eventTimeout Timeout function 373 * @param {Object} settings 374 */ 375 var eventThrottler = function (event) { 376 if ( !eventTimeout ) { 377 eventTimeout = setTimeout(function() { 378 eventTimeout = null; // Reset timeout 379 headerHeight = getHeaderHeight( fixedHeader ); // Get the height of a fixed header if one exists 380 }, 66); 381 } 382 }; 383 384 /** 385 * Destroy the current initialization. 386 * @public 387 */ 388 smoothScroll.destroy = function () { 389 390 // If plugin isn't already initialized, stop 391 if ( !settings ) return; 392 393 // Remove event listeners 394 document.removeEventListener( 'click', eventHandler, false ); 395 root.removeEventListener( 'resize', eventThrottler, false ); 396 397 // Reset varaibles 398 settings = null; 399 eventTimeout = null; 400 fixedHeader = null; 401 headerHeight = null; 402 }; 403 404 /** 405 * Initialize Smooth Scroll 406 * @public 407 * @param {Object} options User settings 408 */ 409 smoothScroll.init = function ( options ) { 410 411 // feature test 412 if ( !supports ) return; 413 414 // Destroy any existing initializations 415 smoothScroll.destroy(); 416 417 // Selectors and variables 418 settings = extend( defaults, options || {} ); // Merge user options with defaults 419 fixedHeader = document.querySelector('[data-scroll-header]'); // Get the fixed header 420 headerHeight = getHeaderHeight( fixedHeader ); 421 422 // When a toggle is clicked, run the click handler 423 document.addEventListener('click', eventHandler, false ); 424 if ( fixedHeader ) { root.addEventListener( 'resize', eventThrottler, false ); } 425 426 }; 427 428 429 // 430 // Public APIs 431 // 432 433 return smoothScroll; 434 435});
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.