Current File : /home/natitnen/elitepathways.com.ng/wp-content/plugins/smoother/js/smoother.js |
/**
* Smoother
* Smoother - Smooth scrolling experience for WordPress Websites.
* Exclusively on https://1.envato.market/smoother
*
* @encoding UTF-8
* @version 2.0.3
* @copyright (C) 2018 - 2021 Merkulove ( https://merkulov.design/ ). All rights reserved.
* @license Envato License https://1.envato.market/KYbje
* @contributors Nemirovskiy Vitaliy (nemirovskiyvitaliy@gmail.com), Dmitry Merkulov (dmitry@merkulov.design)
* @support help@merkulov.design
**/
//
// SmoothScroll for websites v1.4.9 (Balazs Galambosi)
// http://www.smoothscroll.net/
//
// Licensed under the terms of the MIT license.
//
// You may use it in your theme if you credit me.
// It is also free to use on any individual website.
//
// Exception:
// The only restriction is to not publish any
// extension for browsers or native application
// without getting a written permission first.
//
(function () {
"use strict";
const defaultOptions = {
// Scrolling Core
frameRate: 150, // [Hz]
animationTime: 400, // [ms]
stepSize: 100, // [px]
// Pulse (less tweakable)
// ratio of "tail" to "acceleration"
pulseAlgorithm: true,
pulseScale: 4,
pulseNormalize: 1,
// Acceleration
accelerationDelta: 50, // 50
accelerationMax: 3, // 3
// Keyboard Settings
keyboardSupport: true, // option
arrowScroll: 50, // [px]
// Other
fixedBackground: true,
excluded: ''
};
let options = defaultOptions;
// Other Variables
const isExcluded = false;
let isFrame = false;
const direction = {x: 0, y: 0};
let initDone = false;
let root = document.documentElement;
let activeElement;
let observer;
let refreshSize;
let deltaBuffer = [];
let deltaBufferTimer;
const isMac = /^Mac/.test( navigator.platform );
const isItSafari = (navigator.userAgent.toLowerCase().includes( 'safari' ) && ! navigator.userAgent.toLowerCase().includes( 'chrome' ));
const isFireFox = navigator.userAgent.toLowerCase().includes( 'firefox' );
const key = {
left: 37, up: 38, right: 39, down: 40, spacebar: 32,
pageup: 33, pagedown: 34, end: 35, home: 36
};
const arrowKeys = {37: 1, 38: 1, 39: 1, 40: 1};
/***********************************************
* INITIALIZE
***********************************************/
/**
* Tests if smooth scrolling is allowed. Shuts down everything if not.
*/
function initTest() {
if (options.keyboardSupport) {
addEvent('keydown', keydown);
}
}
/**
* Sets up scrolls array, determines if frames are involved.
*/
function init() {
if (initDone || !document.body) return;
initDone = true;
const body = document.body;
const html = document.documentElement;
const windowHeight = window.innerHeight;
const scrollHeight = body.scrollHeight;
// check compat mode for root element
root = (document.compatMode.indexOf('CSS') >= 0) ? html : body;
activeElement = body;
initTest();
// Checks if this script is running in a frame
if (top != self) {
isFrame = true;
}
/**
* Safari 10 fixed it, Chrome fixed it in v45:
* This fixes a bug where the areas left and right to
* the content does not trigger the onmousewheel event
* on some pages. e.g.: html, body { height: 100% }
*/
else if (isOldSafari &&
scrollHeight > windowHeight &&
(body.offsetHeight <= windowHeight ||
html.offsetHeight <= windowHeight)) {
const fullPageElem = document.createElement( 'div' );
fullPageElem.style.cssText = 'position:absolute; z-index:-10000; ' +
'top:0; left:0; right:0; height:' +
root.scrollHeight + 'px';
document.body.appendChild(fullPageElem);
// DOM changed (throttled) to fix height
let pendingRefresh;
refreshSize = function () {
if (pendingRefresh) return; // could also be: clearTimeout(pendingRefresh);
pendingRefresh = setTimeout(function () {
if (isExcluded) return; // could be running after cleanup
fullPageElem.style.height = '0';
fullPageElem.style.height = root.scrollHeight + 'px';
pendingRefresh = null;
}, 500); // act rarely to stay fast
};
setTimeout(refreshSize, 10);
addEvent('resize', refreshSize);
// TODO: attributeFilter?
const config = {
attributes: true,
childList: true,
characterData: false
// subtree: true
};
observer = new MutationObserver(refreshSize);
observer.observe(body, config);
if (root.offsetHeight <= windowHeight) {
const clearfix = document.createElement( 'div' );
clearfix.style.clear = 'both';
body.appendChild(clearfix);
}
}
// disable fixed background
if (!options.fixedBackground && !isExcluded) {
body.style.backgroundAttachment = 'scroll';
html.style.backgroundAttachment = 'scroll';
}
}
/**
* Removes event listeners and other traces left on the page.
*/
function cleanup() {
observer && observer.disconnect();
removeEvent(wheelEvent, wheel);
removeEvent('mousedown', mousedown);
removeEvent('keydown', keydown);
removeEvent('resize', refreshSize);
removeEvent('load', init);
}
/**
* Add event listeners and other traces left on the page.
*/
function restart() {
observer && observer.observe();
addEvent(wheelEvent, wheel, { passive: false } );
addEvent('mousedown', mousedown);
addEvent('keydown', keydown);
addEvent('resize', refreshSize);
addEvent('load', init);
}
/************************************************
* SCROLLING
************************************************/
let que = [];
let pending = false;
let lastScroll = Date.now();
/**
* Pushes scroll actions to the scrolling queue.
*/
function scrollArray( elem, left, top ) {
directionCheck( left, top );
if (options.accelerationMax !== 1) {
const now = Date.now();
const elapsed = now - lastScroll;
if (elapsed < options.accelerationDelta) {
let factor = (1 + (50 / elapsed)) / 2;
if (factor > 1) {
factor = Math.min(factor, options.accelerationMax);
left *= factor;
top *= factor;
}
}
lastScroll = Date.now();
}
// push a scroll command
que.push({
x: left,
y: top,
lastX: (left < 0) ? 0.99 : -0.99,
lastY: (top < 0) ? 0.99 : -0.99,
start: Date.now()
});
// don't act if there's a pending queue
if (pending) {
return;
}
const scrollRoot = getScrollRoot();
const isWindowScroll = (elem === scrollRoot || elem === document.body);
// if we haven't already fixed the behavior,
// and it needs fixing for this sesh
if (elem.$scrollBehavior == null && isScrollBehaviorSmooth(elem)) {
elem.$scrollBehavior = elem.style.scrollBehavior;
elem.style.scrollBehavior = 'auto';
}
const step = function ( time ) {
const now = Date.now();
let scrollX = 0;
let scrollY = 0;
for ( let i = 0; i < que.length; i++ ) {
const item = que[i];
const elapsed = now - item.start;
const finished = (elapsed >= options.animationTime);
// scroll position: [0, 1]
let position = (finished) ? 1 : elapsed / options.animationTime;
// easing [optional]
if ( options.pulseAlgorithm ) {
position = pulse( position );
}
// only need the difference
const x = (item.x * position - item.lastX) >> 0;
const y = (item.y * position - item.lastY) >> 0;
// add this to the total scrolling
scrollX += x;
scrollY += y;
// update last values
item.lastX += x;
item.lastY += y;
// delete and step back if it's over
if ( finished ) {
que.splice( i, 1 );
i--;
}
}
// scroll left and top
if ( isWindowScroll ) {
window.scrollBy( scrollX, scrollY );
} else {
if ( scrollX ) elem.scrollLeft += scrollX;
if ( scrollY ) elem.scrollTop += scrollY;
}
// clean up if there's nothing left to do
if ( ! left && ! top ) {
que = [];
}
if ( que.length ) {
requestFrame( step, elem, (1000 / options.frameRate + 1) );
} else {
pending = false;
// restore default behavior at the end of scrolling sesh
if ( elem.$scrollBehavior != null ) {
elem.style.scrollBehavior = elem.$scrollBehavior;
elem.$scrollBehavior = null;
}
}
};
// start a new queue of actions
requestFrame(step, elem, 0);
pending = true;
}
/***********************************************
* EVENTS
***********************************************/
/**
* Mouse wheel handler.
* @param {Object} event
*/
function wheel(event) {
// Disable TouchPad Smooth scrolling
const isTouchPad = event.wheelDeltaY ? event.wheelDeltaY === -3 * event.deltaY : event.deltaMode === 0;
if ( isTouchPad ) return;
if (!initDone) {
init();
}
const target = event.target;
// leave early if default action is prevented
// or it's a zooming event with CTRL
if (event.defaultPrevented || event.ctrlKey) {
return true;
}
// leave embedded content alone (flash & pdf)
if (isNodeName(activeElement, 'embed') ||
(isNodeName(target, 'embed') && /\.pdf/i.test(target.src)) ||
isNodeName(activeElement, 'object') ||
target.shadowRoot) {
return true;
}
let deltaX = -event.wheelDeltaX || event.deltaX || 0;
let deltaY = -event.wheelDeltaY || event.deltaY || 0;
// Disable macOS wheel acceleration for Safari
if (isItSafari) {
deltaY = 10*deltaY;
if ( Math.abs(deltaY) > 120 ) {
if ( deltaY > 0 ) {
deltaY = 120;
} else {
deltaY = -120;
}
}
}
if ( isMac ) {
if (event.wheelDeltaX && isDivisible(event.wheelDeltaX, 120)) {
deltaX = -120 * (event.wheelDeltaX / Math.abs(event.wheelDeltaX));
}
if (event.wheelDeltaY && isDivisible(event.wheelDeltaY, 120)) {
deltaY = -120 * (event.wheelDeltaY / Math.abs(event.wheelDeltaY));
}
}
// use wheelDelta if deltaX/Y is not available
if (!deltaX && !deltaY) {
deltaY = -event.wheelDelta || 0;
}
// line based scrolling (Firefox mostly)
if (event.deltaMode === 1) {
deltaX *= 40;
deltaY *= 40;
}
const overflowing = overflowingAncestor( target );
// nothing to do if there's no element that's scrollable
if (!overflowing) {
// except Chrome iframes seem to eat wheel events, which we need to
// propagate up, if the iframe has nothing overflowing to scroll
if (isFrame && isChrome) {
// change target to iframe element itself for the parent frame
Object.defineProperty(event, "target", {value: window.frameElement});
return parent.wheel(event);
}
return true;
}
// check if it's a touchpad scroll that should be ignored
if ( isTouchpad(deltaY) ) {
return true;
}
// scale by step size
// delta is 120 most of the time
// synaptics seems to send 1 sometimes
if (Math.abs(deltaX) > 1.2) {
deltaX *= options.stepSize / 120;
}
if (Math.abs(deltaY) > 1.2) {
deltaY *= options.stepSize / 120;
}
scrollArray(overflowing, deltaX, deltaY);
event.preventDefault();
scheduleClearCache();
}
/**
* Keydown event handler.
* @param {Object} event
*/
function keydown(event) {
const target = event.target;
const modifier = event.ctrlKey || event.altKey || event.metaKey ||
(event.shiftKey && event.keyCode !== key.spacebar);
// our own tracked active element could've been removed from the DOM
if (!document.body.contains(activeElement)) {
activeElement = document.activeElement;
}
// do nothing if user is editing text
// or using a modifier key (except shift)
// or in a dropdown
// or inside interactive elements
const inputNodeNames = /^(textarea|select|embed|object)$/i;
const buttonTypes = /^(button|submit|radio|checkbox|file|color|image)$/i;
if ( event.defaultPrevented ||
inputNodeNames.test(target.nodeName) ||
isNodeName(target, 'input') && !buttonTypes.test(target.type) ||
isNodeName(activeElement, 'video') ||
isInsideYoutubeVideo(event) ||
target.isContentEditable ||
modifier ) {
return true;
}
// [spacebar] should trigger button press, leave it alone
if ((isNodeName(target, 'button') ||
isNodeName(target, 'input') && buttonTypes.test(target.type)) &&
event.keyCode === key.spacebar) {
return true;
}
// [arrwow keys] on radio buttons should be left alone
if (isNodeName(target, 'input') && target.type == 'radio' &&
arrowKeys[event.keyCode]) {
return true;
}
let shift, x = 0, y = 0;
let overflowing = overflowingAncestor( activeElement );
if (!overflowing) {
// Chrome iframes seem to eat key events, which we need to
// propagate up, if the iframe has nothing overflowing to scroll
return (isFrame && isChrome) ? parent.keydown(event) : true;
}
let clientHeight = overflowing.clientHeight;
if (overflowing == document.body) {
clientHeight = window.innerHeight;
}
switch (event.keyCode) {
case key.up:
y = -options.arrowScroll;
break;
case key.down:
y = options.arrowScroll;
break;
case key.spacebar: // (+ shift)
shift = event.shiftKey ? 1 : -1;
y = -shift * clientHeight * 0.9;
break;
case key.pageup:
y = -clientHeight * 0.9;
break;
case key.pagedown:
y = clientHeight * 0.9;
break;
case key.home:
if (overflowing == document.body && document.scrollingElement)
overflowing = document.scrollingElement;
y = -overflowing.scrollTop;
break;
case key.end:
const scroll = overflowing.scrollHeight - overflowing.scrollTop;
const scrollRemaining = scroll - clientHeight;
y = (scrollRemaining > 0) ? scrollRemaining + 10 : 0;
break;
case key.left:
x = -options.arrowScroll;
break;
case key.right:
x = options.arrowScroll;
break;
default:
return true; // a key we don't care about
}
scrollArray(overflowing, x, y);
event.preventDefault();
scheduleClearCache();
}
/**
* Mousedown event only for updating activeElement
*/
function mousedown(event) {
activeElement = event.target;
}
/***********************************************
* OVERFLOW
***********************************************/
const uniqueID = (function () {
let i = 0;
return function ( el ) {
return el.uniqueID || (el.uniqueID = i++);
};
})();
let cacheX = {}; // cleared out after a scrolling session
let cacheY = {}; // cleared out after a scrolling session
let clearCacheTimer;
let smoothBehaviorForElement = {};
//setInterval(function () { cache = {}; }, 10 * 1000);
function scheduleClearCache() {
clearTimeout(clearCacheTimer);
/** Never clean cache for the Scroller */
if ( typeof document.getElementById( 'scroller-body' ) === "undefined" ) {
clearCacheTimer = setInterval(function () {
cacheX = cacheY = smoothBehaviorForElement = {};
}, 1000);
}
}
function setCache(elems, overflowing, x) {
const cache = x ? cacheX : cacheY;
for ( let i = elems.length; i--;)
cache[uniqueID(elems[i])] = overflowing;
return overflowing;
}
function getCache(el, x) {
return (x ? cacheX : cacheY)[uniqueID(el)];
}
// (body) (root)
// | hidden | visible | scroll | auto |
// hidden | no | no | YES | YES |
// visible | no | YES | YES | YES |
// scroll | no | YES | YES | YES |
// auto | no | YES | YES | YES |
function overflowingAncestor(el) {
const elems = [];
const body = document.body;
const rootScrollHeight = root.scrollHeight;
do {
const cached = getCache( el, false );
if (cached) {
return setCache(elems, cached);
}
elems.push(el);
if (rootScrollHeight === el.scrollHeight) {
const topOverflowsNotHidden = overflowNotHidden( root ) && overflowNotHidden( body );
const isOverflowCSS = topOverflowsNotHidden || overflowAutoOrScroll( root );
if (isFrame && isContentOverflowing(root) ||
!isFrame && isOverflowCSS) {
return setCache(elems, getScrollRoot());
}
} else if (isContentOverflowing(el) && overflowAutoOrScroll(el)) {
return setCache(elems, el);
}
} while ((el = el.parentElement));
}
function isContentOverflowing(el) {
return (el.clientHeight + 10 < el.scrollHeight);
}
// typically for <body> and <html>
function overflowNotHidden(el) {
const overflow = getComputedStyle( el, '' ).getPropertyValue( 'overflow-y' );
return (overflow !== 'hidden');
}
// for all other elements
function overflowAutoOrScroll(el) {
const overflow = getComputedStyle( el, '' ).getPropertyValue( 'overflow-y' );
return (overflow === 'scroll' || overflow === 'auto');
}
// for all other elements
function isScrollBehaviorSmooth(el) {
const id = uniqueID( el );
if (smoothBehaviorForElement[id] == null) {
const scrollBehavior = getComputedStyle( el, '' )['scroll-behavior'];
smoothBehaviorForElement[id] = ('smooth' == scrollBehavior);
}
return smoothBehaviorForElement[id];
}
/***********************************************
* HELPERS
***********************************************/
function addEvent(type, fn, arg) {
if ( document.getElementById( 'scroller-body' ) !== null ) {
/** Add eventListeners for Scroller plugin */
document.getElementById( 'scroller-body' ).addEventListener(type, fn, arg || false);
} else {
/** Add eventListeners */
window.addEventListener(type, fn, arg || false);
}
}
function removeEvent(type, fn, arg) {
if ( document.getElementById( 'scroller-body' ) !== null ) {
/** Remove eventListeners for Scroller plugin */
document.getElementById( 'scroller-body' ).addEventListener(type, fn, arg || false);
} else {
/** Remove eventListeners */
window.removeEventListener(type, fn, arg || false);
}
}
function isNodeName(el, tag) {
return el && (el.nodeName||'').toLowerCase() === tag.toLowerCase();
}
function directionCheck(x, y) {
x = (x > 0) ? 1 : -1;
y = (y > 0) ? 1 : -1;
if (direction.x !== x || direction.y !== y) {
direction.x = x;
direction.y = y;
que = [];
lastScroll = 0;
}
}
if (window.localStorage && localStorage.SS_deltaBuffer) {
try { // #46 Safari throws in private browsing for localStorage
deltaBuffer = localStorage.SS_deltaBuffer.split(',');
} catch (e) { }
}
/**
* Detect touchpad screen
* @param deltaY
* @return {boolean}
*/
function isTouchpad( deltaY ) {
if ( !deltaY ) return;
if ( !deltaBuffer.length ) {
deltaBuffer = [deltaY, deltaY, deltaY];
}
deltaY = Math.abs(deltaY);
deltaBuffer.push(deltaY);
deltaBuffer.shift();
clearTimeout(deltaBufferTimer);
deltaBufferTimer = setTimeout( function () {
try { // #46 Safari throws in private browsing for localStorage
localStorage.SS_deltaBuffer = deltaBuffer.join( ',' );
} catch (e) { }
}, 1000 );
const dpiScaledWheelDelta = deltaY > 120 && allDeltasDivisableBy(deltaY); // win64
let allDeltasDivisableBy100 = allDeltasDivisableBy(100);
let allDeltasDivisableBy120 = allDeltasDivisableBy(120);
if ( isFireFox && isMac ) { allDeltasDivisableBy120 = true; } // Firefox MacOS Scrolling Fix.\\
return !allDeltasDivisableBy120 && !allDeltasDivisableBy100 && !dpiScaledWheelDelta;
}
function isDivisible(n, divisor) {
return (Math.floor(n / divisor) == n / divisor);
}
function allDeltasDivisableBy(divisor) {
return (isDivisible(deltaBuffer[0], divisor) &&
isDivisible(deltaBuffer[1], divisor) &&
isDivisible(deltaBuffer[2], divisor));
}
function isInsideYoutubeVideo(event) {
let elem = event.target;
let isControl = false;
if (document.URL.indexOf ('www.youtube.com/watch') != -1) {
do {
isControl = (elem.classList &&
elem.classList.contains('html5-video-controls'));
if (isControl) break;
} while ((elem = elem.parentNode));
}
return isControl;
}
var requestFrame = (function () {
return (window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
function (callback, element, delay) {
window.setTimeout(callback, delay || (1000/60));
});
})();
var MutationObserver = (window.MutationObserver ||
window.WebKitMutationObserver ||
window.MozMutationObserver);
var getScrollRoot = (function() {
let SCROLL_ROOT = document.scrollingElement;
return function() {
if (!SCROLL_ROOT) {
const dummy = document.createElement( 'div' );
dummy.style.cssText = 'height:10000px;width:1px;';
document.body.appendChild(dummy);
const bodyScrollTop = document.body.scrollTop;
const docElScrollTop = document.documentElement.scrollTop;
window.scrollBy(0, 3);
if (document.body.scrollTop != bodyScrollTop)
(SCROLL_ROOT = document.body);
else
(SCROLL_ROOT = document.documentElement);
window.scrollBy(0, -3);
document.body.removeChild(dummy);
}
return SCROLL_ROOT;
};
})();
/***********************************************
* PULSE (by Michael Herf)
***********************************************/
/**
* Viscous fluid with a pulse for part and decay for the rest.
* - Applies a fixed force over an interval (a damped acceleration), and
* - Lets the exponential bleed away the velocity over a longer interval
* - Michael Herf, http://stereopsis.com/stopping/
*/
function pulse_(x) {
let val, start, expx;
// test
x = x * options.pulseScale;
if (x < 1) { // acceleartion
val = x - (1 - Math.exp(-x));
} else { // tail
// the previous animation ended here:
start = Math.exp(-1);
// simple viscous drag
x -= 1;
expx = 1 - Math.exp(-x);
val = start + (expx * (1 - start));
}
return val * options.pulseNormalize;
}
function pulse(x) {
if (x >= 1) return 1;
if (x <= 0) return 0;
if (options.pulseNormalize == 1) {
options.pulseNormalize /= pulse_(1);
}
return pulse_(x);
}
/***********************************************
* FIRST RUN
***********************************************/
const userAgent = window.navigator.userAgent;
const isEdge = /Edge/.test(userAgent); // thank you MS
const isChrome = /chrome/i.test(userAgent) && !isEdge;
const isSafari = /safari/i.test(userAgent) && !isEdge;
const isMobile = /mobile/i.test(userAgent);
const isIEWin7 = /Windows NT 6.1/i.test(userAgent) && /rv:11/i.test(userAgent);
const isOldSafari = isSafari && (/Version\/8/i.test(userAgent) || /Version\/9/i.test(userAgent));
const isEnabledForBrowser = ( isChrome || isSafari || isIEWin7 || window.mdSmootherAllBrowsers ) && !isMobile;
let supportsPassive = false;
try {
window.addEventListener("test", null, Object.defineProperty({}, 'passive', {
get: function () {
supportsPassive = true;
}
}));
} catch(e) {}
const wheelOpt = supportsPassive ? {passive: false} : false;
var wheelEvent = 'onwheel' in document.createElement('div') ? 'wheel' : 'mousewheel';
if (wheelEvent && isEnabledForBrowser) {
addEvent(wheelEvent, wheel, wheelOpt);
addEvent('mousedown', mousedown);
addEvent('load', init);
}
/***********************************************
* PUBLIC INTERFACE
***********************************************/
function SmoothScroll(optionsToSet) {
const { anchor } = optionsToSet;
/** Smooth scrolling to anchor */
if ( anchor ) {
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', function (e) {
e.preventDefault();
document.querySelector(this.getAttribute('href')).scrollIntoView({
behavior: 'smooth'
});
});
});
}
for ( let key in optionsToSet)
if (defaultOptions.hasOwnProperty(key))
options[key] = optionsToSet[key];
}
SmoothScroll.destroy = cleanup;
SmoothScroll.restart = restart;
if (window.SmoothScrollOptions) // async API
SmoothScroll(window.SmoothScrollOptions);
if (typeof define === 'function' && define.amd)
define(function() {
return SmoothScroll;
});
else if ('object' == typeof exports)
module.exports = SmoothScroll;
else
window.SmoothScroll = SmoothScroll;
})();