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