1312 lines
42 KiB
JavaScript
1312 lines
42 KiB
JavaScript
|
/*!
|
||
|
* # Semantic UI 2.4.1 - Visibility
|
||
|
* http://github.com/semantic-org/semantic-ui/
|
||
|
*
|
||
|
*
|
||
|
* Released under the MIT license
|
||
|
* http://opensource.org/licenses/MIT
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
;(function ($, window, document, undefined) {
|
||
|
|
||
|
'use strict';
|
||
|
|
||
|
window = (typeof window != 'undefined' && window.Math == Math)
|
||
|
? window
|
||
|
: (typeof self != 'undefined' && self.Math == Math)
|
||
|
? self
|
||
|
: Function('return this')()
|
||
|
;
|
||
|
|
||
|
$.fn.visibility = function(parameters) {
|
||
|
var
|
||
|
$allModules = $(this),
|
||
|
moduleSelector = $allModules.selector || '',
|
||
|
|
||
|
time = new Date().getTime(),
|
||
|
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 = $(settings.context),
|
||
|
|
||
|
$placeholder,
|
||
|
|
||
|
selector = $module.selector || '',
|
||
|
instance = $module.data(moduleNamespace),
|
||
|
|
||
|
requestAnimationFrame = window.requestAnimationFrame
|
||
|
|| window.mozRequestAnimationFrame
|
||
|
|| window.webkitRequestAnimationFrame
|
||
|
|| window.msRequestAnimationFrame
|
||
|
|| function(callback) { setTimeout(callback, 0); },
|
||
|
|
||
|
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 (!(images instanceof Array)) {
|
||
|
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.onload = handleLoad;
|
||
|
cacheImage.onerror = 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.get(0) !== window)
|
||
|
? $context.css('overflow-y')
|
||
|
: false
|
||
|
;
|
||
|
return (overflowY == 'auto' || overflowY == 'scroll');
|
||
|
},
|
||
|
horizontallyScrollableContext: function() {
|
||
|
var
|
||
|
overflowX = ($context.get(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(),
|
||
|
amountInPixels
|
||
|
;
|
||
|
// 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 = new Date().getTime();
|
||
|
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( (console.group !== undefined || console.table !== undefined) && 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 = element || context;
|
||
|
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($.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 whe 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 : 'placeholder',
|
||
|
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 );
|