1281 lines
53 KiB
JavaScript
1281 lines
53 KiB
JavaScript
/*!
|
|
* # Fomantic-UI 2.9.2 - Visibility
|
|
* https://github.com/fomantic/Fomantic-UI/
|
|
*
|
|
*
|
|
* Released under the MIT license
|
|
* https://opensource.org/licenses/MIT
|
|
*
|
|
*/
|
|
|
|
(function ($, window, document) {
|
|
'use strict';
|
|
|
|
function isFunction(obj) {
|
|
return typeof obj === 'function' && typeof obj.nodeType !== 'number';
|
|
}
|
|
|
|
window = window !== undefined && window.Math === Math
|
|
? window
|
|
: globalThis;
|
|
|
|
$.fn.visibility = function (parameters) {
|
|
var
|
|
$allModules = $(this),
|
|
moduleSelector = $allModules.selector || '',
|
|
|
|
time = Date.now(),
|
|
performance = [],
|
|
|
|
query = arguments[0],
|
|
methodInvoked = typeof query === 'string',
|
|
queryArguments = [].slice.call(arguments, 1),
|
|
returnedValue,
|
|
|
|
moduleCount = $allModules.length,
|
|
loadedCount = 0
|
|
;
|
|
|
|
$allModules.each(function () {
|
|
var
|
|
settings = $.isPlainObject(parameters)
|
|
? $.extend(true, {}, $.fn.visibility.settings, parameters)
|
|
: $.extend({}, $.fn.visibility.settings),
|
|
|
|
className = settings.className,
|
|
namespace = settings.namespace,
|
|
error = settings.error,
|
|
metadata = settings.metadata,
|
|
|
|
eventNamespace = '.' + namespace,
|
|
moduleNamespace = 'module-' + namespace,
|
|
|
|
$window = $(window),
|
|
|
|
$module = $(this),
|
|
$context = [window, document].indexOf(settings.context) < 0 ? $(document).find(settings.context) : $(settings.context),
|
|
|
|
$placeholder,
|
|
|
|
instance = $module.data(moduleNamespace),
|
|
|
|
element = this,
|
|
disabled = false,
|
|
|
|
contextObserver,
|
|
observer,
|
|
module
|
|
;
|
|
|
|
module = {
|
|
|
|
initialize: function () {
|
|
module.debug('Initializing', settings);
|
|
|
|
module.setup.cache();
|
|
|
|
if (module.should.trackChanges()) {
|
|
if (settings.type === 'image') {
|
|
module.setup.image();
|
|
}
|
|
if (settings.type === 'fixed') {
|
|
module.setup.fixed();
|
|
}
|
|
|
|
if (settings.observeChanges) {
|
|
module.observeChanges();
|
|
}
|
|
module.bind.events();
|
|
}
|
|
|
|
module.save.position();
|
|
if (!module.is.visible()) {
|
|
module.error(error.visible, $module);
|
|
}
|
|
|
|
if (settings.initialCheck) {
|
|
module.checkVisibility();
|
|
}
|
|
module.instantiate();
|
|
},
|
|
|
|
instantiate: function () {
|
|
module.debug('Storing instance', module);
|
|
$module
|
|
.data(moduleNamespace, module)
|
|
;
|
|
instance = module;
|
|
},
|
|
|
|
destroy: function () {
|
|
module.verbose('Destroying previous module');
|
|
if (observer) {
|
|
observer.disconnect();
|
|
}
|
|
if (contextObserver) {
|
|
contextObserver.disconnect();
|
|
}
|
|
$window
|
|
.off('load' + eventNamespace, module.event.load)
|
|
.off('resize' + eventNamespace, module.event.resize)
|
|
;
|
|
$context
|
|
.off('scroll' + eventNamespace, module.event.scroll)
|
|
.off('scrollchange' + eventNamespace, module.event.scrollchange)
|
|
;
|
|
if (settings.type === 'fixed') {
|
|
module.resetFixed();
|
|
module.remove.placeholder();
|
|
}
|
|
$module
|
|
.off(eventNamespace)
|
|
.removeData(moduleNamespace)
|
|
;
|
|
},
|
|
|
|
observeChanges: function () {
|
|
if ('MutationObserver' in window) {
|
|
contextObserver = new MutationObserver(module.event.contextChanged);
|
|
observer = new MutationObserver(module.event.changed);
|
|
contextObserver.observe(document, {
|
|
childList: true,
|
|
subtree: true,
|
|
});
|
|
observer.observe(element, {
|
|
childList: true,
|
|
subtree: true,
|
|
});
|
|
module.debug('Setting up mutation observer', observer);
|
|
}
|
|
},
|
|
|
|
bind: {
|
|
events: function () {
|
|
module.verbose('Binding visibility events to scroll and resize');
|
|
if (settings.refreshOnLoad) {
|
|
$window
|
|
.on('load' + eventNamespace, module.event.load)
|
|
;
|
|
}
|
|
$window
|
|
.on('resize' + eventNamespace, module.event.resize)
|
|
;
|
|
// pub/sub pattern
|
|
$context
|
|
.off('scroll' + eventNamespace)
|
|
.on('scroll' + eventNamespace, module.event.scroll)
|
|
.on('scrollchange' + eventNamespace, module.event.scrollchange)
|
|
;
|
|
},
|
|
},
|
|
|
|
event: {
|
|
changed: function (mutations) {
|
|
module.verbose('DOM tree modified, updating visibility calculations');
|
|
module.timer = setTimeout(function () {
|
|
module.verbose('DOM tree modified, updating sticky menu');
|
|
module.refresh();
|
|
}, 100);
|
|
},
|
|
contextChanged: function (mutations) {
|
|
[].forEach.call(mutations, function (mutation) {
|
|
if (mutation.removedNodes) {
|
|
[].forEach.call(mutation.removedNodes, function (node) {
|
|
if (node === element || $(node).find(element).length > 0) {
|
|
module.debug('Element removed from DOM, tearing down events');
|
|
module.destroy();
|
|
}
|
|
});
|
|
}
|
|
});
|
|
},
|
|
resize: function () {
|
|
module.debug('Window resized');
|
|
if (settings.refreshOnResize) {
|
|
requestAnimationFrame(module.refresh);
|
|
}
|
|
},
|
|
load: function () {
|
|
module.debug('Page finished loading');
|
|
requestAnimationFrame(module.refresh);
|
|
},
|
|
// publishes scrollchange event on one scroll
|
|
scroll: function () {
|
|
if (settings.throttle) {
|
|
clearTimeout(module.timer);
|
|
module.timer = setTimeout(function () {
|
|
$context.triggerHandler('scrollchange' + eventNamespace, [$context.scrollTop()]);
|
|
}, settings.throttle);
|
|
} else {
|
|
requestAnimationFrame(function () {
|
|
$context.triggerHandler('scrollchange' + eventNamespace, [$context.scrollTop()]);
|
|
});
|
|
}
|
|
},
|
|
// subscribes to scrollchange
|
|
scrollchange: function (event, scrollPosition) {
|
|
module.checkVisibility(scrollPosition);
|
|
},
|
|
},
|
|
|
|
precache: function (images, callback) {
|
|
if (!Array.isArray(images)) {
|
|
images = [images];
|
|
}
|
|
var
|
|
imagesLength = images.length,
|
|
loadedCounter = 0,
|
|
cache = [],
|
|
cacheImage = document.createElement('img'),
|
|
handleLoad = function () {
|
|
loadedCounter++;
|
|
if (loadedCounter >= images.length) {
|
|
if (isFunction(callback)) {
|
|
callback();
|
|
}
|
|
}
|
|
}
|
|
;
|
|
while (imagesLength--) {
|
|
cacheImage = document.createElement('img');
|
|
cacheImage.addEventListener('load', handleLoad);
|
|
cacheImage.addEventListener('error', handleLoad);
|
|
cacheImage.src = images[imagesLength];
|
|
cache.push(cacheImage);
|
|
}
|
|
},
|
|
|
|
enableCallbacks: function () {
|
|
module.debug('Allowing callbacks to occur');
|
|
disabled = false;
|
|
},
|
|
|
|
disableCallbacks: function () {
|
|
module.debug('Disabling all callbacks temporarily');
|
|
disabled = true;
|
|
},
|
|
|
|
should: {
|
|
trackChanges: function () {
|
|
if (methodInvoked) {
|
|
module.debug('One time query, no need to bind events');
|
|
|
|
return false;
|
|
}
|
|
module.debug('Callbacks being attached');
|
|
|
|
return true;
|
|
},
|
|
},
|
|
|
|
setup: {
|
|
cache: function () {
|
|
module.cache = {
|
|
occurred: {},
|
|
screen: {},
|
|
element: {},
|
|
};
|
|
},
|
|
image: function () {
|
|
var
|
|
src = $module.data(metadata.src)
|
|
;
|
|
if (src) {
|
|
module.verbose('Lazy loading image', src);
|
|
settings.once = true;
|
|
settings.observeChanges = false;
|
|
|
|
// show when top visible
|
|
settings.onOnScreen = function () {
|
|
module.debug('Image on screen', element);
|
|
module.precache(src, function () {
|
|
module.set.image(src, function () {
|
|
loadedCount++;
|
|
if (loadedCount === moduleCount) {
|
|
settings.onAllLoaded.call(this);
|
|
}
|
|
settings.onLoad.call(this);
|
|
});
|
|
});
|
|
};
|
|
}
|
|
},
|
|
fixed: function () {
|
|
module.debug('Setting up fixed');
|
|
settings.once = false;
|
|
settings.observeChanges = false;
|
|
settings.initialCheck = true;
|
|
settings.refreshOnLoad = true;
|
|
if (!parameters.transition) {
|
|
settings.transition = false;
|
|
}
|
|
module.create.placeholder();
|
|
module.debug('Added placeholder', $placeholder);
|
|
settings.onTopPassed = function () {
|
|
module.debug('Element passed, adding fixed position', $module);
|
|
module.show.placeholder();
|
|
module.set.fixed();
|
|
if (settings.transition) {
|
|
if ($.fn.transition !== undefined) {
|
|
$module.transition(settings.transition, settings.duration);
|
|
}
|
|
}
|
|
};
|
|
settings.onTopPassedReverse = function () {
|
|
module.debug('Element returned to position, removing fixed', $module);
|
|
module.hide.placeholder();
|
|
module.remove.fixed();
|
|
};
|
|
},
|
|
},
|
|
|
|
create: {
|
|
placeholder: function () {
|
|
module.verbose('Creating fixed position placeholder');
|
|
$placeholder = $module
|
|
.clone(false)
|
|
.css('display', 'none')
|
|
.addClass(className.placeholder)
|
|
.insertAfter($module)
|
|
;
|
|
},
|
|
},
|
|
|
|
show: {
|
|
placeholder: function () {
|
|
module.verbose('Showing placeholder');
|
|
$placeholder
|
|
.css('display', 'block')
|
|
.css('visibility', 'hidden')
|
|
;
|
|
},
|
|
},
|
|
hide: {
|
|
placeholder: function () {
|
|
module.verbose('Hiding placeholder');
|
|
$placeholder
|
|
.css('display', 'none')
|
|
.css('visibility', '')
|
|
;
|
|
},
|
|
},
|
|
|
|
set: {
|
|
fixed: function () {
|
|
module.verbose('Setting element to fixed position');
|
|
$module
|
|
.addClass(className.fixed)
|
|
.css({
|
|
position: 'fixed',
|
|
top: settings.offset + 'px',
|
|
left: 'auto',
|
|
zIndex: settings.zIndex,
|
|
})
|
|
;
|
|
settings.onFixed.call(element);
|
|
},
|
|
image: function (src, callback) {
|
|
$module
|
|
.attr('src', src)
|
|
;
|
|
if (settings.transition) {
|
|
if ($.fn.transition !== undefined) {
|
|
if ($module.hasClass(className.visible)) {
|
|
module.debug('Transition already occurred on this image, skipping animation');
|
|
|
|
return;
|
|
}
|
|
$module.transition(settings.transition, settings.duration, callback);
|
|
} else {
|
|
$module.fadeIn(settings.duration, callback);
|
|
}
|
|
} else {
|
|
$module.show();
|
|
}
|
|
},
|
|
},
|
|
|
|
is: {
|
|
onScreen: function () {
|
|
var
|
|
calculations = module.get.elementCalculations()
|
|
;
|
|
|
|
return calculations.onScreen;
|
|
},
|
|
offScreen: function () {
|
|
var
|
|
calculations = module.get.elementCalculations()
|
|
;
|
|
|
|
return calculations.offScreen;
|
|
},
|
|
visible: function () {
|
|
if (module.cache && module.cache.element) {
|
|
return !(module.cache.element.width === 0 && module.cache.element.offset.top === 0);
|
|
}
|
|
|
|
return false;
|
|
},
|
|
verticallyScrollableContext: function () {
|
|
var
|
|
overflowY = $context[0] !== window
|
|
? $context.css('overflow-y')
|
|
: false
|
|
;
|
|
|
|
return overflowY === 'auto' || overflowY === 'scroll';
|
|
},
|
|
horizontallyScrollableContext: function () {
|
|
var
|
|
overflowX = $context[0] !== window
|
|
? $context.css('overflow-x')
|
|
: false
|
|
;
|
|
|
|
return overflowX === 'auto' || overflowX === 'scroll';
|
|
},
|
|
},
|
|
|
|
refresh: function () {
|
|
module.debug('Refreshing constants (width/height)');
|
|
if (settings.type === 'fixed') {
|
|
module.resetFixed();
|
|
}
|
|
module.reset();
|
|
module.save.position();
|
|
if (settings.checkOnRefresh) {
|
|
module.checkVisibility();
|
|
}
|
|
settings.onRefresh.call(element);
|
|
},
|
|
|
|
resetFixed: function () {
|
|
module.remove.fixed();
|
|
module.remove.occurred();
|
|
},
|
|
|
|
reset: function () {
|
|
module.verbose('Resetting all cached values');
|
|
if ($.isPlainObject(module.cache)) {
|
|
module.cache.screen = {};
|
|
module.cache.element = {};
|
|
}
|
|
},
|
|
|
|
checkVisibility: function (scroll) {
|
|
module.verbose('Checking visibility of element', module.cache.element);
|
|
|
|
if (!disabled && module.is.visible()) {
|
|
// save scroll position
|
|
module.save.scroll(scroll);
|
|
|
|
// update calculations derived from scroll
|
|
module.save.calculations();
|
|
|
|
// percentage
|
|
module.passed();
|
|
|
|
// reverse (must be first)
|
|
module.passingReverse();
|
|
module.topVisibleReverse();
|
|
module.bottomVisibleReverse();
|
|
module.topPassedReverse();
|
|
module.bottomPassedReverse();
|
|
|
|
// one time
|
|
module.onScreen();
|
|
module.offScreen();
|
|
module.passing();
|
|
module.topVisible();
|
|
module.bottomVisible();
|
|
module.topPassed();
|
|
module.bottomPassed();
|
|
|
|
// on update callback
|
|
if (settings.onUpdate) {
|
|
settings.onUpdate.call(element, module.get.elementCalculations());
|
|
}
|
|
}
|
|
},
|
|
|
|
passed: function (amount, newCallback) {
|
|
var
|
|
calculations = module.get.elementCalculations()
|
|
;
|
|
// assign callback
|
|
if (amount && newCallback) {
|
|
settings.onPassed[amount] = newCallback;
|
|
} else if (amount !== undefined) {
|
|
return module.get.pixelsPassed(amount) > calculations.pixelsPassed;
|
|
} else if (calculations.passing) {
|
|
$.each(settings.onPassed, function (amount, callback) {
|
|
if (calculations.bottomVisible || calculations.pixelsPassed > module.get.pixelsPassed(amount)) {
|
|
module.execute(callback, amount);
|
|
} else if (!settings.once) {
|
|
module.remove.occurred(callback);
|
|
}
|
|
});
|
|
}
|
|
},
|
|
|
|
onScreen: function (newCallback) {
|
|
var
|
|
calculations = module.get.elementCalculations(),
|
|
callback = newCallback || settings.onOnScreen,
|
|
callbackName = 'onScreen'
|
|
;
|
|
if (newCallback) {
|
|
module.debug('Adding callback for onScreen', newCallback);
|
|
settings.onOnScreen = newCallback;
|
|
}
|
|
if (calculations.onScreen) {
|
|
module.execute(callback, callbackName);
|
|
} else if (!settings.once) {
|
|
module.remove.occurred(callbackName);
|
|
}
|
|
if (newCallback !== undefined) {
|
|
return calculations.onOnScreen;
|
|
}
|
|
},
|
|
|
|
offScreen: function (newCallback) {
|
|
var
|
|
calculations = module.get.elementCalculations(),
|
|
callback = newCallback || settings.onOffScreen,
|
|
callbackName = 'offScreen'
|
|
;
|
|
if (newCallback) {
|
|
module.debug('Adding callback for offScreen', newCallback);
|
|
settings.onOffScreen = newCallback;
|
|
}
|
|
if (calculations.offScreen) {
|
|
module.execute(callback, callbackName);
|
|
} else if (!settings.once) {
|
|
module.remove.occurred(callbackName);
|
|
}
|
|
if (newCallback !== undefined) {
|
|
return calculations.onOffScreen;
|
|
}
|
|
},
|
|
|
|
passing: function (newCallback) {
|
|
var
|
|
calculations = module.get.elementCalculations(),
|
|
callback = newCallback || settings.onPassing,
|
|
callbackName = 'passing'
|
|
;
|
|
if (newCallback) {
|
|
module.debug('Adding callback for passing', newCallback);
|
|
settings.onPassing = newCallback;
|
|
}
|
|
if (calculations.passing) {
|
|
module.execute(callback, callbackName);
|
|
} else if (!settings.once) {
|
|
module.remove.occurred(callbackName);
|
|
}
|
|
if (newCallback !== undefined) {
|
|
return calculations.passing;
|
|
}
|
|
},
|
|
|
|
topVisible: function (newCallback) {
|
|
var
|
|
calculations = module.get.elementCalculations(),
|
|
callback = newCallback || settings.onTopVisible,
|
|
callbackName = 'topVisible'
|
|
;
|
|
if (newCallback) {
|
|
module.debug('Adding callback for top visible', newCallback);
|
|
settings.onTopVisible = newCallback;
|
|
}
|
|
if (calculations.topVisible) {
|
|
module.execute(callback, callbackName);
|
|
} else if (!settings.once) {
|
|
module.remove.occurred(callbackName);
|
|
}
|
|
if (newCallback === undefined) {
|
|
return calculations.topVisible;
|
|
}
|
|
},
|
|
|
|
bottomVisible: function (newCallback) {
|
|
var
|
|
calculations = module.get.elementCalculations(),
|
|
callback = newCallback || settings.onBottomVisible,
|
|
callbackName = 'bottomVisible'
|
|
;
|
|
if (newCallback) {
|
|
module.debug('Adding callback for bottom visible', newCallback);
|
|
settings.onBottomVisible = newCallback;
|
|
}
|
|
if (calculations.bottomVisible) {
|
|
module.execute(callback, callbackName);
|
|
} else if (!settings.once) {
|
|
module.remove.occurred(callbackName);
|
|
}
|
|
if (newCallback === undefined) {
|
|
return calculations.bottomVisible;
|
|
}
|
|
},
|
|
|
|
topPassed: function (newCallback) {
|
|
var
|
|
calculations = module.get.elementCalculations(),
|
|
callback = newCallback || settings.onTopPassed,
|
|
callbackName = 'topPassed'
|
|
;
|
|
if (newCallback) {
|
|
module.debug('Adding callback for top passed', newCallback);
|
|
settings.onTopPassed = newCallback;
|
|
}
|
|
if (calculations.topPassed) {
|
|
module.execute(callback, callbackName);
|
|
} else if (!settings.once) {
|
|
module.remove.occurred(callbackName);
|
|
}
|
|
if (newCallback === undefined) {
|
|
return calculations.topPassed;
|
|
}
|
|
},
|
|
|
|
bottomPassed: function (newCallback) {
|
|
var
|
|
calculations = module.get.elementCalculations(),
|
|
callback = newCallback || settings.onBottomPassed,
|
|
callbackName = 'bottomPassed'
|
|
;
|
|
if (newCallback) {
|
|
module.debug('Adding callback for bottom passed', newCallback);
|
|
settings.onBottomPassed = newCallback;
|
|
}
|
|
if (calculations.bottomPassed) {
|
|
module.execute(callback, callbackName);
|
|
} else if (!settings.once) {
|
|
module.remove.occurred(callbackName);
|
|
}
|
|
if (newCallback === undefined) {
|
|
return calculations.bottomPassed;
|
|
}
|
|
},
|
|
|
|
passingReverse: function (newCallback) {
|
|
var
|
|
calculations = module.get.elementCalculations(),
|
|
callback = newCallback || settings.onPassingReverse,
|
|
callbackName = 'passingReverse'
|
|
;
|
|
if (newCallback) {
|
|
module.debug('Adding callback for passing reverse', newCallback);
|
|
settings.onPassingReverse = newCallback;
|
|
}
|
|
if (!calculations.passing) {
|
|
if (module.get.occurred('passing')) {
|
|
module.execute(callback, callbackName);
|
|
}
|
|
} else if (!settings.once) {
|
|
module.remove.occurred(callbackName);
|
|
}
|
|
if (newCallback !== undefined) {
|
|
return !calculations.passing;
|
|
}
|
|
},
|
|
|
|
topVisibleReverse: function (newCallback) {
|
|
var
|
|
calculations = module.get.elementCalculations(),
|
|
callback = newCallback || settings.onTopVisibleReverse,
|
|
callbackName = 'topVisibleReverse'
|
|
;
|
|
if (newCallback) {
|
|
module.debug('Adding callback for top visible reverse', newCallback);
|
|
settings.onTopVisibleReverse = newCallback;
|
|
}
|
|
if (!calculations.topVisible) {
|
|
if (module.get.occurred('topVisible')) {
|
|
module.execute(callback, callbackName);
|
|
}
|
|
} else if (!settings.once) {
|
|
module.remove.occurred(callbackName);
|
|
}
|
|
if (newCallback === undefined) {
|
|
return !calculations.topVisible;
|
|
}
|
|
},
|
|
|
|
bottomVisibleReverse: function (newCallback) {
|
|
var
|
|
calculations = module.get.elementCalculations(),
|
|
callback = newCallback || settings.onBottomVisibleReverse,
|
|
callbackName = 'bottomVisibleReverse'
|
|
;
|
|
if (newCallback) {
|
|
module.debug('Adding callback for bottom visible reverse', newCallback);
|
|
settings.onBottomVisibleReverse = newCallback;
|
|
}
|
|
if (!calculations.bottomVisible) {
|
|
if (module.get.occurred('bottomVisible')) {
|
|
module.execute(callback, callbackName);
|
|
}
|
|
} else if (!settings.once) {
|
|
module.remove.occurred(callbackName);
|
|
}
|
|
if (newCallback === undefined) {
|
|
return !calculations.bottomVisible;
|
|
}
|
|
},
|
|
|
|
topPassedReverse: function (newCallback) {
|
|
var
|
|
calculations = module.get.elementCalculations(),
|
|
callback = newCallback || settings.onTopPassedReverse,
|
|
callbackName = 'topPassedReverse'
|
|
;
|
|
if (newCallback) {
|
|
module.debug('Adding callback for top passed reverse', newCallback);
|
|
settings.onTopPassedReverse = newCallback;
|
|
}
|
|
if (!calculations.topPassed) {
|
|
if (module.get.occurred('topPassed')) {
|
|
module.execute(callback, callbackName);
|
|
}
|
|
} else if (!settings.once) {
|
|
module.remove.occurred(callbackName);
|
|
}
|
|
if (newCallback === undefined) {
|
|
return !calculations.onTopPassed;
|
|
}
|
|
},
|
|
|
|
bottomPassedReverse: function (newCallback) {
|
|
var
|
|
calculations = module.get.elementCalculations(),
|
|
callback = newCallback || settings.onBottomPassedReverse,
|
|
callbackName = 'bottomPassedReverse'
|
|
;
|
|
if (newCallback) {
|
|
module.debug('Adding callback for bottom passed reverse', newCallback);
|
|
settings.onBottomPassedReverse = newCallback;
|
|
}
|
|
if (!calculations.bottomPassed) {
|
|
if (module.get.occurred('bottomPassed')) {
|
|
module.execute(callback, callbackName);
|
|
}
|
|
} else if (!settings.once) {
|
|
module.remove.occurred(callbackName);
|
|
}
|
|
if (newCallback === undefined) {
|
|
return !calculations.bottomPassed;
|
|
}
|
|
},
|
|
|
|
execute: function (callback, callbackName) {
|
|
var
|
|
calculations = module.get.elementCalculations(),
|
|
screen = module.get.screenCalculations()
|
|
;
|
|
callback = callback || false;
|
|
if (callback) {
|
|
if (settings.continuous) {
|
|
module.debug('Callback being called continuously', callbackName, calculations);
|
|
callback.call(element, calculations, screen);
|
|
} else if (!module.get.occurred(callbackName)) {
|
|
module.debug('Conditions met', callbackName, calculations);
|
|
callback.call(element, calculations, screen);
|
|
}
|
|
}
|
|
module.save.occurred(callbackName);
|
|
},
|
|
|
|
remove: {
|
|
fixed: function () {
|
|
module.debug('Removing fixed position');
|
|
$module
|
|
.removeClass(className.fixed)
|
|
.css({
|
|
position: '',
|
|
top: '',
|
|
left: '',
|
|
zIndex: '',
|
|
})
|
|
;
|
|
settings.onUnfixed.call(element);
|
|
},
|
|
placeholder: function () {
|
|
module.debug('Removing placeholder content');
|
|
if ($placeholder) {
|
|
$placeholder.remove();
|
|
}
|
|
},
|
|
occurred: function (callback) {
|
|
if (callback) {
|
|
var
|
|
occurred = module.cache.occurred
|
|
;
|
|
if (occurred[callback] !== undefined && occurred[callback] === true) {
|
|
module.debug('Callback can now be called again', callback);
|
|
module.cache.occurred[callback] = false;
|
|
}
|
|
} else {
|
|
module.cache.occurred = {};
|
|
}
|
|
},
|
|
},
|
|
|
|
save: {
|
|
calculations: function () {
|
|
module.verbose('Saving all calculations necessary to determine positioning');
|
|
module.save.direction();
|
|
module.save.screenCalculations();
|
|
module.save.elementCalculations();
|
|
},
|
|
occurred: function (callback) {
|
|
if (callback) {
|
|
if (module.cache.occurred[callback] === undefined || (module.cache.occurred[callback] !== true)) {
|
|
module.verbose('Saving callback occurred', callback);
|
|
module.cache.occurred[callback] = true;
|
|
}
|
|
}
|
|
},
|
|
scroll: function (scrollPosition) {
|
|
scrollPosition = scrollPosition + settings.offset || $context.scrollTop() + settings.offset;
|
|
module.cache.scroll = scrollPosition;
|
|
},
|
|
direction: function () {
|
|
var
|
|
scroll = module.get.scroll(),
|
|
lastScroll = module.get.lastScroll(),
|
|
direction
|
|
;
|
|
if (scroll > lastScroll && lastScroll) {
|
|
direction = 'down';
|
|
} else if (scroll < lastScroll && lastScroll) {
|
|
direction = 'up';
|
|
} else {
|
|
direction = 'static';
|
|
}
|
|
module.cache.direction = direction;
|
|
|
|
return module.cache.direction;
|
|
},
|
|
elementPosition: function () {
|
|
var
|
|
element = module.cache.element,
|
|
screen = module.get.screenSize()
|
|
;
|
|
module.verbose('Saving element position');
|
|
// (quicker than $.extend)
|
|
element.fits = element.height < screen.height;
|
|
element.offset = $module.offset();
|
|
element.width = $module.outerWidth();
|
|
element.height = $module.outerHeight();
|
|
// compensate for scroll in context
|
|
if (module.is.verticallyScrollableContext()) {
|
|
element.offset.top += $context.scrollTop() - $context.offset().top;
|
|
}
|
|
if (module.is.horizontallyScrollableContext()) {
|
|
element.offset.left += $context.scrollLeft() - $context.offset().left;
|
|
}
|
|
// store
|
|
module.cache.element = element;
|
|
|
|
return element;
|
|
},
|
|
elementCalculations: function () {
|
|
var
|
|
screen = module.get.screenCalculations(),
|
|
element = module.get.elementPosition()
|
|
;
|
|
// offset
|
|
if (settings.includeMargin) {
|
|
element.margin = {};
|
|
element.margin.top = parseInt($module.css('margin-top'), 10);
|
|
element.margin.bottom = parseInt($module.css('margin-bottom'), 10);
|
|
element.top = element.offset.top - element.margin.top;
|
|
element.bottom = element.offset.top + element.height + element.margin.bottom;
|
|
} else {
|
|
element.top = element.offset.top;
|
|
element.bottom = element.offset.top + element.height;
|
|
}
|
|
|
|
// visibility
|
|
element.topPassed = screen.top >= element.top;
|
|
element.bottomPassed = screen.top >= element.bottom;
|
|
element.topVisible = (screen.bottom >= element.top) && !element.topPassed;
|
|
element.bottomVisible = (screen.bottom >= element.bottom) && !element.bottomPassed;
|
|
element.pixelsPassed = 0;
|
|
element.percentagePassed = 0;
|
|
|
|
// meta calculations
|
|
element.onScreen = (element.topVisible || element.passing) && !element.bottomPassed;
|
|
element.passing = element.topPassed && !element.bottomPassed;
|
|
element.offScreen = !element.onScreen;
|
|
|
|
// passing calculations
|
|
if (element.passing) {
|
|
element.pixelsPassed = screen.top - element.top;
|
|
element.percentagePassed = (screen.top - element.top) / element.height;
|
|
}
|
|
module.cache.element = element;
|
|
module.verbose('Updated element calculations', element);
|
|
|
|
return element;
|
|
},
|
|
screenCalculations: function () {
|
|
var
|
|
scroll = module.get.scroll()
|
|
;
|
|
module.save.direction();
|
|
module.cache.screen.top = scroll;
|
|
module.cache.screen.bottom = scroll + module.cache.screen.height;
|
|
|
|
return module.cache.screen;
|
|
},
|
|
screenSize: function () {
|
|
module.verbose('Saving window position');
|
|
module.cache.screen = {
|
|
height: $context.height(),
|
|
};
|
|
},
|
|
position: function () {
|
|
module.save.screenSize();
|
|
module.save.elementPosition();
|
|
},
|
|
},
|
|
|
|
get: {
|
|
pixelsPassed: function (amount) {
|
|
var
|
|
element = module.get.elementCalculations()
|
|
;
|
|
if (amount.search('%') > -1) {
|
|
return element.height * (parseInt(amount, 10) / 100);
|
|
}
|
|
|
|
return parseInt(amount, 10);
|
|
},
|
|
occurred: function (callback) {
|
|
return module.cache.occurred !== undefined
|
|
? module.cache.occurred[callback] || false
|
|
: false;
|
|
},
|
|
direction: function () {
|
|
if (module.cache.direction === undefined) {
|
|
module.save.direction();
|
|
}
|
|
|
|
return module.cache.direction;
|
|
},
|
|
elementPosition: function () {
|
|
if (module.cache.element === undefined) {
|
|
module.save.elementPosition();
|
|
}
|
|
|
|
return module.cache.element;
|
|
},
|
|
elementCalculations: function () {
|
|
if (module.cache.element === undefined) {
|
|
module.save.elementCalculations();
|
|
}
|
|
|
|
return module.cache.element;
|
|
},
|
|
screenCalculations: function () {
|
|
if (module.cache.screen === undefined) {
|
|
module.save.screenCalculations();
|
|
}
|
|
|
|
return module.cache.screen;
|
|
},
|
|
screenSize: function () {
|
|
if (module.cache.screen === undefined) {
|
|
module.save.screenSize();
|
|
}
|
|
|
|
return module.cache.screen;
|
|
},
|
|
scroll: function () {
|
|
if (module.cache.scroll === undefined) {
|
|
module.save.scroll();
|
|
}
|
|
|
|
return module.cache.scroll;
|
|
},
|
|
lastScroll: function () {
|
|
if (module.cache.screen === undefined) {
|
|
module.debug('First scroll event, no last scroll could be found');
|
|
|
|
return false;
|
|
}
|
|
|
|
return module.cache.screen.top;
|
|
},
|
|
},
|
|
|
|
setting: function (name, value) {
|
|
if ($.isPlainObject(name)) {
|
|
$.extend(true, settings, name);
|
|
} else if (value !== undefined) {
|
|
settings[name] = value;
|
|
} else {
|
|
return settings[name];
|
|
}
|
|
},
|
|
internal: function (name, value) {
|
|
if ($.isPlainObject(name)) {
|
|
$.extend(true, module, name);
|
|
} else if (value !== undefined) {
|
|
module[name] = value;
|
|
} else {
|
|
return module[name];
|
|
}
|
|
},
|
|
debug: function () {
|
|
if (!settings.silent && settings.debug) {
|
|
if (settings.performance) {
|
|
module.performance.log(arguments);
|
|
} else {
|
|
module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':');
|
|
module.debug.apply(console, arguments);
|
|
}
|
|
}
|
|
},
|
|
verbose: function () {
|
|
if (!settings.silent && settings.verbose && settings.debug) {
|
|
if (settings.performance) {
|
|
module.performance.log(arguments);
|
|
} else {
|
|
module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':');
|
|
module.verbose.apply(console, arguments);
|
|
}
|
|
}
|
|
},
|
|
error: function () {
|
|
if (!settings.silent) {
|
|
module.error = Function.prototype.bind.call(console.error, console, settings.name + ':');
|
|
module.error.apply(console, arguments);
|
|
}
|
|
},
|
|
performance: {
|
|
log: function (message) {
|
|
var
|
|
currentTime,
|
|
executionTime,
|
|
previousTime
|
|
;
|
|
if (settings.performance) {
|
|
currentTime = Date.now();
|
|
previousTime = time || currentTime;
|
|
executionTime = currentTime - previousTime;
|
|
time = currentTime;
|
|
performance.push({
|
|
Name: message[0],
|
|
Arguments: [].slice.call(message, 1) || '',
|
|
Element: element,
|
|
'Execution Time': executionTime,
|
|
});
|
|
}
|
|
clearTimeout(module.performance.timer);
|
|
module.performance.timer = setTimeout(module.performance.display, 500);
|
|
},
|
|
display: function () {
|
|
var
|
|
title = settings.name + ':',
|
|
totalTime = 0
|
|
;
|
|
time = false;
|
|
clearTimeout(module.performance.timer);
|
|
$.each(performance, function (index, data) {
|
|
totalTime += data['Execution Time'];
|
|
});
|
|
title += ' ' + totalTime + 'ms';
|
|
if (moduleSelector) {
|
|
title += ' \'' + moduleSelector + '\'';
|
|
}
|
|
if (performance.length > 0) {
|
|
console.groupCollapsed(title);
|
|
if (console.table) {
|
|
console.table(performance);
|
|
} else {
|
|
$.each(performance, function (index, data) {
|
|
console.log(data.Name + ': ' + data['Execution Time'] + 'ms');
|
|
});
|
|
}
|
|
console.groupEnd();
|
|
}
|
|
performance = [];
|
|
},
|
|
},
|
|
invoke: function (query, passedArguments, context) {
|
|
var
|
|
object = instance,
|
|
maxDepth,
|
|
found,
|
|
response
|
|
;
|
|
passedArguments = passedArguments || queryArguments;
|
|
context = context || element;
|
|
if (typeof query === 'string' && object !== undefined) {
|
|
query = query.split(/[ .]/);
|
|
maxDepth = query.length - 1;
|
|
$.each(query, function (depth, value) {
|
|
var camelCaseValue = depth !== maxDepth
|
|
? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1)
|
|
: query
|
|
;
|
|
if ($.isPlainObject(object[camelCaseValue]) && (depth !== maxDepth)) {
|
|
object = object[camelCaseValue];
|
|
} else if (object[camelCaseValue] !== undefined) {
|
|
found = object[camelCaseValue];
|
|
|
|
return false;
|
|
} else if ($.isPlainObject(object[value]) && (depth !== maxDepth)) {
|
|
object = object[value];
|
|
} else if (object[value] !== undefined) {
|
|
found = object[value];
|
|
|
|
return false;
|
|
} else {
|
|
module.error(error.method, query);
|
|
|
|
return false;
|
|
}
|
|
});
|
|
}
|
|
if (isFunction(found)) {
|
|
response = found.apply(context, passedArguments);
|
|
} else if (found !== undefined) {
|
|
response = found;
|
|
}
|
|
if (Array.isArray(returnedValue)) {
|
|
returnedValue.push(response);
|
|
} else if (returnedValue !== undefined) {
|
|
returnedValue = [returnedValue, response];
|
|
} else if (response !== undefined) {
|
|
returnedValue = response;
|
|
}
|
|
|
|
return found;
|
|
},
|
|
};
|
|
|
|
if (methodInvoked) {
|
|
if (instance === undefined) {
|
|
module.initialize();
|
|
}
|
|
instance.save.scroll();
|
|
instance.save.calculations();
|
|
module.invoke(query);
|
|
} else {
|
|
if (instance !== undefined) {
|
|
instance.invoke('destroy');
|
|
}
|
|
module.initialize();
|
|
}
|
|
});
|
|
|
|
return returnedValue !== undefined
|
|
? returnedValue
|
|
: this;
|
|
};
|
|
|
|
$.fn.visibility.settings = {
|
|
|
|
name: 'Visibility',
|
|
namespace: 'visibility',
|
|
|
|
debug: false,
|
|
verbose: false,
|
|
performance: true,
|
|
|
|
// whether to use mutation observers to follow changes
|
|
observeChanges: true,
|
|
|
|
// check position immediately on init
|
|
initialCheck: true,
|
|
|
|
// whether to refresh calculations after all page images load
|
|
refreshOnLoad: true,
|
|
|
|
// whether to refresh calculations after page resize event
|
|
refreshOnResize: true,
|
|
|
|
// should call callbacks on refresh event (resize, etc)
|
|
checkOnRefresh: true,
|
|
|
|
// callback should only occur one time
|
|
once: true,
|
|
|
|
// callback should fire continuously when evaluates to true
|
|
continuous: false,
|
|
|
|
// offset to use with scroll top
|
|
offset: 0,
|
|
|
|
// whether to include margin in elements position
|
|
includeMargin: false,
|
|
|
|
// scroll context for visibility checks
|
|
context: window,
|
|
|
|
// visibility check delay in ms (defaults to animationFrame)
|
|
throttle: false,
|
|
|
|
// special visibility type (image, fixed)
|
|
type: false,
|
|
|
|
// z-index to use with visibility 'fixed'
|
|
zIndex: '10',
|
|
|
|
// image only animation settings
|
|
transition: 'fade in',
|
|
duration: 1000,
|
|
|
|
// array of callbacks for percentage
|
|
onPassed: {},
|
|
|
|
// standard callbacks
|
|
onOnScreen: false,
|
|
onOffScreen: false,
|
|
onPassing: false,
|
|
onTopVisible: false,
|
|
onBottomVisible: false,
|
|
onTopPassed: false,
|
|
onBottomPassed: false,
|
|
|
|
// reverse callbacks
|
|
onPassingReverse: false,
|
|
onTopVisibleReverse: false,
|
|
onBottomVisibleReverse: false,
|
|
onTopPassedReverse: false,
|
|
onBottomPassedReverse: false,
|
|
|
|
// special callbacks for image
|
|
onLoad: function () {},
|
|
onAllLoaded: function () {},
|
|
|
|
// special callbacks for fixed position
|
|
onFixed: function () {},
|
|
onUnfixed: function () {},
|
|
|
|
// utility callbacks
|
|
onUpdate: false, // disabled by default for performance
|
|
onRefresh: function () {},
|
|
|
|
metadata: {
|
|
src: 'src',
|
|
},
|
|
|
|
className: {
|
|
fixed: 'fixed',
|
|
placeholder: 'constraint',
|
|
visible: 'visible',
|
|
},
|
|
|
|
error: {
|
|
method: 'The method you called is not defined.',
|
|
visible: 'Element is hidden, you must call refresh after element becomes visible',
|
|
},
|
|
|
|
};
|
|
})(jQuery, window, document);
|