├── .gitignore ├── bower.json ├── package.json ├── sandbox ├── html-init.html ├── images.html ├── basic.html ├── adding-items.html └── load-more-images.html ├── README.md └── colcade.js /.gitignore: -------------------------------------------------------------------------------- 1 | bower_components/ 2 | node_modules/ 3 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "colcade", 3 | "description": "Lightweight masonry layout", 4 | "main": "colcade.js", 5 | "authors": [ 6 | "David DeSandro" 7 | ], 8 | "license": "MIT", 9 | "keywords": [ 10 | "masonry" 11 | ], 12 | "homepage": "https://github.com/desandro/colcade", 13 | "ignore": [ 14 | "**/.*", 15 | "node_modules", 16 | "bower_components", 17 | "test", 18 | "tests" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "colcade", 3 | "version": "0.2.0", 4 | "description": "Lightweight masonry layout", 5 | "main": "colcade.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/desandro/colcade.git" 12 | }, 13 | "keywords": [ 14 | "masonry" 15 | ], 16 | "author": "David DeSandro", 17 | "license": "MIT", 18 | "bugs": { 19 | "url": "https://github.com/desandro/colcade/issues" 20 | }, 21 | "homepage": "https://github.com/desandro/colcade#readme" 22 | } 23 | -------------------------------------------------------------------------------- /sandbox/html-init.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | html init 8 | 9 | 54 | 55 | 56 | 57 | 58 |

html init

59 | 60 |
61 |
62 |
63 |
64 |
65 | 66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 | 89 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /sandbox/images.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | images 8 | 9 | 55 | 56 | 57 | 58 | 59 |

images

60 | 61 |
62 |
63 |
64 |
65 |
66 | 67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 | 83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /sandbox/basic.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | basic 8 | 9 | 53 | 54 | 55 | 56 | 57 |

basic

58 | 59 |
60 |
61 |
62 |
63 |
64 | 65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 | 88 | 89 | 90 | 91 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /sandbox/adding-items.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | adding items 8 | 9 | 55 | 56 | 57 | 58 | 59 |

adding items

60 | 61 |

62 | 63 |

64 | 65 |
66 |
67 |
68 |
69 |
70 | 71 |
72 |
73 |
74 |
75 |
76 |
77 | 78 |

79 | 80 |

81 | 82 | 83 | 84 | 85 | 120 | 121 | 122 | 123 | -------------------------------------------------------------------------------- /sandbox/load-more-images.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | load more images 8 | 9 | 55 | 56 | 57 | 58 | 59 |

load more images

60 | 61 |
62 |
63 |
64 |
65 |
66 | 67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 | 83 |

84 | 85 | 86 | 108 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Colcade 2 | 3 | _Lightweight masonry layout_ 4 | 5 | ## Colcade vs. Masonry 6 | 7 | [Masonry is great](http://masonry.desandro.com), but it has grown big as it has grown older. Colcade is designed to be small & fast. I recommend using Colcade over Masonry, but read over this feature comparison. 8 | 9 | ### Same features 10 | 11 | + Masonry grid layout 12 | + Works as a jQuery plugin or with vanilla JS 13 | + Initialize in HTML 14 | 15 | ### Better features 16 | 17 | + Much smaller. 1/8 the size of Masonry 18 | + Better fluid/responsive layout, using native browser positioning 19 | + One file, no dependencies, no package `dist` built file 20 | + Does not require [imagesLoaded when using images](http://masonry.desandro.com/layout.html#imagesloaded) 21 | 22 | ### Worse features 23 | 24 | + No multi-column-spanning items 25 | 26 | ``` 27 | OK | No 28 | #### #### #### | ########## #### 29 | #### #### #### | ########## #### 30 | #### | 31 | #### #### | #### ########## 32 | #### #### #### | #### ########## 33 | #### #### #### | #### 34 | #### | 35 | ``` 36 | 37 | + No built-in item transitions for [layout](http://masonry.desandro.com/methods.html#layout-masonry), [appending](http://masonry.desandro.com/methods.html#appended), [prepending](http://masonry.desandro.com/methods.html#prepended), or [removing](http://masonry.desandro.com/methods.html#remove) 38 | + No [stamps](http://masonry.desandro.com/options.html#stamp) 39 | + No [fitWidth centering](http://masonry.desandro.com/options.html#fitwidth) 40 | 41 | ## Install 42 | 43 | Download: [colcade.js](https://unpkg.com/colcade@0/colcade.js) 44 | 45 | CDN: 46 | 47 | ``` html 48 | 49 | ``` 50 | 51 | [npm](https://www.npmjs.com/package/colcade): `npm install colcade` 52 | 53 | Bower: `bower install colcade` 54 | 55 | ## Demos 56 | 57 | + [Basic, HTML init](http://codepen.io/desandro/pen/EKMdxg) 58 | + [jQuery](http://codepen.io/desandro/pen/vGPVOR) 59 | + [Vanilla JS](http://codepen.io/desandro/pen/EKMdjN) 60 | + [Images](http://codepen.io/desandro/pen/pyYxvz) 61 | 62 | ## Usage 63 | 64 | Colcade works by moving item elements into column elements. 65 | 66 | ### HTML 67 | 68 | ``` html 69 |
70 | 71 |
72 |
73 |
74 |
75 | 76 |
...
77 |
...
78 |
...
79 | ... 80 |
81 | ``` 82 | 83 | ### CSS 84 | 85 | Sizing of the columns is handled by your own CSS. Change the number of columns by hiding or showing them. 86 | 87 | ``` css 88 | /* Using floats */ 89 | .grid-col { 90 | float: left; 91 | width: 50%; 92 | } 93 | 94 | /* 2 columns by default, hide columns 2 & 3 */ 95 | .grid-col--2, .grid-col--3 { display: none } 96 | 97 | /* 3 columns at medium size */ 98 | @media ( min-width: 768px ) { 99 | .grid-col { width: 33.333%; } 100 | .grid-col--2 { display: block; } /* show column 2 */ 101 | } 102 | 103 | /* 4 columns at large size */ 104 | @media ( min-width: 1080px ) { 105 | .grid-col { width: 25%; } 106 | .grid-col--3 { display: block; } /* show column 3 */ 107 | } 108 | ``` 109 | 110 | [Edit float demo on CodePen](http://codepen.io/desandro/pen/EKMdxg) 111 | 112 | ``` css 113 | /* with flexbox */ 114 | .grid { 115 | display: -webkit-box; 116 | display: -webkit-flex; 117 | display: -ms-flexbox; 118 | display: flex; 119 | } 120 | 121 | .grid-col { 122 | -webkit-box-flex: 1; 123 | -webkit-flex-grow: 1; 124 | -ms-flex-positive: 1; 125 | flex-grow: 1; 126 | } 127 | 128 | /* 2 columns by default, hide columns 2 & 3 */ 129 | .grid-col--2, .grid-col--3 { display: none } 130 | 131 | /* 3 columns at medium size */ 132 | @media ( min-width: 768px ) { 133 | .grid-col--2 { display: block; } /* show column 2 */ 134 | } 135 | 136 | /* 4 columns at large size */ 137 | @media ( min-width: 1080px ) { 138 | .grid-col--3 { display: block; } /* show column 3 */ 139 | } 140 | ``` 141 | 142 | [Edit flexbox demo on CodePen](http://codepen.io/desandro/pen/RadeWy) 143 | 144 | ### Initialize Colcade 145 | 146 | Set selectors for column and item elements in the options. 147 | 148 | With jQuery. [Edit jQuery demo on CodePen](http://codepen.io/desandro/pen/vGPVOR) 149 | 150 | ``` js 151 | $('.grid').colcade({ 152 | columns: '.grid-col', 153 | items: '.grid-item' 154 | }) 155 | ``` 156 | 157 | With vanilla JS. [Edit vanilla JS demo on CodePen](http://codepen.io/desandro/pen/EKMdjN) 158 | 159 | ``` js 160 | // element as first argument 161 | var grid = document.querySelector('.grid'); 162 | var colc = new Colcade( grid, { 163 | columns: '.grid-col', 164 | items: '.grid-item' 165 | }); 166 | 167 | // selector string as first argument 168 | var colc = new Colcade( '.grid', { 169 | columns: '.grid-col', 170 | items: '.grid-item' 171 | }); 172 | ``` 173 | 174 | With HTML. [Edit HTML demo on CodePen](http://codepen.io/desandro/pen/EKMdxg) 175 | 176 | ``` html 177 |
178 | ... 179 |
180 | ``` 181 | 182 | ## Methods 183 | 184 | ### append 185 | 186 | Add items to end of layout. 187 | 188 | ``` js 189 | // jQuery 190 | $grid.colcade( 'append', items ) 191 | // vanilla JS 192 | colc.append( items ) 193 | ``` 194 | 195 | ### prepend 196 | 197 | Add items to beginning of layout. 198 | 199 | ``` js 200 | // jQuery 201 | $grid.colcade( 'prepend', items ) 202 | // vanilla JS 203 | colc.prepend( items ) 204 | ``` 205 | 206 | ### destroy 207 | 208 | Remove Colcade behavior completely. 209 | 210 | ``` js 211 | // jQuery 212 | $grid.colcade('destroy') 213 | // vanilla JS 214 | colc.destroy() 215 | ``` 216 | 217 | --- 218 | 219 | By David DeSandro 220 | 221 | MIT License. Have at it. 222 | -------------------------------------------------------------------------------- /colcade.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Colcade v0.2.0 3 | * Lightweight masonry layout 4 | * by David DeSandro 5 | * MIT license 6 | */ 7 | 8 | /*jshint browser: true, undef: true, unused: true */ 9 | 10 | ( function( window, factory ) { 11 | // universal module definition 12 | /*jshint strict: false */ 13 | /*global define: false, module: false */ 14 | if ( typeof define == 'function' && define.amd ) { 15 | // AMD 16 | define( factory ); 17 | } else if ( typeof module == 'object' && module.exports ) { 18 | // CommonJS 19 | module.exports = factory(); 20 | } else { 21 | // browser global 22 | window.Colcade = factory(); 23 | } 24 | 25 | }( window, function factory() { 26 | 27 | // -------------------------- Colcade -------------------------- // 28 | 29 | function Colcade( element, options ) { 30 | element = getQueryElement( element ); 31 | 32 | // do not initialize twice on same element 33 | if ( element && element.colcadeGUID ) { 34 | var instance = instances[ element.colcadeGUID ]; 35 | instance.option( options ); 36 | return instance; 37 | } 38 | 39 | this.element = element; 40 | // options 41 | this.options = {}; 42 | this.option( options ); 43 | // kick things off 44 | this.create(); 45 | } 46 | 47 | var proto = Colcade.prototype; 48 | 49 | proto.option = function( options ) { 50 | this.options = extend( this.options, options ); 51 | }; 52 | 53 | // globally unique identifiers 54 | var GUID = 0; 55 | // internal store of all Colcade intances 56 | var instances = {}; 57 | 58 | proto.create = function() { 59 | this.errorCheck(); 60 | // add guid for Colcade.data 61 | var guid = this.guid = ++GUID; 62 | this.element.colcadeGUID = guid; 63 | instances[ guid ] = this; // associate via id 64 | // update initial properties & layout 65 | this.reload(); 66 | // events 67 | this._windowResizeHandler = this.onWindowResize.bind( this ); 68 | this._loadHandler = this.onLoad.bind( this ); 69 | window.addEventListener( 'resize', this._windowResizeHandler ); 70 | this.element.addEventListener( 'load', this._loadHandler, true ); 71 | }; 72 | 73 | proto.errorCheck = function() { 74 | var errors = []; 75 | if ( !this.element ) { 76 | errors.push( 'Bad element: ' + this.element ); 77 | } 78 | if ( !this.options.columns ) { 79 | errors.push( 'columns option required: ' + this.options.columns ); 80 | } 81 | if ( !this.options.items ) { 82 | errors.push( 'items option required: ' + this.options.items ); 83 | } 84 | 85 | if ( errors.length ) { 86 | throw new Error( '[Colcade error] ' + errors.join('. ') ); 87 | } 88 | }; 89 | 90 | // update properties and do layout 91 | proto.reload = function() { 92 | this.updateColumns(); 93 | this.updateItems(); 94 | this.layout(); 95 | }; 96 | 97 | proto.updateColumns = function() { 98 | this.columns = querySelect( this.options.columns, this.element ); 99 | }; 100 | 101 | proto.updateItems = function() { 102 | this.items = querySelect( this.options.items, this.element ); 103 | }; 104 | 105 | proto.getActiveColumns = function() { 106 | return this.columns.filter( function( column ) { 107 | var style = getComputedStyle( column ); 108 | return style.display != 'none'; 109 | }); 110 | }; 111 | 112 | // ----- layout ----- // 113 | 114 | // public, updates activeColumns 115 | proto.layout = function() { 116 | this.activeColumns = this.getActiveColumns(); 117 | this._layout(); 118 | }; 119 | 120 | // private, does not update activeColumns 121 | proto._layout = function() { 122 | // reset column heights 123 | this.columnHeights = this.activeColumns.map( function() { 124 | return 0; 125 | }); 126 | // layout all items 127 | this.layoutItems( this.items ); 128 | }; 129 | 130 | proto.layoutItems = function( items ) { 131 | items.forEach( this.layoutItem, this ); 132 | }; 133 | 134 | proto.layoutItem = function( item ) { 135 | // layout item by appending to column 136 | var minHeight = Math.min.apply( Math, this.columnHeights ); 137 | var index = this.columnHeights.indexOf( minHeight ); 138 | this.activeColumns[ index ].appendChild( item ); 139 | // at least 1px, if item hasn't loaded 140 | // Not exactly accurate, but it's cool 141 | this.columnHeights[ index ] += item.offsetHeight || 1; 142 | }; 143 | 144 | // ----- adding items ----- // 145 | 146 | proto.append = function( elems ) { 147 | var items = this.getQueryItems( elems ); 148 | // add items to collection 149 | this.items = this.items.concat( items ); 150 | // lay them out 151 | this.layoutItems( items ); 152 | }; 153 | 154 | proto.prepend = function( elems ) { 155 | var items = this.getQueryItems( elems ); 156 | // add items to collection 157 | this.items = items.concat( this.items ); 158 | // lay out everything 159 | this._layout(); 160 | }; 161 | 162 | proto.getQueryItems = function( elems ) { 163 | elems = makeArray( elems ); 164 | var fragment = document.createDocumentFragment(); 165 | elems.forEach( function( elem ) { 166 | fragment.appendChild( elem ); 167 | }); 168 | return querySelect( this.options.items, fragment ); 169 | }; 170 | 171 | // ----- measure column height ----- // 172 | 173 | proto.measureColumnHeight = function( elem ) { 174 | var boundingRect = this.element.getBoundingClientRect(); 175 | this.activeColumns.forEach( function( column, i ) { 176 | // if elem, measure only that column 177 | // if no elem, measure all columns 178 | if ( !elem || column.contains( elem ) ) { 179 | var lastChildRect = column.lastElementChild.getBoundingClientRect(); 180 | // not an exact calculation as it includes top border, and excludes item bottom margin 181 | this.columnHeights[ i ] = lastChildRect.bottom - boundingRect.top; 182 | } 183 | }, this ); 184 | }; 185 | 186 | // ----- events ----- // 187 | 188 | proto.onWindowResize = function() { 189 | clearTimeout( this.resizeTimeout ); 190 | this.resizeTimeout = setTimeout( function() { 191 | this.onDebouncedResize(); 192 | }.bind( this ), 100 ); 193 | }; 194 | 195 | proto.onDebouncedResize = function() { 196 | var activeColumns = this.getActiveColumns(); 197 | // check if columns changed 198 | var isSameLength = activeColumns.length == this.activeColumns.length; 199 | var isSameColumns = true; 200 | this.activeColumns.forEach( function( column, i ) { 201 | isSameColumns = isSameColumns && column == activeColumns[i]; 202 | }); 203 | if ( isSameLength && isSameColumns ) { 204 | return; 205 | } 206 | // activeColumns changed 207 | this.activeColumns = activeColumns; 208 | this._layout(); 209 | }; 210 | 211 | proto.onLoad = function( event ) { 212 | this.measureColumnHeight( event.target ); 213 | }; 214 | 215 | // ----- destroy ----- // 216 | 217 | proto.destroy = function() { 218 | // move items back to container 219 | this.items.forEach( function( item ) { 220 | this.element.appendChild( item ); 221 | }, this ); 222 | // remove events 223 | window.removeEventListener( 'resize', this._windowResizeHandler ); 224 | this.element.removeEventListener( 'load', this._loadHandler, true ); 225 | // remove data 226 | delete this.element.colcadeGUID; 227 | delete instances[ this.guid ]; 228 | }; 229 | 230 | // -------------------------- HTML init -------------------------- // 231 | 232 | docReady( function() { 233 | var dataElems = querySelect('[data-colcade]'); 234 | dataElems.forEach( htmlInit ); 235 | }); 236 | 237 | function htmlInit( elem ) { 238 | // convert attribute "foo: bar, qux: baz" into object 239 | var attr = elem.getAttribute('data-colcade'); 240 | var attrParts = attr.split(','); 241 | var options = {}; 242 | attrParts.forEach( function( part ) { 243 | var pair = part.split(':'); 244 | var key = pair[0].trim(); 245 | var value = pair[1].trim(); 246 | options[ key ] = value; 247 | }); 248 | 249 | new Colcade( elem, options ); 250 | } 251 | 252 | Colcade.data = function( elem ) { 253 | elem = getQueryElement( elem ); 254 | var id = elem && elem.colcadeGUID; 255 | return id && instances[ id ]; 256 | }; 257 | 258 | // -------------------------- jQuery -------------------------- // 259 | 260 | Colcade.makeJQueryPlugin = function( $ ) { 261 | $ = $ || window.jQuery; 262 | if ( !$ ) { 263 | return; 264 | } 265 | 266 | $.fn.colcade = function( arg0 /*, arg1 */) { 267 | // method call $().colcade( 'method', { options } ) 268 | if ( typeof arg0 == 'string' ) { 269 | // shift arguments by 1 270 | var args = Array.prototype.slice.call( arguments, 1 ); 271 | return methodCall( this, arg0, args ); 272 | } 273 | // just $().colcade({ options }) 274 | plainCall( this, arg0 ); 275 | return this; 276 | }; 277 | 278 | function methodCall( $elems, methodName, args ) { 279 | var returnValue; 280 | $elems.each( function( i, elem ) { 281 | // get instance 282 | var colcade = $.data( elem, 'colcade' ); 283 | if ( !colcade ) { 284 | return; 285 | } 286 | // apply method, get return value 287 | var value = colcade[ methodName ].apply( colcade, args ); 288 | // set return value if value is returned, use only first value 289 | returnValue = returnValue === undefined ? value : returnValue; 290 | }); 291 | return returnValue !== undefined ? returnValue : $elems; 292 | } 293 | 294 | function plainCall( $elems, options ) { 295 | $elems.each( function( i, elem ) { 296 | var colcade = $.data( elem, 'colcade' ); 297 | if ( colcade ) { 298 | // set options & init 299 | colcade.option( options ); 300 | colcade.layout(); 301 | } else { 302 | // initialize new instance 303 | colcade = new Colcade( elem, options ); 304 | $.data( elem, 'colcade', colcade ); 305 | } 306 | }); 307 | } 308 | }; 309 | 310 | // try making plugin 311 | Colcade.makeJQueryPlugin(); 312 | 313 | // -------------------------- utils -------------------------- // 314 | 315 | function extend( a, b ) { 316 | for ( var prop in b ) { 317 | a[ prop ] = b[ prop ]; 318 | } 319 | return a; 320 | } 321 | 322 | // turn element or nodeList into an array 323 | function makeArray( obj ) { 324 | var ary = []; 325 | if ( Array.isArray( obj ) ) { 326 | // use object if already an array 327 | ary = obj; 328 | } else if ( obj && typeof obj.length == 'number' ) { 329 | // convert nodeList to array 330 | for ( var i=0; i < obj.length; i++ ) { 331 | ary.push( obj[i] ); 332 | } 333 | } else { 334 | // array of single index 335 | ary.push( obj ); 336 | } 337 | return ary; 338 | } 339 | 340 | // get array of elements 341 | function querySelect( selector, elem ) { 342 | elem = elem || document; 343 | var elems = elem.querySelectorAll( selector ); 344 | return makeArray( elems ); 345 | } 346 | 347 | function getQueryElement( elem ) { 348 | if ( typeof elem == 'string' ) { 349 | elem = document.querySelector( elem ); 350 | } 351 | return elem; 352 | } 353 | 354 | function docReady( onReady ) { 355 | if ( document.readyState == 'complete' ) { 356 | onReady(); 357 | return; 358 | } 359 | document.addEventListener( 'DOMContentLoaded', onReady ); 360 | } 361 | 362 | // -------------------------- end -------------------------- // 363 | 364 | return Colcade; 365 | 366 | })); 367 | --------------------------------------------------------------------------------