From cd54328cb9d919e311bfc5e04aa014b93204523b Mon Sep 17 00:00:00 2001 From: jlukic Date: Thu, 16 Apr 2015 12:04:11 -0400 Subject: [PATCH] Ports sticky/visiblity from 2.x early xmas #2127 --- RELEASE-NOTES.md | 11 ++ src/definitions/behaviors/visibility.js | 131 +++++++++++++++++------- src/definitions/modules/sticky.js | 71 ++++++++----- 3 files changed, 152 insertions(+), 61 deletions(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index f30152b91..fc4e1e5b2 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -1,5 +1,16 @@ ## 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 **Bugs** diff --git a/src/definitions/behaviors/visibility.js b/src/definitions/behaviors/visibility.js index 08ade204a..880dc17bf 100644 --- a/src/definitions/behaviors/visibility.js +++ b/src/definitions/behaviors/visibility.js @@ -42,11 +42,11 @@ $.fn.visibility = function(parameters) { moduleNamespace = 'module-' + namespace, $window = $(window), + $module = $(this), $context = $(settings.context), - $images = $module.find('img'), - selector = $module.selector || '', + instance = $module.data(moduleNamespace), requestAnimationFrame = window.requestAnimationFrame @@ -76,13 +76,16 @@ $.fn.visibility = function(parameters) { if(settings.type == 'fixed') { module.setup.fixed(); } + if(settings.observeChanges) { + module.observeChanges(); + } + if( !module.is.visible() ) { + module.error(error.visible, $module); + } } if(settings.initialCheck) { module.checkVisibility(); } - if(settings.observeChanges) { - module.observeChanges(); - } module.instantiate(); }, @@ -96,12 +99,18 @@ $.fn.visibility = function(parameters) { destroy: function() { 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 .off(eventNamespace) .removeData(moduleNamespace) ; - $window.off('resize' + eventNamespace, module.event.refresh); - $context.off('scroll' + eventNamespace, module.event.scroll); }, observeChanges: function() { @@ -111,7 +120,10 @@ $.fn.visibility = function(parameters) { if('MutationObserver' in window) { observer = new MutationObserver(function(mutations) { 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, { childList : true, @@ -125,17 +137,19 @@ $.fn.visibility = function(parameters) { events: function() { module.verbose('Binding visibility events to scroll and resize'); $window - .on('resize' + eventNamespace, module.event.refresh) + .on('load' + eventNamespace, module.event.load) + .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) ; - if($images.length > 0) { - module.bind.imageLoad(); - } }, imageLoad: function() { var + $images = $module.find('img'), imageCount = $images.length, index = imageCount, 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: { - refresh: function() { + resize: function() { + module.debug('Window resized'); requestAnimationFrame(module.refresh); }, + load: function() { + module.debug('Page finished loading'); + requestAnimationFrame(module.refresh); + }, + // publishes scrollchange event on one scroll scroll: function() { - module.verbose('Scroll position changed'); if(settings.throttle) { clearTimeout(module.timer); module.timer = setTimeout(function() { - module.checkVisibility(); + $context.trigger('scrollchange' + eventNamespace, [ $context.scrollTop() ]); }, settings.throttle); } else { 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: { trackChanges: function() { - if(methodInvoked && queryArguments.length > 0) { + if(methodInvoked) { module.debug('One time query, no need to bind events'); return false; } @@ -287,7 +339,7 @@ $.fn.visibility = function(parameters) { }, refresh: function() { - module.debug('Refreshing constants (element width/height)'); + module.debug('Refreshing constants (width/height)'); module.reset(); module.save.position(); module.checkVisibility(); @@ -302,11 +354,14 @@ $.fn.visibility = function(parameters) { } }, - checkVisibility: function() { + checkVisibility: function(scroll) { module.verbose('Checking visibility of element', module.cache.element); if( module.is.visible() ) { + // save scroll position + module.save.scroll(scroll); + // update calculations derived from scroll module.save.calculations(); @@ -616,7 +671,6 @@ $.fn.visibility = function(parameters) { save: { calculations: function() { module.verbose('Saving all calculations necessary to determine positioning'); - module.save.scroll(); module.save.direction(); module.save.screenCalculations(); 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() { var @@ -855,7 +910,7 @@ $.fn.visibility = function(parameters) { }); } clearTimeout(module.performance.timer); - module.performance.timer = setTimeout(module.performance.display, 100); + module.performance.timer = setTimeout(module.performance.display, 500); }, display: function() { var @@ -975,6 +1030,9 @@ $.fn.visibility.settings = { // whether to use mutation observers to follow changes observeChanges : true, + // whether to refresh calculations after all page images load + refreshOnLoad : true, + // callback should only occur one time once : true, @@ -1029,9 +1087,10 @@ $.fn.visibility.settings = { }, 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 ); \ No newline at end of file diff --git a/src/definitions/modules/sticky.js b/src/definitions/modules/sticky.js index a8a4b221c..40d2e7702 100644 --- a/src/definitions/modules/sticky.js +++ b/src/definitions/modules/sticky.js @@ -87,13 +87,18 @@ $.fn.sticky = function(parameters) { }, destroy: function() { - module.verbose('Destroying previous module'); + module.verbose('Destroying previous instance'); module.reset(); if(observer) { 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); }, @@ -107,7 +112,7 @@ $.fn.sticky = function(parameters) { module.timer = setTimeout(function() { module.verbose('DOM tree modified, updating sticky menu'); module.refresh(); - }, 20); + }, 100); }); observer.observe(element, { childList : true, @@ -147,23 +152,36 @@ $.fn.sticky = function(parameters) { bind: { 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: { + load: function() { + module.verbose('Page contents finished loading'); + requestAnimationFrame(module.refresh); + }, resize: function() { - requestAnimationFrame(function() { - module.refresh(); - module.stick(); - }); + module.verbose('Window resized'); + requestAnimationFrame(module.refresh); }, scroll: 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: { - scroll: function(scroll) { + lastScroll: function(scroll) { module.lastScroll = scroll; }, positions: function() { @@ -268,6 +286,7 @@ $.fn.sticky = function(parameters) { : Math.abs(parseInt($module.css('bottom'), 10)) || 0 ; }, + elementScroll: function(scroll) { scroll = scroll || $scroll.scrollTop(); var @@ -275,14 +294,14 @@ $.fn.sticky = function(parameters) { window = module.cache.window, delta = module.get.scrollChange(scroll), maxScroll = (element.height - window.height + settings.offset), - currentScroll = module.get.currentElementScroll(), - possibleScroll = (currentScroll + delta), + elementScroll = module.get.currentElementScroll(), + possibleScroll = (elementScroll + delta), elementScroll ; if(module.cache.fits || possibleScroll < 0) { elementScroll = 0; } - else if (possibleScroll > maxScroll ) { + else if(possibleScroll > maxScroll ) { elementScroll = maxScroll; } else { @@ -313,8 +332,8 @@ $.fn.sticky = function(parameters) { $container = $module.offsetParent(); } else { - module.debug('Settings container size', module.cache.context.height); 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({ height: module.cache.context.height }); @@ -369,8 +388,9 @@ $.fn.sticky = function(parameters) { } }, - stick: function() { + stick: function(scroll) { var + cachedPosition = scroll || $scroll.scrollTop(), cache = module.cache, fits = cache.fits, element = cache.element, @@ -380,11 +400,13 @@ $.fn.sticky = function(parameters) { ? settings.bottomOffset : settings.offset, 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), - elementScroll = module.get.elementScroll(scroll.top), + elementScroll = (fits) + ? 0 + : module.get.elementScroll(scroll.top), // shorthand doesntFit = !fits, @@ -392,7 +414,7 @@ $.fn.sticky = function(parameters) { ; // save current scroll for next run - module.save.scroll(scroll.top); + module.save.lastScroll(scroll.top); if(elementVisible) { @@ -552,13 +574,12 @@ $.fn.sticky = function(parameters) { module.unbind(); module.unfix(); module.resetCSS(); + module.remove.offset(); }, resetCSS: function() { $module .css({ - top : '', - bottom : '', width : '', height : '' }) @@ -788,4 +809,4 @@ $.fn.sticky.settings = { }; -})( jQuery, window , document ); +})( jQuery, window , document ); \ No newline at end of file