|
1 | 1 | /*! |
2 | | - * smooth-scroll v7.1.1: Animate scrolling to anchor links |
3 | | - * (c) 2015 Chris Ferdinandi |
| 2 | + * smooth-scroll v8.0.0: Animate scrolling to anchor links |
| 3 | + * (c) 2016 Chris Ferdinandi |
4 | 4 | * MIT License |
5 | 5 | * http://github.com/cferdinandi/smooth-scroll |
6 | 6 | */ |
|
22 | 22 | // |
23 | 23 |
|
24 | 24 | var smoothScroll = {}; // Object for public APIs |
25 | | - var supports = 'querySelector' in document && 'addEventListener' in root; // Feature test |
26 | | - var settings, eventTimeout, fixedHeader, headerHeight; |
| 25 | + var supports = 'querySelector' in document && 'addEventListener' in root && 'onhashchange' in root; // Feature test |
| 26 | + var settings, eventTimeout, fixedHeader, headerHeight, anchor; |
27 | 27 |
|
28 | 28 | // Default settings |
29 | 29 | var defaults = { |
|
32 | 32 | speed: 500, |
33 | 33 | easing: 'easeInOutCubic', |
34 | 34 | offset: 0, |
35 | | - updateURL: true, |
| 35 | + scrollOnLoad: true, |
36 | 36 | callback: function () {} |
37 | 37 | }; |
38 | 38 |
|
|
170 | 170 |
|
171 | 171 | /** |
172 | 172 | * Escape special characters for use with querySelector |
173 | | - * @private |
| 173 | + * @public |
174 | 174 | * @param {String} id The anchor ID to escape |
175 | 175 | * @author Mathias Bynens |
176 | 176 | * @link https://github.com/mathiasbynens/CSS.escape |
177 | 177 | */ |
178 | | - var escapeCharacters = function ( id ) { |
| 178 | + smoothScroll.escapeCharacters = function ( id ) { |
| 179 | + |
| 180 | + // Remove leading hash |
| 181 | + if ( id.charAt(0) === '#' ) { |
| 182 | + id = id.substr(1); |
| 183 | + } |
| 184 | + |
179 | 185 | var string = String(id); |
180 | 186 | var length = string.length; |
181 | 187 | var index = -1; |
|
237 | 243 | result += '\\' + string.charAt(index); |
238 | 244 |
|
239 | 245 | } |
240 | | - return result; |
| 246 | + |
| 247 | + return '#' + result; |
| 248 | + |
241 | 249 | }; |
242 | 250 |
|
243 | 251 | /** |
|
309 | 317 | }; |
310 | 318 |
|
311 | 319 | /** |
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 |
| 320 | + * Get the height of a fixed header in pixels |
| 321 | + * @param {Node} header The header |
| 322 | + * @return {Integer} The header height in pixels |
316 | 323 | */ |
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 | 324 | var getHeaderHeight = function ( header ) { |
324 | 325 | return header === null ? 0 : ( getHeight( header ) + header.offsetTop ); |
325 | 326 | }; |
|
331 | 332 | * @param {Element} anchor The element to scroll to |
332 | 333 | * @param {Object} options |
333 | 334 | */ |
334 | | - smoothScroll.animateScroll = function ( toggle, anchor, options ) { |
| 335 | + smoothScroll.animateScroll = function ( anchor, toggle, options ) { |
335 | 336 |
|
336 | 337 | // Options and overrides |
337 | 338 | var overrides = getDataOptions( toggle ? toggle.getAttribute('data-options') : null ); |
338 | 339 | 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 | 340 |
|
341 | 341 | // Selectors and variables |
342 | | - var anchorElem = anchor === '#' ? root.document.documentElement : root.document.querySelector(anchor); |
| 342 | + var isNum = Object.prototype.toString.call( anchor ) === '[object Number]' ? true : false; |
| 343 | + var anchorElem = isNum ? null : root.document.querySelector(anchor); |
| 344 | + if ( !isNum && !anchorElem ) return; |
343 | 345 | var startLocation = root.pageYOffset; // Current location on the page |
344 | 346 | if ( !fixedHeader ) { fixedHeader = root.document.querySelector( settings.selectorHeader ); } // Get the fixed header if not already set |
345 | 347 | 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 |
| 348 | + var endLocation = isNum ? anchor : getEndLocation( anchorElem, headerHeight, parseInt(settings.offset, 10) ); // Location to scroll to |
347 | 349 | var animationInterval; // interval timer |
348 | 350 | var distance = endLocation - startLocation; // distance to travel |
349 | 351 | var documentHeight = getDocumentHeight(); |
350 | 352 | var timeLapsed = 0; |
351 | 353 | var percentage, position; |
352 | 354 |
|
353 | | - // Update URL |
354 | | - updateUrl(anchor, settings.updateURL); |
355 | | - |
356 | 355 | /** |
357 | 356 | * Stop the scroll animation when it reaches its target (or the bottom/top of page) |
358 | 357 | * @private |
|
364 | 363 | var currentLocation = root.pageYOffset; |
365 | 364 | if ( position == endLocation || currentLocation == endLocation || ( (root.innerHeight + currentLocation) >= documentHeight ) ) { |
366 | 365 | clearInterval(animationInterval); |
367 | | - anchorElem.focus(); |
368 | | - settings.callback( toggle, anchor ); // Run callbacks after animation complete |
| 366 | + if ( anchorElem ) { |
| 367 | + anchorElem.focus(); |
| 368 | + } |
| 369 | + settings.callback( anchor, toggle ); // Run callbacks after animation complete |
369 | 370 | } |
370 | 371 | }; |
371 | 372 |
|
|
404 | 405 | }; |
405 | 406 |
|
406 | 407 | /** |
407 | | - * If smooth scroll element clicked, animate scroll |
| 408 | + * Handle has change event |
| 409 | + * @private |
| 410 | + */ |
| 411 | + var hashChangeHandler = function () { |
| 412 | + |
| 413 | + // Get hash from URL |
| 414 | + var hash = root.location.hash; |
| 415 | + |
| 416 | + // If anchor target is cached, reset it's ID |
| 417 | + if ( anchor ) { |
| 418 | + anchor.id = anchor.getAttribute( 'data-scroll-id' ); |
| 419 | + anchor = null; |
| 420 | + } |
| 421 | + |
| 422 | + // If there's a URL hash, animate scrolling to the matching ID |
| 423 | + if ( !hash ) return; |
| 424 | + hash = smoothScroll.escapeCharacters( hash ); // Escape special characters and leading numbers |
| 425 | + var toggle = document.querySelector( settings.select + '[href*="' + hash + '"]'); |
| 426 | + smoothScroll.animateScroll( hash, toggle, settings); // Animate scroll |
| 427 | + |
| 428 | + }; |
| 429 | + |
| 430 | + /** |
| 431 | + * Handle toggle click events |
408 | 432 | * @private |
| 433 | + * @param {Event} event |
409 | 434 | */ |
410 | | - var eventHandler = function (event) { |
| 435 | + var clickHandler = function (event) { |
| 436 | + |
| 437 | + // Check if event target is a smooth scroll link and has a hash |
411 | 438 | 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 |
| 439 | + if ( !toggle || !toggle.hash ) return; |
| 440 | + |
| 441 | + // Escape the hash characters |
| 442 | + var hash = smoothScroll.escapeCharacters( toggle.hash ); // Escape special characters and leading numbers |
| 443 | + |
| 444 | + // If hash is already active, animate immediately (no hash change will fire) |
| 445 | + if ( hash === ( root.location.hash || '#top' ) ) { |
| 446 | + smoothScroll.animateScroll( hash, toggle, settings); |
| 447 | + return; |
| 448 | + } |
| 449 | + |
| 450 | + // Get the anchor target |
| 451 | + anchor = document.querySelector( hash ); |
| 452 | + |
| 453 | + // If anchor exists, save the ID as a data attribute and remove it (prevents scroll jump) |
| 454 | + if ( anchor ) { |
| 455 | + anchor.setAttribute( 'data-scroll-id', anchor.id ); |
| 456 | + anchor.id = ''; |
415 | 457 | } |
| 458 | + |
416 | 459 | }; |
417 | 460 |
|
418 | 461 | /** |
419 | | - * On window scroll and resize, only run events at a rate of 15fps for better performance |
| 462 | + * On window resize, only run events at a rate of 15fps for better performance |
420 | 463 | * @private |
421 | | - * @param {Function} eventTimeout Timeout function |
422 | | - * @param {Object} settings |
| 464 | + * @param {Event} event |
423 | 465 | */ |
424 | | - var eventThrottler = function (event) { |
| 466 | + var resizeThrottler = function (event) { |
425 | 467 | if ( !eventTimeout ) { |
426 | 468 | eventTimeout = setTimeout(function() { |
427 | 469 | eventTimeout = null; // Reset timeout |
|
440 | 482 | if ( !settings ) return; |
441 | 483 |
|
442 | 484 | // Remove event listeners |
443 | | - root.document.removeEventListener( 'click', eventHandler, false ); |
444 | | - root.removeEventListener( 'resize', eventThrottler, false ); |
| 485 | + document.removeEventListener( 'click', clickHandler, false ); |
| 486 | + root.removeEventListener( 'hashchange', hashChangeHandler, false ); |
| 487 | + root.removeEventListener( 'resize', resizeThrottler, false ); |
445 | 488 |
|
446 | | - // Reset varaibles |
| 489 | + // Reset variables |
447 | 490 | settings = null; |
448 | 491 | eventTimeout = null; |
449 | 492 | fixedHeader = null; |
450 | 493 | headerHeight = null; |
| 494 | + anchor = null; |
451 | 495 | }; |
452 | 496 |
|
453 | 497 | /** |
|
468 | 512 | fixedHeader = root.document.querySelector( settings.selectorHeader ); // Get the fixed header |
469 | 513 | headerHeight = getHeaderHeight( fixedHeader ); |
470 | 514 |
|
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 ); } |
| 515 | + // Event listeners |
| 516 | + document.addEventListener('click', clickHandler, false); |
| 517 | + root.addEventListener('hashchange', hashChangeHandler, false); |
| 518 | + if ( fixedHeader ) { root.addEventListener( 'resize', resizeThrottler, false ); } |
| 519 | + |
| 520 | + // Scroll on load |
| 521 | + if ( settings.scrollOnLoad ) { |
| 522 | + setTimeout(function() { |
| 523 | + window.scrollTo(0, 0); |
| 524 | + }, 1); |
| 525 | + hashChangeHandler(); |
| 526 | + } |
474 | 527 |
|
475 | 528 | }; |
476 | 529 |
|
|
0 commit comments