laipower/wp-content/plugins/gp-premium/general/js/smooth-scroll.js

744 lines
22 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*!
* smooth-scroll v14.2.1: Animate scrolling to anchor links
* (c) 2018 Chris Ferdinandi
* MIT License
* http://github.com/cferdinandi/smooth-scroll
*/
/**
* closest() polyfill
* @link https://developer.mozilla.org/en-US/docs/Web/API/Element/closest#Polyfill
*/
if (window.Element && !Element.prototype.closest) {
Element.prototype.closest = function(s) {
var matches = (this.document || this.ownerDocument).querySelectorAll(s),
i,
el = this;
do {
i = matches.length;
while (--i >= 0 && matches.item(i) !== el) {}
} while ((i < 0) && (el = el.parentElement));
return el;
};
}
/**
* CustomEvent() polyfill
* https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent#Polyfill
*/
(function () {
if (typeof window.CustomEvent === "function") return false;
function CustomEvent(event, params) {
params = params || { bubbles: false, cancelable: false, detail: undefined };
var evt = document.createEvent('CustomEvent');
evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
return evt;
}
CustomEvent.prototype = window.Event.prototype;
window.CustomEvent = CustomEvent;
})();
/**
* requestAnimationFrame() polyfill
* By Erik Möller. Fixes from Paul Irish and Tino Zijdel.
* @link http://paulirish.com/2011/requestanimationframe-for-smart-animating/
* @link http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating
* @license MIT
*/
(function() {
var lastTime = 0;
var vendors = ['ms', 'moz', 'webkit', 'o'];
for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] ||
window[vendors[x]+'CancelRequestAnimationFrame'];
}
if (!window.requestAnimationFrame) {
window.requestAnimationFrame = function(callback, element) {
var currTime = new Date().getTime();
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
var id = window.setTimeout((function() { callback(currTime + timeToCall); }),
timeToCall);
lastTime = currTime + timeToCall;
return id;
};
}
if (!window.cancelAnimationFrame) {
window.cancelAnimationFrame = function(id) {
clearTimeout(id);
};
}
}());
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
define([], (function () {
return factory(root);
}));
} else if (typeof exports === 'object') {
module.exports = factory(root);
} else {
root.SmoothScroll = factory(root);
}
})(typeof global !== 'undefined' ? global : typeof window !== 'undefined' ? window : this, (function (window) {
'use strict';
//
// Default settings
//
var defaults = {
// Selectors
ignore: '[data-scroll-ignore]',
header: null,
topOnEmptyHash: true,
// Speed & Easing
speed: 500,
clip: true,
offset: 0,
easing: 'easeInOutCubic',
customEasing: null,
// History
updateURL: true,
popstate: true,
// Custom Events
emitEvents: true
};
//
// Utility Methods
//
/**
* Check if browser supports required methods
* @return {Boolean} Returns true if all required methods are supported
*/
var supports = function () {
return (
'querySelector' in document &&
'addEventListener' in window &&
'requestAnimationFrame' in window &&
'closest' in window.Element.prototype
);
};
/**
* Merge two or more objects. Returns a new object.
* @param {Object} objects The objects to merge together
* @returns {Object} Merged values of defaults and options
*/
var extend = function () {
// Variables
var extended = {};
// Merge the object into the extended object
var merge = function (obj) {
for (var prop in obj) {
if (obj.hasOwnProperty(prop)) {
extended[prop] = obj[prop];
}
}
};
// Loop through each object and conduct a merge
for (var i = 0; i < arguments.length; i++) {
merge(arguments[i]);
}
return extended;
};
/**
* Check to see if user prefers reduced motion
* @param {Object} settings Script settings
*/
var reduceMotion = function (settings) {
if ('matchMedia' in window && window.matchMedia('(prefers-reduced-motion)').matches) {
return true;
}
return false;
};
/**
* Get the height of an element.
* @param {Node} elem The element to get the height of
* @return {Number} The element's height in pixels
*/
var getHeight = function (elem) {
return parseInt(window.getComputedStyle(elem).height, 10);
};
/**
* Decode a URI, with error check
* @param {String} hash The URI to decode
* @return {String} A decoded URI (or the original string if an error is thrown)
*/
var decode = function (hash) {
var decoded;
try {
decoded = decodeURIComponent(hash);
} catch(e) {
decoded = hash;
}
return decoded;
};
/**
* Escape special characters for use with querySelector
* @author Mathias Bynens
* @link https://github.com/mathiasbynens/CSS.escape
* @param {String} id The anchor ID to escape
*/
var escapeCharacters = function (id) {
// Remove leading hash
if (id.charAt(0) === '#') {
id = id.substr(1);
}
var string = String(id);
var length = string.length;
var index = -1;
var codeUnit;
var result = '';
var firstCodeUnit = string.charCodeAt(0);
while (++index < length) {
codeUnit = string.charCodeAt(index);
// Note: theres no need to special-case astral symbols, surrogate
// pairs, or lone surrogates.
// If the character is NULL (U+0000), then throw an
// `InvalidCharacterError` exception and terminate these steps.
if (codeUnit === 0x0000) {
throw new InvalidCharacterError(
'Invalid character: the input contains U+0000.'
);
}
if (
// If the character is in the range [\1-\1F] (U+0001 to U+001F) or is
// U+007F, […]
(codeUnit >= 0x0001 && codeUnit <= 0x001F) || codeUnit == 0x007F ||
// If the character is the first character and is in the range [0-9]
// (U+0030 to U+0039), […]
(index === 0 && codeUnit >= 0x0030 && codeUnit <= 0x0039) ||
// If the character is the second character and is in the range [0-9]
// (U+0030 to U+0039) and the first character is a `-` (U+002D), […]
(
index === 1 &&
codeUnit >= 0x0030 && codeUnit <= 0x0039 &&
firstCodeUnit === 0x002D
)
) {
// http://dev.w3.org/csswg/cssom/#escape-a-character-as-code-point
result += '\\' + codeUnit.toString(16) + ' ';
continue;
}
// If the character is not handled by one of the above rules and is
// greater than or equal to U+0080, is `-` (U+002D) or `_` (U+005F), or
// is in one of the ranges [0-9] (U+0030 to U+0039), [A-Z] (U+0041 to
// U+005A), or [a-z] (U+0061 to U+007A), […]
if (
codeUnit >= 0x0080 ||
codeUnit === 0x002D ||
codeUnit === 0x005F ||
codeUnit >= 0x0030 && codeUnit <= 0x0039 ||
codeUnit >= 0x0041 && codeUnit <= 0x005A ||
codeUnit >= 0x0061 && codeUnit <= 0x007A
) {
// the character itself
result += string.charAt(index);
continue;
}
// Otherwise, the escaped character.
// http://dev.w3.org/csswg/cssom/#escape-a-character
result += '\\' + string.charAt(index);
}
// Return sanitized hash
var hash;
try {
hash = decodeURIComponent('#' + result);
} catch(e) {
hash = '#' + result;
}
return hash;
};
/**
* Calculate the easing pattern
* @link https://gist.github.com/gre/1650294
* @param {String} type Easing pattern
* @param {Number} time Time animation should take to complete
* @returns {Number}
*/
var easingPattern = function (settings, time) {
var pattern;
// Default Easing Patterns
if (settings.easing === 'easeInQuad') pattern = time * time; // accelerating from zero velocity
if (settings.easing === 'easeOutQuad') pattern = time * (2 - time); // decelerating to zero velocity
if (settings.easing === 'easeInOutQuad') pattern = time < 0.5 ? 2 * time * time : -1 + (4 - 2 * time) * time; // acceleration until halfway, then deceleration
if (settings.easing === 'easeInCubic') pattern = time * time * time; // accelerating from zero velocity
if (settings.easing === 'easeOutCubic') pattern = (--time) * time * time + 1; // decelerating to zero velocity
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
if (settings.easing === 'easeInQuart') pattern = time * time * time * time; // accelerating from zero velocity
if (settings.easing === 'easeOutQuart') pattern = 1 - (--time) * time * time * time; // decelerating to zero velocity
if (settings.easing === 'easeInOutQuart') pattern = time < 0.5 ? 8 * time * time * time * time : 1 - 8 * (--time) * time * time * time; // acceleration until halfway, then deceleration
if (settings.easing === 'easeInQuint') pattern = time * time * time * time * time; // accelerating from zero velocity
if (settings.easing === 'easeOutQuint') pattern = 1 + (--time) * time * time * time * time; // decelerating to zero velocity
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
// Custom Easing Patterns
if (!!settings.customEasing) pattern = settings.customEasing(time);
return pattern || time; // no easing, no acceleration
};
/**
* Determine the document's height
* @returns {Number}
*/
var getDocumentHeight = function () {
return Math.max(
document.body.scrollHeight, document.documentElement.scrollHeight,
document.body.offsetHeight, document.documentElement.offsetHeight,
document.body.clientHeight, document.documentElement.clientHeight
);
};
/**
* Calculate how far to scroll
* Clip support added by robjtede - https://github.com/cferdinandi/smooth-scroll/issues/405
* @param {Element} anchor The anchor element to scroll to
* @param {Number} headerHeight Height of a fixed header, if any
* @param {Number} offset Number of pixels by which to offset scroll
* @param {Boolean} clip If true, adjust scroll distance to prevent abrupt stops near the bottom of the page
* @returns {Number}
*/
var getEndLocation = function (anchor, headerHeight, offset, clip) {
var location = 0;
if (anchor.offsetParent) {
do {
location += anchor.offsetTop;
anchor = anchor.offsetParent;
} while (anchor);
}
location = Math.max(location - headerHeight - offset, 0);
if (clip) {
location = Math.min(location, getDocumentHeight() - window.innerHeight);
}
return location;
};
/**
* Get the height of the fixed header
* @param {Node} header The header
* @return {Number} The height of the header
*/
var getHeaderHeight = function (header) {
return !header ? 0 : (getHeight(header) + header.offsetTop);
};
/**
* Update the URL
* @param {Node} anchor The anchor that was scrolled to
* @param {Boolean} isNum If true, anchor is a number
* @param {Object} options Settings for Smooth Scroll
*/
var updateURL = function (anchor, isNum, options) {
// Bail if the anchor is a number
if (isNum) return;
// Verify that pushState is supported and the updateURL option is enabled
if (!history.pushState || !options.updateURL) return;
// Update URL
history.pushState(
{
smoothScroll: JSON.stringify(options),
anchor: anchor.id
},
document.title,
anchor === document.documentElement ? '#top' : '#' + anchor.id
);
};
/**
* Bring the anchored element into focus
* @param {Node} anchor The anchor element
* @param {Number} endLocation The end location to scroll to
* @param {Boolean} isNum If true, scroll is to a position rather than an element
*/
var adjustFocus = function (anchor, endLocation, isNum) {
// Is scrolling to top of page, blur
if (anchor === 0) {
document.body.focus();
}
// Don't run if scrolling to a number on the page
if (isNum) return;
// Otherwise, bring anchor element into focus
anchor.focus();
if (document.activeElement !== anchor) {
anchor.setAttribute('tabindex', '-1');
anchor.focus();
anchor.style.outline = 'none';
}
window.scrollTo(0 , endLocation);
};
/**
* Emit a custom event
* @param {String} type The event type
* @param {Object} options The settings object
* @param {Node} anchor The anchor element
* @param {Node} toggle The toggle element
*/
var emitEvent = function (type, options, anchor, toggle) {
if (!options.emitEvents || typeof window.CustomEvent !== 'function') return;
var event = new CustomEvent(type, {
bubbles: true,
detail: {
anchor: anchor,
toggle: toggle
}
});
document.dispatchEvent(event);
};
//
// SmoothScroll Constructor
//
var SmoothScroll = function (selector, options) {
//
// Variables
//
var smoothScroll = {}; // Object for public APIs
var settings, anchor, toggle, fixedHeader, headerHeight, eventTimeout, animationInterval;
//
// Methods
//
/**
* Cancel a scroll-in-progress
*/
smoothScroll.cancelScroll = function (noEvent) {
cancelAnimationFrame(animationInterval);
animationInterval = null;
if (noEvent) return;
emitEvent('scrollCancel', settings);
};
/**
* Start/stop the scrolling animation
* @param {Node|Number} anchor The element or position to scroll to
* @param {Element} toggle The element that toggled the scroll event
* @param {Object} options
*/
smoothScroll.animateScroll = function (anchor, toggle, options) {
// Local settings
var animateSettings = extend(settings || defaults, options || {}); // Merge user options with defaults
// Selectors and variables
var isNum = Object.prototype.toString.call(anchor) === '[object Number]' ? true : false;
var anchorElem = isNum || !anchor.tagName ? null : anchor;
if (!isNum && !anchorElem) return;
var startLocation = window.pageYOffset; // Current location on the page
if (animateSettings.header && !fixedHeader) {
// Get the fixed header if not already set
fixedHeader = document.querySelector(animateSettings.header);
}
if (!headerHeight) {
// Get the height of a fixed header if one exists and not already set
headerHeight = getHeaderHeight(fixedHeader);
}
var endLocation = isNum ? anchor : getEndLocation(anchorElem, headerHeight, parseInt((typeof animateSettings.offset === 'function' ? animateSettings.offset(anchor, toggle) : animateSettings.offset), 10), animateSettings.clip); // Location to scroll to
var distance = endLocation - startLocation; // distance to travel
var documentHeight = getDocumentHeight();
var timeLapsed = 0;
var start, percentage, position;
/**
* Stop the scroll animation when it reaches its target (or the bottom/top of page)
* @param {Number} position Current position on the page
* @param {Number} endLocation Scroll to location
* @param {Number} animationInterval How much to scroll on this loop
*/
var stopAnimateScroll = function (position, endLocation) {
// Get the current location
var currentLocation = window.pageYOffset;
// Check if the end location has been reached yet (or we've hit the end of the document)
if (position == endLocation || currentLocation == endLocation || ((startLocation < endLocation && window.innerHeight + currentLocation) >= documentHeight)) {
// Clear the animation timer
smoothScroll.cancelScroll(true);
// Bring the anchored element into focus
adjustFocus(anchor, endLocation, isNum);
// Emit a custom event
emitEvent('scrollStop', animateSettings, anchor, toggle);
// Reset start
start = null;
animationInterval = null;
return true;
}
};
/**
* Loop scrolling animation
*/
var loopAnimateScroll = function (timestamp) {
if (!start) { start = timestamp; }
timeLapsed += timestamp - start;
percentage = (timeLapsed / parseInt(animateSettings.speed, 10));
percentage = (percentage > 1) ? 1 : percentage;
position = startLocation + (distance * easingPattern(animateSettings, percentage));
window.scrollTo(0, Math.floor(position));
if (!stopAnimateScroll(position, endLocation)) {
animationInterval = window.requestAnimationFrame(loopAnimateScroll);
start = timestamp;
}
};
/**
* Reset position to fix weird iOS bug
* @link https://github.com/cferdinandi/smooth-scroll/issues/45
*/
if (window.pageYOffset === 0) {
window.scrollTo(0, 0);
}
// Update the URL
updateURL(anchor, isNum, animateSettings);
// Emit a custom event
emitEvent('scrollStart', animateSettings, anchor, toggle);
// Start scrolling animation
smoothScroll.cancelScroll(true);
window.requestAnimationFrame(loopAnimateScroll);
};
/**
* If smooth scroll element clicked, animate scroll
*/
var clickHandler = function (event) {
// Don't run if the user prefers reduced motion
if (reduceMotion(settings)) return;
// Don't run if right-click or command/control + click
if (event.button !== 0 || event.metaKey || event.ctrlKey) return;
// Check if event.target has closest() method
// By @totegi - https://github.com/cferdinandi/smooth-scroll/pull/401/
if(!('closest' in event.target))return;
// Check if a smooth scroll link was clicked
toggle = event.target.closest(selector);
if (!toggle || toggle.tagName.toLowerCase() !== 'a' || event.target.closest(settings.ignore)) return;
// Only run if link is an anchor and points to the current page
if (toggle.hostname !== window.location.hostname || toggle.pathname !== window.location.pathname || !/#/.test(toggle.href)) return;
// Get an escaped version of the hash
var hash = escapeCharacters(decode(toggle.hash));
// Get the anchored element
var anchor = settings.topOnEmptyHash && hash === '#' ? document.documentElement : document.querySelector(hash);
anchor = !anchor && hash === '#top' ? document.documentElement : anchor;
// If anchored element exists, scroll to it
if (!anchor) return;
event.preventDefault();
smoothScroll.animateScroll(anchor, toggle);
};
/**
* Animate scroll on popstate events
*/
var popstateHandler = function (event) {
// Stop if history.state doesn't exist (ex. if clicking on a broken anchor link).
// fixes `Cannot read property 'smoothScroll' of null` error getting thrown.
if (history.state === null) return;
// Only run if state is a popstate record for this instantiation
if (!history.state.smoothScroll || history.state.smoothScroll !== JSON.stringify(settings)) return;
// Only run if state includes an anchor
if (!history.state.anchor) return;
// Get the anchor
var anchor = document.querySelector(escapeCharacters(decode(history.state.anchor)));
if (!anchor) return;
// Animate scroll to anchor link
smoothScroll.animateScroll(anchor, null, {updateURL: false});
};
/**
* On window scroll and resize, only run events at a rate of 15fps for better performance
*/
var resizeThrottler = function (event) {
if (!eventTimeout) {
eventTimeout = setTimeout((function() {
eventTimeout = null; // Reset timeout
headerHeight = getHeaderHeight(fixedHeader); // Get the height of a fixed header if one exists
}), 66);
}
};
/**
* Destroy the current initialization.
*/
smoothScroll.destroy = function () {
// If plugin isn't already initialized, stop
if (!settings) return;
// Remove event listeners
document.removeEventListener('click', clickHandler, false);
window.removeEventListener('resize', resizeThrottler, false);
window.removeEventListener('popstate', popstateHandler, false);
// Cancel any scrolls-in-progress
smoothScroll.cancelScroll();
// Reset variables
settings = null;
anchor = null;
toggle = null;
fixedHeader = null;
headerHeight = null;
eventTimeout = null;
animationInterval = null;
};
/**
* Initialize Smooth Scroll
* @param {Object} options User settings
*/
smoothScroll.init = function (options) {
// feature test
if (!supports()) throw 'Smooth Scroll: This browser does not support the required JavaScript methods and browser APIs.';
// Destroy any existing initializations
smoothScroll.destroy();
// Selectors and variables
settings = extend(defaults, options || {}); // Merge user options with defaults
fixedHeader = settings.header ? document.querySelector(settings.header) : null; // Get the fixed header
headerHeight = getHeaderHeight(fixedHeader);
// When a toggle is clicked, run the click handler
document.addEventListener('click', clickHandler, false);
// If window is resized and there's a fixed header, recalculate its size
if (fixedHeader) {
window.addEventListener('resize', resizeThrottler, false);
}
// If updateURL and popState are enabled, listen for pop events
if (settings.updateURL && settings.popstate) {
window.addEventListener('popstate', popstateHandler, false);
}
};
//
// Initialize plugin
//
smoothScroll.init(options);
//
// Public APIs
//
return smoothScroll;
};
return SmoothScroll;
}));
/* GP */
var gpscroll = new SmoothScroll( gpSmoothScroll.elements.join(), {
speed: gpSmoothScroll.duration,
offset: function( anchor, toggle ) {
var body = document.body,
nav = document.querySelector( '#site-navigation' ),
stickyNav = document.querySelector( '#sticky-navigation' ),
mobileHeader = document.querySelector( '#mobile-header' ),
menuToggle = document.querySelector( '.menu-toggle' ),
offset = 0;
if ( mobileHeader && ( mobileHeader.offsetWidth || mobileHeader.offsetHeight || mobileHeader.getClientRects().length ) ) {
if ( body.classList.contains( 'mobile-header-sticky' ) ) {
offset = offset + mobileHeader.clientHeight;
}
} else if ( menuToggle && ( menuToggle.offsetWidth || menuToggle.offsetHeight || menuToggle.getClientRects().length ) ) {
if ( body.classList.contains( 'both-sticky-menu' ) || body.classList.contains( 'mobile-sticky-menu' ) ) {
if ( stickyNav ) {
offset = offset + stickyNav.clientHeight;
} else if ( nav ) {
offset = offset + nav.clientHeight;
}
}
} else if ( body.classList.contains( 'both-sticky-menu' ) || body.classList.contains( 'desktop-sticky-menu' ) ) {
if ( stickyNav ) {
offset = offset + stickyNav.clientHeight;
} else if ( nav ) {
offset = offset + nav.clientHeight;
}
}
return gpSmoothScroll.offset
? gpSmoothScroll.offset
: offset;
}
} );