diff --git a/src/pig.js b/src/pig.js index 7718041..c1ae56a 100644 --- a/src/pig.js +++ b/src/pig.js @@ -59,7 +59,7 @@ */ reEnable: function() { window.addEventListener('resize', resize); - }, + } }; }()); @@ -211,6 +211,63 @@ */ figureTagName: 'figure', + /** + * Type: string + * Default: 'group' + * Description: The tag name to use for each group. The default setting is + * to use a tag. + */ + groupTagName: 'group', + + /** + * Type: string + * Default: 'groupheadline' + * Description: The tag name to use for each group headline. The default setting is + * to use a tag. + */ + groupHeadlineTagName: 'groupheadline', + + /** + * Type: boolean + * Default: false + * Description: Enables/disables the group headline, the default is false. + */ + enableGroupHeadline: false, + + /** + * Type: boolean + * Default: false + * Description: True: Start a new row per group. + * False: Try to fit multiple groups into one row. If a group requires + * more than one row, fallback to start with a new row for this group. + */ + newRowPerGroup: true, + + /** + * Type: number + * Default: 'groupHeadlineHeight' + * Description: The height of the group headline in pixel. The default is 48px. + */ + groupHeadlineHeight: 48, + + /** + * Get the HTML code for a group headline. + * + * @param {Number} groupid - The id of the group. This id is equal to the id given in the imageData. + * + * @returns {String} The HTML code for the group headline. + */ + getGroupHeadlineHTML: function(groupid) { + return '

' + groupid + '

'; + }, + + /** + * Type: Number + * Default: 64 + * Description: Size in pixels of the gap between groups in one row. This setting is only used if enableGroupHeadline == true. + */ + spaceBetweenGroups: 64, + /** * Type: Number * Default: 8 @@ -391,16 +448,21 @@ * instances that we created. */ Pig.prototype._parseImageData = function(imageData) { - var progressiveImages = []; + var progressiveGroups = []; + + imageData.forEach(function(group, gindex) { + var progressive_group = new ProgressiveGroup(group.groupid, gindex, this); - imageData.forEach(function(image, index) { - var progressiveImage = new ProgressiveImage(image, index, this); - progressiveImages.push(progressiveImage); + group.images.forEach(function(image, index) { + var progressiveImage = new ProgressiveImage(image, index, this, progressive_group); + progressive_group.addImage(progressiveImage); + }.bind(this)); + + progressiveGroups.push(progressive_group); }.bind(this)); - return progressiveImages; + return progressiveGroups; }; - /** * This computes the layout of the entire grid, setting the height, width, * translateX, translateY, and transtion values for each ProgessiveImage in @@ -421,9 +483,9 @@ var wrapperWidth = parseInt(this.container.clientWidth); // State - var row = []; // The list of images in the current row. - var translateX = 0; // The current translateX value that we are at - var translateY = 0; // The current translateY value that we are at + var row = []; // The list of images in the current row. + var translateX = 0; // The current translateX value that we are at + var translateY = 0; // The current translateY value that we are at var rowAspectRatio = 0; // The aspect ratio of the row we are building // Compute the minimum aspect ratio that should be applied to the rows. @@ -446,62 +508,152 @@ // Get the valid-CSS transition string. var transition = this._getTransitionString(); + var rows = []; // Loop through all our images, building them up into rows and computing // the working rowAspectRatio. - [].forEach.call(this.images, function(image, index) { - rowAspectRatio += parseFloat(image.aspectRatio); - row.push(image); - - // When the rowAspectRatio exceeeds the minimum acceptable aspect ratio, - // or when we're out of images, we say that we have all the images we - // need for this row, and compute the style values for each of these - // images. - if (rowAspectRatio >= this.minAspectRatio || index + 1 === this.images.length) { - - // Make sure that the last row also has a reasonable height - rowAspectRatio = Math.max(rowAspectRatio, this.minAspectRatio); - - // Compute this row's height. - var totalDesiredWidthOfImages = wrapperWidth - this.settings.spaceBetweenImages * (row.length - 1); - var rowHeight = totalDesiredWidthOfImages / rowAspectRatio; - - // For each image in the row, compute the width, height, translateX, - // and translateY values, and set them (and the transition value we - // found above) on each image. - // - // NOTE: This does not manipulate the DOM, rather it just sets the - // style values on the ProgressiveImage instance. The DOM nodes - // will be updated in _doLayout. - row.forEach(function(img) { - - var imageWidth = rowHeight * img.aspectRatio; - - // This is NOT DOM manipulation. - img.style = { - width: parseInt(imageWidth), - height: parseInt(rowHeight), - translateX: translateX, - translateY: translateY, - transition: transition, - }; - - // The next image is this.settings.spaceBetweenImages pixels to the - // right of this image. - translateX += imageWidth + this.settings.spaceBetweenImages; - - }.bind(this)); - - // Reset our state variables for next row. - row = []; - rowAspectRatio = 0; - translateY += parseInt(rowHeight) + this.settings.spaceBetweenImages; - translateX = 0; - } + [].forEach.call(this.images, function(group, gindex) { + var self = this; + //either get the aspect ratio of the group or take a real big value + var next_group_aspect_ratio = gindex + 1 < this.images.length ? this.images[gindex + 1].getAspectRatio() : 1000; + + [].forEach.call(group.images, function(image, index) { + var self = this; + rowAspectRatio += parseFloat(image.aspectRatio); + row.push(image); + + + var last_group_row = index + 1 === group.images.length; + var last_global_row = last_group_row && gindex == this.images.length; + // Either we have enabled a new row per group, the row aspect ratio + // plus the next group aspect ratio is greater than the allowed aspect ratio + // or the current row belongs to a group that occupies multiple rows + var new_row_for_next_group = this.settings.newRowPerGroup || + (rowAspectRatio + next_group_aspect_ratio) >= this.minAspectRatio || + translateY !== 0; + var out_of_images = last_global_row || (last_group_row && new_row_for_next_group); + + // When the rowAspectRatio exceeds the minimum acceptable aspect ratio, + // or when we're out of images, we say that we have all the images we + // need for this row, and compute the style values for each of these + // images. + if (rowAspectRatio >= this.minAspectRatio || out_of_images) { + // count the number of groups in this row + var groups_in_row = row.reduce(function (groups, current) { + if (groups.indexOf(current.getGroupIndex()) === -1) { + groups.push(current.getGroupIndex()); + } + + return groups; + }, []).length; + + // Make sure that the last row also has a reasonable height + rowAspectRatio = Math.max(rowAspectRatio, this.minAspectRatio); + + // Compute this row's height. + var totalDesiredWidthOfImages = wrapperWidth - this.settings.spaceBetweenImages * (row.length - 1) - + this.settings.spaceBetweenGroups * (groups_in_row - 1); + var rowHeight = totalDesiredWidthOfImages / rowAspectRatio; + + // For each image in the row, compute the width, height, translateX, + // and translateY values, and set them (and the transition value we + // found above) on each image. + // + // NOTE: This does not manipulate the DOM, rather it just sets the + // style values on the ProgressiveImage instance. The DOM nodes + // will be updated in _doLayout. + var last_group_index = row[0].getGroupIndex(); + row.forEach(function(img) { + var imageWidth = rowHeight * img.aspectRatio; + + // If we start with a new group in the row, reset the translateX + if (last_group_index !== img.getGroupIndex()) { + translateX = 0; + last_group_index = img.getGroupIndex(); + } + + // This is NOT DOM manipulation. + img.style = { + width: parseInt(imageWidth), + height: parseInt(rowHeight), + translateX: translateX, + translateY: translateY, + transition: transition + }; + + // The next image is this.settings.spaceBetweenImages pixels to the + // right of this image. + translateX += imageWidth + this.settings.spaceBetweenImages; + }.bind(self)); + + rows.push(row); + // Reset our state variables for next row. + row = []; + rowAspectRatio = 0; + translateY += parseInt(rowHeight) + this.settings.spaceBetweenImages; + translateX = 0; + } + }.bind(self)); + + translateY = 0; + }.bind(this)); + + // Now we iterate over all generated rows and update the corresponding groups. + // This means we set the width, height, top and left of each group. + var group_top = 0; + rows.forEach(function(row, rindex) { + var seen_groups = []; + var group_width = 0; + var row_height = 0; + var group_left = 0; + var last_row = rindex + 1 === rows.length; + var group_top_updated_for_row = false; + var self = this; + row.forEach(function(img, index) { + var last_row_img = index + 1 === row.length + 1; + var last_global_img = last_row_img && last_row; + + row_height = img.style.height + (last_row ? 0 : this.settings.spaceBetweenImages); + + // If we did not have seen this group in this row already, we have to set some variables: + // First, we update the group_left value, if we got multiple groups per row, we need to move them + // to the right, so that they do not overlap each other. + // Second, we reset the group_width, because we start with a new group. + // Third, we update the height of the group. + if (seen_groups.indexOf(img.getGroupIndex()) === -1) { + group_left += group_width + (group_width === 0 ? 0 : this.settings.spaceBetweenGroups - this.settings.spaceBetweenImages); + group_width = 0; + seen_groups.push(img.getGroupIndex()); + img.getGroup().style.height += row_height; + } + + group_width += img.style.width + (last_row_img ? 0 : this.settings.spaceBetweenImages); + + img.getGroup().style.width = Math.max(img.getGroup().style.width, group_width); + + img.getGroup().style.left = group_left; + + // Now we need to update the group top value. + // But, as we can have one group in multiple rows, we only want to update the top of the group once. + // So, we first check that the current group top is equal to 0 and then we check that either we have group headlines enabled + // or that the current group is not the first group. + if (img.getGroup().style.top === 0 && (this.settings.groupHeadlineHeight || img.getGroupIndex() > 0)) { + // We only update the group_top once per row, because when we have multiple groups in one row, + // all should start at the same top. + if (!group_top_updated_for_row) { + group_top += (this.settings.enableGroupHeadline ? this.settings.groupHeadlineHeight : 0); + group_top_updated_for_row = true; + } + + img.getGroup().style.top = group_top; + } + }.bind(self)); + + group_top += row_height; }.bind(this)); // No space below the last image - this.totalHeight = translateY - this.settings.spaceBetweenImages; + this.totalHeight = group_top - this.settings.spaceBetweenImages; }; @@ -596,16 +748,8 @@ // Here, we loop over every image, determine if it is inside our buffers or // no, and either insert it or remove it appropriately. - this.images.forEach(function(image) { - - if (image.style.translateY + image.style.height < minTranslateYPlusHeight || - image.style.translateY > maxTranslateY) { - // Hide Image - image.hide(); - } else { - // Load Image - image.load(); - } + this.images.forEach(function(group) { + group.updateVisibility(minTranslateYPlusHeight, maxTranslateY); }.bind(this)); }; @@ -683,6 +827,174 @@ return this; }; + /** + * This class manages a single group, a group contains one to multiple images. + * It keeps track of the group's height, width, and position in the grid. + * + * However, this element may or may not actually exist in the DOM. The actual + * DOM element may loaded and unloaded depending on where it is with respect + * to the viewport. This class is responsible for managing the DOM elements. + * + * @param {number} groupid - This id is equal to the id given by the user in the image data. + * @param {index} index - The index of the group in the pig.images array. + * @param {object} pig - The pig instance. + */ + function ProgressiveGroup(groupid, index, pig) { + this.groupid = groupid; + this.index = index; + this.images = []; + this.pig = pig; + + this.classNames = { + group: pig.settings.classPrefix + '-group', + headline: pig.settings.classPrefix + 'group-headline' + }; + + this.existsOnPage = false; + + if (pig.settings.enableGroupHeadline) { + this.createHeadline(); + } + + this.style = { + width: 0, + height: 0, + top: 0, + left: 0 + }; + } + + /** + * Adds an image to this group. + */ + ProgressiveGroup.prototype.addImage = function(image) { + this.images.push(image); + }; + + /** + * Computes the aspect ratio of the whole group. + * + * @returns {number} The aspect ratio of this group. + */ + ProgressiveGroup.prototype.getAspectRatio = function() { + var aspect_ratio = 0.0; + this.images.forEach(function(image) { + aspect_ratio += parseFloat(image.aspectRatio); + }); + + return aspect_ratio; + }; + + /** + * Returns the index of this group. + * + * @returns {number} The index of this group in the pig.images array. + */ + ProgressiveGroup.prototype.getIndex = function() { + return this.index; + }; + + /** + * Checks if this group is currently visible/invisible in the viewport. + * If the group is visible in the current viewport, the dom element is + * added to the root dom element, otherwise the group dom element is removed. + * This function also calls the updateVisibility function of all images, but + * only if the group itself is visible. + * + * @param {number} miny - The top of the viewport. + * @param {number} maxy - The bottom of the viewport. + */ + ProgressiveGroup.prototype.updateVisibility = function(miny, maxy) { + var top = this.style.top; + var top_plus_height = this.style.height + top; + + if (this.headline) { + top -= this.pig.settings.groupHeadlineHeight; + } + + if (top <= maxy && top_plus_height >= miny) { + this.load(); + this.images.forEach(function(img) { img.updateVisibility(miny - top, maxy - top); }); + } else { + this.hide(); + } + }; + + /** + * Get the DOM element associated with this ProgressiveGroup. We default to + * using this.element, and we create it if it doesn't exist. + * + * @returns {HTMLElement} The DOM element associated with this instance. + */ + ProgressiveGroup.prototype.getElement = function() { + if (!this.element) { + this.element = document.createElement(this.pig.settings.groupTagName); + this.element.className = this.classNames.group; + + if (this.headline) { + this.element.appendChild(this.headline); + } + + this._updateStyles(); + } + + return this.element; + }; + + /** + * Creates the headline for this group. + */ + ProgressiveGroup.prototype.createHeadline = function() { + if (!this.headline) { + this.headline = document.createElement(this.pig.settings.groupHeadlineTagName); + this.headline.className = this.classNames.headline; + } + }; + + /** + * Updates the style attribute to reflect this style property on this object. + */ + ProgressiveGroup.prototype._updateStyles = function() { + this.getElement().style.position = 'absolute'; + this.getElement().style.width = this.style.width + 'px'; + this.getElement().style.height = this.style.height + 'px'; + this.getElement().style.top = this.style.top + 'px'; + this.getElement().style.left = this.style.left + 'px'; + + if (this.headline) { + var height = this.pig.settings.groupHeadlineHeight; + + this.headline.style.minWidth = '100%'; + this.headline.style.height = height + 'px'; + this.headline.style.top = '-' + height + 'px'; + this.headline.innerHTML = this.pig.settings.getGroupHeadlineHTML(this.groupid); + this.headline.style.position = 'absolute'; + } + }; + + /** + * Loads this group into the DOM. + */ + ProgressiveGroup.prototype.load = function() { + if (!this.existsOnPage) { + this.pig.container.appendChild(this.getElement()); + this._updateStyles(); + } + + this.existsOnPage = true; + }; + + /** + * Unloads this group from the DOM. + */ + ProgressiveGroup.prototype.hide = function() { + if (this.existsOnPage) { + this.pig.container.removeChild(this.getElement()); + } + + this.existsOnPage = false; + }; + /** * This class manages a single image. It keeps track of the image's height, * width, and position in the grid. An instance of this class is associated @@ -695,9 +1007,7 @@ * * However, this element may or may not actually exist in the DOM. The actual * DOM element may loaded and unloaded depending on where it is with respect - * to the viewport. This class is responsible for managing the DOM elements, - * but does not include logic to determine _when_ the DOM elements should - * be removed. + * to the viewport. This class is responsible for managing the DOM elements. * * This class also manages the blur-into-focus load effect. First, the *
element is inserted into the page. Then, a very small thumbnail @@ -713,9 +1023,9 @@ * @param {string} singleImageData[0].filename - The filename of the image. * @param {string} singleImageData[0].aspectRatio - The aspect ratio of the * image. + * @param {object} group - The group this image belongs to. */ - function ProgressiveImage(singleImageData, index, pig) { - + function ProgressiveImage(singleImageData, index, pig, group) { // Global State this.existsOnPage = false; // True if the element exists on the page. @@ -727,15 +1037,46 @@ // The Pig instance this.pig = pig; + // The group the image belongs to + this.group = group; + this.classNames = { figure: pig.settings.classPrefix + '-figure', thumbnail: pig.settings.classPrefix + '-thumbnail', - loaded: pig.settings.classPrefix + '-loaded', + loaded: pig.settings.classPrefix + '-loaded' }; return this; } + /** + * Returns the index of the group this image belongs to. + */ + ProgressiveImage.prototype.getGroupIndex = function() { + return this.group.getIndex(); + }; + + /** + * Returns the group this image belongs to. + */ + ProgressiveImage.prototype.getGroup = function() { + return this.group; + }; + + /** + * Updates the visibility of this image. + * + * @param {number} miny - The top of the viewport. + * @param {number} maxy - The bottom of the viewport. + */ + ProgressiveImage.prototype.updateVisibility = function(miny, maxy) { + if (this.style.translateY <= maxy && this.style.translateY + this.style.height >= miny) { + this.load(); + } else { + this.hide(); + } + }; + /** * Load the image element associated with this ProgressiveImage into the DOM. * @@ -748,7 +1089,7 @@ // is done using transforms. this.existsOnPage = true; this._updateStyles(); - this.pig.container.appendChild(this.getElement()); + this.group.getElement().appendChild(this.getElement()); // We run the rest of the function in a 100ms setTimeout so that if the // user is scrolling down the page very fast and hide() is called within @@ -821,11 +1162,10 @@ // Remove the image from the DOM. if (this.existsOnPage) { - this.pig.container.removeChild(this.getElement()); + this.group.getElement().removeChild(this.getElement()); } this.existsOnPage = false; - }; /** diff --git a/src/pig.min.js b/src/pig.min.js index d88d946..454d226 100644 --- a/src/pig.min.js +++ b/src/pig.min.js @@ -1 +1 @@ -!function(t){"use strict";function i(t,i,e){var s="#"+t+" { position: relative;}."+i+"-figure { background-color: #D5D5D5; overflow: hidden; left: 0; position: absolute; top: 0; margin: 0;}."+i+"-figure img { left: 0; position: absolute; top: 0; height: 100%; opacity: 0; transition: "+e/1e3+"s ease opacity; -webkit-transition: "+e/1e3+"s ease opacity;}."+i+"-figure img."+i+"-thumbnail { -webkit-filter: blur(30px); filter: blur(30px); left: auto; position: relative; width: auto;}."+i+"-figure img."+i+"-loaded { opacity: 1;}",n=document.head||document.getElementsByTagName("head")[0],a=document.createElement("style");a.type="text/css",a.styleSheet?a.styleSheet.cssText=s:a.appendChild(document.createTextNode(s)),n.appendChild(a)}function e(t,i){for(var e in i)i.hasOwnProperty(e)&&(t[e]=i[e])}function s(t){var i=0;do isNaN(t.offsetTop)||(i+=t.offsetTop),t=t.offsetParent;while(t);return i}function n(t,s){return this.inRAF=!1,this.isTransitioning=!1,this.minAspectRatioRequiresTransition=!1,this.minAspectRatio=null,this.latestYOffset=0,this.lastWindowWidth=window.innerWidth,this.scrollDirection="down",this.visibleImages=[],this.settings={containerId:"pig",classPrefix:"pig",figureTagName:"figure",spaceBetweenImages:8,transitionSpeed:500,primaryImageBufferHeight:1e3,secondaryImageBufferHeight:300,thumbnailSize:20,urlForSize:function(t,i){return"/img/"+i+"/"+t},getMinAspectRatio:function(t){return t<=640?2:t<=1280?4:t<=1920?5:6},getImageSize:function(t){return t<=640?100:t<=1920?250:500}},e(this.settings,s||{}),this.container=document.getElementById(this.settings.containerId),this.container||console.error("Could not find element with ID "+this.settings.containerId),this.images=this._parseImageData(t),i(this.settings.containerId,this.settings.classPrefix,this.settings.transitionSpeed),this}function a(t,i,e){return this.existsOnPage=!1,this.aspectRatio=t.aspectRatio,this.filename=t.filename,this.index=i,this.pig=e,this.classNames={figure:e.settings.classPrefix+"-figure",thumbnail:e.settings.classPrefix+"-thumbnail",loaded:e.settings.classPrefix+"-loaded"},this}var o=function(){function t(){s||(s=!0,window.requestAnimationFrame?window.requestAnimationFrame(i):setTimeout(i,66))}function i(){e.forEach(function(t){t()}),s=!1}var e=[],s=!1;return{add:function(i){e.length||window.addEventListener("resize",t),e.push(i)},disable:function(){window.removeEventListener("resize",t)},reEnable:function(){window.addEventListener("resize",t)}}}();n.prototype._getTransitionTimeout=function(){var t=1.5;return this.settings.transitionSpeed*t},n.prototype._getTransitionString=function(){return this.isTransitioning?this.settings.transitionSpeed/1e3+"s transform ease":"none"},n.prototype._recomputeMinAspectRatio=function(){var t=this.minAspectRatio;this.minAspectRatio=this.settings.getMinAspectRatio(this.lastWindowWidth),null!==t&&t!==this.minAspectRatio?this.minAspectRatioRequiresTransition=!0:this.minAspectRatioRequiresTransition=!1},n.prototype._parseImageData=function(t){var i=[];return t.forEach(function(t,e){var s=new a(t,e,this);i.push(s)}.bind(this)),i},n.prototype._computeLayout=function(){var t=parseInt(this.container.clientWidth),i=[],e=0,s=0,n=0;this._recomputeMinAspectRatio(),!this.isTransitioning&&this.minAspectRatioRequiresTransition&&(this.isTransitioning=!0,setTimeout(function(){this.isTransitioning=!1},this._getTransitionTimeout()));var a=this._getTransitionString();[].forEach.call(this.images,function(o,h){if(n+=parseFloat(o.aspectRatio),i.push(o),n>=this.minAspectRatio||h+1===this.images.length){var r=t-this.settings.spaceBetweenImages*(i.length-1),l=r/n;i.forEach(function(t){var i=l*t.aspectRatio;t.style={width:parseInt(i),height:parseInt(l),translateX:e,translateY:s,transition:a},e+=i+this.settings.spaceBetweenImages}.bind(this)),i=[],n=0,s+=parseInt(l)+this.settings.spaceBetweenImages,e=0}}.bind(this)),this.totalHeight=s-this.settings.spaceBetweenImages},n.prototype._doLayout=function(){this.container.style.height=this.totalHeight+"px";var t="up"===this.scrollDirection?this.settings.primaryImageBufferHeight:this.settings.secondaryImageBufferHeight,i="down"===this.scrollDirection?this.settings.secondaryImageBufferHeight:this.settings.primaryImageBufferHeight,e=s(this.container),n=window.innerHeight,a=this.latestYOffset-e-t,o=this.latestYOffset+n+i;this.images.forEach(function(t){t.style.translateY+t.style.heighto?t.hide():t.load()}.bind(this))},n.prototype._getOnScroll=function(){var t=this,i=function(){var i=window.pageYOffset;t.previousYOffset=t.latestYOffset||i,t.latestYOffset=i,t.scrollDirection=t.latestYOffset>t.previousYOffset?"down":"up",t.inRAF||(t.inRAF=!0,window.requestAnimationFrame(function(){t._doLayout(),t.inRAF=!1}))};return i},n.prototype.enable=function(){return this.onScroll=this._getOnScroll(),window.addEventListener("scroll",this.onScroll),this.onScroll(),this._computeLayout(),this._doLayout(),o.add(function(){this.lastWindowWidth=window.innerWidth,this._computeLayout(),this._doLayout()}.bind(this)),this},n.prototype.disable=function(){return window.removeEventListener("scroll",this.onScroll),o.disable(),this},a.prototype.load=function(){this.existsOnPage=!0,this._updateStyles(),this.pig.container.appendChild(this.getElement()),setTimeout(function(){this.existsOnPage&&(this.thumbnail||(this.thumbnail=new Image,this.thumbnail.src=this.pig.settings.urlForSize(this.filename,this.pig.settings.thumbnailSize),this.thumbnail.className=this.classNames.thumbnail,this.thumbnail.onload=function(){this.thumbnail&&(this.thumbnail.className+=" "+this.classNames.loaded)}.bind(this),this.getElement().appendChild(this.thumbnail)),this.fullImage||(this.fullImage=new Image,this.fullImage.src=this.pig.settings.urlForSize(this.filename,this.pig.settings.getImageSize(this.pig.lastWindowWidth)),this.fullImage.onload=function(){this.fullImage&&(this.fullImage.className+=" "+this.classNames.loaded)}.bind(this),this.getElement().appendChild(this.fullImage)))}.bind(this),100)},a.prototype.hide=function(){this.getElement()&&(this.thumbnail&&(this.thumbnail.src="",this.getElement().removeChild(this.thumbnail),delete this.thumbnail),this.fullImage&&(this.fullImage.src="",this.getElement().removeChild(this.fullImage),delete this.fullImage)),this.existsOnPage&&this.pig.container.removeChild(this.getElement()),this.existsOnPage=!1},a.prototype.getElement=function(){return this.element||(this.element=document.createElement(this.pig.settings.figureTagName),this.element.className=this.classNames.figure,this._updateStyles()),this.element},a.prototype._updateStyles=function(){this.getElement().style.transition=this.style.transition,this.getElement().style.width=this.style.width+"px",this.getElement().style.height=this.style.height+"px",this.getElement().style.transform="translate3d("+this.style.translateX+"px,"+this.style.translateY+"px, 0)"},"function"==typeof define&&define.amd?define(n):"undefined"!=typeof module&&module.exports?module.exports=n:t.Pig=n}(this); \ No newline at end of file +!function(t){"use strict";function e(t,e,i){var s="#"+t+" { position: relative;}."+e+"-figure { background-color: #D5D5D5; overflow: hidden; left: 0; position: absolute; top: 0; margin: 0;}."+e+"-figure img { left: 0; position: absolute; top: 0; height: 100%; width: 100%; opacity: 0; transition: "+i/1e3+"s ease opacity; -webkit-transition: "+i/1e3+"s ease opacity;}."+e+"-figure img."+e+"-thumbnail { -webkit-filter: blur(30px); filter: blur(30px); left: auto; position: relative; width: auto;}."+e+"-figure img."+e+"-loaded { opacity: 1;}",n=document.head||document.getElementsByTagName("head")[0],a=document.createElement("style");a.type="text/css",a.styleSheet?a.styleSheet.cssText=s:a.appendChild(document.createTextNode(s)),n.appendChild(a)}function i(t,e){for(var i in e)e.hasOwnProperty(i)&&(t[i]=e[i])}function s(t){var e=0;do isNaN(t.offsetTop)||(e+=t.offsetTop),t=t.offsetParent;while(t);return e}function n(t,s){return this.inRAF=!1,this.isTransitioning=!1,this.minAspectRatioRequiresTransition=!1,this.minAspectRatio=null,this.latestYOffset=0,this.lastWindowWidth=window.innerWidth,this.scrollDirection="down",this.visibleImages=[],this.settings={containerId:"pig",classPrefix:"pig",figureTagName:"figure",groupTagName:"group",groupHeadlineTagName:"groupheadline",enableGroupHeadline:!1,newRowPerGroup:!0,groupHeadlineHeight:48,getGroupHeadlineHTML:function(t){return"

"+t+"

"},spaceBetweenGroups:64,spaceBetweenImages:8,transitionSpeed:500,primaryImageBufferHeight:1e3,secondaryImageBufferHeight:300,thumbnailSize:20,urlForSize:function(t,e){return"/img/"+e+"/"+t},getMinAspectRatio:function(t){return t<=640?2:t<=1280?4:t<=1920?5:6},getImageSize:function(t){return t<=640?100:t<=1920?250:500}},i(this.settings,s||{}),this.container=document.getElementById(this.settings.containerId),this.container||console.error("Could not find element with ID "+this.settings.containerId),this.images=this._parseImageData(t),e(this.settings.containerId,this.settings.classPrefix,this.settings.transitionSpeed),this}function a(t,e,i){this.groupid=t,this.index=e,this.images=[],this.pig=i,this.classNames={group:i.settings.classPrefix+"-group",headline:i.settings.classPrefix+"group-headline"},this.existsOnPage=!1,i.settings.enableGroupHeadline&&this.createHeadline(),this.style={width:0,height:0,top:0,left:0}}function o(t,e,i,s){return this.existsOnPage=!1,this.aspectRatio=t.aspectRatio,this.filename=t.filename,this.index=e,this.pig=i,this.group=s,this.classNames={figure:i.settings.classPrefix+"-figure",thumbnail:i.settings.classPrefix+"-thumbnail",loaded:i.settings.classPrefix+"-loaded"},this}var h=function(){function t(){s||(s=!0,window.requestAnimationFrame?window.requestAnimationFrame(e):setTimeout(e,66))}function e(){i.forEach(function(t){t()}),s=!1}var i=[],s=!1;return{add:function(e){i.length||window.addEventListener("resize",t),i.push(e)},disable:function(){window.removeEventListener("resize",t)},reEnable:function(){window.addEventListener("resize",t)}}}();n.prototype._getTransitionTimeout=function(){var t=1.5;return this.settings.transitionSpeed*t},n.prototype._getTransitionString=function(){return this.isTransitioning?this.settings.transitionSpeed/1e3+"s transform ease":"none"},n.prototype._recomputeMinAspectRatio=function(){var t=this.minAspectRatio;this.minAspectRatio=this.settings.getMinAspectRatio(this.lastWindowWidth),null!==t&&t!==this.minAspectRatio?this.minAspectRatioRequiresTransition=!0:this.minAspectRatioRequiresTransition=!1},n.prototype._parseImageData=function(t){var e=[];return t.forEach(function(t,i){var s=new a(t.groupid,i,this);t.images.forEach(function(t,e){var i=new o(t,e,this,s);s.addImage(i)}.bind(this)),e.push(s)}.bind(this)),e},n.prototype._computeLayout=function(){var t=parseInt(this.container.clientWidth),e=[],i=0,s=0,n=0;this._recomputeMinAspectRatio(),!this.isTransitioning&&this.minAspectRatioRequiresTransition&&(this.isTransitioning=!0,setTimeout(function(){this.isTransitioning=!1},this._getTransitionTimeout()));var a=this._getTransitionString(),o=[];[].forEach.call(this.images,function(h,r){var l=this,g=r+1=this.minAspectRatio||0!==s,f=c||d&&m;if(n>=this.minAspectRatio||f){var y=e.reduce(function(t,e){return t.indexOf(e.getGroupIndex())===-1&&t.push(e.getGroupIndex()),t},[]).length;n=Math.max(n,this.minAspectRatio);var w=t-this.settings.spaceBetweenImages*(e.length-1)-this.settings.spaceBetweenGroups*(y-1),I=w/n,x=e[0].getGroupIndex();e.forEach(function(t){var e=I*t.aspectRatio;x!==t.getGroupIndex()&&(i=0,x=t.getGroupIndex()),t.style={width:parseInt(e),height:parseInt(I),translateX:i,translateY:s,transition:a},i+=e+this.settings.spaceBetweenImages}.bind(p)),o.push(e),e=[],n=0,s+=parseInt(I)+this.settings.spaceBetweenImages,i=0}}.bind(l)),s=0}.bind(this));var h=0;o.forEach(function(t,e){var i=[],s=0,n=0,a=0,r=e+1===o.length,l=!1,g=this;t.forEach(function(e,o){var g=o+1===t.length+1;n=e.style.height+(r?0:this.settings.spaceBetweenImages),i.indexOf(e.getGroupIndex())===-1&&(a+=s+(0===s?0:this.settings.spaceBetweenGroups-this.settings.spaceBetweenImages),s=0,i.push(e.getGroupIndex()),e.getGroup().style.height+=n),s+=e.style.width+(g?0:this.settings.spaceBetweenImages),e.getGroup().style.width=Math.max(e.getGroup().style.width,s),e.getGroup().style.left=a,0===e.getGroup().style.top&&(this.settings.groupHeadlineHeight||e.getGroupIndex()>0)&&(l||(h+=this.settings.enableGroupHeadline?this.settings.groupHeadlineHeight:0,l=!0),e.getGroup().style.top=h)}.bind(g)),h+=n}.bind(this)),this.totalHeight=h-this.settings.spaceBetweenImages},n.prototype._doLayout=function(){this.container.style.height=this.totalHeight+"px";var t="up"===this.scrollDirection?this.settings.primaryImageBufferHeight:this.settings.secondaryImageBufferHeight,e="down"===this.scrollDirection?this.settings.secondaryImageBufferHeight:this.settings.primaryImageBufferHeight,i=s(this.container),n=window.innerHeight,a=this.latestYOffset-i-t,o=this.latestYOffset+n+e;this.images.forEach(function(t){t.updateVisibility(a,o)}.bind(this))},n.prototype._getOnScroll=function(){var t=this,e=function(){var e=window.pageYOffset;t.previousYOffset=t.latestYOffset||e,t.latestYOffset=e,t.scrollDirection=t.latestYOffset>t.previousYOffset?"down":"up",t.inRAF||(t.inRAF=!0,window.requestAnimationFrame(function(){t._doLayout(),t.inRAF=!1}))};return e},n.prototype.enable=function(){return this.onScroll=this._getOnScroll(),window.addEventListener("scroll",this.onScroll),this.onScroll(),this._computeLayout(),this._doLayout(),h.add(function(){this.lastWindowWidth=window.innerWidth,this._computeLayout(),this._doLayout()}.bind(this)),this},n.prototype.disable=function(){return window.removeEventListener("scroll",this.onScroll),h.disable(),this},a.prototype.addImage=function(t){this.images.push(t)},a.prototype.getAspectRatio=function(){var t=0;return this.images.forEach(function(e){t+=parseFloat(e.aspectRatio)}),t},a.prototype.getIndex=function(){return this.index},a.prototype.updateVisibility=function(t,e){var i=this.style.top,s=this.style.height+i;this.headline&&(i-=this.pig.settings.groupHeadlineHeight),i<=e&&s>=t?(this.load(),this.images.forEach(function(s){s.updateVisibility(t-i,e-i)})):this.hide()},a.prototype.getElement=function(){return this.element||(this.element=document.createElement(this.pig.settings.groupTagName),this.element.className=this.classNames.group,this.headline&&this.element.appendChild(this.headline),this._updateStyles()),this.element},a.prototype.createHeadline=function(){this.headline||(this.headline=document.createElement(this.pig.settings.groupHeadlineTagName),this.headline.className=this.classNames.headline)},a.prototype._updateStyles=function(){if(this.getElement().style.position="absolute",this.getElement().style.width=this.style.width+"px",this.getElement().style.height=this.style.height+"px",this.getElement().style.top=this.style.top+"px",this.getElement().style.left=this.style.left+"px",this.headline){var t=this.pig.settings.groupHeadlineHeight;this.headline.style.minWidth="100%",this.headline.style.height=t+"px",this.headline.style.top="-"+t+"px",this.headline.innerHTML=this.pig.settings.getGroupHeadlineHTML(this.groupid),this.headline.style.position="absolute"}},a.prototype.load=function(){this.existsOnPage||(this.pig.container.appendChild(this.getElement()),this._updateStyles()),this.existsOnPage=!0},a.prototype.hide=function(){this.existsOnPage&&this.pig.container.removeChild(this.getElement()),this.existsOnPage=!1},o.prototype.getGroupIndex=function(){return this.group.getIndex()},o.prototype.getGroup=function(){return this.group},o.prototype.updateVisibility=function(t,e){this.style.translateY<=e&&this.style.translateY+this.style.height>=t?this.load():this.hide()},o.prototype.load=function(){this.existsOnPage=!0,this._updateStyles(),this.group.getElement().appendChild(this.getElement()),setTimeout(function(){this.existsOnPage&&(this.thumbnail||(this.thumbnail=new Image,this.thumbnail.src=this.pig.settings.urlForSize(this.filename,this.pig.settings.thumbnailSize),this.thumbnail.className=this.classNames.thumbnail,this.thumbnail.onload=function(){this.thumbnail&&(this.thumbnail.className+=" "+this.classNames.loaded)}.bind(this),this.getElement().appendChild(this.thumbnail)),this.fullImage||(this.fullImage=new Image,this.fullImage.src=this.pig.settings.urlForSize(this.filename,this.pig.settings.getImageSize(this.pig.lastWindowWidth)),this.fullImage.onload=function(){this.fullImage&&(this.fullImage.className+=" "+this.classNames.loaded)}.bind(this),this.getElement().appendChild(this.fullImage)))}.bind(this),100)},o.prototype.hide=function(){this.getElement()&&(this.thumbnail&&(this.thumbnail.src="",this.getElement().removeChild(this.thumbnail),delete this.thumbnail),this.fullImage&&(this.fullImage.src="",this.getElement().removeChild(this.fullImage),delete this.fullImage)),this.existsOnPage&&this.group.getElement().removeChild(this.getElement()),this.existsOnPage=!1},o.prototype.getElement=function(){return this.element||(this.element=document.createElement(this.pig.settings.figureTagName),this.element.className=this.classNames.figure,this._updateStyles()),this.element},o.prototype._updateStyles=function(){this.getElement().style.transition=this.style.transition,this.getElement().style.width=this.style.width+"px",this.getElement().style.height=this.style.height+"px",this.getElement().style.transform="translate3d("+this.style.translateX+"px,"+this.style.translateY+"px, 0)"},"function"==typeof define&&define.amd?define(n):"undefined"!=typeof module&&module.exports?module.exports=n:t.Pig=n}(this); \ No newline at end of file diff --git a/test/index.html b/test/index.html index e515505..9693e09 100644 --- a/test/index.html +++ b/test/index.html @@ -25,95 +25,96 @@

Scroll to see some images...

+ + +