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})(typeof global !== 'undefined' ? global : this.window || this.global, function (root) { 10 11 'use strict'; 12 13 // 14 // Variables 15 // 16 17 var smoothScroll = {}; // Object for public APIs 18 var supports = 'querySelector' in document && 'addEventListener' in root; // Feature test 19 var settings, anchor, toggle, fixedHeader, headerHeight, eventTimeout, animationInterval; 20 21 // Default settings 22 var defaults = { 23 // Selectors 24 selector: '[data-scroll]', 25 selectorHeader: null, 26 27 // Speed & Easing 28 speed: 500, 29 offset: 0, 30 easing: 'easeInOutCubic', 31 easingPatterns: {}, 32 33 // Callback API 34 before: function () {}, 35 after: function () {} 36 }; 37 38 39 // 40 // Methods 41 // 42 43 /** 44 * Merge two or more objects. Returns a new object. 45 * @private 46 * @param {Boolean} deep If true, do a deep (or recursive) merge [optional] 47 * @param {Object} objects The objects to merge together 48 * @returns {Object} Merged values of defaults and options 49 */ 50 var extend = function () { 51 52 // Variables 53 var extended = {}; 54 var deep = false; 55 var i = 0; 56 var length = arguments.length; 57 58 // Check if a deep merge 59 if ( Object.prototype.toString.call( arguments[0] ) === '[object Boolean]' ) { 60 deep = arguments[0]; 61 i++; 62 } 63 64 // Merge the object into the extended object 65 var merge = function (obj) { 66 for ( var prop in obj ) { 67 if ( Object.prototype.hasOwnProperty.call( obj, prop ) ) { 68 // If deep merge and property is an object, merge properties 69 if ( deep && Object.prototype.toString.call(obj[prop]) === '[object Object]' ) { 70 extended[prop] = extend( true, extended[prop], obj[prop] ); 71 } else { 72 extended[prop] = obj[prop]; 73 } 74 } 75 } 76 }; 77 78 // Loop through each object and conduct a merge 79 for ( ; i < length; i++ ) { 80 var obj = arguments[i]; 81 merge(obj); 82 } 83 84 return extended; 85 86 }; 87 88 /** 89 * Get the height of an element. 90 * @private 91 * @param {Node} elem The element to get the height of 92 * @return {Number} The element's height in pixels 93 */ 94 var getHeight = function ( elem ) { 95 return Math.max( elem.scrollHeight, elem.offsetHeight, elem.clientHeight ); 96 }; 97 98 /** 99 * Get the closest matching element up the DOM tree. 100 * @private 101 * @param {Element} elem Starting element 102 * @param {String} selector Selector to match against 103 * @return {Boolean|Element} Returns null if not match found 104 */ 105 var getClosest = function ( elem, selector ) { 106 107 // Element.matches() polyfill 108 if (!Element.prototype.matches) { 109 Element.prototype.matches = 110 Element.prototype.matchesSelector || 111 Element.prototype.mozMatchesSelector || 112 Element.prototype.msMatchesSelector || 113 Element.prototype.oMatchesSelector || 114 Element.prototype.webkitMatchesSelector || 115 function(s) { 116 var matches = (this.document || this.ownerDocument).querySelectorAll(s), 117 i = matches.length; 118 while (--i >= 0 && matches.item(i) !== this) {} 119 return i > -1; 120 }; 121 } 122 123 // Get closest match 124 for ( ; elem && elem !== document; elem = elem.parentNode ) { 125 if ( elem.matches( selector ) ) return elem; 126 } 127 128 return null; 129 130 }; 131 132 /** 133 * Escape special characters for use with querySelector 134 * @private 135 * @param {String} id The anchor ID to escape 136 * @author Mathias Bynens 137 * @link https://github.com/mathiasbynens/CSS.escape 138 */ 139 var escapeCharacters = function ( id ) { 140 141 // Remove leading hash 142 if ( id.charAt(0) === '#' ) { 143 id = id.substr(1); 144 } 145 146 var string = String(id); 147 var length = string.length; 148 var index = -1; 149 var codeUnit; 150 var result = ''; 151 var firstCodeUnit = string.charCodeAt(0); 152 while (++index < length) { 153 codeUnit = string.charCodeAt(index); 154 // Note: thereâs no need to special-case astral symbols, surrogate 155 // pairs, or lone surrogates. 156 157 // If the character is NULL (U+0000), then throw an 158 // `InvalidCharacterError` exception and terminate these steps. 159 if (codeUnit === 0x0000) { 160 throw new InvalidCharacterError( 161 'Invalid character: the input contains U+0000.' 162 ); 163 } 164 165 if ( 166 // If the character is in the range [\1-\1F] (U+0001 to U+001F) or is 167 // U+007F, [â¦] 168 (codeUnit >
vendor: 4,147 bytes, lines 168-261
168= 0x0001 && codeUnit <= 0x001F) || codeUnit == 0x007F || 169 // If the character is the first character and is in the range [0-9] 170 // (U+0030 to U+0039), [â¦] 171 (index === 0 && codeUnit >= 0x0030 && codeUnit <= 0x0039) || 172 // If the character is the second character and is in the range [0-9] 173 // (U+0030 to U+0039) and the first character is a `-` (U+002D), [â¦] 174 ( 175 index === 1 && 176 codeUnit >= 0x0030 && codeUnit <= 0x0039 && 177 firstCodeUnit === 0x002D 178 ) 179 ) { 180 // http://dev.w3.org/csswg/cssom/#escape-a-character-as-code-point 181 result += '\\' + codeUnit.toString(16) + ' '; 182 continue; 183 } 184 185 // If the character is not handled by one of the above rules and is 186 // greater than or equal to U+0080, is `-` (U+002D) or `_` (U+005F), or 187 // is in one of the ranges [0-9] (U+0030 to U+0039), [A-Z] (U+0041 to 188 // U+005A), or [a-z] (U+0061 to U+007A), [â¦] 189 if ( 190 codeUnit >= 0x0080 || 191 codeUnit === 0x002D || 192 codeUnit === 0x005F || 193 codeUnit >= 0x0030 && codeUnit <= 0x0039 || 194 codeUnit >= 0x0041 && codeUnit <= 0x005A || 195 codeUnit >= 0x0061 && codeUnit <= 0x007A 196 ) { 197 // the character itself 198 result += string.charAt(index); 199 continue; 200 } 201 202 // Otherwise, the escaped character. 203 // http://dev.w3.org/csswg/cssom/#escape-a-character 204 result += '\\' + string.charAt(index); 205 206 } 207 208 return '#' + result; 209 210 }; 211 212 /** 213 * Calculate the easing pattern 214 * @private 215 * @link https://gist.github.com/gre/1650294 216 * @param {String} type Easing pattern 217 * @param {Number} time Time animation should take to complete 218 * @returns {Number} 219 */ 220 var easingPattern = function ( settings, time ) { 221 var pattern; 222 223 // Default Easing Patterns 224 if ( settings.easing === 'easeInQuad' ) pattern = time * time; // accelerating from zero velocity 225 if ( settings.easing === 'easeOutQuad' ) pattern = time * (2 - time); // decelerating to zero velocity 226 if ( settings.easing === 'easeInOutQuad' ) pattern = time < 0.5 ? 2 * time * time : -1 + (4 - 2 * time) * time; // acceleration until halfway, then deceleration 227 if ( settings.easing === 'easeInCubic' ) pattern = time * time * time; // accelerating from zero velocity 228 if ( settings.easing === 'easeOutCubic' ) pattern = (--time) * time * time + 1; // decelerating to zero velocity 229 if ( settings.easing === 'easeInOutCubic' ) pattern = time < 0.5 ? 4 * time * time * time : (time - 1) * (2 * time - 2) * (2 * time - 2) + 1; // acceleration until halfway, then deceleration 230 if ( settings.easing === 'easeInQuart' ) pattern = time * time * time * time; // accelerating from zero velocity 231 if ( settings.easing === 'easeOutQuart' ) pattern = 1 - (--time) * time * time * time; // decelerating to zero velocity 232 if ( settings.easing === 'easeInOutQuart' ) pattern = time < 0.5 ? 8 * time * time * time * time : 1 - 8 * (--time) * time * time * time; // acceleration until halfway, then deceleration 233 if ( settings.easing === 'easeInQuint' ) pattern = time * time * time * time * time; // accelerating from zero velocity 234 if ( settings.easing === 'easeOutQuint' ) pattern = 1 + (--time) * time * time * time * time; // decelerating to zero velocity 235 if ( settings.easing === 'easeInOutQuint' ) pattern = time < 0.5 ? 16 * time * time * time * time * time : 1 + 16 * (--time) * time * time * time * time; // acceleration until halfway, then deceleration 236 237 // Custom Easing Patterns 238 if ( settings.easingPatterns[settings.easing] ) { 239 pattern = settings.easingPatterns[settings.easing]( time ); 240 } 241 242 return pattern || time; // no easing, no acceleration 243 }; 244 245 /** 246 * Calculate how far to scroll 247 * @private 248 * @param {Element} anchor The anchor element to scroll to 249 * @param {Number} headerHeight Height of a fixed header, if any 250 * @param {Number} offset Number of pixels by which to offset scroll 251 * @returns {Number} 252 */ 253 var getEndLocation = function ( anchor, headerHeight, offset ) { 254 var location = 0; 255 if (anchor.offsetParent) { 256 do { 257 location += anchor.offsetTop; 258 anchor = anchor.offsetParent; 259 } while (anchor); 260 } 261 location = Math.max(location - headerHeight - offset, 0);
262 return Math.min(location, getDocumentHeight() - getViewportHeight()); 263 }; 264 265 /** 266 * Determine the viewport's height 267 * @private 268 * @returns {Number} 269 */ 270 var getViewportHeight = function() { 271 return Math.max( document.documentElement.clientHeight, root.innerHeight || 0 ); 272 }; 273 274 /** 275 * Determine the document's height 276 * @private 277 * @returns {Number} 278 */ 279 var getDocumentHeight = function () { 280 return Math.max( 281 document.body.scrollHeight, document.documentElement.scrollHeight, 282 document.body.offsetHeight, document.documentElement.offsetHeight, 283 document.body.clientHeight, document.documentElement.clientHeight 284 ); 285 }; 286 287 /** 288 * Convert data-options attribute into an object of key/value pairs 289 * @private 290 * @param {String} options Link-specific options as a data attribute string 291 * @returns {Object} 292 */ 293 var getDataOptions = function ( options ) { 294 return !options || !(typeof JSON === 'object' && typeof JSON.parse === 'function') ? {} : JSON.parse( options ); 295 }; 296 297 /** 298 * Get the height of the fixed header 299 * @private 300 * @param {Node} header The header 301 * @return {Number} The height of the header 302 */ 303 var getHeaderHeight = function ( header ) { 304 return !header ? 0 : ( getHeight( header ) + header.offsetTop ); 305 }; 306 307 /** 308 * Bring the anchored element into focus 309 * @private 310 */ 311 var adjustFocus = function ( anchor, endLocation, isNum ) { 312 313 // Don't run if scrolling to a number on the page 314 if ( isNum ) return; 315 316 // Otherwise, bring anchor element into focus 317 anchor.focus(); 318 if ( document.activeElement.id !== anchor.id ) { 319 anchor.setAttribute( 'tabindex', '-1' ); 320 anchor.focus(); 321 anchor.style.outline = 'none'; 322 } 323 root.scrollTo( 0 , endLocation ); 324 325 }; 326 327 /** 328 * Start/stop the scrolling animation 329 * @public 330 * @param {Node|Number} anchor The element or position to scroll to 331 * @param {Element} toggle The element that toggled the scroll event 332 * @param {Object} options 333 */ 334 smoothScroll.animateScroll = function ( anchor, toggle, options ) { 335 336 // Options and overrides 337 var overrides = getDataOptions( toggle ? toggle.getAttribute('data-options') : null ); 338 var animateSettings = extend( settings || defaults, options || {}, overrides ); // Merge user options with defaults 339 340 // Selectors and variables 341 var isNum = Object.prototype.toString.call( anchor ) === '[object Number]' ? true : false; 342 var anchorElem = isNum || !anchor.tagName ? null : anchor; 343 if ( !isNum && !anchorElem ) return; 344 var startLocation = root.pageYOffset; // Current location on the page 345 if ( animateSettings.selectorHeader && !fixedHeader ) { 346 // Get the fixed header if not already set 347 fixedHeader = document.querySelector( animateSettings.selectorHeader ); 348 } 349 if ( !headerHeight ) { 350 // Get the height of a fixed header if one exists and not already set 351 headerHeight = getHeaderHeight( fixedHeader ); 352 } 353 var endLocation = isNum ? anchor : getEndLocation( anchorElem, headerHeight, parseInt((typeof animateSettings.offset === 'function' ? animateSettings.offset() : animateSettings.offset), 10) ); // Location to scroll to 354 var distance = endLocation - startLocation; // distance to travel 355 var documentHeight = getDocumentHeight(); 356 var timeLapsed = 0; 357 var percentage, position; 358 359 /** 360 * Stop the scroll animation when it reaches its target (or the bottom/top of page) 361 * @private 362 * @param {Number} position Current position on the page 363 * @param {Number} endLocation Scroll to location 364 * @param {Number} animationInterval How much to scroll on this loop 365 */ 366 var stopAnimateScroll = function ( position, endLocation, animationInterval ) { 367 var currentLocation = root.pageYOffset; 368 if ( position == endLocation || currentLocation == endLocation || ( (root.innerHeight + currentLocation) >= documentHeight ) ) { 369 370 // Clear the animation timer 371 clearInterval(animationInterval); 372 373 // Bring the anchored element into focus 374 adjustFocus( anchor, endLocation, isNum ); 375 376 // Run callback after animation complete 377 animateSettings.after( anchor, toggle ); 378 379 } 380 }; 381 382 /** 383 * Loop scrolling animation 384 * @private 385 */ 386 var loopAnimateScroll = function () { 387 timeLapsed += 16; 388 percentage = ( timeLapsed / parseInt(animateSettings.speed, 10) ); 389 percentage = ( percentage > 1 ) ? 1 : percentage; 390 position = startLocation + ( distance * easingPattern(animateSettings, percentage) ); 391 root.scrollTo( 0, Math.floor(position) ); 392 stopAnimateScroll(position, endLocation, animationInterval); 393 }; 394 395 /** 396 * Set interval timer 397 * @private 398 */ 399 var startAnimateScroll = function () { 400 clearInterval(animationInterval); 401 animationInterval = setInterval(loopAnimateScroll, 16); 402 }; 403 404 /** 405 * Reset position to fix weird iOS bug 406 * @link https://github.com/cferdinandi/smooth-scroll/issues/45 407 */ 408 if ( root.pageYOffset === 0 ) { 409 root.scrollTo( 0, 0 ); 410 } 411 412 // Run callback before animation starts 413 animateSettings.before( anchor, toggle ); 414 415 // Start scrolling animation 416 startAnimateScroll(); 417 418 }; 419 420 /** 421 * Handle has change event 422 * @private 423 */ 424 var hashChangeHandler = function (event) { 425 426 // Get hash from URL 427 // var hash = decodeURIComponent( escapeCharacters( root.location.hash ) ); 428 var hash; 429 try {
430 hash = escapeCharacters( decodeURIComponent( root.location.hash ) ); 431 } catch(e) { 432 hash = escapeCharacters( root.location.hash ); 433 } 434 435 // Only run if there's an anchor element to scroll to 436 if ( !anchor ) return; 437 438 // Reset the anchor element's ID 439 anchor.id = anchor.getAttribute( 'data-scroll-id' ); 440 441 // Scroll to the anchored content 442 smoothScroll.animateScroll( anchor, toggle ); 443 444 // Reset anchor and toggle 445 anchor = null; 446 toggle = null; 447 448 }; 449 450 /** 451 * If smooth scroll element clicked, animate scroll 452 * @private 453 */ 454 var clickHandler = function (event) { 455 456 // Don't run if right-click or command/control + click 457 if ( event.button !== 0 || event.metaKey || event.ctrlKey ) return; 458 459 // Check if a smooth scroll link was clicked 460 toggle = getClosest( event.target, settings.selector ); 461 if ( !toggle || toggle.tagName.toLowerCase() !== 'a' ) return; 462 463 // Only run if link is an anchor and points to the current page 464 if ( toggle.hostname !== root.location.hostname || toggle.pathname !== root.location.pathname || !/#/.test(toggle.href) ) return; 465 466 // Get the sanitized hash 467 // var hash = decodeURIComponent( escapeCharacters( toggle.hash ) ); 468 // console.log(hash); 469 var hash; 470 try { 471 hash = escapeCharacters( decodeURIComponent( toggle.hash ) ); 472 } catch(e) { 473 hash = escapeCharacters( toggle.hash ); 474 } 475 476 // If the hash is empty, scroll to the top of the page 477 if ( hash === '#' ) { 478 479 // Prevent default link behavior 480 event.preventDefault(); 481 482 // Set the anchored element 483 anchor = document.body; 484 485 // Save or create the ID as a data attribute and remove it (prevents scroll jump) 486 var id = anchor.id ? anchor.id : 'smooth-scroll-top'; 487 anchor.setAttribute( 'data-scroll-id', id ); 488 anchor.id = ''; 489 490 // If no hash change event will happen, fire manually 491 // Otherwise, update the hash 492 if ( root.location.hash.substring(1) === id ) { 493 hashChangeHandler(); 494 } else { 495 root.location.hash = id; 496 } 497 498 return; 499 500 } 501 502 // Get the anchored element 503 anchor = document.querySelector( hash ); 504 505 // If anchored element exists, save the ID as a data attribute and remove it (prevents scroll jump) 506 if ( !anchor ) return; 507 anchor.setAttribute( 'data-scroll-id', anchor.id ); 508 anchor.id = ''; 509 510 // If no hash change event will happen, fire manually 511 if ( toggle.hash === root.location.hash ) { 512 event.preventDefault(); 513 hashChangeHandler(); 514 } 515 516 }; 517 518 /** 519 * On window scroll and resize, only run events at a rate of 15fps for better performance 520 * @private 521 * @param {Function} eventTimeout Timeout function 522 * @param {Object} settings 523 */ 524 var resizeThrottler = function (event) { 525 if ( !eventTimeout ) { 526 eventTimeout = setTimeout(function() { 527 eventTimeout = null; // Reset timeout 528 headerHeight = getHeaderHeight( fixedHeader ); // Get the height of a fixed header if one exists 529 }, 66); 530 } 531 }; 532 533 /** 534 * Destroy the current initialization. 535 * @public 536 */ 537 smoothScroll.destroy = function () { 538 539 // If plugin isn't already initialized, stop 540 if ( !settings ) return; 541 542 // Remove event listeners 543 document.removeEventListener( 'click', clickHandler, false ); 544 root.removeEventListener( 'resize', resizeThrottler, false ); 545 546 // Reset varaibles 547 settings = null; 548 anchor = null; 549 toggle = null; 550 fixedHeader = null; 551 headerHeight = null; 552 eventTimeout = null; 553 animationInterval = null; 554 }; 555 556 /** 557 * Initialize Smooth Scroll 558 * @public 559 * @param {Object} options User settings 560 */ 561 smoothScroll.init = function ( options ) { 562 563 // feature test 564 if ( !supports ) return; 565 566 // Destroy any existing initializations 567 smoothScroll.destroy(); 568 569 // Selectors and variables 570 settings = extend( defaults, options || {} ); // Merge user options with defaults 571 fixedHeader = settings.selectorHeader ? document.querySelector( settings.selectorHeader ) : null; // Get the fixed header 572 headerHeight = getHeaderHeight( fixedHeader ); 573 574 // When a toggle is clicked, run the click handler 575 document.addEventListener( 'click', clickHandler, false ); 576 577 // Listen for hash changes 578 root.addEventListener('hashchange', hashChangeHandler, false); 579 580 // If window is resized and there's a fixed header, recalculate its size 581 if ( fixedHeader ) { 582 root.addEventListener( 'resize', resizeThrottler, false ); 583 } 584 585 }; 586 587 588 // 589 // Public APIs 590 // 591 592 return smoothScroll; 593 594});
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.