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