Browse Source

Ports sticky/visiblity from 2.x early xmas #2127

pull/2136/merge
jlukic 10 years ago
parent
commit
cd54328cb9
3 changed files with 152 additions and 61 deletions
  1. 11
      RELEASE-NOTES.md
  2. 131
      src/definitions/behaviors/visibility.js
  3. 71
      src/definitions/modules/sticky.js

11
RELEASE-NOTES.md

@ -1,5 +1,16 @@
## RELEASE NOTES ## RELEASE NOTES
### Version 1.12.0 - April 13, 2015
**Enhancements**
- **Visibility** - Adds updated visibility module from `2.x` channel. Visibility will automatically refresh by default after images load on page refresh. Fixes issues with element positions after image loading.
- **Sticky** - Adds sticky module from `2.x` branch. Sticky elements now use pub/sub with drastically improved performance. Sticky elements that do not fit on page will now scroll at the same speed as the page is scrolled instead of slower.
**Bugs**
- **Build Tools** - Fixes issue with component glob matching twice (causing build to include file twice) if duplicate values found in `semantic.json` component.
- **Input** - Backports fix from `2.x` for `ui fluid input` not appearing correctly.
- **Visibility** - Fixed issue where `precache` behavior was missing from visibility causing `image` lazy loading to fail
### Version 1.11.8 - April 13, 2015 ### Version 1.11.8 - April 13, 2015
**Bugs** **Bugs**

131
src/definitions/behaviors/visibility.js

@ -42,11 +42,11 @@ $.fn.visibility = function(parameters) {
moduleNamespace = 'module-' + namespace, moduleNamespace = 'module-' + namespace,
$window = $(window), $window = $(window),
$module = $(this), $module = $(this),
$context = $(settings.context), $context = $(settings.context),
$images = $module.find('img'),
selector = $module.selector || '', selector = $module.selector || '',
instance = $module.data(moduleNamespace), instance = $module.data(moduleNamespace),
requestAnimationFrame = window.requestAnimationFrame requestAnimationFrame = window.requestAnimationFrame
@ -76,13 +76,16 @@ $.fn.visibility = function(parameters) {
if(settings.type == 'fixed') { if(settings.type == 'fixed') {
module.setup.fixed(); module.setup.fixed();
} }
if(settings.observeChanges) {
module.observeChanges();
}
if( !module.is.visible() ) {
module.error(error.visible, $module);
}
} }
if(settings.initialCheck) { if(settings.initialCheck) {
module.checkVisibility(); module.checkVisibility();
} }
if(settings.observeChanges) {
module.observeChanges();
}
module.instantiate(); module.instantiate();
}, },
@ -96,12 +99,18 @@ $.fn.visibility = function(parameters) {
destroy: function() { destroy: function() {
module.verbose('Destroying previous module'); module.verbose('Destroying previous module');
if(observer) {
observer.disconnect();
}
$window
.off('load' + eventNamespace, module.event.load)
.off('resize' + eventNamespace, module.event.resize)
;
$context.off('scrollchange' + eventNamespace, module.event.scrollchange);
$module $module
.off(eventNamespace) .off(eventNamespace)
.removeData(moduleNamespace) .removeData(moduleNamespace)
; ;
$window.off('resize' + eventNamespace, module.event.refresh);
$context.off('scroll' + eventNamespace, module.event.scroll);
}, },
observeChanges: function() { observeChanges: function() {
@ -111,7 +120,10 @@ $.fn.visibility = function(parameters) {
if('MutationObserver' in window) { if('MutationObserver' in window) {
observer = new MutationObserver(function(mutations) { observer = new MutationObserver(function(mutations) {
module.verbose('DOM tree modified, updating visibility calculations'); module.verbose('DOM tree modified, updating visibility calculations');
module.refresh();
module.timer = setTimeout(function() {
module.verbose('DOM tree modified, updating sticky menu');
module.refresh();
}, 100);
}); });
observer.observe(element, { observer.observe(element, {
childList : true, childList : true,
@ -125,17 +137,19 @@ $.fn.visibility = function(parameters) {
events: function() { events: function() {
module.verbose('Binding visibility events to scroll and resize'); module.verbose('Binding visibility events to scroll and resize');
$window $window
.on('resize' + eventNamespace, module.event.refresh)
.on('load' + eventNamespace, module.event.load)
.on('resize' + eventNamespace, module.event.resize)
; ;
// pub/sub pattern
$context $context
.off('scroll' + eventNamespace)
.on('scroll' + eventNamespace, module.event.scroll) .on('scroll' + eventNamespace, module.event.scroll)
.on('scrollchange' + eventNamespace, module.event.scrollchange)
; ;
if($images.length > 0) {
module.bind.imageLoad();
}
}, },
imageLoad: function() { imageLoad: function() {
var var
$images = $module.find('img'),
imageCount = $images.length, imageCount = $images.length,
index = imageCount, index = imageCount,
loadedCount = 0, loadedCount = 0,
@ -150,44 +164,82 @@ $.fn.visibility = function(parameters) {
} }
} }
; ;
$images
.each(function() {
images.push( $(this).attr('src') );
})
;
while(index--) {
cacheImage = document.createElement('img');
cacheImage.onload = handleLoad;
cacheImage.onerror = handleLoad;
cacheImage.src = images[index];
cache.push(cacheImage);
if(imageCount > 0) {
$images
.each(function() {
images.push( $(this).attr('src') );
})
;
while(index--) {
cacheImage = document.createElement('img');
cacheImage.onload = handleLoad;
cacheImage.onerror = handleLoad;
cacheImage.src = images[index];
cache.push(cacheImage);
}
} }
} }
}, },
event: { event: {
refresh: function() {
resize: function() {
module.debug('Window resized');
requestAnimationFrame(module.refresh); requestAnimationFrame(module.refresh);
}, },
load: function() {
module.debug('Page finished loading');
requestAnimationFrame(module.refresh);
},
// publishes scrollchange event on one scroll
scroll: function() { scroll: function() {
module.verbose('Scroll position changed');
if(settings.throttle) { if(settings.throttle) {
clearTimeout(module.timer); clearTimeout(module.timer);
module.timer = setTimeout(function() { module.timer = setTimeout(function() {
module.checkVisibility();
$context.trigger('scrollchange' + eventNamespace, [ $context.scrollTop() ]);
}, settings.throttle); }, settings.throttle);
} }
else { else {
requestAnimationFrame(function() { requestAnimationFrame(function() {
module.checkVisibility();
$context.trigger('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);
} }
}, },
should: { should: {
trackChanges: function() { trackChanges: function() {
if(methodInvoked && queryArguments.length > 0) {
if(methodInvoked) {
module.debug('One time query, no need to bind events'); module.debug('One time query, no need to bind events');
return false; return false;
} }
@ -287,7 +339,7 @@ $.fn.visibility = function(parameters) {
}, },
refresh: function() { refresh: function() {
module.debug('Refreshing constants (element width/height)');
module.debug('Refreshing constants (width/height)');
module.reset(); module.reset();
module.save.position(); module.save.position();
module.checkVisibility(); module.checkVisibility();
@ -302,11 +354,14 @@ $.fn.visibility = function(parameters) {
} }
}, },
checkVisibility: function() {
checkVisibility: function(scroll) {
module.verbose('Checking visibility of element', module.cache.element); module.verbose('Checking visibility of element', module.cache.element);
if( module.is.visible() ) { if( module.is.visible() ) {
// save scroll position
module.save.scroll(scroll);
// update calculations derived from scroll // update calculations derived from scroll
module.save.calculations(); module.save.calculations();
@ -616,7 +671,6 @@ $.fn.visibility = function(parameters) {
save: { save: {
calculations: function() { calculations: function() {
module.verbose('Saving all calculations necessary to determine positioning'); module.verbose('Saving all calculations necessary to determine positioning');
module.save.scroll();
module.save.direction(); module.save.direction();
module.save.screenCalculations(); module.save.screenCalculations();
module.save.elementCalculations(); module.save.elementCalculations();
@ -629,8 +683,9 @@ $.fn.visibility = function(parameters) {
} }
} }
}, },
scroll: function() {
module.cache.scroll = $context.scrollTop() + settings.offset;
scroll: function(scrollPosition) {
scrollPosition = scrollPosition + settings.offset || $context.scrollTop() + settings.offset;
module.cache.scroll = scrollPosition;
}, },
direction: function() { direction: function() {
var var
@ -855,7 +910,7 @@ $.fn.visibility = function(parameters) {
}); });
} }
clearTimeout(module.performance.timer); clearTimeout(module.performance.timer);
module.performance.timer = setTimeout(module.performance.display, 100);
module.performance.timer = setTimeout(module.performance.display, 500);
}, },
display: function() { display: function() {
var var
@ -975,6 +1030,9 @@ $.fn.visibility.settings = {
// whether to use mutation observers to follow changes // whether to use mutation observers to follow changes
observeChanges : true, observeChanges : true,
// whether to refresh calculations after all page images load
refreshOnLoad : true,
// callback should only occur one time // callback should only occur one time
once : true, once : true,
@ -1029,9 +1087,10 @@ $.fn.visibility.settings = {
}, },
error : { error : {
method : 'The method you called is not defined.'
method : 'The method you called is not defined.',
visible : 'Element is hidden, you must call refresh after element becomes visible'
} }
}; };
})( jQuery, window , document );
})( jQuery, window , document );

71
src/definitions/modules/sticky.js

@ -87,13 +87,18 @@ $.fn.sticky = function(parameters) {
}, },
destroy: function() { destroy: function() {
module.verbose('Destroying previous module');
module.verbose('Destroying previous instance');
module.reset(); module.reset();
if(observer) { if(observer) {
observer.disconnect(); observer.disconnect();
} }
$window.off('resize' + eventNamespace, module.event.resize);
$scroll.off('scroll' + eventNamespace, module.event.scroll);
$window
.off('load' + eventNamespace, module.event.load)
.off('resize' + eventNamespace, module.event.resize)
;
$scroll
.off('scrollchange' + eventNamespace, module.event.scrollchange)
;
$module.removeData(moduleNamespace); $module.removeData(moduleNamespace);
}, },
@ -107,7 +112,7 @@ $.fn.sticky = function(parameters) {
module.timer = setTimeout(function() { module.timer = setTimeout(function() {
module.verbose('DOM tree modified, updating sticky menu'); module.verbose('DOM tree modified, updating sticky menu');
module.refresh(); module.refresh();
}, 20);
}, 100);
}); });
observer.observe(element, { observer.observe(element, {
childList : true, childList : true,
@ -147,23 +152,36 @@ $.fn.sticky = function(parameters) {
bind: { bind: {
events: function() { events: function() {
$window.on('resize' + eventNamespace, module.event.resize);
$scroll.on('scroll' + eventNamespace, module.event.scroll);
$window
.on('load' + eventNamespace, module.event.load)
.on('resize' + eventNamespace, module.event.resize)
;
// pub/sub pattern
$scroll
.off('scroll' + eventNamespace)
.on('scroll' + eventNamespace, module.event.scroll)
.on('scrollchange' + eventNamespace, module.event.scrollchange)
;
} }
}, },
event: { event: {
load: function() {
module.verbose('Page contents finished loading');
requestAnimationFrame(module.refresh);
},
resize: function() { resize: function() {
requestAnimationFrame(function() {
module.refresh();
module.stick();
});
module.verbose('Window resized');
requestAnimationFrame(module.refresh);
}, },
scroll: function() { scroll: function() {
requestAnimationFrame(function() { requestAnimationFrame(function() {
module.stick();
settings.onScroll.call(element);
$scroll.trigger('scrollchange' + eventNamespace, $scroll.scrollTop() );
}); });
},
scrollchange: function(event, scrollPosition) {
module.stick(scrollPosition);
settings.onScroll.call(element);
} }
}, },
@ -189,7 +207,7 @@ $.fn.sticky = function(parameters) {
}, },
save: { save: {
scroll: function(scroll) {
lastScroll: function(scroll) {
module.lastScroll = scroll; module.lastScroll = scroll;
}, },
positions: function() { positions: function() {
@ -268,6 +286,7 @@ $.fn.sticky = function(parameters) {
: Math.abs(parseInt($module.css('bottom'), 10)) || 0 : Math.abs(parseInt($module.css('bottom'), 10)) || 0
; ;
}, },
elementScroll: function(scroll) { elementScroll: function(scroll) {
scroll = scroll || $scroll.scrollTop(); scroll = scroll || $scroll.scrollTop();
var var
@ -275,14 +294,14 @@ $.fn.sticky = function(parameters) {
window = module.cache.window, window = module.cache.window,
delta = module.get.scrollChange(scroll), delta = module.get.scrollChange(scroll),
maxScroll = (element.height - window.height + settings.offset), maxScroll = (element.height - window.height + settings.offset),
currentScroll = module.get.currentElementScroll(),
possibleScroll = (currentScroll + delta),
elementScroll = module.get.currentElementScroll(),
possibleScroll = (elementScroll + delta),
elementScroll elementScroll
; ;
if(module.cache.fits || possibleScroll < 0) { if(module.cache.fits || possibleScroll < 0) {
elementScroll = 0; elementScroll = 0;
} }
else if (possibleScroll > maxScroll ) {
else if(possibleScroll > maxScroll ) {
elementScroll = maxScroll; elementScroll = maxScroll;
} }
else { else {
@ -313,8 +332,8 @@ $.fn.sticky = function(parameters) {
$container = $module.offsetParent(); $container = $module.offsetParent();
} }
else { else {
module.debug('Settings container size', module.cache.context.height);
if( Math.abs($container.height() - module.cache.context.height) > 5) { if( Math.abs($container.height() - module.cache.context.height) > 5) {
module.debug('Context has padding, specifying exact height for container', module.cache.context.height);
$container.css({ $container.css({
height: module.cache.context.height height: module.cache.context.height
}); });
@ -369,8 +388,9 @@ $.fn.sticky = function(parameters) {
} }
}, },
stick: function() {
stick: function(scroll) {
var var
cachedPosition = scroll || $scroll.scrollTop(),
cache = module.cache, cache = module.cache,
fits = cache.fits, fits = cache.fits,
element = cache.element, element = cache.element,
@ -380,11 +400,13 @@ $.fn.sticky = function(parameters) {
? settings.bottomOffset ? settings.bottomOffset
: settings.offset, : settings.offset,
scroll = { scroll = {
top : $scroll.scrollTop() + offset,
bottom : $scroll.scrollTop() + offset + window.height
top : cachedPosition + offset,
bottom : cachedPosition + offset + window.height
}, },
direction = module.get.direction(scroll.top), direction = module.get.direction(scroll.top),
elementScroll = module.get.elementScroll(scroll.top),
elementScroll = (fits)
? 0
: module.get.elementScroll(scroll.top),
// shorthand // shorthand
doesntFit = !fits, doesntFit = !fits,
@ -392,7 +414,7 @@ $.fn.sticky = function(parameters) {
; ;
// save current scroll for next run // save current scroll for next run
module.save.scroll(scroll.top);
module.save.lastScroll(scroll.top);
if(elementVisible) { if(elementVisible) {
@ -552,13 +574,12 @@ $.fn.sticky = function(parameters) {
module.unbind(); module.unbind();
module.unfix(); module.unfix();
module.resetCSS(); module.resetCSS();
module.remove.offset();
}, },
resetCSS: function() { resetCSS: function() {
$module $module
.css({ .css({
top : '',
bottom : '',
width : '', width : '',
height : '' height : ''
}) })
@ -788,4 +809,4 @@ $.fn.sticky.settings = {
}; };
})( jQuery, window , document );
})( jQuery, window , document );
Loading…
Cancel
Save