|
|
/*! * # Semantic UI 1.11.3 - Visibility * http://github.com/semantic-org/semantic-ui/
* * * Copyright 2014 Contributors * Released under the MIT license * http://opensource.org/licenses/MIT
* */
;(function ( $, window, document, undefined ) {
"use strict";
$.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 ;
$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,
eventNamespace = '.' + namespace, moduleNamespace = 'module-' + namespace,
$window = $(window), $module = $(this), $context = $(settings.context), $container = $module.offsetParent(),
selector = $module.selector || '', instance = $module.data(moduleNamespace),
requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame || function(callback) { setTimeout(callback, 0); },
element = this, observer, module ;
module = {
initialize: function() { module.debug('Initializing', settings);
module.setup.cache(); module.save.position();
if( module.should.trackChanges() ) { module.bind.events(); if(settings.type == 'image') { module.setup.image(); } if(settings.type == 'fixed') { module.setup.fixed(); } } if(settings.initialCheck) { module.checkVisibility(); } if(settings.observeChanges) { module.observeChanges(); } module.instantiate(); },
instantiate: function() { module.debug('Storing instance', module); $module .data(moduleNamespace, module) ; instance = module; },
destroy: function() { module.verbose('Destroying previous module'); $module .off(eventNamespace) .removeData(moduleNamespace) ; $window.off('resize' + eventNamespace, module.event.refresh); $context.off('scroll' + eventNamespace, module.event.scroll); },
observeChanges: function() { var context = $context[0] ; if('MutationObserver' in window) { observer = new MutationObserver(function(mutations) { module.verbose('DOM tree modified, updating visibility calculations'); module.refresh(); }); 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'); $window .on('resize' + eventNamespace, module.event.refresh) ; $context .on('scroll' + eventNamespace, module.event.scroll) ; } },
event: { refresh: function() { requestAnimationFrame(module.refresh); }, scroll: function() { module.verbose('Scroll position changed'); if(settings.throttle) { clearTimeout(module.timer); module.timer = setTimeout(function() { module.checkVisibility(); }, settings.throttle); } else { requestAnimationFrame(function() { module.checkVisibility(); }); } } },
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); } },
should: { trackChanges: function() { if(methodInvoked && queryArguments.length > 0) { 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('src') ; if(src) { module.verbose('Lazy loading image', src); settings.observeChanges = false; // show when top visible
module.topVisible(function() { module.debug('Image top visible', element); module.precache(src, function() { module.set.image(src); settings.onTopVisible = false; }); }); } }, fixed: function() { module.verbose('Setting up fixed on element pass'); settings.once = false; settings.onTopPassed = function() { $module .addClass(className.fixed) .css({ top: settings.offset + 'px' }) ; if(settings.transition) { if($.fn.transition !== undefined) { $module.transition(settings.transition, settings.duration); } } }; settings.onTopPassedReverse = function() { $module .removeClass(className.fixed) .css({ position: '', top: '' }) ; }; } },
set: { image: function(src) { var offScreen = (module.cache.screen.bottom < module.cache.element.top) ; $module .attr('src', src) ; if(offScreen) { module.verbose('Image outside browser, no show animation'); $module.show(); } else { if(settings.transition) { if( $.fn.transition !== undefined ) { $module.transition(settings.transition, settings.duration); } else { $module.fadeIn(settings.duration); } } else { $module.show(); } } } },
is: { visible: function() { if(module.cache && module.cache.element) { return (module.cache.element.width > 0); } return false; } },
refresh: function() { module.debug('Refreshing constants (element width/height)'); module.reset(); module.save.position(); module.checkVisibility(); settings.onRefresh.call(element); },
reset: function() { module.verbose('Reseting all cached values'); if( $.isPlainObject(module.cache) ) { module.cache.screen = {}; module.cache.element = {}; } },
checkVisibility: function() { module.verbose('Checking visibility of element', module.cache.element);
if( module.is.visible() ) {
// 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.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 !== undefined && newCallback !== undefined) { 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); } }); } },
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: { occurred: function(callback) { if(callback) { if(module.cache.occurred[callback] !== undefined && module.cache.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.scroll(); 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() { module.cache.scroll = $context.scrollTop() + settings.offset; }, 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(); // 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.topVisible = (screen.bottom >= element.top); element.topPassed = (screen.top >= element.top); element.bottomVisible = (screen.bottom >= element.bottom); element.bottomPassed = (screen.top >= element.bottom); element.pixelsPassed = 0; element.percentagePassed = 0;
// meta calculations
element.visible = (element.topVisible || element.bottomVisible); element.passing = (element.topPassed && !element.bottomPassed); element.hidden = (!element.topVisible && !element.bottomVisible);
// 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.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.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() { 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, 100); }, 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(); } 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,
// 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,
// check position immediately on init
initialCheck : true,
// visibility check delay in ms (defaults to animationFrame)
throttle : false,
// special visibility type (image, fixed)
type : false,
// image only animation settings
transition : false, duration : 1000,
// array of callbacks for percentage
onPassed : {},
// standard callbacks
onPassing : false, onTopVisible : false, onBottomVisible : false, onTopPassed : false, onBottomPassed : false,
// reverse callbacks
onPassingReverse : false, onTopVisibleReverse : false, onBottomVisibleReverse : false, onTopPassedReverse : false, onBottomPassedReverse : false,
// utility callbacks
onUpdate : false, // disabled by default for performance
onRefresh : function(){},
className: { fixed: 'fixed' },
error : { method : 'The method you called is not defined.' }
};
})( jQuery, window , document );
|