/*! * masonry packaged v4.2.0 * cascading grid layout library * http://masonry.desandro.com * mit license * by david desandro */ /** * bridget makes jquery widgets * v2.0.1 * mit license */ /* jshint browser: true, strict: true, undef: true, unused: true */ ( function( window, factory ) { // universal module definition /*jshint strict: false */ /* globals define, module, require */ if ( typeof define == 'function' && define.amd ) { // amd define( 'jquery-bridget/jquery-bridget',[ 'jquery' ], function( jquery ) { return factory( window, jquery ); }); } else if ( typeof module == 'object' && module.exports ) { // commonjs module.exports = factory( window, require('jquery') ); } else { // browser global window.jquerybridget = factory( window, window.jquery ); } }( window, function factory( window, jquery ) { 'use strict'; // ----- utils ----- // var arrayslice = array.prototype.slice; // helper function for logging errors // $.error breaks jquery chaining var console = window.console; var logerror = typeof console == 'undefined' ? function() {} : function( message ) { console.error( message ); }; // ----- jquerybridget ----- // function jquerybridget( namespace, pluginclass, $ ) { $ = $ || jquery || window.jquery; if ( !$ ) { return; } // add option method -> $().plugin('option', {...}) if ( !pluginclass.prototype.option ) { // option setter pluginclass.prototype.option = function( opts ) { // bail out if not an object if ( !$.isplainobject( opts ) ){ return; } this.options = $.extend( true, this.options, opts ); }; } // make jquery plugin $.fn[ namespace ] = function( arg0 /*, arg1 */ ) { if ( typeof arg0 == 'string' ) { // method call $().plugin( 'methodname', { options } ) // shift arguments by 1 var args = arrayslice.call( arguments, 1 ); return methodcall( this, arg0, args ); } // just $().plugin({ options }) plaincall( this, arg0 ); return this; }; // $().plugin('methodname') function methodcall( $elems, methodname, args ) { var returnvalue; var pluginmethodstr = '$().' + namespace + '("' + methodname + '")'; $elems.each( function( i, elem ) { // get instance var instance = $.data( elem, namespace ); if ( !instance ) { logerror( namespace + ' not initialized. cannot call methods, i.e. ' + pluginmethodstr ); return; } var method = instance[ methodname ]; if ( !method || methodname.charat(0) == '_' ) { logerror( pluginmethodstr + ' is not a valid method' ); return; } // apply method, get return value var value = method.apply( instance, args ); // set return value if value is returned, use only first value returnvalue = returnvalue === undefined ? value : returnvalue; }); return returnvalue !== undefined ? returnvalue : $elems; } function plaincall( $elems, options ) { $elems.each( function( i, elem ) { var instance = $.data( elem, namespace ); if ( instance ) { // set options & init instance.option( options ); instance._init(); } else { // initialize new instance instance = new pluginclass( elem, options ); $.data( elem, namespace, instance ); } }); } updatejquery( $ ); } // ----- updatejquery ----- // // set $.bridget for v1 backwards compatibility function updatejquery( $ ) { if ( !$ || ( $ && $.bridget ) ) { return; } $.bridget = jquerybridget; } updatejquery( jquery || window.jquery ); // ----- ----- // return jquerybridget; })); /** * evemitter v1.0.3 * lil' event emitter * mit license */ /* jshint unused: true, undef: true, strict: true */ ( function( global, factory ) { // universal module definition /* jshint strict: false */ /* globals define, module, window */ if ( typeof define == 'function' && define.amd ) { // amd - requirejs define( 'ev-emitter/ev-emitter',factory ); } else if ( typeof module == 'object' && module.exports ) { // commonjs - browserify, webpack module.exports = factory(); } else { // browser globals global.evemitter = factory(); } }( typeof window != 'undefined' ? window : this, function() { function evemitter() {} var proto = evemitter.prototype; proto.on = function( eventname, listener ) { if ( !eventname || !listener ) { return; } // set events hash var events = this._events = this._events || {}; // set listeners array var listeners = events[ eventname ] = events[ eventname ] || []; // only add once if ( listeners.indexof( listener ) == -1 ) { listeners.push( listener ); } return this; }; proto.once = function( eventname, listener ) { if ( !eventname || !listener ) { return; } // add event this.on( eventname, listener ); // set once flag // set onceevents hash var onceevents = this._onceevents = this._onceevents || {}; // set oncelisteners object var oncelisteners = onceevents[ eventname ] = onceevents[ eventname ] || {}; // set flag oncelisteners[ listener ] = true; return this; }; proto.off = function( eventname, listener ) { var listeners = this._events && this._events[ eventname ]; if ( !listeners || !listeners.length ) { return; } var index = listeners.indexof( listener ); if ( index != -1 ) { listeners.splice( index, 1 ); } return this; }; proto.emitevent = function( eventname, args ) { var listeners = this._events && this._events[ eventname ]; if ( !listeners || !listeners.length ) { return; } var i = 0; var listener = listeners[i]; args = args || []; // once stuff var oncelisteners = this._onceevents && this._onceevents[ eventname ]; while ( listener ) { var isonce = oncelisteners && oncelisteners[ listener ]; if ( isonce ) { // remove listener // remove before trigger to prevent recursion this.off( eventname, listener ); // unset once flag delete oncelisteners[ listener ]; } // trigger listener listener.apply( this, args ); // get next listener i += isonce ? 0 : 1; listener = listeners[i]; } return this; }; return evemitter; })); /*! * getsize v2.0.2 * measure size of elements * mit license */ /*jshint browser: true, strict: true, undef: true, unused: true */ /*global define: false, module: false, console: false */ ( function( window, factory ) { 'use strict'; if ( typeof define == 'function' && define.amd ) { // amd define( 'get-size/get-size',[],function() { return factory(); }); } else if ( typeof module == 'object' && module.exports ) { // commonjs module.exports = factory(); } else { // browser global window.getsize = factory(); } })( window, function factory() { 'use strict'; // -------------------------- helpers -------------------------- // // get a number from a string, not a percentage function getstylesize( value ) { var num = parsefloat( value ); // not a percent like '100%', and a number var isvalid = value.indexof('%') == -1 && !isnan( num ); return isvalid && num; } function noop() {} var logerror = typeof console == 'undefined' ? noop : function( message ) { console.error( message ); }; // -------------------------- measurements -------------------------- // var measurements = [ 'paddingleft', 'paddingright', 'paddingtop', 'paddingbottom', 'marginleft', 'marginright', 'margintop', 'marginbottom', 'borderleftwidth', 'borderrightwidth', 'bordertopwidth', 'borderbottomwidth' ]; var measurementslength = measurements.length; function getzerosize() { var size = { width: 0, height: 0, innerwidth: 0, innerheight: 0, outerwidth: 0, outerheight: 0 }; for ( var i=0; i < measurementslength; i++ ) { var measurement = measurements[i]; size[ measurement ] = 0; } return size; } // -------------------------- getstyle -------------------------- // /** * getstyle, get style of element, check for firefox bug * https://bugzilla.mozilla.org/show_bug.cgi?id=548397 */ function getstyle( elem ) { var style = getcomputedstyle( elem ); if ( !style ) { logerror( 'style returned ' + style + '. are you running this code in a hidden iframe on firefox? ' + 'see http://bit.ly/getsizebug1' ); } return style; } // -------------------------- setup -------------------------- // var issetup = false; var isboxsizeouter; /** * setup * check isboxsizerouter * do on first getsize() rather than on page load for firefox bug */ function setup() { // setup once if ( issetup ) { return; } issetup = true; // -------------------------- box sizing -------------------------- // /** * webkit measures the outer-width on style.width on border-box elems * ie & firefox<29 measures the inner-width */ var div = document.createelement('div'); div.style.width = '200px'; div.style.padding = '1px 2px 3px 4px'; div.style.borderstyle = 'solid'; div.style.borderwidth = '1px 2px 3px 4px'; div.style.boxsizing = 'border-box'; var body = document.body || document.documentelement; body.appendchild( div ); var style = getstyle( div ); getsize.isboxsizeouter = isboxsizeouter = getstylesize( style.width ) == 200; body.removechild( div ); } // -------------------------- getsize -------------------------- // function getsize( elem ) { setup(); // use queryseletor if elem is string if ( typeof elem == 'string' ) { elem = document.queryselector( elem ); } // do not proceed on non-objects if ( !elem || typeof elem != 'object' || !elem.nodetype ) { return; } var style = getstyle( elem ); // if hidden, everything is 0 if ( style.display == 'none' ) { return getzerosize(); } var size = {}; size.width = elem.offsetwidth; size.height = elem.offsetheight; var isborderbox = size.isborderbox = style.boxsizing == 'border-box'; // get all measurements for ( var i=0; i < measurementslength; i++ ) { var measurement = measurements[i]; var value = style[ measurement ]; var num = parsefloat( value ); // any 'auto', 'medium' value will be 0 size[ measurement ] = !isnan( num ) ? num : 0; } var paddingwidth = size.paddingleft + size.paddingright; var paddingheight = size.paddingtop + size.paddingbottom; var marginwidth = size.marginleft + size.marginright; var marginheight = size.margintop + size.marginbottom; var borderwidth = size.borderleftwidth + size.borderrightwidth; var borderheight = size.bordertopwidth + size.borderbottomwidth; var isborderboxsizeouter = isborderbox && isboxsizeouter; // overwrite width and height if we can get it from style var stylewidth = getstylesize( style.width ); if ( stylewidth !== false ) { size.width = stylewidth + // add padding and border unless it's already including it ( isborderboxsizeouter ? 0 : paddingwidth + borderwidth ); } var styleheight = getstylesize( style.height ); if ( styleheight !== false ) { size.height = styleheight + // add padding and border unless it's already including it ( isborderboxsizeouter ? 0 : paddingheight + borderheight ); } size.innerwidth = size.width - ( paddingwidth + borderwidth ); size.innerheight = size.height - ( paddingheight + borderheight ); size.outerwidth = size.width + marginwidth; size.outerheight = size.height + marginheight; return size; } return getsize; }); /** * matchesselector v2.0.2 * matchesselector( element, '.selector' ) * mit license */ /*jshint browser: true, strict: true, undef: true, unused: true */ ( function( window, factory ) { /*global define: false, module: false */ 'use strict'; // universal module definition if ( typeof define == 'function' && define.amd ) { // amd define( 'desandro-matches-selector/matches-selector',factory ); } else if ( typeof module == 'object' && module.exports ) { // commonjs module.exports = factory(); } else { // browser global window.matchesselector = factory(); } }( window, function factory() { 'use strict'; var matchesmethod = ( function() { var elemproto = window.element.prototype; // check for the standard method name first if ( elemproto.matches ) { return 'matches'; } // check un-prefixed if ( elemproto.matchesselector ) { return 'matchesselector'; } // check vendor prefixes var prefixes = [ 'webkit', 'moz', 'ms', 'o' ]; for ( var i=0; i < prefixes.length; i++ ) { var prefix = prefixes[i]; var method = prefix + 'matchesselector'; if ( elemproto[ method ] ) { return method; } } })(); return function matchesselector( elem, selector ) { return elem[ matchesmethod ]( selector ); }; })); /** * fizzy ui utils v2.0.4 * mit license */ /*jshint browser: true, undef: true, unused: true, strict: true */ ( function( window, factory ) { // universal module definition /*jshint strict: false */ /*globals define, module, require */ if ( typeof define == 'function' && define.amd ) { // amd define( 'fizzy-ui-utils/utils',[ 'desandro-matches-selector/matches-selector' ], function( matchesselector ) { return factory( window, matchesselector ); }); } else if ( typeof module == 'object' && module.exports ) { // commonjs module.exports = factory( window, require('desandro-matches-selector') ); } else { // browser global window.fizzyuiutils = factory( window, window.matchesselector ); } }( window, function factory( window, matchesselector ) { var utils = {}; // ----- extend ----- // // extends objects utils.extend = function( a, b ) { for ( var prop in b ) { a[ prop ] = b[ prop ]; } return a; }; // ----- modulo ----- // utils.modulo = function( num, div ) { return ( ( num % div ) + div ) % div; }; // ----- makearray ----- // // turn element or nodelist into an array utils.makearray = function( obj ) { var ary = []; if ( array.isarray( obj ) ) { // use object if already an array ary = obj; } else if ( obj && typeof obj == 'object' && typeof obj.length == 'number' ) { // convert nodelist to array for ( var i=0; i < obj.length; i++ ) { ary.push( obj[i] ); } } else { // array of single index ary.push( obj ); } return ary; }; // ----- removefrom ----- // utils.removefrom = function( ary, obj ) { var index = ary.indexof( obj ); if ( index != -1 ) { ary.splice( index, 1 ); } }; // ----- getparent ----- // utils.getparent = function( elem, selector ) { while ( elem != document.body ) { elem = elem.parentnode; if ( matchesselector( elem, selector ) ) { return elem; } } }; // ----- getqueryelement ----- // // use element as selector string utils.getqueryelement = function( elem ) { if ( typeof elem == 'string' ) { return document.queryselector( elem ); } return elem; }; // ----- handleevent ----- // // enable .ontype to trigger from .addeventlistener( elem, 'type' ) utils.handleevent = function( event ) { var method = 'on' + event.type; if ( this[ method ] ) { this[ method ]( event ); } }; // ----- filterfindelements ----- // utils.filterfindelements = function( elems, selector ) { // make array of elems elems = utils.makearray( elems ); var ffelems = []; elems.foreach( function( elem ) { // check that elem is an actual element if ( !( elem instanceof htmlelement ) ) { return; } // add elem if no selector if ( !selector ) { ffelems.push( elem ); return; } // filter & find items if we have a selector // filter if ( matchesselector( elem, selector ) ) { ffelems.push( elem ); } // find children var childelems = elem.queryselectorall( selector ); // concat childelems to filterfound array for ( var i=0; i < childelems.length; i++ ) { ffelems.push( childelems[i] ); } }); return ffelems; }; // ----- debouncemethod ----- // utils.debouncemethod = function( _class, methodname, threshold ) { // original method var method = _class.prototype[ methodname ]; var timeoutname = methodname + 'timeout'; _class.prototype[ methodname ] = function() { var timeout = this[ timeoutname ]; if ( timeout ) { cleartimeout( timeout ); } var args = arguments; var _this = this; this[ timeoutname ] = settimeout( function() { method.apply( _this, args ); delete _this[ timeoutname ]; }, threshold || 100 ); }; }; // ----- docready ----- // utils.docready = function( callback ) { var readystate = document.readystate; if ( readystate == 'complete' || readystate == 'interactive' ) { // do async to allow for other scripts to run. metafizzy/flickity#441 settimeout( callback ); } else { document.addeventlistener( 'domcontentloaded', callback ); } }; // ----- htmlinit ----- // // http://jamesroberts.name/blog/2010/02/22/string-functions-for-javascript-trim-to-camel-case-to-dashed-and-to-underscore/ utils.todashed = function( str ) { return str.replace( /(.)([a-z])/g, function( match, $1, $2 ) { return $1 + '-' + $2; }).tolowercase(); }; var console = window.console; /** * allow user to initialize classes via [data-namespace] or .js-namespace class * htmlinit( widget, 'widgetname' ) * options are parsed from data-namespace-options */ utils.htmlinit = function( widgetclass, namespace ) { utils.docready( function() { var dashednamespace = utils.todashed( namespace ); var dataattr = 'data-' + dashednamespace; var dataattrelems = document.queryselectorall( '[' + dataattr + ']' ); var jsdashelems = document.queryselectorall( '.js-' + dashednamespace ); var elems = utils.makearray( dataattrelems ) .concat( utils.makearray( jsdashelems ) ); var dataoptionsattr = dataattr + '-options'; var jquery = window.jquery; elems.foreach( function( elem ) { var attr = elem.getattribute( dataattr ) || elem.getattribute( dataoptionsattr ); var options; try { options = attr && json.parse( attr ); } catch ( error ) { // log error, do not initialize if ( console ) { console.error( 'error parsing ' + dataattr + ' on ' + elem.classname + ': ' + error ); } return; } // initialize var instance = new widgetclass( elem, options ); // make available via $().data('namespace') if ( jquery ) { jquery.data( elem, namespace, instance ); } }); }); }; // ----- ----- // return utils; })); /** * outlayer item */ ( function( window, factory ) { // universal module definition /* jshint strict: false */ /* globals define, module, require */ if ( typeof define == 'function' && define.amd ) { // amd - requirejs define( 'outlayer/item',[ 'ev-emitter/ev-emitter', 'get-size/get-size' ], factory ); } else if ( typeof module == 'object' && module.exports ) { // commonjs - browserify, webpack module.exports = factory( require('ev-emitter'), require('get-size') ); } else { // browser global window.outlayer = {}; window.outlayer.item = factory( window.evemitter, window.getsize ); } }( window, function factory( evemitter, getsize ) { 'use strict'; // ----- helpers ----- // function isemptyobj( obj ) { for ( var prop in obj ) { return false; } prop = null; return true; } // -------------------------- css3 support -------------------------- // var docelemstyle = document.documentelement.style; var transitionproperty = typeof docelemstyle.transition == 'string' ? 'transition' : 'webkittransition'; var transformproperty = typeof docelemstyle.transform == 'string' ? 'transform' : 'webkittransform'; var transitionendevent = { webkittransition: 'webkittransitionend', transition: 'transitionend' }[ transitionproperty ]; // cache all vendor properties that could have vendor prefix var vendorproperties = { transform: transformproperty, transition: transitionproperty, transitionduration: transitionproperty + 'duration', transitionproperty: transitionproperty + 'property', transitiondelay: transitionproperty + 'delay' }; // -------------------------- item -------------------------- // function item( element, layout ) { if ( !element ) { return; } this.element = element; // parent layout class, i.e. masonry, isotope, or packery this.layout = layout; this.position = { x: 0, y: 0 }; this._create(); } // inherit evemitter var proto = item.prototype = object.create( evemitter.prototype ); proto.constructor = item; proto._create = function() { // transition objects this._transn = { ingproperties: {}, clean: {}, onend: {} }; this.css({ position: 'absolute' }); }; // trigger specified handler for event type proto.handleevent = function( event ) { var method = 'on' + event.type; if ( this[ method ] ) { this[ method ]( event ); } }; proto.getsize = function() { this.size = getsize( this.element ); }; /** * apply css styles to element * @param {object} style */ proto.css = function( style ) { var elemstyle = this.element.style; for ( var prop in style ) { // use vendor property if available var supportedprop = vendorproperties[ prop ] || prop; elemstyle[ supportedprop ] = style[ prop ]; } }; // measure position, and sets it proto.getposition = function() { var style = getcomputedstyle( this.element ); var isoriginleft = this.layout._getoption('originleft'); var isorigintop = this.layout._getoption('origintop'); var xvalue = style[ isoriginleft ? 'left' : 'right' ]; var yvalue = style[ isorigintop ? 'top' : 'bottom' ]; // convert percent to pixels var layoutsize = this.layout.size; var x = xvalue.indexof('%') != -1 ? ( parsefloat( xvalue ) / 100 ) * layoutsize.width : parseint( xvalue, 10 ); var y = yvalue.indexof('%') != -1 ? ( parsefloat( yvalue ) / 100 ) * layoutsize.height : parseint( yvalue, 10 ); // clean up 'auto' or other non-integer values x = isnan( x ) ? 0 : x; y = isnan( y ) ? 0 : y; // remove padding from measurement x -= isoriginleft ? layoutsize.paddingleft : layoutsize.paddingright; y -= isorigintop ? layoutsize.paddingtop : layoutsize.paddingbottom; this.position.x = x; this.position.y = y; }; // set settled position, apply padding proto.layoutposition = function() { var layoutsize = this.layout.size; var style = {}; var isoriginleft = this.layout._getoption('originleft'); var isorigintop = this.layout._getoption('origintop'); // x var xpadding = isoriginleft ? 'paddingleft' : 'paddingright'; var xproperty = isoriginleft ? 'left' : 'right'; var xresetproperty = isoriginleft ? 'right' : 'left'; var x = this.position.x + layoutsize[ xpadding ]; // set in percentage or pixels style[ xproperty ] = this.getxvalue( x ); // reset other property style[ xresetproperty ] = ''; // y var ypadding = isorigintop ? 'paddingtop' : 'paddingbottom'; var yproperty = isorigintop ? 'top' : 'bottom'; var yresetproperty = isorigintop ? 'bottom' : 'top'; var y = this.position.y + layoutsize[ ypadding ]; // set in percentage or pixels style[ yproperty ] = this.getyvalue( y ); // reset other property style[ yresetproperty ] = ''; this.css( style ); this.emitevent( 'layout', [ this ] ); }; proto.getxvalue = function( x ) { var ishorizontal = this.layout._getoption('horizontal'); return this.layout.options.percentposition && !ishorizontal ? ( ( x / this.layout.size.width ) * 100 ) + '%' : x + 'px'; }; proto.getyvalue = function( y ) { var ishorizontal = this.layout._getoption('horizontal'); return this.layout.options.percentposition && ishorizontal ? ( ( y / this.layout.size.height ) * 100 ) + '%' : y + 'px'; }; proto._transitionto = function( x, y ) { this.getposition(); // get current x & y from top/left var curx = this.position.x; var cury = this.position.y; var comparex = parseint( x, 10 ); var comparey = parseint( y, 10 ); var didnotmove = comparex === this.position.x && comparey === this.position.y; // save end position this.setposition( x, y ); // if did not move and not transitioning, just go to layout if ( didnotmove && !this.istransitioning ) { this.layoutposition(); return; } var transx = x - curx; var transy = y - cury; var transitionstyle = {}; transitionstyle.transform = this.gettranslate( transx, transy ); this.transition({ to: transitionstyle, ontransitionend: { transform: this.layoutposition }, iscleaning: true }); }; proto.gettranslate = function( x, y ) { // flip cooridinates if origin on right or bottom var isoriginleft = this.layout._getoption('originleft'); var isorigintop = this.layout._getoption('origintop'); x = isoriginleft ? x : -x; y = isorigintop ? y : -y; return 'translate3d(' + x + 'px, ' + y + 'px, 0)'; }; // non transition + transform support proto.goto = function( x, y ) { this.setposition( x, y ); this.layoutposition(); }; proto.moveto = proto._transitionto; proto.setposition = function( x, y ) { this.position.x = parseint( x, 10 ); this.position.y = parseint( y, 10 ); }; // ----- transition ----- // /** * @param {object} style - css * @param {function} ontransitionend */ // non transition, just trigger callback proto._nontransition = function( args ) { this.css( args.to ); if ( args.iscleaning ) { this._removestyles( args.to ); } for ( var prop in args.ontransitionend ) { args.ontransitionend[ prop ].call( this ); } }; /** * proper transition * @param {object} args - arguments * @param {object} to - style to transition to * @param {object} from - style to start transition from * @param {boolean} iscleaning - removes transition styles after transition * @param {function} ontransitionend - callback */ proto.transition = function( args ) { // redirect to nontransition if no transition duration if ( !parsefloat( this.layout.options.transitionduration ) ) { this._nontransition( args ); return; } var _transition = this._transn; // keep track of ontransitionend callback by css property for ( var prop in args.ontransitionend ) { _transition.onend[ prop ] = args.ontransitionend[ prop ]; } // keep track of properties that are transitioning for ( prop in args.to ) { _transition.ingproperties[ prop ] = true; // keep track of properties to clean up when transition is done if ( args.iscleaning ) { _transition.clean[ prop ] = true; } } // set from styles if ( args.from ) { this.css( args.from ); // force redraw. http://blog.alexmaccaw.com/css-transitions var h = this.element.offsetheight; // hack for jshint to hush about unused var h = null; } // enable transition this.enabletransition( args.to ); // set styles that are transitioning this.css( args.to ); this.istransitioning = true; }; // dash before all cap letters, including first for // webkittransform => -webkit-transform function todashedall( str ) { return str.replace( /([a-z])/g, function( $1 ) { return '-' + $1.tolowercase(); }); } var transitionprops = 'opacity,' + todashedall( transformproperty ); proto.enabletransition = function(/* style */) { // hack changing transitionproperty during a transition // will cause transition to jump if ( this.istransitioning ) { return; } // make `transition: foo, bar, baz` from style object // hack un-comment this when enabletransition can work // while a transition is happening // var transitionvalues = []; // for ( var prop in style ) { // // dash-ify camelcased properties like webkittransition // prop = vendorproperties[ prop ] || prop; // transitionvalues.push( todashedall( prop ) ); // } // munge number to millisecond, to match stagger var duration = this.layout.options.transitionduration; duration = typeof duration == 'number' ? duration + 'ms' : duration; // enable transition styles this.css({ transitionproperty: transitionprops, transitionduration: duration, transitiondelay: this.staggerdelay || 0 }); // listen for transition end event this.element.addeventlistener( transitionendevent, this, false ); }; // ----- events ----- // proto.onwebkittransitionend = function( event ) { this.ontransitionend( event ); }; proto.onotransitionend = function( event ) { this.ontransitionend( event ); }; // properties that i munge to make my life easier var dashedvendorproperties = { '-webkit-transform': 'transform' }; proto.ontransitionend = function( event ) { // disregard bubbled events from children if ( event.target !== this.element ) { return; } var _transition = this._transn; // get property name of transitioned property, convert to prefix-free var propertyname = dashedvendorproperties[ event.propertyname ] || event.propertyname; // remove property that has completed transitioning delete _transition.ingproperties[ propertyname ]; // check if any properties are still transitioning if ( isemptyobj( _transition.ingproperties ) ) { // all properties have completed transitioning this.disabletransition(); } // clean style if ( propertyname in _transition.clean ) { // clean up style this.element.style[ event.propertyname ] = ''; delete _transition.clean[ propertyname ]; } // trigger ontransitionend callback if ( propertyname in _transition.onend ) { var ontransitionend = _transition.onend[ propertyname ]; ontransitionend.call( this ); delete _transition.onend[ propertyname ]; } this.emitevent( 'transitionend', [ this ] ); }; proto.disabletransition = function() { this.removetransitionstyles(); this.element.removeeventlistener( transitionendevent, this, false ); this.istransitioning = false; }; /** * removes style property from element * @param {object} style **/ proto._removestyles = function( style ) { // clean up transition styles var cleanstyle = {}; for ( var prop in style ) { cleanstyle[ prop ] = ''; } this.css( cleanstyle ); }; var cleantransitionstyle = { transitionproperty: '', transitionduration: '', transitiondelay: '' }; proto.removetransitionstyles = function() { // remove transition this.css( cleantransitionstyle ); }; // ----- stagger ----- // proto.stagger = function( delay ) { delay = isnan( delay ) ? 0 : delay; this.staggerdelay = delay + 'ms'; }; // ----- show/hide/remove ----- // // remove element from dom proto.removeelem = function() { this.element.parentnode.removechild( this.element ); // remove display: none this.css({ display: '' }); this.emitevent( 'remove', [ this ] ); }; proto.remove = function() { // just remove element if no transition support or no transition if ( !transitionproperty || !parsefloat( this.layout.options.transitionduration ) ) { this.removeelem(); return; } // start transition this.once( 'transitionend', function() { this.removeelem(); }); this.hide(); }; proto.reveal = function() { delete this.ishidden; // remove display: none this.css({ display: '' }); var options = this.layout.options; var ontransitionend = {}; var transitionendproperty = this.gethiderevealtransitionendproperty('visiblestyle'); ontransitionend[ transitionendproperty ] = this.onrevealtransitionend; this.transition({ from: options.hiddenstyle, to: options.visiblestyle, iscleaning: true, ontransitionend: ontransitionend }); }; proto.onrevealtransitionend = function() { // check if still visible // during transition, item may have been hidden if ( !this.ishidden ) { this.emitevent('reveal'); } }; /** * get style property use for hide/reveal transition end * @param {string} styleproperty - hiddenstyle/visiblestyle * @returns {string} */ proto.gethiderevealtransitionendproperty = function( styleproperty ) { var optionstyle = this.layout.options[ styleproperty ]; // use opacity if ( optionstyle.opacity ) { return 'opacity'; } // get first property for ( var prop in optionstyle ) { return prop; } }; proto.hide = function() { // set flag this.ishidden = true; // remove display: none this.css({ display: '' }); var options = this.layout.options; var ontransitionend = {}; var transitionendproperty = this.gethiderevealtransitionendproperty('hiddenstyle'); ontransitionend[ transitionendproperty ] = this.onhidetransitionend; this.transition({ from: options.visiblestyle, to: options.hiddenstyle, // keep hidden stuff hidden iscleaning: true, ontransitionend: ontransitionend }); }; proto.onhidetransitionend = function() { // check if still hidden // during transition, item may have been un-hidden if ( this.ishidden ) { this.css({ display: 'none' }); this.emitevent('hide'); } }; proto.destroy = function() { this.css({ position: '', left: '', right: '', top: '', bottom: '', transition: '', transform: '' }); }; return item; })); /*! * outlayer v2.1.0 * the brains and guts of a layout library * mit license */ ( function( window, factory ) { 'use strict'; // universal module definition /* jshint strict: false */ /* globals define, module, require */ if ( typeof define == 'function' && define.amd ) { // amd - requirejs define( 'outlayer/outlayer',[ 'ev-emitter/ev-emitter', 'get-size/get-size', 'fizzy-ui-utils/utils', './item' ], function( evemitter, getsize, utils, item ) { return factory( window, evemitter, getsize, utils, item); } ); } else if ( typeof module == 'object' && module.exports ) { // commonjs - browserify, webpack module.exports = factory( window, require('ev-emitter'), require('get-size'), require('fizzy-ui-utils'), require('./item') ); } else { // browser global window.outlayer = factory( window, window.evemitter, window.getsize, window.fizzyuiutils, window.outlayer.item ); } }( window, function factory( window, evemitter, getsize, utils, item ) { 'use strict'; // ----- vars ----- // var console = window.console; var jquery = window.jquery; var noop = function() {}; // -------------------------- outlayer -------------------------- // // globally unique identifiers var guid = 0; // internal store of all outlayer intances var instances = {}; /** * @param {element, string} element * @param {object} options * @constructor */ function outlayer( element, options ) { var queryelement = utils.getqueryelement( element ); if ( !queryelement ) { if ( console ) { console.error( 'bad element for ' + this.constructor.namespace + ': ' + ( queryelement || element ) ); } return; } this.element = queryelement; // add jquery if ( jquery ) { this.$element = jquery( this.element ); } // options this.options = utils.extend( {}, this.constructor.defaults ); this.option( options ); // add id for outlayer.getfromelement var id = ++guid; this.element.outlayerguid = id; // expando instances[ id ] = this; // associate via id // kick it off this._create(); var isinitlayout = this._getoption('initlayout'); if ( isinitlayout ) { this.layout(); } } // settings are for internal use only outlayer.namespace = 'outlayer'; outlayer.item = item; // default options outlayer.defaults = { containerstyle: { position: 'relative' }, initlayout: true, originleft: true, origintop: true, resize: true, resizecontainer: true, // item options transitionduration: '0.4s', hiddenstyle: { opacity: 0, transform: 'scale(0.001)' }, visiblestyle: { opacity: 1, transform: 'scale(1)' } }; var proto = outlayer.prototype; // inherit evemitter utils.extend( proto, evemitter.prototype ); /** * set options * @param {object} opts */ proto.option = function( opts ) { utils.extend( this.options, opts ); }; /** * get backwards compatible option value, check old name */ proto._getoption = function( option ) { var oldoption = this.constructor.compatoptions[ option ]; return oldoption && this.options[ oldoption ] !== undefined ? this.options[ oldoption ] : this.options[ option ]; }; outlayer.compatoptions = { // currentname: oldname initlayout: 'isinitlayout', horizontal: 'ishorizontal', layoutinstant: 'islayoutinstant', originleft: 'isoriginleft', origintop: 'isorigintop', resize: 'isresizebound', resizecontainer: 'isresizingcontainer' }; proto._create = function() { // get items from children this.reloaditems(); // elements that affect layout, but are not laid out this.stamps = []; this.stamp( this.options.stamp ); // set container style utils.extend( this.element.style, this.options.containerstyle ); // bind resize method var canbindresize = this._getoption('resize'); if ( canbindresize ) { this.bindresize(); } }; // goes through all children again and gets bricks in proper order proto.reloaditems = function() { // collection of item elements this.items = this._itemize( this.element.children ); }; /** * turn elements into outlayer.items to be used in layout * @param {array or nodelist or htmlelement} elems * @returns {array} items - collection of new outlayer items */ proto._itemize = function( elems ) { var itemelems = this._filterfinditemelements( elems ); var item = this.constructor.item; // create new outlayer items for collection var items = []; for ( var i=0; i < itemelems.length; i++ ) { var elem = itemelems[i]; var item = new item( elem, this ); items.push( item ); } return items; }; /** * get item elements to be used in layout * @param {array or nodelist or htmlelement} elems * @returns {array} items - item elements */ proto._filterfinditemelements = function( elems ) { return utils.filterfindelements( elems, this.options.itemselector ); }; /** * getter method for getting item elements * @returns {array} elems - collection of item elements */ proto.getitemelements = function() { return this.items.map( function( item ) { return item.element; }); }; // ----- init & layout ----- // /** * lays out all items */ proto.layout = function() { this._resetlayout(); this._managestamps(); // don't animate first layout var layoutinstant = this._getoption('layoutinstant'); var isinstant = layoutinstant !== undefined ? layoutinstant : !this._islayoutinited; this.layoutitems( this.items, isinstant ); // flag for initalized this._islayoutinited = true; }; // _init is alias for layout proto._init = proto.layout; /** * logic before any new layout */ proto._resetlayout = function() { this.getsize(); }; proto.getsize = function() { this.size = getsize( this.element ); }; /** * get measurement from option, for columnwidth, rowheight, gutter * if option is string -> get element from selector string, & get size of element * if option is element -> get size of element * else use option as a number * * @param {string} measurement * @param {string} size - width or height * @private */ proto._getmeasurement = function( measurement, size ) { var option = this.options[ measurement ]; var elem; if ( !option ) { // default to 0 this[ measurement ] = 0; } else { // use option as an element if ( typeof option == 'string' ) { elem = this.element.queryselector( option ); } else if ( option instanceof htmlelement ) { elem = option; } // use size of element, if element this[ measurement ] = elem ? getsize( elem )[ size ] : option; } }; /** * layout a collection of item elements * @api public */ proto.layoutitems = function( items, isinstant ) { items = this._getitemsforlayout( items ); this._layoutitems( items, isinstant ); this._postlayout(); }; /** * get the items to be laid out * you may want to skip over some items * @param {array} items * @returns {array} items */ proto._getitemsforlayout = function( items ) { return items.filter( function( item ) { return !item.isignored; }); }; /** * layout items * @param {array} items * @param {boolean} isinstant */ proto._layoutitems = function( items, isinstant ) { this._emitcompleteonitems( 'layout', items ); if ( !items || !items.length ) { // no items, emit event with empty array return; } var queue = []; items.foreach( function( item ) { // get x/y object from method var position = this._getitemlayoutposition( item ); // enqueue position.item = item; position.isinstant = isinstant || item.islayoutinstant; queue.push( position ); }, this ); this._processlayoutqueue( queue ); }; /** * get item layout position * @param {outlayer.item} item * @returns {object} x and y position */ proto._getitemlayoutposition = function( /* item */ ) { return { x: 0, y: 0 }; }; /** * iterate over array and position each item * reason being - separating this logic prevents 'layout invalidation' * thx @paul_irish * @param {array} queue */ proto._processlayoutqueue = function( queue ) { this.updatestagger(); queue.foreach( function( obj, i ) { this._positionitem( obj.item, obj.x, obj.y, obj.isinstant, i ); }, this ); }; // set stagger from option in milliseconds number proto.updatestagger = function() { var stagger = this.options.stagger; if ( stagger === null || stagger === undefined ) { this.stagger = 0; return; } this.stagger = getmilliseconds( stagger ); return this.stagger; }; /** * sets position of item in dom * @param {outlayer.item} item * @param {number} x - horizontal position * @param {number} y - vertical position * @param {boolean} isinstant - disables transitions */ proto._positionitem = function( item, x, y, isinstant, i ) { if ( isinstant ) { // if not transition, just set css item.goto( x, y ); } else { item.stagger( i * this.stagger ); item.moveto( x, y ); } }; /** * any logic you want to do after each layout, * i.e. size the container */ proto._postlayout = function() { this.resizecontainer(); }; proto.resizecontainer = function() { var isresizingcontainer = this._getoption('resizecontainer'); if ( !isresizingcontainer ) { return; } var size = this._getcontainersize(); if ( size ) { this._setcontainermeasure( size.width, true ); this._setcontainermeasure( size.height, false ); } }; /** * sets width or height of container if returned * @returns {object} size * @param {number} width * @param {number} height */ proto._getcontainersize = noop; /** * @param {number} measure - size of width or height * @param {boolean} iswidth */ proto._setcontainermeasure = function( measure, iswidth ) { if ( measure === undefined ) { return; } var elemsize = this.size; // add padding and border width if border box if ( elemsize.isborderbox ) { measure += iswidth ? elemsize.paddingleft + elemsize.paddingright + elemsize.borderleftwidth + elemsize.borderrightwidth : elemsize.paddingbottom + elemsize.paddingtop + elemsize.bordertopwidth + elemsize.borderbottomwidth; } measure = math.max( measure, 0 ); this.element.style[ iswidth ? 'width' : 'height' ] = measure + 'px'; }; /** * emit eventcomplete on a collection of items events * @param {string} eventname * @param {array} items - outlayer.items */ proto._emitcompleteonitems = function( eventname, items ) { var _this = this; function oncomplete() { _this.dispatchevent( eventname + 'complete', null, [ items ] ); } var count = items.length; if ( !items || !count ) { oncomplete(); return; } var donecount = 0; function tick() { donecount++; if ( donecount == count ) { oncomplete(); } } // bind callback items.foreach( function( item ) { item.once( eventname, tick ); }); }; /** * emits events via evemitter and jquery events * @param {string} type - name of event * @param {event} event - original event * @param {array} args - extra arguments */ proto.dispatchevent = function( type, event, args ) { // add original event to arguments var emitargs = event ? [ event ].concat( args ) : args; this.emitevent( type, emitargs ); if ( jquery ) { // set this.$element this.$element = this.$element || jquery( this.element ); if ( event ) { // create jquery event var $event = jquery.event( event ); $event.type = type; this.$element.trigger( $event, args ); } else { // just trigger with type if no event available this.$element.trigger( type, args ); } } }; // -------------------------- ignore & stamps -------------------------- // /** * keep item in collection, but do not lay it out * ignored items do not get skipped in layout * @param {element} elem */ proto.ignore = function( elem ) { var item = this.getitem( elem ); if ( item ) { item.isignored = true; } }; /** * return item to layout collection * @param {element} elem */ proto.unignore = function( elem ) { var item = this.getitem( elem ); if ( item ) { delete item.isignored; } }; /** * adds elements to stamps * @param {nodelist, array, element, or string} elems */ proto.stamp = function( elems ) { elems = this._find( elems ); if ( !elems ) { return; } this.stamps = this.stamps.concat( elems ); // ignore elems.foreach( this.ignore, this ); }; /** * removes elements to stamps * @param {nodelist, array, or element} elems */ proto.unstamp = function( elems ) { elems = this._find( elems ); if ( !elems ){ return; } elems.foreach( function( elem ) { // filter out removed stamp elements utils.removefrom( this.stamps, elem ); this.unignore( elem ); }, this ); }; /** * finds child elements * @param {nodelist, array, element, or string} elems * @returns {array} elems */ proto._find = function( elems ) { if ( !elems ) { return; } // if string, use argument as selector string if ( typeof elems == 'string' ) { elems = this.element.queryselectorall( elems ); } elems = utils.makearray( elems ); return elems; }; proto._managestamps = function() { if ( !this.stamps || !this.stamps.length ) { return; } this._getboundingrect(); this.stamps.foreach( this._managestamp, this ); }; // update boundingleft / top proto._getboundingrect = function() { // get bounding rect for container element var boundingrect = this.element.getboundingclientrect(); var size = this.size; this._boundingrect = { left: boundingrect.left + size.paddingleft + size.borderleftwidth, top: boundingrect.top + size.paddingtop + size.bordertopwidth, right: boundingrect.right - ( size.paddingright + size.borderrightwidth ), bottom: boundingrect.bottom - ( size.paddingbottom + size.borderbottomwidth ) }; }; /** * @param {element} stamp **/ proto._managestamp = noop; /** * get x/y position of element relative to container element * @param {element} elem * @returns {object} offset - has left, top, right, bottom */ proto._getelementoffset = function( elem ) { var boundingrect = elem.getboundingclientrect(); var thisrect = this._boundingrect; var size = getsize( elem ); var offset = { left: boundingrect.left - thisrect.left - size.marginleft, top: boundingrect.top - thisrect.top - size.margintop, right: thisrect.right - boundingrect.right - size.marginright, bottom: thisrect.bottom - boundingrect.bottom - size.marginbottom }; return offset; }; // -------------------------- resize -------------------------- // // enable event handlers for listeners // i.e. resize -> onresize proto.handleevent = utils.handleevent; /** * bind layout to window resizing */ proto.bindresize = function() { window.addeventlistener( 'resize', this ); this.isresizebound = true; }; /** * unbind layout to window resizing */ proto.unbindresize = function() { window.removeeventlistener( 'resize', this ); this.isresizebound = false; }; proto.onresize = function() { this.resize(); }; utils.debouncemethod( outlayer, 'onresize', 100 ); proto.resize = function() { // don't trigger if size did not change // or if resize was unbound. see #9 if ( !this.isresizebound || !this.needsresizelayout() ) { return; } this.layout(); }; /** * check if layout is needed post layout * @returns boolean */ proto.needsresizelayout = function() { var size = getsize( this.element ); // check that this.size and size are there // ie8 triggers resize on body size change, so they might not be var hassizes = this.size && size; return hassizes && size.innerwidth !== this.size.innerwidth; }; // -------------------------- methods -------------------------- // /** * add items to outlayer instance * @param {array or nodelist or element} elems * @returns {array} items - outlayer.items **/ proto.additems = function( elems ) { var items = this._itemize( elems ); // add items to collection if ( items.length ) { this.items = this.items.concat( items ); } return items; }; /** * layout newly-appended item elements * @param {array or nodelist or element} elems */ proto.appended = function( elems ) { var items = this.additems( elems ); if ( !items.length ) { return; } // layout and reveal just the new items this.layoutitems( items, true ); this.reveal( items ); }; /** * layout prepended elements * @param {array or nodelist or element} elems */ proto.prepended = function( elems ) { var items = this._itemize( elems ); if ( !items.length ) { return; } // add items to beginning of collection var previousitems = this.items.slice(0); this.items = items.concat( previousitems ); // start new layout this._resetlayout(); this._managestamps(); // layout new stuff without transition this.layoutitems( items, true ); this.reveal( items ); // layout previous items this.layoutitems( previousitems ); }; /** * reveal a collection of items * @param {array of outlayer.items} items */ proto.reveal = function( items ) { this._emitcompleteonitems( 'reveal', items ); if ( !items || !items.length ) { return; } var stagger = this.updatestagger(); items.foreach( function( item, i ) { item.stagger( i * stagger ); item.reveal(); }); }; /** * hide a collection of items * @param {array of outlayer.items} items */ proto.hide = function( items ) { this._emitcompleteonitems( 'hide', items ); if ( !items || !items.length ) { return; } var stagger = this.updatestagger(); items.foreach( function( item, i ) { item.stagger( i * stagger ); item.hide(); }); }; /** * reveal item elements * @param {array}, {element}, {nodelist} items */ proto.revealitemelements = function( elems ) { var items = this.getitems( elems ); this.reveal( items ); }; /** * hide item elements * @param {array}, {element}, {nodelist} items */ proto.hideitemelements = function( elems ) { var items = this.getitems( elems ); this.hide( items ); }; /** * get outlayer.item, given an element * @param {element} elem * @param {function} callback * @returns {outlayer.item} item */ proto.getitem = function( elem ) { // loop through items to get the one that matches for ( var i=0; i < this.items.length; i++ ) { var item = this.items[i]; if ( item.element == elem ) { // return item return item; } } }; /** * get collection of outlayer.items, given elements * @param {array} elems * @returns {array} items - outlayer.items */ proto.getitems = function( elems ) { elems = utils.makearray( elems ); var items = []; elems.foreach( function( elem ) { var item = this.getitem( elem ); if ( item ) { items.push( item ); } }, this ); return items; }; /** * remove element(s) from instance and dom * @param {array or nodelist or element} elems */ proto.remove = function( elems ) { var removeitems = this.getitems( elems ); this._emitcompleteonitems( 'remove', removeitems ); // bail if no items to remove if ( !removeitems || !removeitems.length ) { return; } removeitems.foreach( function( item ) { item.remove(); // remove item from collection utils.removefrom( this.items, item ); }, this ); }; // ----- destroy ----- // // remove and disable outlayer instance proto.destroy = function() { // clean up dynamic styles var style = this.element.style; style.height = ''; style.position = ''; style.width = ''; // destroy items this.items.foreach( function( item ) { item.destroy(); }); this.unbindresize(); var id = this.element.outlayerguid; delete instances[ id ]; // remove reference to instance by id delete this.element.outlayerguid; // remove data for jquery if ( jquery ) { jquery.removedata( this.element, this.constructor.namespace ); } }; // -------------------------- data -------------------------- // /** * get outlayer instance from element * @param {element} elem * @returns {outlayer} */ outlayer.data = function( elem ) { elem = utils.getqueryelement( elem ); var id = elem && elem.outlayerguid; return id && instances[ id ]; }; // -------------------------- create outlayer class -------------------------- // /** * create a layout class * @param {string} namespace */ outlayer.create = function( namespace, options ) { // sub-class outlayer var layout = subclass( outlayer ); // apply new options and compatoptions layout.defaults = utils.extend( {}, outlayer.defaults ); utils.extend( layout.defaults, options ); layout.compatoptions = utils.extend( {}, outlayer.compatoptions ); layout.namespace = namespace; layout.data = outlayer.data; // sub-class item layout.item = subclass( item ); // -------------------------- declarative -------------------------- // utils.htmlinit( layout, namespace ); // -------------------------- jquery bridge -------------------------- // // make into jquery plugin if ( jquery && jquery.bridget ) { jquery.bridget( namespace, layout ); } return layout; }; function subclass( parent ) { function subclass() { parent.apply( this, arguments ); } subclass.prototype = object.create( parent.prototype ); subclass.prototype.constructor = subclass; return subclass; } // ----- helpers ----- // // how many milliseconds are in each unit var msunits = { ms: 1, s: 1000 }; // munge time-like parameter into millisecond number // '0.4s' -> 40 function getmilliseconds( time ) { if ( typeof time == 'number' ) { return time; } var matches = time.match( /(^\d*\.?\d*)(\w*)/ ); var num = matches && matches[1]; var unit = matches && matches[2]; if ( !num.length ) { return 0; } num = parsefloat( num ); var mult = msunits[ unit ] || 1; return num * mult; } // ----- fin ----- // // back in global outlayer.item = item; return outlayer; })); /*! * masonry v4.2.0 * cascading grid layout library * http://masonry.desandro.com * mit license * by david desandro */ ( function( window, factory ) { // universal module definition /* jshint strict: false */ /*globals define, module, require */ if ( typeof define == 'function' && define.amd ) { // amd define( [ 'outlayer/outlayer', 'get-size/get-size' ], factory ); } else if ( typeof module == 'object' && module.exports ) { // commonjs module.exports = factory( require('outlayer'), require('get-size') ); } else { // browser global window.masonry = factory( window.outlayer, window.getsize ); } }( window, function factory( outlayer, getsize ) { // -------------------------- masonrydefinition -------------------------- // // create an outlayer layout class var masonry = outlayer.create('masonry'); // isfitwidth -> fitwidth masonry.compatoptions.fitwidth = 'isfitwidth'; var proto = masonry.prototype; proto._resetlayout = function() { this.getsize(); this._getmeasurement( 'columnwidth', 'outerwidth' ); this._getmeasurement( 'gutter', 'outerwidth' ); this.measurecolumns(); // reset column y this.colys = []; for ( var i=0; i < this.cols; i++ ) { this.colys.push( 0 ); } this.maxy = 0; this.horizontalcolindex = 0; }; proto.measurecolumns = function() { this.getcontainerwidth(); // if columnwidth is 0, default to outerwidth of first item if ( !this.columnwidth ) { var firstitem = this.items[0]; var firstitemelem = firstitem && firstitem.element; // columnwidth fall back to item of first element this.columnwidth = firstitemelem && getsize( firstitemelem ).outerwidth || // if first elem has no width, default to size of container this.containerwidth; } var columnwidth = this.columnwidth += this.gutter; // calculate columns var containerwidth = this.containerwidth + this.gutter; var cols = containerwidth / columnwidth; // fix rounding errors, typically with gutters var excess = columnwidth - containerwidth % columnwidth; // if overshoot is less than a pixel, round up, otherwise floor it var mathmethod = excess && excess < 1 ? 'round' : 'floor'; cols = math[ mathmethod ]( cols ); this.cols = math.max( cols, 1 ); }; proto.getcontainerwidth = function() { // container is parent if fit width var isfitwidth = this._getoption('fitwidth'); var container = isfitwidth ? this.element.parentnode : this.element; // check that this.size and size are there // ie8 triggers resize on body size change, so they might not be var size = getsize( container ); this.containerwidth = size && size.innerwidth; }; proto._getitemlayoutposition = function( item ) { item.getsize(); // how many columns does this brick span var remainder = item.size.outerwidth % this.columnwidth; var mathmethod = remainder && remainder < 1 ? 'round' : 'ceil'; // round if off by 1 pixel, otherwise use ceil var colspan = math[ mathmethod ]( item.size.outerwidth / this.columnwidth ); colspan = math.min( colspan, this.cols ); // use horizontal or top column position var colposmethod = this.options.horizontalorder ? '_gethorizontalcolposition' : '_gettopcolposition'; var colposition = this[ colposmethod ]( colspan, item ); // position the brick var position = { x: this.columnwidth * colposition.col, y: colposition.y }; // apply setheight to necessary columns var setheight = colposition.y + item.size.outerheight; var setmax = colspan + colposition.col; for ( var i = colposition.col; i < setmax; i++ ) { this.colys[i] = setheight; } return position; }; proto._gettopcolposition = function( colspan ) { var colgroup = this._gettopcolgroup( colspan ); // get the minimum y value from the columns var minimumy = math.min.apply( math, colgroup ); return { col: colgroup.indexof( minimumy ), y: minimumy, }; }; /** * @param {number} colspan - number of columns the element spans * @returns {array} colgroup */ proto._gettopcolgroup = function( colspan ) { if ( colspan < 2 ) { // if brick spans only one column, use all the column ys return this.colys; } var colgroup = []; // how many different places could this brick fit horizontally var groupcount = this.cols + 1 - colspan; // for each group potential horizontal position for ( var i = 0; i < groupcount; i++ ) { colgroup[i] = this._getcolgroupy( i, colspan ); } return colgroup; }; proto._getcolgroupy = function( col, colspan ) { if ( colspan < 2 ) { return this.colys[ col ]; } // make an array of coly values for that one group var groupcolys = this.colys.slice( col, col + colspan ); // and get the max value of the array return math.max.apply( math, groupcolys ); }; // get column position based on horizontal index. #873 proto._gethorizontalcolposition = function( colspan, item ) { var col = this.horizontalcolindex % this.cols; var isover = colspan > 1 && col + colspan > this.cols; // shift to next row if item can't fit on current row col = isover ? 0 : col; // don't let zero-size items take up space var hassize = item.size.outerwidth && item.size.outerheight; this.horizontalcolindex = hassize ? col + colspan : this.horizontalcolindex; return { col: col, y: this._getcolgroupy( col, colspan ), }; }; proto._managestamp = function( stamp ) { var stampsize = getsize( stamp ); var offset = this._getelementoffset( stamp ); // get the columns that this stamp affects var isoriginleft = this._getoption('originleft'); var firstx = isoriginleft ? offset.left : offset.right; var lastx = firstx + stampsize.outerwidth; var firstcol = math.floor( firstx / this.columnwidth ); firstcol = math.max( 0, firstcol ); var lastcol = math.floor( lastx / this.columnwidth ); // lastcol should not go over if multiple of columnwidth #425 lastcol -= lastx % this.columnwidth ? 0 : 1; lastcol = math.min( this.cols - 1, lastcol ); // set colys to bottom of the stamp var isorigintop = this._getoption('origintop'); var stampmaxy = ( isorigintop ? offset.top : offset.bottom ) + stampsize.outerheight; for ( var i = firstcol; i <= lastcol; i++ ) { this.colys[i] = math.max( stampmaxy, this.colys[i] ); } }; proto._getcontainersize = function() { this.maxy = math.max.apply( math, this.colys ); var size = { height: this.maxy }; if ( this._getoption('fitwidth') ) { size.width = this._getcontainerfitwidth(); } return size; }; proto._getcontainerfitwidth = function() { var unusedcols = 0; // count unused columns var i = this.cols; while ( --i ) { if ( this.colys[i] !== 0 ) { break; } unusedcols++; } // fit container to columns that have been used return ( this.cols - unusedcols ) * this.columnwidth - this.gutter; }; proto.needsresizelayout = function() { var previouswidth = this.containerwidth; this.getcontainerwidth(); return previouswidth != this.containerwidth; }; return masonry; }));