1/*! 2 * smooth-scroll v16.1.1 3 * Animate scrolling to anchor links 4 * (c) 2019 Chris Ferdinandi 5 * MIT License 6 * http://github.com/cferdinandi/smooth-scroll 7 */ 8 9(function (root, factory) { 10 if (typeof define === 'function' && define.amd) { 11 define([], (function () { 12 return factory(root); 13 })); 14 } else if (typeof exports === 'object') { 15 module.exports = factory(root); 16 } else { 17 root.SmoothScroll = factory(root); 18 } 19})(typeof global !== 'undefined' ? global : typeof window !== 'undefined' ? window : this, (function (window) { 20 21 'use strict'; 22 23 // 24 // Default settings 25 // 26 27 var defaults = { 28 29 // Selectors 30 ignore: '[data-scroll-ignore]', 31 header: '.data-scroll-header'
vendor: 14,517 bytes, lines 31-491
31, 32 topOnEmptyHash: true, 33 34 // Speed & Duration 35 speed: 500, 36 speedAsDuration: false, 37 durationMax: null, 38 durationMin: null, 39 clip: true, 40 offset: 0, 41 42 // Easing 43 easing: 'easeInOutCubic', 44 customEasing: null, 45 46 // History 47 updateURL: true, 48 popstate: true, 49 50 // Custom Events 51 emitEvents: true 52 53 }; 54 55 56 // 57 // Utility Methods 58 // 59 60 /** 61 * Check if browser supports required methods 62 * @return {Boolean} Returns true if all required methods are supported 63 */ 64 var supports = function () { 65 return ( 66 'querySelector' in document && 67 'addEventListener' in window && 68 'requestAnimationFrame' in window && 69 'closest' in window.Element.prototype 70 ); 71 }; 72 73 /** 74 * Merge two or more objects together. 75 * @param {Object} objects The objects to merge together 76 * @returns {Object} Merged values of defaults and options 77 */ 78 var extend = function () { 79 var merged = {}; 80 Array.prototype.forEach.call(arguments, (function (obj) { 81 for (var key in obj) { 82 if (!obj.hasOwnProperty(key)) return; 83 merged[key] = obj[key]; 84 } 85 })); 86 return merged; 87 }; 88 89 /** 90 * Check to see if user prefers reduced motion 91 * @param {Object} settings Script settings 92 */ 93 var reduceMotion = function () { 94 if ('matchMedia' in window && window.matchMedia('(prefers-reduced-motion)').matches) { 95 return true; 96 } 97 return false; 98 }; 99 100 /** 101 * Get the height of an element. 102 * @param {Node} elem The element to get the height of 103 * @return {Number} The element's height in pixels 104 */ 105 var getHeight = function (elem) { 106 return parseInt(window.getComputedStyle(elem).height, 10); 107 }; 108 109 /** 110 * Escape special characters for use with querySelector 111 * @author Mathias Bynens 112 * @link https://github.com/mathiasbynens/CSS.escape 113 * @param {String} id The anchor ID to escape 114 */ 115 var escapeCharacters = function (id) { 116 117 // Remove leading hash 118 if (id.charAt(0) === '#') { 119 id = id.substr(1); 120 } 121 122 var string = String(id); 123 var length = string.length; 124 var index = -1; 125 var codeUnit; 126 var result = ''; 127 var firstCodeUnit = string.charCodeAt(0); 128 while (++index < length) { 129 codeUnit = string.charCodeAt(index); 130 // Note: thereâs no need to special-case astral symbols, surrogate 131 // pairs, or lone surrogates. 132 133 // If the character is NULL (U+0000), then throw an 134 // `InvalidCharacterError` exception and terminate these steps. 135 if (codeUnit === 0x0000) { 136 throw new InvalidCharacterError( 137 'Invalid character: the input contains U+0000.' 138 ); 139 } 140 141 if ( 142 // If the character is in the range [\1-\1F] (U+0001 to U+001F) or is 143 // U+007F, [â¦] 144 (codeUnit >= 0x0001 && codeUnit <= 0x001F) || codeUnit == 0x007F || 145 // If the character is the first character and is in the range [0-9] 146 // (U+0030 to U+0039), [â¦] 147 (index === 0 && codeUnit >= 0x0030 && codeUnit <= 0x0039) || 148 // If the character is the second character and is in the range [0-9] 149 // (U+0030 to U+0039) and the first character is a `-` (U+002D), [â¦] 150 ( 151 index === 1 && 152 codeUnit >= 0x0030 && codeUnit <= 0x0039 && 153 firstCodeUnit === 0x002D 154 ) 155 ) { 156 // http://dev.w3.org/csswg/cssom/#escape-a-character-as-code-point 157 result += '\\' + codeUnit.toString(16) + ' '; 158 continue; 159 } 160 161 // If the character is not handled by one of the above rules and is 162 // greater than or equal to U+0080, is `-` (U+002D) or `_` (U+005F), or 163 // is in one of the ranges [0-9] (U+0030 to U+0039), [A-Z] (U+0041 to 164 // U+005A), or [a-z] (U+0061 to U+007A), [â¦] 165 if ( 166 codeUnit >= 0x0080 || 167 codeUnit === 0x002D || 168 codeUnit === 0x005F || 169 codeUnit >= 0x0030 && codeUnit <= 0x0039 || 170 codeUnit >= 0x0041 && codeUnit <= 0x005A || 171 codeUnit >= 0x0061 && codeUnit <= 0x007A 172 ) { 173 // the character itself 174 result += string.charAt(index); 175 continue; 176 } 177 178 // Otherwise, the escaped character. 179 // http://dev.w3.org/csswg/cssom/#escape-a-character 180 result += '\\' + string.charAt(index); 181 182 } 183 184 // Return sanitized hash 185 return '#' + result; 186 187 }; 188 189 /** 190 * Calculate the easing pattern 191 * @link https://gist.github.com/gre/1650294 192 * @param {String} type Easing pattern 193 * @param {Number} time Time animation should take to complete 194 * @returns {Number} 195 */ 196 var easingPattern = function (settings, time) { 197 var pattern; 198 199 // Default Easing Patterns 200 if (settings.easing === 'easeInQuad') pattern = time * time; // accelerating from zero velocity 201 if (settings.easing === 'easeOutQuad') pattern = time * (2 - time); // decelerating to zero velocity 202 if (settings.easing === 'easeInOutQuad') pattern = time < 0.5 ? 2 * time * time : -1 + (4 - 2 * time) * time; // acceleration until halfway, then deceleration 203 if (settings.easing === 'easeInCubic') pattern = time * time * time; // accelerating from zero velocity 204 if (settings.easing === 'easeOutCubic') pattern = (--time) * time * time + 1; // decelerating to zero velocity 205 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 206 if (settings.easing === 'easeInQuart') pattern = time * time * time * time; // accelerating from zero velocity 207 if (settings.easing === 'easeOutQuart') pattern = 1 - (--time) * time * time * time; // decelerating to zero velocity 208 if (settings.easing === 'easeInOutQuart') pattern = time < 0.5 ? 8 * time * time * time * time : 1 - 8 * (--time) * time * time * time; // acceleration until halfway, then deceleration 209 if (settings.easing === 'easeInQuint') pattern = time * time * time * time * time; // accelerating from zero velocity 210 if (settings.easing === 'easeOutQuint') pattern = 1 + (--time) * time * time * time * time; // decelerating to zero velocity 211 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 212 213 // Custom Easing Patterns 214 if (!!settings.customEasing) pattern = settings.customEasing(time); 215 216 return pattern || time; // no easing, no acceleration 217 }; 218 219 /** 220 * Determine the document's height 221 * @returns {Number} 222 */ 223 var getDocumentHeight = function () { 224 return Math.max( 225 document.body.scrollHeight, document.documentElement.scrollHeight, 226 document.body.offsetHeight, document.documentElement.offsetHeight, 227 document.body.clientHeight, document.documentElement.clientHeight 228 ); 229 }; 230 231 /** 232 * Calculate how far to scroll 233 * Clip support added by robjtede - https://github.com/cferdinandi/smooth-scroll/issues/405 234 * @param {Element} anchor The anchor element to scroll to 235 * @param {Number} headerHeight Height of a fixed header, if any 236 * @param {Number} offset Number of pixels by which to offset scroll 237 * @param {Boolean} clip If true, adjust scroll distance to prevent abrupt stops near the bottom of the page 238 * @returns {Number} 239 */ 240 var getEndLocation = function (anchor, headerHeight, offset, clip) { 241 var location = 0; 242 if (anchor.offsetParent) { 243 do { 244 location += anchor.offsetTop; 245 anchor = anchor.offsetParent; 246 } while (anchor); 247 } 248 location = Math.max(location - headerHeight - offset, 0); 249 if (clip) { 250 location = Math.min(location, getDocumentHeight() - window.innerHeight); 251 } 252 return location; 253 }; 254 255 /** 256 * Get the height of the fixed header 257 * @param {Node} header The header 258 * @return {Number} The height of the header 259 */ 260 var getHeaderHeight = function (header) { 261 return !header ? 0 : (getHeight(header) + header.offsetTop); 262 }; 263 264 /** 265 * Calculate the speed to use for the animation 266 * @param {Number} distance The distance to travel 267 * @param {Object} settings The plugin settings 268 * @return {Number} How fast to animate 269 */ 270 var getSpeed = function (distance, settings) { 271 var speed = settings.speedAsDuration ? settings.speed : Math.abs(distance / 1000 * settings.speed); 272 if (settings.durationMax && speed > settings.durationMax) return settings.durationMax; 273 if (settings.durationMin && speed < settings.durationMin) return settings.durationMin; 274 return parseInt(speed, 10); 275 }; 276 277 var setHistory = function (options) { 278 279 // Make sure this should run 280 if (!history.replaceState || !options.updateURL || history.state) return; 281 282 // Get the hash to use 283 var hash = window.location.hash; 284 hash = hash ? hash : ''; 285 286 // Set a default history 287 history.replaceState( 288 { 289 smoothScroll: JSON.stringify(options), 290 anchor: hash ? hash : window.pageYOffset 291 }, 292 document.title, 293 hash ? hash : window.location.href 294 ); 295 296 }; 297 298 /** 299 * Update the URL 300 * @param {Node} anchor The anchor that was scrolled to 301 * @param {Boolean} isNum If true, anchor is a number 302 * @param {Object} options Settings for Smooth Scroll 303 */ 304 var updateURL = function (anchor, isNum, options) { 305 306 // Bail if the anchor is a number 307 if (isNum) return; 308 309 // Verify that pushState is supported and the updateURL option is enabled 310 if (!history.pushState || !options.updateURL) return; 311 312 // Update URL 313 history.pushState( 314 { 315 smoothScroll: JSON.stringify(options), 316 anchor: anchor.id 317 }, 318 document.title, 319 anchor === document.documentElement ? '#top' : '#' + anchor.id 320 ); 321 322 }; 323 324 /** 325 * Bring the anchored element into focus 326 * @param {Node} anchor The anchor element 327 * @param {Number} endLocation The end location to scroll to 328 * @param {Boolean} isNum If true, scroll is to a position rather than an element 329 */ 330 var adjustFocus = function (anchor, endLocation, isNum) { 331 332 // Is scrolling to top of page, blur 333 if (anchor === 0) { 334 document.body.focus(); 335 } 336 337 // Don't run if scrolling to a number on the page 338 if (isNum) return; 339 340 // Otherwise, bring anchor element into focus 341 anchor.focus(); 342 if (document.activeElement !== anchor) { 343 anchor.setAttribute('tabindex', '-1'); 344 anchor.focus(); 345 anchor.style.outline = 'none'; 346 } 347 window.scrollTo(0 , endLocation); 348 349 }; 350 351 /** 352 * Emit a custom event 353 * @param {String} type The event type 354 * @param {Object} options The settings object 355 * @param {Node} anchor The anchor element 356 * @param {Node} toggle The toggle element 357 */ 358 var emitEvent = function (type, options, anchor, toggle) { 359 if (!options.emitEvents || typeof window.CustomEvent !== 'function') return; 360 var event = new CustomEvent(type, { 361 bubbles: true, 362 detail: { 363 anchor: anchor, 364 toggle: toggle 365 } 366 }); 367 document.dispatchEvent(event); 368 }; 369 370 371 // 372 // SmoothScroll Constructor 373 // 374 375 var SmoothScroll = function (selector, options) { 376 377 // 378 // Variables 379 // 380 381 var smoothScroll = {}; // Object for public APIs 382 var settings, anchor, toggle, fixedHeader, eventTimeout, animationInterval; 383 384 385 // 386 // Methods 387 // 388 389 /** 390 * Cancel a scroll-in-progress 391 */ 392 smoothScroll.cancelScroll = function (noEvent) { 393 cancelAnimationFrame(animationInterval); 394 animationInterval = null; 395 if (noEvent) return; 396 emitEvent('scrollCancel', settings); 397 }; 398 399 /** 400 * Start/stop the scrolling animation 401 * @param {Node|Number} anchor The element or position to scroll to 402 * @param {Element} toggle The element that toggled the scroll event 403 * @param {Object} options 404 */ 405 smoothScroll.animateScroll = function (anchor, toggle, options) { 406 407 // Cancel any in progress scrolls 408 smoothScroll.cancelScroll(); 409 410 // Local settings 411 var _settings = extend(settings || defaults, options || {}); // Merge user options with defaults 412 413 // Selectors and variables 414 var isNum = Object.prototype.toString.call(anchor) === '[object Number]' ? true : false; 415 var anchorElem = isNum || !anchor.tagName ? null : anchor; 416 if (!isNum && !anchorElem) return; 417 var startLocation = window.pageYOffset; // Current location on the page 418 if (_settings.header && !fixedHeader) { 419 // Get the fixed header if not already set 420 fixedHeader = document.querySelector(_settings.header); 421 } 422 var headerHeight = getHeaderHeight(fixedHeader); 423 var endLocation = isNum ? anchor : getEndLocation(anchorElem, headerHeight, parseInt((typeof _settings.offset === 'function' ? _settings.offset(anchor, toggle) : _settings.offset), 10), _settings.clip); // Location to scroll to 424 var distance = endLocation - startLocation; // distance to travel 425 var documentHeight = getDocumentHeight(); 426 var timeLapsed = 0; 427 var speed = getSpeed(distance, _settings); 428 var start, percentage, position; 429 430 /** 431 * Stop the scroll animation when it reaches its target (or the bottom/top of page) 432 * @param {Number} position Current position on the page 433 * @param {Number} endLocation Scroll to location 434 * @param {Number} animationInterval How much to scroll on this loop 435 */ 436 var stopAnimateScroll = function (position, endLocation) { 437 438 // Get the current location 439 var currentLocation = window.pageYOffset; 440 441 // Check if the end location has been reached yet (or we've hit the end of the document) 442 if (position == endLocation || currentLocation == endLocation || ((startLocation < endLocation && window.innerHeight + currentLocation) >= documentHeight)) { 443 444 // Clear the animation timer 445 smoothScroll.cancelScroll(true); 446 447 // Bring the anchored element into focus 448 adjustFocus(anchor, endLocation, isNum); 449 450 // Emit a custom event 451 emitEvent('scrollStop', _settings, anchor, toggle); 452 453 // Reset start 454 start = null; 455 animationInterval = null; 456 457 return true; 458 459 } 460 }; 461 462 /** 463 * Loop scrolling animation 464 */ 465 var loopAnimateScroll = function (timestamp) { 466 if (!start) { start = timestamp; } 467 timeLapsed += timestamp - start; 468 percentage = speed === 0 ? 0 : (timeLapsed / speed); 469 percentage = (percentage > 1) ? 1 : percentage; 470 position = startLocation + (distance * easingPattern(_settings, percentage)); 471 window.scrollTo(0, Math.floor(position)); 472 if (!stopAnimateScroll(position, endLocation)) { 473 animationInterval = window.requestAnimationFrame(loopAnimateScroll); 474 start = timestamp; 475 } 476 }; 477 478 /** 479 * Reset position to fix weird iOS bug 480 * @link https://github.com/cferdinandi/smooth-scroll/issues/45 481 */ 482 if (window.pageYOffset === 0) { 483 window.scrollTo(0, 0); 484 } 485 486 // Update the URL 487 updateURL(anchor, isNum, _settings); 488 489 // If the user prefers reduced motion, jump to location 490 if (reduceMotion()) { 491
vendor: 4,421 bytes, lines 491-651
491window.scrollTo(0, Math.floor(endLocation)); 492 return; 493 } 494 495 // Emit a custom event 496 emitEvent('scrollStart', _settings, anchor, toggle); 497 498 // Start scrolling animation 499 smoothScroll.cancelScroll(true); 500 window.requestAnimationFrame(loopAnimateScroll); 501 502 }; 503 504 /** 505 * If smooth scroll element clicked, animate scroll 506 */ 507 var clickHandler = function (event) { 508 509 // Don't run if event was canceled but still bubbled up 510 // By @mgreter - https://github.com/cferdinandi/smooth-scroll/pull/462/ 511 if (event.defaultPrevented) return; 512 513 // Don't run if right-click or command/control + click or shift + click 514 if (event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey) return; 515 516 // Check if event.target has closest() method 517 // By @totegi - https://github.com/cferdinandi/smooth-scroll/pull/401/ 518 if (!('closest' in event.target)) return; 519 520 // Check if a smooth scroll link was clicked 521 toggle = event.target.closest(selector); 522 if (!toggle || toggle.tagName.toLowerCase() !== 'a' || event.target.closest(settings.ignore)) return; 523 524 // Only run if link is an anchor and points to the current page 525 if (toggle.hostname !== window.location.hostname || toggle.pathname !== window.location.pathname || !/#/.test(toggle.href)) return; 526 527 // Get an escaped version of the hash 528 var hash; 529 try { 530 hash = escapeCharacters(decodeURIComponent(toggle.hash)); 531 } catch(e) { 532 hash = escapeCharacters(toggle.hash); 533 } 534 console.log(hash); 535 536 // Get the anchored element 537 var anchor; 538 if (hash === '#') { 539 if (!settings.topOnEmptyHash) return; 540 anchor = document.documentElement; 541 } else { 542 anchor = document.querySelector(hash); 543 } 544 anchor = !anchor && hash === '#top' ? document.documentElement : anchor; 545 546 // If anchored element exists, scroll to it 547 if (!anchor) return; 548 event.preventDefault(); 549 setHistory(settings); 550 smoothScroll.animateScroll(anchor, toggle); 551 552 }; 553 554 /** 555 * Animate scroll on popstate events 556 */ 557 var popstateHandler = function (event) { 558 559 // Stop if history.state doesn't exist (ex. if clicking on a broken anchor link). 560 // fixes `Cannot read property 'smoothScroll' of null` error getting thrown. 561 if (history.state === null) return; 562 563 // Only run if state is a popstate record for this instantiation 564 if (!history.state.smoothScroll || history.state.smoothScroll !== JSON.stringify(settings)) return; 565 566 // Only run if state includes an anchor 567 568 // if (!history.state.anchor && history.state.anchor !== 0) return; 569 570 // Get the anchor 571 var anchor = history.state.anchor; 572 if (typeof anchor === 'string' && anchor) { 573 anchor = document.querySelector(escapeCharacters(history.state.anchor)); 574 if (!anchor) return; 575 } 576 577 // Animate scroll to anchor link 578 smoothScroll.animateScroll(anchor, null, {updateURL: false}); 579 580 }; 581 582 /** 583 * Destroy the current initialization. 584 */ 585 smoothScroll.destroy = function () { 586 587 // If plugin isn't already initialized, stop 588 if (!settings) return; 589 590 // Remove event listeners 591 document.removeEventListener('click', clickHandler, false); 592 window.removeEventListener('popstate', popstateHandler, false); 593 594 // Cancel any scrolls-in-progress 595 smoothScroll.cancelScroll(); 596 597 // Reset variables 598 settings = null; 599 anchor = null; 600 toggle = null; 601 fixedHeader = null; 602 eventTimeout = null; 603 animationInterval = null; 604 605 }; 606 607 /** 608 * Initialize Smooth Scroll 609 * @param {Object} options User settings 610 */ 611 var init = function () { 612 613 // feature test 614 if (!supports()) throw 'Smooth Scroll: This browser does not support the required JavaScript methods and browser APIs.'; 615 616 // Destroy any existing initializations 617 smoothScroll.destroy(); 618 619 // Selectors and variables 620 settings = extend(defaults, options || {}); // Merge user options with defaults 621 fixedHeader = settings.header ? document.querySelector(settings.header) : null; // Get the fixed header 622 623 // When a toggle is clicked, run the click handler 624 document.addEventListener('click', clickHandler, false); 625 626 // If updateURL and popState are enabled, listen for pop events 627 if (settings.updateURL && settings.popstate) { 628 window.addEventListener('popstate', popstateHandler, false); 629 } 630 631 }; 632 633 634 // 635 // Initialize plugin 636 // 637 638 init(); 639 640 641 // 642 // Public APIs 643 // 644 645 return smoothScroll; 646 647 }; 648 649 return SmoothScroll; 650 651}));
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.