├── .github ├── contributing.md └── issue_template.md ├── .gitignore ├── .jshintrc ├── README.md ├── bower.json ├── dist ├── packery.pkgd.js └── packery.pkgd.min.js ├── gulpfile.js ├── js ├── item.js ├── packer.js ├── packery.js └── rect.js ├── package.json ├── sandbox ├── add-remove.html ├── basics.html ├── browserify │ ├── browserify.html │ ├── jquery-main.js │ └── main.js ├── css │ ├── basics.css │ └── examples.css ├── draggabilly.html ├── examples.js ├── fit.html ├── fluid.html ├── horizontal.html ├── jquery-ui-draggable.html ├── jquery.html ├── merge-rects.html ├── origin.html ├── requirejs │ ├── index.html │ └── main.js ├── save-positions.html ├── shift-drag-uneven.html ├── shift-drag.html └── single.html └── test ├── .jshintrc ├── helpers.js ├── index.html ├── test.css └── unit ├── add-items.js ├── basics.js ├── consecutive.js ├── container-size.js ├── declarative.js ├── defaults-empty.js ├── draggable.js ├── fit.js ├── get-items.js ├── grid.js ├── hidden-items.js ├── is-init-layout.js ├── jquery-plugin.js ├── layout-extra-big.js ├── layout.js ├── prepend.js ├── remove.js ├── stamped.js ├── sub-pixel-fit.js ├── test-packer.js └── test-rect.js /.github/contributing.md: -------------------------------------------------------------------------------- 1 | ## Submitting issues 2 | 3 | ### Reduced test case required 4 | 5 | All bug reports and problem issues require a [**reduced test case**](http://css-tricks.com/reduced-test-cases/). Create one by forking any one of the [CodePen examples](http://codepen.io/desandro/pens/tags/?grid_type=list&selected_tag=packery-docs) from [the docs](http://packery.metafizzy.co). 6 | 7 | **CodePens** 8 | 9 | + [Basic layout](http://codepen.io/desandro/pen/QyrEgX) 10 | + [Fluid layout](http://codepen.io/desandro/pen/xZjOXq) 11 | + [Images](http://codepen.io/desandro/pen/pgVbLv) 12 | + [Draggable](http://codepen.io/desandro/pen/aGvIq) 13 | 14 | **Test cases** 15 | 16 | + A reduced test case clearly demonstrates the bug or issue. 17 | + It contains the bare minimum HTML, CSS, and JavaScript required to demonstrate the bug. 18 | + A link to your production site is **not** a reduced test case. 19 | 20 | Providing a reduced test case is the best way to get your issue addressed. Without a reduced test case, your issue may be closed. 21 | 22 | ## Pull requests 23 | 24 | Contributions are welcome! Please note: your code may be used as part of a commercial product if merged. Be clear about what license applies to your patch. [The MIT license](http://choosealicense.com/licenses/mit/) or [public domain unlicense](http://choosealicense.com/licenses/unlicense/) are permissive, and allow integration of your patch into Packery as part of a commercial product. 25 | -------------------------------------------------------------------------------- /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | **Test case:** http://codepen.io/desandro/pen/QyrEgX 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bower_components/ 2 | node_modules/ 3 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "browser": true, 3 | "devel": false, 4 | "strict": true, 5 | "undef": true, 6 | "unused": true, 7 | "globals": { 8 | "Packery": true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Packery 2 | 3 | _Bin-packing layout library_ 4 | 5 | See [packery.metafizzy.co](https://packery.metafizzy.co) for complete docs and demos 6 | 7 | ## Install 8 | 9 | ### Download 10 | 11 | + [packery.pkgd.js](https://unpkg.com/packery@3/dist/packery.pkgd.js) un-minified, or 12 | + [packery.pkgd.min.js](https://unpkg.com/packery@3/dist/packery.pkgd.min.js) minified 13 | 14 | ### CDN 15 | 16 | Link directly to Packery files on [unpkg](https://unpkg.com). 17 | 18 | ``` html 19 | 20 | 21 | 22 | ``` 23 | 24 | ### Package managers 25 | 26 | [npm](https://www.npmjs.com/package/packery): `npm install packery --save` 27 | 28 | Bower: `bower install packery --save` 29 | 30 | ## License 31 | 32 | Packery v3 is licensed under the MIT license. 33 | 34 | ## Initialize 35 | 36 | With jQuery 37 | 38 | ``` js 39 | $('.grid').packery({ 40 | // options... 41 | itemSelector: '.grid-item' 42 | }); 43 | ``` 44 | 45 | With vanilla JavaScript 46 | 47 | ``` js 48 | // vanilla JS 49 | var grid = document.querySelector('.grid'); 50 | // initialize with element 51 | var pckry = new Packery( grid, { 52 | // options... 53 | itemSelector: '.grid-item' 54 | }); 55 | 56 | // initialize with selector string 57 | var pckry = new Packery('.grid', { 58 | // options... 59 | }); 60 | ``` 61 | 62 | With HTML 63 | 64 | Add a `data-packery` attribute to your element. Options can be set in JSON in the value. 65 | 66 | ``` html 67 |
68 |
69 |
70 | ... 71 |
72 | ``` 73 | 74 | --- 75 | 76 | By [Metafizzy](http://metafizzy.co) 77 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "packery", 3 | "author": "David DeSandro", 4 | "description": "Gapless, draggable grid layouts", 5 | "main": "js/packery.js", 6 | "dependencies": { 7 | "get-size": "^2.0.2", 8 | "outlayer": "^2.0.0" 9 | }, 10 | "devDependencies": { 11 | "draggabilly": "^2.1.0", 12 | "jquery": ">=2 <4", 13 | "jquery-ui-draggable": "https://gist.github.com/4985610.git", 14 | "jquery-bridget": "~2.0.0", 15 | "qunit": "^1.15" 16 | }, 17 | "ignore": [ 18 | "**/.*", 19 | "contributing.md", 20 | "package.json", 21 | "gulpfile.js", 22 | "sandbox/", 23 | "node_modules", 24 | "bower_components", 25 | "test", 26 | "tests" 27 | ], 28 | "homepage": "https://packery.metafizzy.co", 29 | "authors": [ 30 | "David DeSandro " 31 | ], 32 | "moduleType": [ 33 | "amd", 34 | "globals", 35 | "node" 36 | ], 37 | "keywords": [ 38 | "layout", 39 | "grid", 40 | "draggable" 41 | ], 42 | "license": "MIT" 43 | } 44 | -------------------------------------------------------------------------------- /dist/packery.pkgd.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Packery v3.0.0 3 | * Gapless, draggable grid layouts 4 | * MIT License 5 | * https://packery.metafizzy.co 6 | * Copyright 2013-2025 Metafizzy 7 | */ 8 | 9 | !function(t,e){"function"==typeof define&&define.amd?define("jquery-bridget/jquery-bridget",["jquery"],function(i){return e(t,i)}):"object"==typeof module&&module.exports?module.exports=e(t,require("jquery")):t.jQueryBridget=e(t,t.jQuery)}(window,function(t,e){"use strict";function i(i,s,a){function h(t,e,n){var o,s="$()."+i+'("'+e+'")';return t.each(function(t,h){var u=a.data(h,i);if(!u)return void r(i+" not initialized. Cannot call methods, i.e. "+s);var c=u[e];if(!c||"_"==e.charAt(0))return void r(s+" is not a valid method");var d=c.apply(u,n);o=void 0===o?d:o}),void 0!==o?o:t}function u(t,e){t.each(function(t,n){var o=a.data(n,i);o?(o.option(e),o._init()):(o=new s(n,e),a.data(n,i,o))})}a=a||e||t.jQuery,a&&(s.prototype.option||(s.prototype.option=function(t){a.isPlainObject(t)&&(this.options=a.extend(!0,this.options,t))}),a.fn[i]=function(t){if("string"==typeof t){var e=o.call(arguments,1);return h(this,t,e)}return u(this,t),this},n(a))}function n(t){!t||t&&t.bridget||(t.bridget=i)}var o=Array.prototype.slice,s=t.console,r="undefined"==typeof s?function(){}:function(t){s.error(t)};return n(e||t.jQuery),i}),function(t,e){"function"==typeof define&&define.amd?define("get-size/get-size",e):"object"==typeof module&&module.exports?module.exports=e():t.getSize=e()}(window,function(){"use strict";function t(t){var e=parseFloat(t),i=-1==t.indexOf("%")&&!isNaN(e);return i&&e}function e(){}function i(){for(var t={width:0,height:0,innerWidth:0,innerHeight:0,outerWidth:0,outerHeight:0},e=0;u>e;e++){var i=h[e];t[i]=0}return t}function n(t){var e=getComputedStyle(t);return e||a("Style returned "+e+". Are you running this code in a hidden iframe on Firefox? See https://bit.ly/getsizebug1"),e}function o(){if(!c){c=!0;var e=document.createElement("div");e.style.width="200px",e.style.padding="1px 2px 3px 4px",e.style.borderStyle="solid",e.style.borderWidth="1px 2px 3px 4px",e.style.boxSizing="border-box";var i=document.body||document.documentElement;i.appendChild(e);var o=n(e);r=200==Math.round(t(o.width)),s.isBoxSizeOuter=r,i.removeChild(e)}}function s(e){if(o(),"string"==typeof e&&(e=document.querySelector(e)),e&&"object"==typeof e&&e.nodeType){var s=n(e);if("none"==s.display)return i();var a={};a.width=e.offsetWidth,a.height=e.offsetHeight;for(var c=a.isBorderBox="border-box"==s.boxSizing,d=0;u>d;d++){var l=h[d],f=s[l],p=parseFloat(f);a[l]=isNaN(p)?0:p}var g=a.paddingLeft+a.paddingRight,m=a.paddingTop+a.paddingBottom,y=a.marginLeft+a.marginRight,v=a.marginTop+a.marginBottom,_=a.borderLeftWidth+a.borderRightWidth,x=a.borderTopWidth+a.borderBottomWidth,b=c&&r,E=t(s.width);E!==!1&&(a.width=E+(b?0:g+_));var w=t(s.height);return w!==!1&&(a.height=w+(b?0:m+x)),a.innerWidth=a.width-(g+_),a.innerHeight=a.height-(m+x),a.outerWidth=a.width+y,a.outerHeight=a.height+v,a}}var r,a="undefined"==typeof console?e:function(t){console.error(t)},h=["paddingLeft","paddingRight","paddingTop","paddingBottom","marginLeft","marginRight","marginTop","marginBottom","borderLeftWidth","borderRightWidth","borderTopWidth","borderBottomWidth"],u=h.length,c=!1;return s}),function(t,e){"function"==typeof define&&define.amd?define("ev-emitter/ev-emitter",e):"object"==typeof module&&module.exports?module.exports=e():t.EvEmitter=e()}("undefined"!=typeof window?window:this,function(){function t(){}var e=t.prototype;return e.on=function(t,e){if(t&&e){var i=this._events=this._events||{},n=i[t]=i[t]||[];return-1==n.indexOf(e)&&n.push(e),this}},e.once=function(t,e){if(t&&e){this.on(t,e);var i=this._onceEvents=this._onceEvents||{},n=i[t]=i[t]||{};return n[e]=!0,this}},e.off=function(t,e){var i=this._events&&this._events[t];if(i&&i.length){var n=i.indexOf(e);return-1!=n&&i.splice(n,1),this}},e.emitEvent=function(t,e){var i=this._events&&this._events[t];if(i&&i.length){i=i.slice(0),e=e||[];for(var n=this._onceEvents&&this._onceEvents[t],o=0;o=t.x+e&&this.y+this.height>=t.y+i},e.overlaps=function(t){var e=this.x+this.width,i=this.y+this.height,n=t.x+t.width,o=t.y+t.height;return this.xt.x&&this.yt.y},e.getMaximalFreeRects=function(e){if(!this.overlaps(e))return!1;var i,n=[],o=this.x+this.width,s=this.y+this.height,r=e.x+e.width,a=e.y+e.height;return this.yr&&(i=new t({x:r,y:this.y,width:o-r,height:this.height}),n.push(i)),s>a&&(i=new t({x:this.x,y:a,width:this.width,height:s-a}),n.push(i)),this.x=t.width&&this.height>=t.height},t}),function(t,e){if("function"==typeof define&&define.amd)define("packery/js/packer",["./rect"],e);else if("object"==typeof module&&module.exports)module.exports=e(require("./rect"));else{var i=t.Packery=t.Packery||{};i.Packer=e(i.Rect)}}(window,function(t){"use strict";function e(t,e,i){this.width=t||0,this.height=e||0,this.sortDirection=i||"downwardLeftToRight",this.reset()}var i=e.prototype;i.reset=function(){this.spaces=[];var e=new t({x:0,y:0,width:this.width,height:this.height});this.spaces.push(e),this.sorter=n[this.sortDirection]||n.downwardLeftToRight},i.pack=function(t){for(var e=0;e=t.x+t.width&&i.height>=t.height-.01;if(n){t.y=i.y,this.placed(t);break}}},i.rowPack=function(t){for(var e=0;e=t.y+t.height&&i.width>=t.width-.01;if(n){t.x=i.x,this.placed(t);break}}},i.placeInSpace=function(t,e){t.x=e.x,t.y=e.y,this.placed(t)},i.placed=function(t){for(var e=[],i=0;ii&&1>n;return o?void this.goTo(t,e):void a.apply(this,arguments)},s.enablePlacing=function(){this.removeTransitionStyles(),this.isTransitioning&&n&&(this.element.style[n]="none"),this.isTransitioning=!1,this.getSize(),this.layout._setRectSize(this.element,this.rect),this.isPlacing=!0},s.disablePlacing=function(){this.isPlacing=!1},s.removeElem=function(){var t=this.element.parentNode;t&&t.removeChild(this.element),this.layout.packer.addSpace(this.rect),this.emitEvent("remove",[this])},s.showDropPlaceholder=function(){var t=this.dropPlaceholder;t||(t=this.dropPlaceholder=document.createElement("div"),t.className="packery-drop-placeholder",t.style.position="absolute"),t.style.width=this.size.width+"px",t.style.height=this.size.height+"px",this.positionDropPlaceholder(),this.layout.element.appendChild(t)},s.positionDropPlaceholder=function(){this.dropPlaceholder.style[n]="translate("+this.rect.x+"px, "+this.rect.y+"px)"},s.hideDropPlaceholder=function(){var t=this.dropPlaceholder.parentNode;t&&t.removeChild(this.dropPlaceholder)},o}),function(t,e){"function"==typeof define&&define.amd?define(["get-size/get-size","outlayer/outlayer","packery/js/rect","packery/js/packer","packery/js/item"],e):"object"==typeof module&&module.exports?module.exports=e(require("get-size"),require("outlayer"),require("./rect"),require("./packer"),require("./item")):t.Packery=e(t.getSize,t.Outlayer,t.Packery.Rect,t.Packery.Packer,t.Packery.Item)}(window,function(t,e,i,n,o){"use strict";function s(t,e){return t.position.y-e.position.y||t.position.x-e.position.x}function r(t,e){return t.position.x-e.position.x||t.position.y-e.position.y}function a(t,e){var i=e.x-t.x,n=e.y-t.y;return Math.sqrt(i*i+n*n)}i.prototype.canFit=function(t){return this.width>=t.width-1&&this.height>=t.height-1};var h=e.create("packery");h.Item=o;var u=h.prototype;u._create=function(){e.prototype._create.call(this),this.packer=new n,this.shiftPacker=new n,this.isEnabled=!0,this.dragItemCount=0;var t=this;this.handleDraggabilly={dragStart:function(){t.itemDragStart(this.element)},dragMove:function(){t.itemDragMove(this.element,this.position.x,this.position.y)},dragEnd:function(){t.itemDragEnd(this.element)}},this.handleUIDraggable={start:function(e,i){i&&t.itemDragStart(e.currentTarget)},drag:function(e,i){i&&t.itemDragMove(e.currentTarget,i.position.left,i.position.top)},stop:function(e,i){i&&t.itemDragEnd(e.currentTarget)}}},u._resetLayout=function(){this.getSize(),this._getMeasurements();var t,e,i;this._getOption("horizontal")?(t=1/0,e=this.size.innerHeight+this.gutter,i="rightwardTopToBottom"):(t=this.size.innerWidth+this.gutter,e=1/0,i="downwardLeftToRight"),this.packer.width=this.shiftPacker.width=t,this.packer.height=this.shiftPacker.height=e,this.packer.sortDirection=this.shiftPacker.sortDirection=i,this.packer.reset(),this.maxY=0,this.maxX=0},u._getMeasurements=function(){this._getMeasurement("columnWidth","width"),this._getMeasurement("rowHeight","height"),this._getMeasurement("gutter","width")},u._getItemLayoutPosition=function(t){if(this._setRectSize(t.element,t.rect),this.isShifting||this.dragItemCount>0){var e=this._getPackMethod();this.packer[e](t.rect)}else this.packer.pack(t.rect);return this._setMaxXY(t.rect),t.rect},u.shiftLayout=function(){this.isShifting=!0,this.layout(),delete this.isShifting},u._getPackMethod=function(){return this._getOption("horizontal")?"rowPack":"columnPack"},u._setMaxXY=function(t){this.maxX=Math.max(t.x+t.width,this.maxX),this.maxY=Math.max(t.y+t.height,this.maxY)},u._setRectSize=function(e,i){var n=t(e),o=n.outerWidth,s=n.outerHeight;(o||s)&&(o=this._applyGridGutter(o,this.columnWidth),s=this._applyGridGutter(s,this.rowHeight)),i.width=Math.min(o,this.packer.width),i.height=Math.min(s,this.packer.height)},u._applyGridGutter=function(t,e){if(!e)return t+this.gutter;e+=this.gutter;var i=t%e,n=i&&1>i?"round":"ceil";return t=Math[n](t/e)*e},u._getContainerSize=function(){return this._getOption("horizontal")?{width:this.maxX-this.gutter}:{height:this.maxY-this.gutter}},u._manageStamp=function(t){var e,n=this.getItem(t);if(n&&n.isPlacing)e=n.rect;else{var o=this._getElementOffset(t);e=new i({x:this._getOption("originLeft")?o.left:o.right,y:this._getOption("originTop")?o.top:o.bottom})}this._setRectSize(t,e),this.packer.placed(e),this._setMaxXY(e)},u.sortItemsByPosition=function(){var t=this._getOption("horizontal")?r:s;this.items.sort(t)},u.fit=function(t,e,i){var n=this.getItem(t);n&&(this.stamp(n.element),n.enablePlacing(),this.updateShiftTargets(n),e=void 0===e?n.rect.x:e,i=void 0===i?n.rect.y:i,this.shift(n,e,i),this._bindFitEvents(n),n.moveTo(n.rect.x,n.rect.y),this.shiftLayout(),this.unstamp(n.element),this.sortItemsByPosition(),n.disablePlacing())},u._bindFitEvents=function(t){function e(){n++,2==n&&i.dispatchEvent("fitComplete",null,[t])}var i=this,n=0;t.once("layout",e),this.once("layoutComplete",e)},u.resize=function(){this.isResizeBound&&this.needsResizeLayout()&&(this.options.shiftPercentResize?this.resizeShiftPercentLayout():this.layout())},u.needsResizeLayout=function(){var e=t(this.element),i=this._getOption("horizontal")?"innerHeight":"innerWidth";return e[i]!=this.size[i]},u.resizeShiftPercentLayout=function(){var e=this._getItemsForLayout(this.items),i=this._getOption("horizontal"),n=i?"y":"x",o=i?"height":"width",s=i?"rowHeight":"columnWidth",r=i?"innerHeight":"innerWidth",a=this[s];if(a=a&&a+this.gutter){this._getMeasurements();var h=this[s]+this.gutter;e.forEach(function(t){var e=Math.round(t.rect[n]/a);t.rect[n]=e*h})}else{var u=t(this.element)[r]+this.gutter,c=this.packer[o];e.forEach(function(t){t.rect[n]=t.rect[n]/c*u})}this.shiftLayout()},u.itemDragStart=function(t){if(this.isEnabled){this.stamp(t);var e=this.getItem(t);e&&(e.enablePlacing(),e.showDropPlaceholder(),this.dragItemCount++,this.updateShiftTargets(e))}},u.updateShiftTargets=function(t){this.shiftPacker.reset(),this._getBoundingRect();var e=this._getOption("originLeft"),n=this._getOption("originTop");this.stamps.forEach(function(t){var o=this.getItem(t);if(!o||!o.isPlacing){var s=this._getElementOffset(t),r=new i({x:e?s.left:s.right,y:n?s.top:s.bottom});this._setRectSize(t,r),this.shiftPacker.placed(r)}},this);var o=this._getOption("horizontal"),s=o?"rowHeight":"columnWidth",r=o?"height":"width";this.shiftTargetKeys=[],this.shiftTargets=[];var a,h=this[s];if(h=h&&h+this.gutter){var u=Math.ceil(t.rect[r]/h),c=Math.floor((this.shiftPacker[r]+this.gutter)/h);a=(c-u)*h;for(var d=0;c>d;d++){var l=o?0:d*h,f=o?d*h:0;this._addShiftTarget(l,f,a)}}else a=this.shiftPacker[r]+this.gutter-t.rect[r],this._addShiftTarget(0,0,a);var p=this._getItemsForLayout(this.items),g=this._getPackMethod();p.forEach(function(t){var e=t.rect;this._setRectSize(t.element,e),this.shiftPacker[g](e),this._addShiftTarget(e.x,e.y,a);var i=o?e.x+e.width:e.x,n=o?e.y:e.y+e.height;if(this._addShiftTarget(i,n,a),h)for(var s=Math.round(e[r]/h),u=1;s>u;u++){var c=o?i:e.x+h*u,d=o?e.y+h*u:n;this._addShiftTarget(c,d,a)}},this)},u._addShiftTarget=function(t,e,i){var n=this._getOption("horizontal")?e:t;if(!(0!==n&&n>i)){var o=t+","+e,s=-1!=this.shiftTargetKeys.indexOf(o);s||(this.shiftTargetKeys.push(o),this.shiftTargets.push({x:t,y:e}))}},u.shift=function(t,e,i){var n,o=1/0,s={x:e,y:i};this.shiftTargets.forEach(function(t){ 10 | var e=a(t,s);o>e&&(n=t,o=e)}),t.rect.x=n.x,t.rect.y=n.y};var c=120;u.itemDragMove=function(t,e,i){function n(){s.shift(o,e,i),o.positionDropPlaceholder(),s.layout()}var o=this.isEnabled&&this.getItem(t);if(o){e-=this.size.paddingLeft,i-=this.size.paddingTop;var s=this,r=new Date,a=this._itemDragTime&&r-this._itemDragTime 'packery/rect' 81 | .replace( /'.\//g, "'packery/js/" ); 82 | }) ) 83 | .pipe( replace( "define( 'packery/", "define( 'packery/js/" ) ) 84 | // add banner 85 | .pipe( addBanner( banner ) ) 86 | .pipe( rename('packery.pkgd.js') ) 87 | .pipe( gulp.dest('dist') ); 88 | }); 89 | 90 | 91 | // ----- uglify ----- // 92 | 93 | var uglify = require('gulp-uglify'); 94 | 95 | gulp.task( 'uglify', [ 'requirejs' ], function() { 96 | var banner = getBanner(); 97 | gulp.src('dist/packery.pkgd.js') 98 | .pipe( uglify() ) 99 | // add banner 100 | .pipe( addBanner( banner ) ) 101 | .pipe( rename('packery.pkgd.min.js') ) 102 | .pipe( gulp.dest('dist') ); 103 | }); 104 | 105 | // ----- version ----- // 106 | 107 | // set version in source files 108 | 109 | var minimist = require('minimist'); 110 | var gutil = require('gulp-util'); 111 | var chalk = require('chalk'); 112 | 113 | // use gulp version -t 1.2.3 114 | gulp.task( 'version', function() { 115 | var args = minimist( process.argv.slice(3) ); 116 | var version = args.t; 117 | if ( !version || !/^\d\.\d+\.\d+/.test( version ) ) { 118 | gutil.log( 'invalid version: ' + chalk.red( version ) ); 119 | return; 120 | } 121 | gutil.log( 'ticking version to ' + chalk.green( version ) ); 122 | 123 | gulp.src('js/packery.js') 124 | .pipe( replace( /Packery v\d\.\d+\.\d+/, 'Packery v' + version ) ) 125 | .pipe( gulp.dest('js') ); 126 | 127 | gulp.src( [ 'package.json' ] ) 128 | .pipe( replace( /"version": "\d\.\d+\.\d+"/, '"version": "' + version + '"' ) ) 129 | .pipe( gulp.dest('.') ); 130 | // replace CDN links in README 131 | var minorVersion = version.match( /^\d\.\d+/ )[0]; 132 | gulp.src('README.md') 133 | .pipe( replace( /packery@\d\.\d+/g, 'packery@' + minorVersion )) 134 | .pipe( gulp.dest('.') ); 135 | }); 136 | 137 | // ----- default ----- // 138 | 139 | gulp.task( 'default', [ 140 | 'hint', 141 | 'uglify' 142 | ]); 143 | -------------------------------------------------------------------------------- /js/item.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Packery Item Element 3 | **/ 4 | 5 | ( function( window, factory ) { 6 | // universal module definition 7 | /* jshint strict: false */ /* globals define, module, require */ 8 | if ( typeof define == 'function' && define.amd ) { 9 | // AMD 10 | define( [ 11 | 'outlayer/outlayer', 12 | './rect' 13 | ], 14 | factory ); 15 | } else if ( typeof module == 'object' && module.exports ) { 16 | // CommonJS 17 | module.exports = factory( 18 | require('outlayer'), 19 | require('./rect') 20 | ); 21 | } else { 22 | // browser global 23 | window.Packery.Item = factory( 24 | window.Outlayer, 25 | window.Packery.Rect 26 | ); 27 | } 28 | 29 | }( window, function factory( Outlayer, Rect ) { 30 | 'use strict'; 31 | 32 | // -------------------------- Item -------------------------- // 33 | 34 | var docElemStyle = document.documentElement.style; 35 | 36 | var transformProperty = typeof docElemStyle.transform == 'string' ? 37 | 'transform' : 'WebkitTransform'; 38 | 39 | // sub-class Item 40 | var Item = function PackeryItem() { 41 | Outlayer.Item.apply( this, arguments ); 42 | }; 43 | 44 | var proto = Item.prototype = Object.create( Outlayer.Item.prototype ); 45 | 46 | var __create = proto._create; 47 | proto._create = function() { 48 | // call default _create logic 49 | __create.call( this ); 50 | this.rect = new Rect(); 51 | }; 52 | 53 | var _moveTo = proto.moveTo; 54 | proto.moveTo = function( x, y ) { 55 | // don't shift 1px while dragging 56 | var dx = Math.abs( this.position.x - x ); 57 | var dy = Math.abs( this.position.y - y ); 58 | 59 | var canHackGoTo = this.layout.dragItemCount && !this.isPlacing && 60 | !this.isTransitioning && dx < 1 && dy < 1; 61 | if ( canHackGoTo ) { 62 | this.goTo( x, y ); 63 | return; 64 | } 65 | _moveTo.apply( this, arguments ); 66 | }; 67 | 68 | // -------------------------- placing -------------------------- // 69 | 70 | proto.enablePlacing = function() { 71 | this.removeTransitionStyles(); 72 | // remove transform property from transition 73 | if ( this.isTransitioning && transformProperty ) { 74 | this.element.style[ transformProperty ] = 'none'; 75 | } 76 | this.isTransitioning = false; 77 | this.getSize(); 78 | this.layout._setRectSize( this.element, this.rect ); 79 | this.isPlacing = true; 80 | }; 81 | 82 | proto.disablePlacing = function() { 83 | this.isPlacing = false; 84 | }; 85 | 86 | // ----- ----- // 87 | 88 | // remove element from DOM 89 | proto.removeElem = function() { 90 | var parent = this.element.parentNode; 91 | if ( parent ) { 92 | parent.removeChild( this.element ); 93 | } 94 | // add space back to packer 95 | this.layout.packer.addSpace( this.rect ); 96 | this.emitEvent( 'remove', [ this ] ); 97 | }; 98 | 99 | // ----- dropPlaceholder ----- // 100 | 101 | proto.showDropPlaceholder = function() { 102 | var dropPlaceholder = this.dropPlaceholder; 103 | if ( !dropPlaceholder ) { 104 | // create dropPlaceholder 105 | dropPlaceholder = this.dropPlaceholder = document.createElement('div'); 106 | dropPlaceholder.className = 'packery-drop-placeholder'; 107 | dropPlaceholder.style.position = 'absolute'; 108 | } 109 | 110 | dropPlaceholder.style.width = this.size.width + 'px'; 111 | dropPlaceholder.style.height = this.size.height + 'px'; 112 | this.positionDropPlaceholder(); 113 | this.layout.element.appendChild( dropPlaceholder ); 114 | }; 115 | 116 | proto.positionDropPlaceholder = function() { 117 | this.dropPlaceholder.style[ transformProperty ] = 'translate(' + 118 | this.rect.x + 'px, ' + this.rect.y + 'px)'; 119 | }; 120 | 121 | proto.hideDropPlaceholder = function() { 122 | // only remove once, #333 123 | var parent = this.dropPlaceholder.parentNode; 124 | if ( parent ) { 125 | parent.removeChild( this.dropPlaceholder ); 126 | } 127 | }; 128 | 129 | // ----- ----- // 130 | 131 | return Item; 132 | 133 | })); 134 | -------------------------------------------------------------------------------- /js/packer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Packer 3 | * bin-packing algorithm 4 | */ 5 | 6 | ( function( window, factory ) { 7 | // universal module definition 8 | /* jshint strict: false */ /* globals define, module, require */ 9 | if ( typeof define == 'function' && define.amd ) { 10 | // AMD 11 | define( [ './rect' ], factory ); 12 | } else if ( typeof module == 'object' && module.exports ) { 13 | // CommonJS 14 | module.exports = factory( 15 | require('./rect') 16 | ); 17 | } else { 18 | // browser global 19 | var Packery = window.Packery = window.Packery || {}; 20 | Packery.Packer = factory( Packery.Rect ); 21 | } 22 | 23 | }( window, function factory( Rect ) { 24 | 'use strict'; 25 | 26 | // -------------------------- Packer -------------------------- // 27 | 28 | /** 29 | * @param {Number} width 30 | * @param {Number} height 31 | * @param {String} sortDirection 32 | * topLeft for vertical, leftTop for horizontal 33 | */ 34 | function Packer( width, height, sortDirection ) { 35 | this.width = width || 0; 36 | this.height = height || 0; 37 | this.sortDirection = sortDirection || 'downwardLeftToRight'; 38 | 39 | this.reset(); 40 | } 41 | 42 | var proto = Packer.prototype; 43 | 44 | proto.reset = function() { 45 | this.spaces = []; 46 | 47 | var initialSpace = new Rect({ 48 | x: 0, 49 | y: 0, 50 | width: this.width, 51 | height: this.height 52 | }); 53 | 54 | this.spaces.push( initialSpace ); 55 | // set sorter 56 | this.sorter = sorters[ this.sortDirection ] || sorters.downwardLeftToRight; 57 | }; 58 | 59 | // change x and y of rect to fit with in Packer's available spaces 60 | proto.pack = function( rect ) { 61 | for ( var i=0; i < this.spaces.length; i++ ) { 62 | var space = this.spaces[i]; 63 | if ( space.canFit( rect ) ) { 64 | this.placeInSpace( rect, space ); 65 | break; 66 | } 67 | } 68 | }; 69 | 70 | proto.columnPack = function( rect ) { 71 | for ( var i=0; i < this.spaces.length; i++ ) { 72 | var space = this.spaces[i]; 73 | var canFitInSpaceColumn = space.x <= rect.x && 74 | space.x + space.width >= rect.x + rect.width && 75 | space.height >= rect.height - 0.01; // fudge number for rounding error 76 | if ( canFitInSpaceColumn ) { 77 | rect.y = space.y; 78 | this.placed( rect ); 79 | break; 80 | } 81 | } 82 | }; 83 | 84 | proto.rowPack = function( rect ) { 85 | for ( var i=0; i < this.spaces.length; i++ ) { 86 | var space = this.spaces[i]; 87 | var canFitInSpaceRow = space.y <= rect.y && 88 | space.y + space.height >= rect.y + rect.height && 89 | space.width >= rect.width - 0.01; // fudge number for rounding error 90 | if ( canFitInSpaceRow ) { 91 | rect.x = space.x; 92 | this.placed( rect ); 93 | break; 94 | } 95 | } 96 | }; 97 | 98 | proto.placeInSpace = function( rect, space ) { 99 | // place rect in space 100 | rect.x = space.x; 101 | rect.y = space.y; 102 | 103 | this.placed( rect ); 104 | }; 105 | 106 | // update spaces with placed rect 107 | proto.placed = function( rect ) { 108 | // update spaces 109 | var revisedSpaces = []; 110 | for ( var i=0; i < this.spaces.length; i++ ) { 111 | var space = this.spaces[i]; 112 | var newSpaces = space.getMaximalFreeRects( rect ); 113 | // add either the original space or the new spaces to the revised spaces 114 | if ( newSpaces ) { 115 | revisedSpaces.push.apply( revisedSpaces, newSpaces ); 116 | } else { 117 | revisedSpaces.push( space ); 118 | } 119 | } 120 | 121 | this.spaces = revisedSpaces; 122 | 123 | this.mergeSortSpaces(); 124 | }; 125 | 126 | proto.mergeSortSpaces = function() { 127 | // remove redundant spaces 128 | Packer.mergeRects( this.spaces ); 129 | this.spaces.sort( this.sorter ); 130 | }; 131 | 132 | // add a space back 133 | proto.addSpace = function( rect ) { 134 | this.spaces.push( rect ); 135 | this.mergeSortSpaces(); 136 | }; 137 | 138 | // -------------------------- utility functions -------------------------- // 139 | 140 | /** 141 | * Remove redundant rectangle from array of rectangles 142 | * @param {Array} rects: an array of Rects 143 | * @returns {Array} rects: an array of Rects 144 | **/ 145 | Packer.mergeRects = function( rects ) { 146 | var i = 0; 147 | var rect = rects[i]; 148 | 149 | rectLoop: 150 | while ( rect ) { 151 | var j = 0; 152 | var compareRect = rects[ i + j ]; 153 | 154 | while ( compareRect ) { 155 | if ( compareRect == rect ) { 156 | j++; // next 157 | } else if ( compareRect.contains( rect ) ) { 158 | // remove rect 159 | rects.splice( i, 1 ); 160 | rect = rects[i]; // set next rect 161 | continue rectLoop; // bail on compareLoop 162 | } else if ( rect.contains( compareRect ) ) { 163 | // remove compareRect 164 | rects.splice( i + j, 1 ); 165 | } else { 166 | j++; 167 | } 168 | compareRect = rects[ i + j ]; // set next compareRect 169 | } 170 | i++; 171 | rect = rects[i]; 172 | } 173 | 174 | return rects; 175 | }; 176 | 177 | 178 | // -------------------------- sorters -------------------------- // 179 | 180 | // functions for sorting rects in order 181 | var sorters = { 182 | // top down, then left to right 183 | downwardLeftToRight: function( a, b ) { 184 | return a.y - b.y || a.x - b.x; 185 | }, 186 | // left to right, then top down 187 | rightwardTopToBottom: function( a, b ) { 188 | return a.x - b.x || a.y - b.y; 189 | } 190 | }; 191 | 192 | 193 | // -------------------------- -------------------------- // 194 | 195 | return Packer; 196 | 197 | })); 198 | -------------------------------------------------------------------------------- /js/packery.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Packery v3.0.0 3 | * Gapless, draggable grid layouts 4 | * MIT License 5 | * https://packery.metafizzy.co 6 | * Copyright 2013-2025 Metafizzy 7 | */ 8 | 9 | ( function( window, factory ) { 10 | // universal module definition 11 | /* jshint strict: false */ /* globals define, module, require */ 12 | if ( typeof define == 'function' && define.amd ) { 13 | // AMD 14 | define( [ 15 | 'get-size/get-size', 16 | 'outlayer/outlayer', 17 | './rect', 18 | './packer', 19 | './item' 20 | ], 21 | factory ); 22 | } else if ( typeof module == 'object' && module.exports ) { 23 | // CommonJS 24 | module.exports = factory( 25 | require('get-size'), 26 | require('outlayer'), 27 | require('./rect'), 28 | require('./packer'), 29 | require('./item') 30 | ); 31 | } else { 32 | // browser global 33 | window.Packery = factory( 34 | window.getSize, 35 | window.Outlayer, 36 | window.Packery.Rect, 37 | window.Packery.Packer, 38 | window.Packery.Item 39 | ); 40 | } 41 | 42 | }( window, function factory( getSize, Outlayer, Rect, Packer, Item ) { 43 | 'use strict'; 44 | 45 | // ----- Rect ----- // 46 | 47 | // allow for pixel rounding errors IE8-IE11 & Firefox; #227 48 | Rect.prototype.canFit = function( rect ) { 49 | return this.width >= rect.width - 1 && this.height >= rect.height - 1; 50 | }; 51 | 52 | // -------------------------- Packery -------------------------- // 53 | 54 | // create an Outlayer layout class 55 | var Packery = Outlayer.create('packery'); 56 | Packery.Item = Item; 57 | 58 | var proto = Packery.prototype; 59 | 60 | proto._create = function() { 61 | // call super 62 | Outlayer.prototype._create.call( this ); 63 | 64 | // initial properties 65 | this.packer = new Packer(); 66 | // packer for drop targets 67 | this.shiftPacker = new Packer(); 68 | this.isEnabled = true; 69 | 70 | this.dragItemCount = 0; 71 | 72 | // create drag handlers 73 | var _this = this; 74 | this.handleDraggabilly = { 75 | dragStart: function() { 76 | _this.itemDragStart( this.element ); 77 | }, 78 | dragMove: function() { 79 | _this.itemDragMove( this.element, this.position.x, this.position.y ); 80 | }, 81 | dragEnd: function() { 82 | _this.itemDragEnd( this.element ); 83 | } 84 | }; 85 | 86 | this.handleUIDraggable = { 87 | start: function handleUIDraggableStart( event, ui ) { 88 | // HTML5 may trigger dragstart, dismiss HTML5 dragging 89 | if ( !ui ) { 90 | return; 91 | } 92 | _this.itemDragStart( event.currentTarget ); 93 | }, 94 | drag: function handleUIDraggableDrag( event, ui ) { 95 | if ( !ui ) { 96 | return; 97 | } 98 | _this.itemDragMove( event.currentTarget, ui.position.left, ui.position.top ); 99 | }, 100 | stop: function handleUIDraggableStop( event, ui ) { 101 | if ( !ui ) { 102 | return; 103 | } 104 | _this.itemDragEnd( event.currentTarget ); 105 | } 106 | }; 107 | 108 | }; 109 | 110 | 111 | // ----- init & layout ----- // 112 | 113 | /** 114 | * logic before any new layout 115 | */ 116 | proto._resetLayout = function() { 117 | this.getSize(); 118 | 119 | this._getMeasurements(); 120 | 121 | // reset packer 122 | var width, height, sortDirection; 123 | // packer settings, if horizontal or vertical 124 | if ( this._getOption('horizontal') ) { 125 | width = Infinity; 126 | height = this.size.innerHeight + this.gutter; 127 | sortDirection = 'rightwardTopToBottom'; 128 | } else { 129 | width = this.size.innerWidth + this.gutter; 130 | height = Infinity; 131 | sortDirection = 'downwardLeftToRight'; 132 | } 133 | 134 | this.packer.width = this.shiftPacker.width = width; 135 | this.packer.height = this.shiftPacker.height = height; 136 | this.packer.sortDirection = this.shiftPacker.sortDirection = sortDirection; 137 | 138 | this.packer.reset(); 139 | 140 | // layout 141 | this.maxY = 0; 142 | this.maxX = 0; 143 | }; 144 | 145 | /** 146 | * update columnWidth, rowHeight, & gutter 147 | * @private 148 | */ 149 | proto._getMeasurements = function() { 150 | this._getMeasurement( 'columnWidth', 'width' ); 151 | this._getMeasurement( 'rowHeight', 'height' ); 152 | this._getMeasurement( 'gutter', 'width' ); 153 | }; 154 | 155 | proto._getItemLayoutPosition = function( item ) { 156 | this._setRectSize( item.element, item.rect ); 157 | if ( this.isShifting || this.dragItemCount > 0 ) { 158 | var packMethod = this._getPackMethod(); 159 | this.packer[ packMethod ]( item.rect ); 160 | } else { 161 | this.packer.pack( item.rect ); 162 | } 163 | 164 | this._setMaxXY( item.rect ); 165 | return item.rect; 166 | }; 167 | 168 | proto.shiftLayout = function() { 169 | this.isShifting = true; 170 | this.layout(); 171 | delete this.isShifting; 172 | }; 173 | 174 | proto._getPackMethod = function() { 175 | return this._getOption('horizontal') ? 'rowPack' : 'columnPack'; 176 | }; 177 | 178 | 179 | /** 180 | * set max X and Y value, for size of container 181 | * @param {Packery.Rect} rect 182 | * @private 183 | */ 184 | proto._setMaxXY = function( rect ) { 185 | this.maxX = Math.max( rect.x + rect.width, this.maxX ); 186 | this.maxY = Math.max( rect.y + rect.height, this.maxY ); 187 | }; 188 | 189 | /** 190 | * set the width and height of a rect, applying columnWidth and rowHeight 191 | * @param {Element} elem 192 | * @param {Packery.Rect} rect 193 | */ 194 | proto._setRectSize = function( elem, rect ) { 195 | var size = getSize( elem ); 196 | var w = size.outerWidth; 197 | var h = size.outerHeight; 198 | // size for columnWidth and rowHeight, if available 199 | // only check if size is non-zero, #177 200 | if ( w || h ) { 201 | w = this._applyGridGutter( w, this.columnWidth ); 202 | h = this._applyGridGutter( h, this.rowHeight ); 203 | } 204 | // rect must fit in packer 205 | rect.width = Math.min( w, this.packer.width ); 206 | rect.height = Math.min( h, this.packer.height ); 207 | }; 208 | 209 | /** 210 | * fits item to columnWidth/rowHeight and adds gutter 211 | * @param {Number} measurement - item width or height 212 | * @param {Number} gridSize - columnWidth or rowHeight 213 | * @returns measurement 214 | */ 215 | proto._applyGridGutter = function( measurement, gridSize ) { 216 | // just add gutter if no gridSize 217 | if ( !gridSize ) { 218 | return measurement + this.gutter; 219 | } 220 | gridSize += this.gutter; 221 | // fit item to columnWidth/rowHeight 222 | var remainder = measurement % gridSize; 223 | var mathMethod = remainder && remainder < 1 ? 'round' : 'ceil'; 224 | measurement = Math[ mathMethod ]( measurement / gridSize ) * gridSize; 225 | return measurement; 226 | }; 227 | 228 | proto._getContainerSize = function() { 229 | if ( this._getOption('horizontal') ) { 230 | return { 231 | width: this.maxX - this.gutter 232 | }; 233 | } else { 234 | return { 235 | height: this.maxY - this.gutter 236 | }; 237 | } 238 | }; 239 | 240 | 241 | // -------------------------- stamp -------------------------- // 242 | 243 | /** 244 | * makes space for element 245 | * @param {Element} elem 246 | */ 247 | proto._manageStamp = function( elem ) { 248 | 249 | var item = this.getItem( elem ); 250 | var rect; 251 | if ( item && item.isPlacing ) { 252 | rect = item.rect; 253 | } else { 254 | var offset = this._getElementOffset( elem ); 255 | rect = new Rect({ 256 | x: this._getOption('originLeft') ? offset.left : offset.right, 257 | y: this._getOption('originTop') ? offset.top : offset.bottom 258 | }); 259 | } 260 | 261 | this._setRectSize( elem, rect ); 262 | // save its space in the packer 263 | this.packer.placed( rect ); 264 | this._setMaxXY( rect ); 265 | }; 266 | 267 | // -------------------------- methods -------------------------- // 268 | 269 | function verticalSorter( a, b ) { 270 | return a.position.y - b.position.y || a.position.x - b.position.x; 271 | } 272 | 273 | function horizontalSorter( a, b ) { 274 | return a.position.x - b.position.x || a.position.y - b.position.y; 275 | } 276 | 277 | proto.sortItemsByPosition = function() { 278 | var sorter = this._getOption('horizontal') ? horizontalSorter : verticalSorter; 279 | this.items.sort( sorter ); 280 | }; 281 | 282 | /** 283 | * Fit item element in its current position 284 | * Packery will position elements around it 285 | * useful for expanding elements 286 | * 287 | * @param {Element} elem 288 | * @param {Number} x - horizontal destination position, optional 289 | * @param {Number} y - vertical destination position, optional 290 | */ 291 | proto.fit = function( elem, x, y ) { 292 | var item = this.getItem( elem ); 293 | if ( !item ) { 294 | return; 295 | } 296 | 297 | // stamp item to get it out of layout 298 | this.stamp( item.element ); 299 | // set placing flag 300 | item.enablePlacing(); 301 | this.updateShiftTargets( item ); 302 | // fall back to current position for fitting 303 | x = x === undefined ? item.rect.x: x; 304 | y = y === undefined ? item.rect.y: y; 305 | // position it best at its destination 306 | this.shift( item, x, y ); 307 | this._bindFitEvents( item ); 308 | item.moveTo( item.rect.x, item.rect.y ); 309 | // layout everything else 310 | this.shiftLayout(); 311 | // return back to regularly scheduled programming 312 | this.unstamp( item.element ); 313 | this.sortItemsByPosition(); 314 | item.disablePlacing(); 315 | }; 316 | 317 | /** 318 | * emit event when item is fit and other items are laid out 319 | * @param {Packery.Item} item 320 | * @private 321 | */ 322 | proto._bindFitEvents = function( item ) { 323 | var _this = this; 324 | var ticks = 0; 325 | function onLayout() { 326 | ticks++; 327 | if ( ticks != 2 ) { 328 | return; 329 | } 330 | _this.dispatchEvent( 'fitComplete', null, [ item ] ); 331 | } 332 | // when item is laid out 333 | item.once( 'layout', onLayout ); 334 | // when all items are laid out 335 | this.once( 'layoutComplete', onLayout ); 336 | }; 337 | 338 | // -------------------------- resize -------------------------- // 339 | 340 | // debounced, layout on resize 341 | proto.resize = function() { 342 | // don't trigger if size did not change 343 | // or if resize was unbound. See #285, outlayer#9 344 | if ( !this.isResizeBound || !this.needsResizeLayout() ) { 345 | return; 346 | } 347 | 348 | if ( this.options.shiftPercentResize ) { 349 | this.resizeShiftPercentLayout(); 350 | } else { 351 | this.layout(); 352 | } 353 | }; 354 | 355 | /** 356 | * check if layout is needed post layout 357 | * @returns Boolean 358 | */ 359 | proto.needsResizeLayout = function() { 360 | var size = getSize( this.element ); 361 | var innerSize = this._getOption('horizontal') ? 'innerHeight' : 'innerWidth'; 362 | return size[ innerSize ] != this.size[ innerSize ]; 363 | }; 364 | 365 | proto.resizeShiftPercentLayout = function() { 366 | var items = this._getItemsForLayout( this.items ); 367 | 368 | var isHorizontal = this._getOption('horizontal'); 369 | var coord = isHorizontal ? 'y' : 'x'; 370 | var measure = isHorizontal ? 'height' : 'width'; 371 | var segmentName = isHorizontal ? 'rowHeight' : 'columnWidth'; 372 | var innerSize = isHorizontal ? 'innerHeight' : 'innerWidth'; 373 | 374 | // proportional re-align items 375 | var previousSegment = this[ segmentName ]; 376 | previousSegment = previousSegment && previousSegment + this.gutter; 377 | 378 | if ( previousSegment ) { 379 | this._getMeasurements(); 380 | var currentSegment = this[ segmentName ] + this.gutter; 381 | items.forEach( function( item ) { 382 | var seg = Math.round( item.rect[ coord ] / previousSegment ); 383 | item.rect[ coord ] = seg * currentSegment; 384 | }); 385 | } else { 386 | var currentSize = getSize( this.element )[ innerSize ] + this.gutter; 387 | var previousSize = this.packer[ measure ]; 388 | items.forEach( function( item ) { 389 | item.rect[ coord ] = ( item.rect[ coord ] / previousSize ) * currentSize; 390 | }); 391 | } 392 | 393 | this.shiftLayout(); 394 | }; 395 | 396 | // -------------------------- drag -------------------------- // 397 | 398 | /** 399 | * handle an item drag start event 400 | * @param {Element} elem 401 | */ 402 | proto.itemDragStart = function( elem ) { 403 | if ( !this.isEnabled ) { 404 | return; 405 | } 406 | this.stamp( elem ); 407 | // this.ignore( elem ); 408 | var item = this.getItem( elem ); 409 | if ( !item ) { 410 | return; 411 | } 412 | 413 | item.enablePlacing(); 414 | item.showDropPlaceholder(); 415 | this.dragItemCount++; 416 | this.updateShiftTargets( item ); 417 | }; 418 | 419 | proto.updateShiftTargets = function( dropItem ) { 420 | this.shiftPacker.reset(); 421 | 422 | // pack stamps 423 | this._getBoundingRect(); 424 | var isOriginLeft = this._getOption('originLeft'); 425 | var isOriginTop = this._getOption('originTop'); 426 | this.stamps.forEach( function( stamp ) { 427 | // ignore dragged item 428 | var item = this.getItem( stamp ); 429 | if ( item && item.isPlacing ) { 430 | return; 431 | } 432 | var offset = this._getElementOffset( stamp ); 433 | var rect = new Rect({ 434 | x: isOriginLeft ? offset.left : offset.right, 435 | y: isOriginTop ? offset.top : offset.bottom 436 | }); 437 | this._setRectSize( stamp, rect ); 438 | // save its space in the packer 439 | this.shiftPacker.placed( rect ); 440 | }, this ); 441 | 442 | // reset shiftTargets 443 | var isHorizontal = this._getOption('horizontal'); 444 | var segmentName = isHorizontal ? 'rowHeight' : 'columnWidth'; 445 | var measure = isHorizontal ? 'height' : 'width'; 446 | 447 | this.shiftTargetKeys = []; 448 | this.shiftTargets = []; 449 | var boundsSize; 450 | var segment = this[ segmentName ]; 451 | segment = segment && segment + this.gutter; 452 | 453 | if ( segment ) { 454 | var segmentSpan = Math.ceil( dropItem.rect[ measure ] / segment ); 455 | var segs = Math.floor( ( this.shiftPacker[ measure ] + this.gutter ) / segment ); 456 | boundsSize = ( segs - segmentSpan ) * segment; 457 | // add targets on top 458 | for ( var i=0; i < segs; i++ ) { 459 | var initialX = isHorizontal ? 0 : i * segment; 460 | var initialY = isHorizontal ? i * segment : 0; 461 | this._addShiftTarget( initialX, initialY, boundsSize ); 462 | } 463 | } else { 464 | boundsSize = ( this.shiftPacker[ measure ] + this.gutter ) - dropItem.rect[ measure ]; 465 | this._addShiftTarget( 0, 0, boundsSize ); 466 | } 467 | 468 | // pack each item to measure where shiftTargets are 469 | var items = this._getItemsForLayout( this.items ); 470 | var packMethod = this._getPackMethod(); 471 | items.forEach( function( item ) { 472 | var rect = item.rect; 473 | this._setRectSize( item.element, rect ); 474 | this.shiftPacker[ packMethod ]( rect ); 475 | 476 | // add top left corner 477 | this._addShiftTarget( rect.x, rect.y, boundsSize ); 478 | // add bottom left / top right corner 479 | var cornerX = isHorizontal ? rect.x + rect.width : rect.x; 480 | var cornerY = isHorizontal ? rect.y : rect.y + rect.height; 481 | this._addShiftTarget( cornerX, cornerY, boundsSize ); 482 | 483 | if ( segment ) { 484 | // add targets for each column on bottom / row on right 485 | var segSpan = Math.round( rect[ measure ] / segment ); 486 | for ( var i=1; i < segSpan; i++ ) { 487 | var segX = isHorizontal ? cornerX : rect.x + segment * i; 488 | var segY = isHorizontal ? rect.y + segment * i : cornerY; 489 | this._addShiftTarget( segX, segY, boundsSize ); 490 | } 491 | } 492 | }, this ); 493 | 494 | }; 495 | 496 | proto._addShiftTarget = function( x, y, boundsSize ) { 497 | var checkCoord = this._getOption('horizontal') ? y : x; 498 | if ( checkCoord !== 0 && checkCoord > boundsSize ) { 499 | return; 500 | } 501 | // create string for a key, easier to keep track of what targets 502 | var key = x + ',' + y; 503 | var hasKey = this.shiftTargetKeys.indexOf( key ) != -1; 504 | if ( hasKey ) { 505 | return; 506 | } 507 | this.shiftTargetKeys.push( key ); 508 | this.shiftTargets.push({ x: x, y: y }); 509 | }; 510 | 511 | // -------------------------- drop -------------------------- // 512 | 513 | proto.shift = function( item, x, y ) { 514 | var shiftPosition; 515 | var minDistance = Infinity; 516 | var position = { x: x, y: y }; 517 | this.shiftTargets.forEach( function( target ) { 518 | var distance = getDistance( target, position ); 519 | if ( distance < minDistance ) { 520 | shiftPosition = target; 521 | minDistance = distance; 522 | } 523 | }); 524 | item.rect.x = shiftPosition.x; 525 | item.rect.y = shiftPosition.y; 526 | }; 527 | 528 | function getDistance( a, b ) { 529 | var dx = b.x - a.x; 530 | var dy = b.y - a.y; 531 | return Math.sqrt( dx * dx + dy * dy ); 532 | } 533 | 534 | // -------------------------- drag move -------------------------- // 535 | 536 | var DRAG_THROTTLE_TIME = 120; 537 | 538 | /** 539 | * handle an item drag move event 540 | * @param {Element} elem 541 | * @param {Number} x - horizontal change in position 542 | * @param {Number} y - vertical change in position 543 | */ 544 | proto.itemDragMove = function( elem, x, y ) { 545 | var item = this.isEnabled && this.getItem( elem ); 546 | if ( !item ) { 547 | return; 548 | } 549 | 550 | x -= this.size.paddingLeft; 551 | y -= this.size.paddingTop; 552 | 553 | var _this = this; 554 | function onDrag() { 555 | _this.shift( item, x, y ); 556 | item.positionDropPlaceholder(); 557 | _this.layout(); 558 | } 559 | 560 | // throttle 561 | var now = new Date(); 562 | var isThrottled = this._itemDragTime && now - this._itemDragTime < DRAG_THROTTLE_TIME; 563 | if ( isThrottled ) { 564 | clearTimeout( this.dragTimeout ); 565 | this.dragTimeout = setTimeout( onDrag, DRAG_THROTTLE_TIME ); 566 | } else { 567 | onDrag(); 568 | this._itemDragTime = now; 569 | } 570 | }; 571 | 572 | // -------------------------- drag end -------------------------- // 573 | 574 | /** 575 | * handle an item drag end event 576 | * @param {Element} elem 577 | */ 578 | proto.itemDragEnd = function( elem ) { 579 | var item = this.isEnabled && this.getItem( elem ); 580 | if ( !item ) { 581 | return; 582 | } 583 | 584 | clearTimeout( this.dragTimeout ); 585 | item.element.classList.add('is-positioning-post-drag'); 586 | 587 | var completeCount = 0; 588 | var _this = this; 589 | function onDragEndLayoutComplete() { 590 | completeCount++; 591 | if ( completeCount != 2 ) { 592 | return; 593 | } 594 | // reset drag item 595 | item.element.classList.remove('is-positioning-post-drag'); 596 | item.hideDropPlaceholder(); 597 | _this.dispatchEvent( 'dragItemPositioned', null, [ item ] ); 598 | } 599 | 600 | item.once( 'layout', onDragEndLayoutComplete ); 601 | this.once( 'layoutComplete', onDragEndLayoutComplete ); 602 | item.moveTo( item.rect.x, item.rect.y ); 603 | this.layout(); 604 | this.dragItemCount = Math.max( 0, this.dragItemCount - 1 ); 605 | this.sortItemsByPosition(); 606 | item.disablePlacing(); 607 | this.unstamp( item.element ); 608 | }; 609 | 610 | /** 611 | * binds Draggabilly events 612 | * @param {Draggabilly} draggie 613 | */ 614 | proto.bindDraggabillyEvents = function( draggie ) { 615 | this._bindDraggabillyEvents( draggie, 'on' ); 616 | }; 617 | 618 | proto.unbindDraggabillyEvents = function( draggie ) { 619 | this._bindDraggabillyEvents( draggie, 'off' ); 620 | }; 621 | 622 | proto._bindDraggabillyEvents = function( draggie, method ) { 623 | var handlers = this.handleDraggabilly; 624 | draggie[ method ]( 'dragStart', handlers.dragStart ); 625 | draggie[ method ]( 'dragMove', handlers.dragMove ); 626 | draggie[ method ]( 'dragEnd', handlers.dragEnd ); 627 | }; 628 | 629 | /** 630 | * binds jQuery UI Draggable events 631 | * @param {jQuery} $elems 632 | */ 633 | proto.bindUIDraggableEvents = function( $elems ) { 634 | this._bindUIDraggableEvents( $elems, 'on' ); 635 | }; 636 | 637 | proto.unbindUIDraggableEvents = function( $elems ) { 638 | this._bindUIDraggableEvents( $elems, 'off' ); 639 | }; 640 | 641 | proto._bindUIDraggableEvents = function( $elems, method ) { 642 | var handlers = this.handleUIDraggable; 643 | $elems 644 | [ method ]( 'dragstart', handlers.start ) 645 | [ method ]( 'drag', handlers.drag ) 646 | [ method ]( 'dragstop', handlers.stop ); 647 | }; 648 | 649 | // ----- destroy ----- // 650 | 651 | var _destroy = proto.destroy; 652 | proto.destroy = function() { 653 | _destroy.apply( this, arguments ); 654 | // disable flag; prevent drag events from triggering. #72 655 | this.isEnabled = false; 656 | }; 657 | 658 | // ----- ----- // 659 | 660 | Packery.Rect = Rect; 661 | Packery.Packer = Packer; 662 | 663 | return Packery; 664 | 665 | })); 666 | -------------------------------------------------------------------------------- /js/rect.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Rect 3 | * low-level utility class for basic geometry 4 | */ 5 | 6 | ( function( window, factory ) { 7 | // universal module definition 8 | /* jshint strict: false */ /* globals define, module */ 9 | if ( typeof define == 'function' && define.amd ) { 10 | // AMD 11 | define( factory ); 12 | } else if ( typeof module == 'object' && module.exports ) { 13 | // CommonJS 14 | module.exports = factory(); 15 | } else { 16 | // browser global 17 | window.Packery = window.Packery || {}; 18 | window.Packery.Rect = factory(); 19 | } 20 | 21 | }( window, function factory() { 22 | 'use strict'; 23 | 24 | // -------------------------- Rect -------------------------- // 25 | 26 | function Rect( props ) { 27 | // extend properties from defaults 28 | for ( var prop in Rect.defaults ) { 29 | this[ prop ] = Rect.defaults[ prop ]; 30 | } 31 | 32 | for ( prop in props ) { 33 | this[ prop ] = props[ prop ]; 34 | } 35 | 36 | } 37 | 38 | Rect.defaults = { 39 | x: 0, 40 | y: 0, 41 | width: 0, 42 | height: 0 43 | }; 44 | 45 | var proto = Rect.prototype; 46 | 47 | /** 48 | * Determines whether or not this rectangle wholly encloses another rectangle or point. 49 | * @param {Rect} rect 50 | * @returns {Boolean} 51 | **/ 52 | proto.contains = function( rect ) { 53 | // points don't have width or height 54 | var otherWidth = rect.width || 0; 55 | var otherHeight = rect.height || 0; 56 | return this.x <= rect.x && 57 | this.y <= rect.y && 58 | this.x + this.width >= rect.x + otherWidth && 59 | this.y + this.height >= rect.y + otherHeight; 60 | }; 61 | 62 | /** 63 | * Determines whether or not the rectangle intersects with another. 64 | * @param {Rect} rect 65 | * @returns {Boolean} 66 | **/ 67 | proto.overlaps = function( rect ) { 68 | var thisRight = this.x + this.width; 69 | var thisBottom = this.y + this.height; 70 | var rectRight = rect.x + rect.width; 71 | var rectBottom = rect.y + rect.height; 72 | 73 | // http://stackoverflow.com/a/306332 74 | return this.x < rectRight && 75 | thisRight > rect.x && 76 | this.y < rectBottom && 77 | thisBottom > rect.y; 78 | }; 79 | 80 | /** 81 | * @param {Rect} rect - the overlapping rect 82 | * @returns {Array} freeRects - rects representing the area around the rect 83 | **/ 84 | proto.getMaximalFreeRects = function( rect ) { 85 | 86 | // if no intersection, return false 87 | if ( !this.overlaps( rect ) ) { 88 | return false; 89 | } 90 | 91 | var freeRects = []; 92 | var freeRect; 93 | 94 | var thisRight = this.x + this.width; 95 | var thisBottom = this.y + this.height; 96 | var rectRight = rect.x + rect.width; 97 | var rectBottom = rect.y + rect.height; 98 | 99 | // top 100 | if ( this.y < rect.y ) { 101 | freeRect = new Rect({ 102 | x: this.x, 103 | y: this.y, 104 | width: this.width, 105 | height: rect.y - this.y 106 | }); 107 | freeRects.push( freeRect ); 108 | } 109 | 110 | // right 111 | if ( thisRight > rectRight ) { 112 | freeRect = new Rect({ 113 | x: rectRight, 114 | y: this.y, 115 | width: thisRight - rectRight, 116 | height: this.height 117 | }); 118 | freeRects.push( freeRect ); 119 | } 120 | 121 | // bottom 122 | if ( thisBottom > rectBottom ) { 123 | freeRect = new Rect({ 124 | x: this.x, 125 | y: rectBottom, 126 | width: this.width, 127 | height: thisBottom - rectBottom 128 | }); 129 | freeRects.push( freeRect ); 130 | } 131 | 132 | // left 133 | if ( this.x < rect.x ) { 134 | freeRect = new Rect({ 135 | x: this.x, 136 | y: this.y, 137 | width: rect.x - this.x, 138 | height: this.height 139 | }); 140 | freeRects.push( freeRect ); 141 | } 142 | 143 | return freeRects; 144 | }; 145 | 146 | proto.canFit = function( rect ) { 147 | return this.width >= rect.width && this.height >= rect.height; 148 | }; 149 | 150 | return Rect; 151 | 152 | })); 153 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "packery", 3 | "version": "3.0.0", 4 | "description": "Gapless, draggable grid layouts", 5 | "main": "js/packery.js", 6 | "dependencies": { 7 | "get-size": "^2.0.2", 8 | "outlayer": "^2.0.0" 9 | }, 10 | "devDependencies": { 11 | "chalk": "^1.1.1", 12 | "draggabilly": "^2.1.0", 13 | "gulp": "^3.9.0", 14 | "gulp-jshint": "^2.0.0", 15 | "gulp-json-lint": "^0.1.0", 16 | "gulp-rename": "^1.2.2", 17 | "gulp-replace": "^0.5.4", 18 | "gulp-requirejs-optimize": "github:metafizzy/gulp-requirejs-optimize", 19 | "gulp-uglify": "^1.5.1", 20 | "gulp-util": "^3.0.7", 21 | "jquery": ">=2 <4", 22 | "jquery-bridget": "~2.0.0", 23 | "jshint": "^2.9.1", 24 | "minimist": "^1.2.0", 25 | "qunitjs": "^1.15" 26 | }, 27 | "directories": { 28 | "test": "test" 29 | }, 30 | "scripts": { 31 | "test": "echo \"Error: no test specified\" && exit 1" 32 | }, 33 | "repository": { 34 | "type": "git", 35 | "url": "git://github.com/metafizzy/packery.git" 36 | }, 37 | "keywords": [ 38 | "DOM", 39 | "browser", 40 | "layout", 41 | "bin", 42 | "binpacking", 43 | "packing", 44 | "masonry", 45 | "gapless", 46 | "draggable" 47 | ], 48 | "author": "Metafizzy", 49 | "bugs": { 50 | "url": "https://github.com/metafizzy/packery/issues" 51 | }, 52 | "homepage": "https://packery.metafizzy.co", 53 | "license": "MIT" 54 | } 55 | -------------------------------------------------------------------------------- /sandbox/add-remove.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | add/remove 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |

add/remove

15 | 16 |

17 | 18 | 19 |

20 | 21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /sandbox/basics.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Packery basic examples 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |

Packery basic examples

15 | 16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | 42 |
43 |
44 | 45 |
46 |
47 | 48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
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 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 | 100 |
101 |
102 |
103 |
104 | 105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 187 | 188 | 189 | 190 | -------------------------------------------------------------------------------- /sandbox/browserify/browserify.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Browserify 7 | 8 | 9 | 10 | 11 | 12 | 13 |

Browserify

14 | 15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /sandbox/browserify/jquery-main.js: -------------------------------------------------------------------------------- 1 | // ----- jQuery ----- // 2 | 3 | var Packery = require('../../js/packery'); 4 | var Draggabilly = require('draggabilly'); 5 | var $ = require('jquery'); 6 | var jQBridget = require('jquery-bridget'); 7 | 8 | $.bridget( 'packery', Packery ); 9 | 10 | var $container = $('#basic').packery({ 11 | columnWidth: 50, 12 | rowHeight: 50 13 | }); 14 | 15 | var pckry = $container.data('packery'); 16 | 17 | $.each( pckry.items, function( i, item ) { 18 | var draggie = new Draggabilly( item.element ); 19 | $container.packery( 'bindDraggabillyEvents', draggie ); 20 | }); 21 | 22 | $container.packery( 'on', 'dragItemPositioned', function( pckry, item ) { 23 | console.log( 'drag item positioned', item.position.x, item.position.y ); 24 | }); 25 | -------------------------------------------------------------------------------- /sandbox/browserify/main.js: -------------------------------------------------------------------------------- 1 | // ----- vanilla JS ----- // 2 | 3 | var Packery = require('../../js/packery'); 4 | var Draggabilly = require('draggabilly'); 5 | 6 | var pckry = new Packery( '#basic', { 7 | columnWidth: 50, 8 | rowHeight: 50 9 | }); 10 | 11 | var draggies = []; 12 | var item, draggie; 13 | 14 | for ( var i=0, len = pckry.items.length; i < len; i++ ) { 15 | item = pckry.items[i].element; 16 | draggie = new Draggabilly( item ); 17 | pckry.bindDraggabillyEvents( draggie ); 18 | draggies.push( draggie ); 19 | } 20 | 21 | pckry.on( 'dragItemPositioned', function( pckry, item ) { 22 | console.log( 'drag item positioned', item.position.x, item.position.y ); 23 | }); 24 | -------------------------------------------------------------------------------- /sandbox/css/basics.css: -------------------------------------------------------------------------------- 1 | 2 | #ex5 .item { 3 | margin-left: 1%; 4 | margin-right: 1%; 5 | width: 7.9%; 6 | } 7 | 8 | #ex5 .item.w2 { width: 17.9%; } 9 | #ex5 .item.w4 { width: 37.9%; } 10 | 11 | @media ( max-width: 800px ) { 12 | body { background: red ;} 13 | 14 | #ex5 .item { width: 17.9%; } 15 | #ex5 .item.w2 { width: 37.9%; } 16 | #ex5 .item.w4 { width: 57.9%; } 17 | 18 | } 19 | 20 | .bogey { 21 | background: red; 22 | position: absolute; 23 | } 24 | 25 | #ex6 .bogey1 { 26 | width: 30%; 27 | height: 140px; 28 | left: 20%; 29 | top: 20px; 30 | } 31 | 32 | #ex6 .bogey2 { 33 | width: 140px; 34 | height: 180px; 35 | right: 20px; 36 | top: 140px; 37 | } 38 | 39 | #fit-demo { 40 | width: 200px; 41 | } 42 | 43 | /* 44 | #fit-demo .item { 45 | width: 40px; 46 | height: 40px; 47 | } 48 | */ 49 | 50 | #fit-demo .item.gigante { 51 | width: 150px; 52 | height: 100px; 53 | } 54 | -------------------------------------------------------------------------------- /sandbox/css/examples.css: -------------------------------------------------------------------------------- 1 | * { box-sizing: border-box; } 2 | 3 | body { 4 | font-family: sans-serif; 5 | } 6 | 7 | .container { 8 | width: 500px; 9 | margin-bottom: 20px; 10 | background: #EEE; 11 | } 12 | 13 | .item { 14 | float: left; 15 | width: 50px; 16 | height: 50px; 17 | border: 1px solid; 18 | background: #ACE; 19 | } 20 | 21 | .item.w2 { width: 100px; } 22 | .item.h2 { height: 100px; } 23 | .item.w4 { width: 200px; } 24 | .item.h4 { height: 200px; } 25 | 26 | .fluid { 27 | width: 50%; 28 | } 29 | 30 | .has-padding { 31 | padding: 10px 20px 30px 40px; 32 | } 33 | 34 | /* dragging */ 35 | .is-dragging, 36 | .is-positioning-post-drag, 37 | .ui-draggable-dragging { 38 | z-index: 10; 39 | background: orange; 40 | } 41 | -------------------------------------------------------------------------------- /sandbox/draggabilly.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Draggabilly 7 | 8 | 9 | 22 | 23 | 24 | 25 | 26 |

Draggabilly

27 | 28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 | 54 | 55 |
56 | 57 |
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 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 185 | 186 | 187 | 188 | -------------------------------------------------------------------------------- /sandbox/examples.js: -------------------------------------------------------------------------------- 1 | function appendRandomSizedItems( container ) { 2 | var frag = document.createDocumentFragment(); 3 | for ( var i=0; i < 35; i++ ) { 4 | var item = document.createElement('div'); 5 | item.className = 'item'; 6 | var w = Math.floor( Math.random() * Math.random() * 180 ) + 20; 7 | var h = Math.floor( Math.random() * Math.random() * 180 ) + 20; 8 | item.style.width = w + 'px'; 9 | item.style.height = h + 'px'; 10 | frag.appendChild( item ); 11 | } 12 | 13 | container.appendChild( frag ); 14 | } 15 | -------------------------------------------------------------------------------- /sandbox/fit.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | fit 7 | 8 | 9 | 34 | 35 | 36 | 37 | 38 |

fit

39 | 40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | 52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 118 | 119 | 120 | 121 | -------------------------------------------------------------------------------- /sandbox/fluid.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | fluid 8 | 9 | 43 | 44 | 45 | 46 | 47 |

fluid

48 | 49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 | 61 |

Draggable

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 | 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /sandbox/horizontal.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Packery horizontal examples 7 | 8 | 9 | 10 | 39 | 40 | 41 | 42 | 43 | 44 |

horizontal examples

45 | 46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
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 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 | 104 |
105 |
106 |
107 |
108 | 109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 189 | 190 | 191 | 192 | -------------------------------------------------------------------------------- /sandbox/jquery-ui-draggable.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | jQuery UI Draggable 7 | 8 | 9 | 10 | 11 | 12 | 13 |

jQuery UI Draggable

14 | 15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | 41 | 42 |
43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /sandbox/jquery.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Packery with jQuery 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |

Packery with jQuery

15 | 16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | 42 |
43 |
44 | 45 |
46 |
47 | 48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
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 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 | 100 |
101 |
102 |
103 |
104 | 105 | 106 |

107 | 108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 | 120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 226 | 227 | 228 | 229 | -------------------------------------------------------------------------------- /sandbox/merge-rects.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | merge rects 8 | 9 | 10 | 11 | 12 |

merge rects

13 | 14 |

15 | 16 | 17 |

18 | 19 | 20 | 21 | 22 | 23 | 24 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /sandbox/origin.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Packery basic examples 7 | 8 | 9 | 10 | 11 | 51 | 52 | 53 | 54 | 55 |

Packery basic examples

56 | 57 |
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 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 | 101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 166 | 167 | 168 | 169 | -------------------------------------------------------------------------------- /sandbox/requirejs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | require js 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |

require js

16 | 17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /sandbox/requirejs/main.js: -------------------------------------------------------------------------------- 1 | /*global requirejs: false*/ 2 | 3 | /* 4 | // bower components 5 | requirejs.config({ 6 | baseUrl: '../../bower_components' 7 | }); 8 | 9 | requirejs( [ '../../js/packery' ], function( Packery ) { 10 | 11 | new Packery( document.querySelector('#basic') ); 12 | 13 | }); 14 | // */ 15 | 16 | /* 17 | // packery.pkgd.js 18 | requirejs( [ 19 | './../dist/packery.pkgd.js', 20 | 'require' 21 | ], function( pkgd, require ) { 22 | require( ['packery/js/packery'], function ( Packery ) { 23 | new Packery('#basic'); 24 | }); 25 | }); 26 | // */ 27 | 28 | requirejs( [ 29 | '../../dist/packery.pkgd.js' 30 | ], function( Packery ) { 31 | new Packery('#basic'); 32 | }); 33 | -------------------------------------------------------------------------------- /sandbox/save-positions.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | save positions 8 | 9 | 57 | 58 | 59 | 60 | 61 |

save positions

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 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 161 | 162 | 163 | 164 | -------------------------------------------------------------------------------- /sandbox/shift-drag-uneven.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | shift drag uneven 8 | 9 | 37 | 38 | 39 | 40 | 41 |

shift drag uneven

42 | 43 |

44 | 45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /sandbox/shift-drag.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | shift drag 8 | 9 | 35 | 36 | 37 | 38 | 39 |

shift drag

40 | 41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /sandbox/single.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | single 8 | 9 | 29 | 30 | 31 | 32 | 33 |

single

34 | 35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /test/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "browser": true, 3 | "undef": true, 4 | "unused": true, 5 | "globals": { 6 | "appendRandomSizedItems": true, 7 | "gimmeAnItemElement": true, 8 | "Packery": false, 9 | "QUnit": false 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test/helpers.js: -------------------------------------------------------------------------------- 1 | ( function( window ) { 2 | 3 | 'use strict'; 4 | 5 | window.appendRandomSizedItems = function( container ) { 6 | var frag = document.createDocumentFragment(); 7 | var item; 8 | for ( var i=0; i < 9; i++ ) { 9 | item = document.createElement('div'); 10 | item.className = 'item'; 11 | var w = Math.floor( Math.random() * Math.random() * 70 ) + 10; 12 | var h = Math.floor( Math.random() * Math.random() * 70 ) + 10; 13 | item.style.width = w + 'px'; 14 | item.style.height = h + 'px'; 15 | frag.appendChild( item ); 16 | } 17 | 18 | // last one isn't random, but is needed for checking 19 | // bigger than colum width and stuff 20 | item = document.createElement('div'); 21 | item.className = 'item'; 22 | item.style.width = '72px'; 23 | item.style.height = '25px'; 24 | frag.appendChild( item ); 25 | 26 | container.appendChild( frag ); 27 | }; 28 | 29 | window.gimmeAnItemElement = function() { 30 | var elem = document.createElement('div'); 31 | elem.className = 'item'; 32 | return elem; 33 | }; 34 | 35 | })( window ); 36 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Packery tests 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |

Packery tests

17 | 18 |
19 | 20 |
21 | 22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | 32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | 42 |
43 |
44 |
45 |
46 |
47 |
48 | 49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 | 58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 | 67 |

Layout extra big

68 | 69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 | 77 |

Consecutive

78 | 79 |
80 |
81 |
82 |
83 |
84 |
85 | 86 |
87 |
88 |
89 |
90 | 91 |
92 |
93 |
94 |
95 | 96 |
97 |
98 |
99 |
100 | 101 |
102 |
103 |
104 |
105 |
106 |
107 | 108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 | 119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 | 131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 | 142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 | 153 |

Placed borders

154 | 155 |
156 |
157 |
158 |
159 |
160 |
161 | 162 |

Adding

163 | 164 |
165 |
166 |
167 |
168 | 169 |

Prepend

170 | 171 |
172 |
173 |
174 |
175 | 176 |

Draggable

177 | 178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 | 189 |

Fitting

190 | 191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 | 202 |

Declarative

203 | 204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 | 213 | 214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 | 223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 | 232 |

jQuery

233 | 234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 | 244 |

isInitLayout option

245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 | 255 | 265 | 266 |
267 | 268 |
269 |
270 |
271 |
272 | 273 |

Sub-pixel fit

274 | 275 |
276 |
277 |
278 |
279 |
280 |
281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | -------------------------------------------------------------------------------- /test/test.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: sans-serif; 3 | } 4 | 5 | .container { 6 | width: 80px; 7 | margin-bottom: 20px; 8 | background: #EEE; 9 | } 10 | 11 | .container:after { 12 | content: ''; 13 | display: table; 14 | } 15 | 16 | .item { 17 | float: left; 18 | width: 18px; 19 | height: 18px; 20 | margin: 1px; 21 | background: #444; 22 | } 23 | 24 | .item.w2 { width: 38px; } 25 | .item.h2 { height: 38px; } 26 | .item.w4 { width: 78px; } 27 | .item.h4 { height: 78px; } 28 | .item.w5 { width: 98px; } 29 | 30 | .gridded .grid-sizer { 31 | width: 30px; 32 | height: 25px; 33 | } 34 | 35 | .gridded .gutter-sizer { 36 | width: 10px; 37 | } 38 | 39 | .stamp { 40 | background: red; 41 | } 42 | 43 | .has-stamps { 44 | position: relative; 45 | } 46 | 47 | .place-stamps .stamp { 48 | position: absolute; 49 | } 50 | 51 | .place-stamps .stamp1 { 52 | width: 30px; 53 | height: 25px; 54 | left: 22px; 55 | top: 10px; 56 | } 57 | 58 | .place-stamps .stamp2 { 59 | width: 10px; 60 | height: 45px; 61 | left: 3px; 62 | top: 30px; 63 | } 64 | 65 | #stamped2 .stamp1 { 66 | position: relative; 67 | left: 8px; 68 | top: 8px; 69 | } 70 | 71 | #stamped2 .stamp2 { 72 | position: absolute; 73 | right: 8px; 74 | top: 23px; 75 | } 76 | 77 | #stamped-borders { 78 | border-left: 10px solid; 79 | border-top: 15px solid; 80 | } 81 | 82 | #stamped-borders .stamp1 { 83 | width: 50px; 84 | height: 30px; 85 | } 86 | 87 | .dragger { 88 | background: green; 89 | } 90 | 91 | /*#fitting { 92 | position: absolute; 93 | top: 0; 94 | }*/ 95 | 96 | #hidden-items .hidden { 97 | display: none; 98 | } 99 | 100 | #sub-pixel-fit { 101 | width: 290px; 102 | } 103 | 104 | #sub-pixel-fit .item, 105 | #sub-pixel-fit .grid-sizer { 106 | width: 20%; 107 | } 108 | 109 | #sub-pixel-fit .item { 110 | margin: 0; 111 | } 112 | 113 | #sub-pixel-fit .item.w2 { width: 40%; } 114 | -------------------------------------------------------------------------------- /test/unit/add-items.js: -------------------------------------------------------------------------------- 1 | QUnit.test( 'addItems', function( assert ) { 2 | var container = document.querySelector('#adding'); 3 | var pckry = new Packery( container, { 4 | itemSelector: '.item' 5 | }); 6 | 7 | var elem = gimmeAnItemElement(); 8 | var items = pckry.addItems( elem ); 9 | 10 | assert.equal( items.length, 1, 'method return array of 1' ); 11 | assert.equal( pckry.items[2].element, elem, 'item was added, element matches' ); 12 | assert.equal( items[0] instanceof Packery.Item, true, 'item is instance of Packery.Item' ); 13 | assert.equal( pckry.items.length, 3, 'item added to items' ); 14 | 15 | // try it with an array 16 | var elems = [ gimmeAnItemElement(), gimmeAnItemElement(), document.createElement('div') ]; 17 | items = pckry.addItems( elems ); 18 | assert.equal( items.length, 2, 'method return array of 2' ); 19 | assert.equal( pckry.items[3].element, elems[0], 'item was added, element matches' ); 20 | assert.equal( pckry.items.length, 5, 'two items added to items' ); 21 | 22 | // try it with HTMLCollection / NodeList 23 | var fragment = document.createDocumentFragment(); 24 | fragment.appendChild( gimmeAnItemElement() ); 25 | fragment.appendChild( document.createElement('div') ); 26 | fragment.appendChild( gimmeAnItemElement() ); 27 | 28 | var divs = fragment.querySelectorAll('div'); 29 | items = pckry.addItems( divs ); 30 | assert.equal( items.length, 2, 'method return array of 2' ); 31 | assert.equal( pckry.items[5].element, divs[0], 'item was added, element matches' ); 32 | assert.equal( pckry.items.length, 7, 'two items added to items' ); 33 | 34 | }); 35 | -------------------------------------------------------------------------------- /test/unit/basics.js: -------------------------------------------------------------------------------- 1 | QUnit.module('Packery'); 2 | 3 | QUnit.test( 'basics', function( assert ) { 4 | assert.equal( typeof Packery, 'function', 'Packery is a function' ); 5 | // TODO pckry should be null or something 6 | var pckry1 = new Packery(); 7 | // console.log( pckry, typeof pckry ); 8 | assert.ok( !pckry1._isLayoutInited, 'packery not inited on undefined' ); 9 | 10 | // var pckry2 = new Packery({}); 11 | // console.log( pckry, typeof pckry ); 12 | // FIXME Outlayer should throw error up top 13 | // assert.ok( !pckry2._isLayoutInited, 'packery not inited on object' ); 14 | }); 15 | -------------------------------------------------------------------------------- /test/unit/consecutive.js: -------------------------------------------------------------------------------- 1 | QUnit.test( 'consecutive', function( assert ) { 2 | var container = document.querySelector('#consecutive'); 3 | var pckry = new Packery( container ); 4 | 5 | var i0 = container.querySelector('.i0'); 6 | var i1 = container.querySelector('.i1'); 7 | var i3 = container.querySelector('.i3'); 8 | i1.style.width = '28px'; 9 | i1.style.height = '28px'; 10 | i1.style.background = 'blue'; 11 | 12 | var done = assert.async(); 13 | 14 | pckry.once( 'layoutComplete', function() { 15 | assert.ok( true, 'layoutComplete triggered' ); 16 | assert.equal( i3.style.left, '0px', 'i3.style.left' ); 17 | assert.equal( i3.style.top, '20px', 'i3.style.top' ); 18 | setTimeout( fit1 ); 19 | }); 20 | 21 | pckry.layout(); 22 | 23 | function fit1() { 24 | pckry.once( 'layoutComplete', function() { 25 | assert.equal( i3.style.left, '60px', 'i3.style.left' ); 26 | assert.equal( i3.style.top, '30px', 'i3.style.top' ); 27 | // all done 28 | done(); 29 | }); 30 | i0.style.width = '38px'; 31 | i0.style.height = '38px'; 32 | i0.style.background = 'orange'; 33 | pckry.layout(); 34 | } 35 | 36 | }); 37 | -------------------------------------------------------------------------------- /test/unit/container-size.js: -------------------------------------------------------------------------------- 1 | QUnit.test( 'container size', function( assert ) { 2 | var container = document.querySelector('#container-size'); 3 | var pckry = new Packery( container ); 4 | assert.equal( container.style.height, '40px', 'default height' ); 5 | 6 | pckry.options.gutter = 4; 7 | pckry._getMeasurements(); 8 | pckry.layout(); 9 | assert.equal( container.style.height, '40px', 'added gutter, height same' ); 10 | 11 | // addPaddingBorders() { 12 | container.style.padding = '1px 2px 3px 4px'; 13 | container.style.borderStyle = 'solid'; 14 | container.style.borderWidth = '1px 2px 3px 4px'; 15 | pckry.layout(); 16 | assert.equal( container.style.height, '40px', 'add padding, height same' ); 17 | 18 | // border box 19 | container.style.WebkitBoxSizing = 'border-box'; 20 | container.style.MozBoxSizing = 'border-box'; 21 | container.style.boxSizing = 'border-box'; 22 | pckry.layout(); 23 | assert.equal( container.style.height, '48px', 'border-box, height + padding + border' ); 24 | }); 25 | -------------------------------------------------------------------------------- /test/unit/declarative.js: -------------------------------------------------------------------------------- 1 | QUnit.test( 'declarative', function( assert ) { 2 | 3 | var $ = window.jQuery; 4 | 5 | // no data-packery-options 6 | var container1 = document.querySelector('#declarative'); 7 | var pckry1 = Packery.data( container1 ); 8 | assert.ok( pckry1 instanceof Packery, 'Packery instance retrieved from element' ); 9 | assert.deepEqual( pckry1.options, Packery.defaults, 'options match defaults' ); 10 | assert.ok( pckry1._isLayoutInited, 'Packer was initialized' ); 11 | 12 | // has data-packery-options, but bad JSON 13 | var container2 = document.querySelector('#declarative-bad-json'); 14 | var pckry2 = Packery.data( container2 ); 15 | assert.ok( !pckry2, 'bad JSON in data-packery-options does not init Packery' ); 16 | assert.ok( !container2.packeryGUID, 'no expando property on element' ); 17 | 18 | // has good data-packery-options 19 | var container3 = document.querySelector('#declarative-good-json'); 20 | var pckry3 = Packery.data( container3 ); 21 | assert.ok( pckry3 instanceof Packery, 'Packery instance retrieved from element, with good JSON in data-packery-options' ); 22 | assert.strictEqual( pckry3.options.columnWidth, 25, 'columnWidth option was set' ); 23 | assert.strictEqual( pckry3.options.rowHeight, 30, 'rowHeight option was set' ); 24 | assert.strictEqual( pckry3.options.transitionDuration, '1.2s', 'transitionDuration option was set' ); 25 | assert.strictEqual( pckry3.options.isResizable, false, 'isResizable option was set' ); 26 | 27 | assert.equal( $.data( container3, 'packery' ), pckry3, '$.data( elem, "packery") returns Packery instance' ); 28 | 29 | }); 30 | -------------------------------------------------------------------------------- /test/unit/defaults-empty.js: -------------------------------------------------------------------------------- 1 | QUnit.test( 'defaults / empty', function( assert ) { 2 | var empty = document.querySelector('#empty'); 3 | var pckry = new Packery( empty ); 4 | var done = assert.async(); 5 | 6 | assert.deepEqual( pckry.options, Packery.defaults, 'default options match prototype' ); 7 | assert.equal( pckry.items.length, 0, 'zero items' ); 8 | assert.equal( pckry.stamps.length, 0, 'zero stamped elements' ); 9 | assert.equal( Packery.data( empty ), pckry, 'data method returns instance' ); 10 | assert.ok( pckry.isResizeBound, 'isResizeBound' ); 11 | 12 | pckry.once( 'layoutComplete', function( items ) { 13 | assert.ok( true, 'layoutComplete triggered with no items' ); 14 | assert.equal( items.length, 0, 'no items' ); 15 | done(); 16 | }); 17 | 18 | // add gutter, to check that container size doesn't get negative number 19 | pckry.options.gutter = 20; 20 | pckry.layout(); 21 | }); 22 | -------------------------------------------------------------------------------- /test/unit/draggable.js: -------------------------------------------------------------------------------- 1 | QUnit.test( 'draggable', function( assert ) { 2 | var container = document.querySelector('#draggable'); 3 | var itemElems = container.querySelectorAll('.item'); 4 | var dragElem = container.querySelector('.dragger'); 5 | 6 | var pckry = new Packery( container, { 7 | transitionDuration: '0.2s' 8 | }); 9 | 10 | var done = assert.async(); 11 | 12 | function simulateDrag( x, y ) { 13 | pckry.itemDragStart( dragElem ); 14 | pckry.itemDragMove( dragElem, x, y ); 15 | pckry.itemDragEnd( dragElem ); 16 | } 17 | 18 | function checkItemPosition( itemElem, x, y, message ) { 19 | var actual = itemElem.style.left + ' ' + itemElem.style.top; 20 | var expected = x + 'px ' + y + 'px'; 21 | message = message || 'item position'; 22 | assert.equal( actual, expected, message ); 23 | } 24 | 25 | // simulate drag to middle 26 | pckry.once( 'dragItemPositioned', function() { 27 | assert.ok( true, 'dragItemPositioned did trigger, 4th item moved to 35, 15' ); 28 | checkItemPosition( itemElems[0], 0, 0, '1st item' ); 29 | checkItemPosition( itemElems[1], 20, 0, '2nd item' ); 30 | checkItemPosition( itemElems[2], 40, 0, '3rd item' ); 31 | checkItemPosition( itemElems[6], 40, 40, '7th item, moved down below dragged item' ); 32 | checkItemPosition( itemElems[7], 60, 0, '8th item, moved up' ); 33 | checkItemPosition( dragElem, 40, 20, 'drag item, 2nd row, 3rd column' ); 34 | assert.equal( pckry.items[6].element, dragElem, 'dragged elem in now 7th in items' ); 35 | // HACK setTimeout because of Packery bug 36 | setTimeout( dragOutside ); 37 | }); 38 | 39 | simulateDrag( 35, 15 ); 40 | 41 | function dragOutside() { 42 | pckry.once( 'dragItemPositioned', function() { 43 | assert.ok( true, 'dragItemPositioned event did trigger' ); 44 | checkItemPosition( dragElem, 60, 0, 'drag item, 1st row, 4th column' ); 45 | checkItemPosition( itemElems[4], 0, 20, '5th item, did not move' ); 46 | checkItemPosition( itemElems[6], 40, 20, '7th item, moved back up' ); 47 | checkItemPosition( itemElems[7], 60, 20, '8th item, moved back down' ); 48 | assert.equal( pckry.items[3].element, dragElem, 'dragged elem in now 4th in items' ); 49 | 50 | setTimeout( dragWithGrid ); 51 | }); 52 | 53 | simulateDrag( 300, -60 ); 54 | } 55 | 56 | function dragWithGrid() { 57 | pckry.options.columnWidth = 25; 58 | pckry.options.rowHeight = 25; 59 | // disable transition, layout, re-enable transition 60 | pckry.options.transitionDuration = 0; 61 | pckry.layout(); 62 | pckry.options.transitionDuration = '0.2s'; 63 | 64 | pckry.once( 'dragItemPositioned', function() { 65 | checkItemPosition( dragElem, 25, 25, 'drag item, 2nd row, 2nd column' ); 66 | checkItemPosition( itemElems[4], 25, 50, '5th item, 3rd row, 2nd column, moved down' ); 67 | checkItemPosition( itemElems[5], 50, 25, '6th item, 2nd row, 3rd column, did not move' ); 68 | checkItemPosition( itemElems[6], 0, 25, '7th item, 2nd row, 1st column, moved up' ); 69 | checkItemPosition( itemElems[7], 25, 75, '7th item, 4th row, 2nd column, moved down' ); 70 | 71 | setTimeout( dragOutsideWithGrid ); 72 | }); 73 | simulateDrag( 35, 35 ); 74 | } 75 | 76 | function dragOutsideWithGrid() { 77 | pckry.once( 'dragItemPositioned', function() { 78 | checkItemPosition( dragElem, 50, 0, 'dragged, top right corner in grid' ); 79 | done(); 80 | }); 81 | simulateDrag( 300, -30 ); 82 | } 83 | 84 | }); 85 | -------------------------------------------------------------------------------- /test/unit/fit.js: -------------------------------------------------------------------------------- 1 | QUnit.test( '.fit()', function( assert ) { 2 | var container = document.querySelector('#fitting'); 3 | var pckry = new Packery( container, { 4 | transitionDuration: '0.2s' 5 | }); 6 | 7 | var done = assert.async(); 8 | 9 | var elem1 = container.querySelector('.i1'); 10 | var elem2 = container.querySelector('.i2'); 11 | var elem3 = container.querySelector('.i3'); 12 | var elem5 = container.querySelector('.i5'); 13 | var elem6 = container.querySelector('.i6'); 14 | var item3 = pckry.getItem( elem3 ); 15 | 16 | function checkItemPosition( itemElem, x, y, message ) { 17 | var actual = itemElem.style.left + ' ' + itemElem.style.top; 18 | var expected = x + 'px ' + y + 'px'; 19 | message = message || 'item position'; 20 | assert.equal( actual, expected, message ); 21 | } 22 | 23 | // expand item3 24 | elem3.style.width = '48px'; 25 | elem3.style.height = '28px'; 26 | elem3.style.background = 'blue'; 27 | 28 | // quickie async 29 | var isFit, isLaidOut; 30 | function resetAsync() { 31 | isFit = false; 32 | isLaidOut = false; 33 | } 34 | 35 | // -------------------------- fit -------------------------- // 36 | 37 | function ready1() { 38 | if ( !isFit || !isLaidOut ) { 39 | return; 40 | } 41 | checkItemPosition( elem1, 20, 30, 'elem1 shifted down' ); 42 | checkItemPosition( elem2, 40, 30, 'elem2 shifted down' ); 43 | checkItemPosition( elem5, 20, 50, 'elem5 shifted down, 2nd row' ); 44 | resetAsync(); 45 | // HACK setTimeout for Packery bug 46 | setTimeout( fit2 ); 47 | } 48 | 49 | pckry.once( 'fitComplete', function( item ) { 50 | assert.ok( true, 'fitComplete event emitted' ); 51 | assert.equal( item, item3, 'item argument returned' ); 52 | checkItemPosition( elem3, 20, 0, 'fit elem3 shifted into 2nd spot' ); 53 | isFit = true; 54 | ready1(); 55 | }); 56 | 57 | pckry.once( 'layoutComplete', function() { 58 | assert.ok( true, 'layoutComplete event emitted' ); 59 | isLaidOut = true; 60 | ready1(); 61 | }); 62 | 63 | // fit it 64 | pckry.fit( elem3 ); 65 | 66 | // -------------------------- fit into spot -------------------------- // 67 | 68 | function ready2() { 69 | if ( !isFit || !isLaidOut ) { 70 | return; 71 | } 72 | resetAsync(); 73 | 74 | setTimeout( fit3 ); 75 | } 76 | 77 | function fit2() { 78 | // reset small size 79 | elem3.style.width = '18px'; 80 | elem3.style.height = '18px'; 81 | 82 | pckry.once( 'fitComplete', function() { 83 | assert.ok( true, 'fitComplete event emitted' ); 84 | checkItemPosition( elem3, 40, 20, 'fit item in 40, 20' ); 85 | isFit = true; 86 | ready2(); 87 | }); 88 | 89 | pckry.once( 'layoutComplete', function() { 90 | assert.ok( true, 'layoutComplete event emitted' ); 91 | checkItemPosition( elem3, 40, 20, 'fit item in 40, 20' ); 92 | checkItemPosition( elem1, 20, 0, 'elem1 shifted up' ); 93 | checkItemPosition( elem2, 40, 0, 'elem2 shifted up' ); 94 | checkItemPosition( elem5, 20, 20, 'elem5 shifted up' ); 95 | checkItemPosition( elem6, 40, 40, 'elem6 shifted down' ); 96 | isLaidOut = true; 97 | ready2(); 98 | }); 99 | 100 | // fit to spot 101 | pckry.fit( elem3, 40, 20 ); 102 | } 103 | 104 | // -------------------------- fit outside container -------------------------- // 105 | 106 | function ready3() { 107 | if ( !isFit || !isLaidOut ) { 108 | return; 109 | } 110 | resetAsync(); 111 | 112 | setTimeout( fit4 ); 113 | } 114 | 115 | function fit3() { 116 | pckry.once( 'fitComplete', function() { 117 | checkItemPosition( elem3, 40, 40, 'fit elem in 3rd row, 3rd column' ); 118 | isFit = true; 119 | ready3(); 120 | }); 121 | pckry.once( 'layoutComplete', function() { 122 | isLaidOut = true; 123 | ready3(); 124 | }); 125 | 126 | // try to position item outside container 127 | pckry.fit( elem3, 120, 120 ); 128 | } 129 | 130 | // -------------------------- columnWidth & rowHeight -------------------------- // 131 | 132 | // fit with columnWidth and rowHeight 133 | function fit4() { 134 | pckry.options.columnWidth = 25; 135 | pckry.options.rowHeight = 30; 136 | // disable transition, trigger layout, re-enable transition 137 | pckry.options.transitionDuration = 0; 138 | pckry.layout(); 139 | pckry.options.transitionDuration = '0.2s'; 140 | 141 | function ready4() { 142 | if ( !isFit || !isLaidOut ) { 143 | return; 144 | } 145 | done(); 146 | } 147 | 148 | pckry.on( 'fitComplete', function() { 149 | assert.ok( true, 'fitComplete event emitted' ); 150 | checkItemPosition( elem3, 50, 30, 'fit item, 2nd row, 3rd column' ); 151 | isFit = true; 152 | ready4(); 153 | }); 154 | 155 | pckry.on( 'layoutComplete', function() { 156 | checkItemPosition( elem5, 50, 60, 'elem5 shifted down' ); 157 | isLaidOut = true; 158 | ready4(); 159 | }); 160 | 161 | pckry.fit( elem3, 55, 28 ); 162 | } 163 | 164 | }); 165 | -------------------------------------------------------------------------------- /test/unit/get-items.js: -------------------------------------------------------------------------------- 1 | QUnit.test( 'getItems', function( assert ) { 2 | var defaultElem = document.querySelector('#default'); 3 | var defaultPckry = new Packery( defaultElem ); 4 | 5 | var filtered = document.querySelector('#filtered'); 6 | var filteredPckry = new Packery( filtered, { 7 | itemSelector: '.item' 8 | }); 9 | 10 | var found = document.querySelector('#found'); 11 | var foundPckry = new Packery( found, { 12 | itemSelector: '.item' 13 | }); 14 | 15 | var filterFound = document.querySelector('#filter-found'); 16 | var filterFoundPckry = new Packery( filterFound, { 17 | itemSelector: '.item' 18 | }); 19 | 20 | assert.equal( defaultPckry.items.length, defaultElem.children.length, 'no itemSelector, all children' ); 21 | assert.equal( filteredPckry.items.length, 6, 'filtered, itemSelector = .item, not all children' ); 22 | assert.equal( foundPckry.items.length, 4, 'found itemSelector = .item, querySelectoring' ); 23 | assert.equal( filterFoundPckry.items.length, 5, 'filter found' ); 24 | }); 25 | -------------------------------------------------------------------------------- /test/unit/grid.js: -------------------------------------------------------------------------------- 1 | QUnit.test( 'layout with columnWidth and rowHeight', function( assert ) { 2 | var container = document.querySelector('#gridded1'); 3 | appendRandomSizedItems( container ); 4 | 5 | var pckry = new Packery( container, { 6 | itemSelector: '.item', 7 | columnWidth: 25, 8 | rowHeight: 30 9 | }); 10 | 11 | var done = assert.async(); 12 | 13 | function checkPackeryGrid() { 14 | var colW = pckry.columnWidth + pckry.gutter; 15 | var rowH = pckry.rowHeight + pckry.gutter; 16 | for ( var i=0, len = pckry.items.length; i < len; i++ ) { 17 | var elem = pckry.items[i].element; 18 | var x = parseInt( elem.style.left, 10 ); 19 | var y = parseInt( elem.style.top, 10 ); 20 | assert.equal( x % colW, 0, 'item ' + i + ' x position is multiple of columnWidth' ); 21 | assert.equal( y % rowH, 0, 'item ' + i + ' y position is multiple of rowHeight' ); 22 | } 23 | } 24 | 25 | assert.equal( pckry.options.columnWidth, 25, 'columnWidth option is set' ); 26 | assert.equal( pckry.options.rowHeight, 30, 'rowHeight option is set' ); 27 | assert.equal( pckry.columnWidth, 25, 'columnWidth is set' ); 28 | assert.equal( pckry.rowHeight, 30, 'rowHeight is set' ); 29 | checkPackeryGrid( pckry ); 30 | 31 | var gridSizer = container.querySelector('.grid-sizer'); 32 | 33 | pckry.options.columnWidth = gridSizer; 34 | pckry.options.rowHeight = gridSizer; 35 | pckry.once( 'layoutComplete', function() { 36 | checkPackeryGrid( pckry ); 37 | setTimeout( setGutter ); 38 | }); 39 | 40 | pckry.layout(); 41 | 42 | assert.equal( pckry.columnWidth, 30, 'columnWidth is set from element width, in px' ); 43 | assert.equal( pckry.rowHeight, 25, 'rowHeight is set from element height, in px' ); 44 | 45 | function setGutter() { 46 | pckry.options.gutter = container.querySelector('.gutter-sizer'); 47 | pckry.once( 'layoutComplete', function() { 48 | checkPackeryGrid( pckry ); 49 | setTimeout( setPercentageGrid ); 50 | }); 51 | pckry.layout(); 52 | assert.equal( pckry.gutter, 10, 'gutter set from element width, in px' ); 53 | } 54 | 55 | function setPercentageGrid() { 56 | gridSizer.style.width = '40%'; 57 | pckry.once( 'layoutComplete', function() { 58 | checkPackeryGrid( pckry ); 59 | done(); 60 | }); 61 | pckry.layout(); 62 | assert.equal( pckry.columnWidth, 32, 'columnWidth is set from element width, in percentage' ); 63 | } 64 | 65 | }); 66 | 67 | QUnit.test( 'columnWidth, rowHeight, gutter via selector', function( assert ) { 68 | var container = document.querySelector('#gridded2'); 69 | appendRandomSizedItems( container ); 70 | 71 | var pckry = new Packery( container, { 72 | itemSelector: '.item', 73 | columnWidth: '.grid-sizer', 74 | rowHeight: '.grid-sizer', 75 | gutter: '.gutter-sizer' 76 | }); 77 | 78 | assert.equal( pckry.columnWidth, 30, 'columnWidth' ); 79 | assert.equal( pckry.rowHeight, 25, 'rowHeight' ); 80 | assert.equal( pckry.gutter, 10, 'gutter' ); 81 | }); 82 | -------------------------------------------------------------------------------- /test/unit/hidden-items.js: -------------------------------------------------------------------------------- 1 | QUnit.test( 'hidden items', function( assert ) { 2 | var container = document.querySelector('#hidden-items'); 3 | var pckry = new Packery( container ); 4 | var itemElem1 = pckry.items[1].element; 5 | var itemElem2 = pckry.items[2].element; 6 | assert.equal( itemElem1.style.left, '0px', '2nd item on left' ); 7 | assert.equal( itemElem2.style.top, '0px', '3rd item on top' ); 8 | }); 9 | -------------------------------------------------------------------------------- /test/unit/is-init-layout.js: -------------------------------------------------------------------------------- 1 | QUnit.test( 'initLayout', function( assert ) { 2 | var container = document.querySelector('#is-init-layout'); 3 | var pckry = new Packery( container, { 4 | initLayout: false 5 | }); 6 | assert.ok( !pckry._isLayoutInited, 'packy is not layout initialized' ); 7 | assert.equal( container.children[0].style.left, '', 'no style on first child' ); 8 | }); 9 | -------------------------------------------------------------------------------- /test/unit/jquery-plugin.js: -------------------------------------------------------------------------------- 1 | QUnit.test( 'jQuery plugin', function( assert ) { 2 | var $ = window.jQuery; 3 | var done = assert.async(); 4 | var $elem = $('#jquery'); 5 | 6 | assert.ok( $.fn.packery, '.packery is in jQuery.fn namespace' ); 7 | assert.equal( typeof $elem.packery, 'function', '.packery is a plugin' ); 8 | $elem.packery(); 9 | var pckry = $elem.data('packery'); 10 | assert.ok( pckry, 'packery instance via .data()' ); 11 | assert.equal( pckry, Packery.data( $elem[0] ), 'instance matches the same one via Packery.data()' ); 12 | 13 | var item4 = $elem.children()[4]; 14 | assert.equal( item4.style.left, '0px', '5th item left' ); 15 | assert.equal( item4.style.top, '40px', '5th item top' ); 16 | 17 | $elem.children().first().css({ 18 | width: 48, 19 | height: 8, 20 | background: 'blue' 21 | }); 22 | 23 | $elem.packery( 'on', 'layoutComplete', function() { 24 | assert.ok( true, 'layoutComplete event emitted' ); 25 | assert.equal( item4.style.left, '20px', '4th item left after layout' ); 26 | assert.equal( item4.style.top, '30px', '4th item top after layout' ); 27 | done(); 28 | }); 29 | 30 | $elem.packery(); 31 | }); 32 | -------------------------------------------------------------------------------- /test/unit/layout-extra-big.js: -------------------------------------------------------------------------------- 1 | QUnit.test( 'layout extra big', function( assert ) { 2 | var container = document.querySelector('#layout-extra-big'); 3 | var pckry = new Packery( container ); 4 | 5 | var elem1 = pckry.items[1].element; 6 | var elem2 = pckry.items[2].element; 7 | var elem3 = pckry.items[3].element; 8 | var elem4 = pckry.items[4].element; 9 | 10 | assert.equal( elem1.style.top, '20px', '2nd item top' ); 11 | assert.equal( elem2.style.top, '40px', '3rd item top' ); 12 | assert.equal( elem3.style.top, '20px', '4th item top' ); 13 | assert.equal( elem4.style.top, '60px', '5th item top' ); 14 | }); 15 | -------------------------------------------------------------------------------- /test/unit/layout.js: -------------------------------------------------------------------------------- 1 | QUnit.test( 'layout', function( assert ) { 2 | 3 | var done = assert.async(); 4 | 5 | function checkItemPosition( elem, left, top, message ) { 6 | message = message || ''; 7 | var actual = elem.style.left + ' ' + elem.style.top; 8 | var expected = left + 'px ' + top + 'px'; 9 | assert.equal( actual, expected, expected + ' ' + message ); 10 | } 11 | 12 | var container = document.querySelector('#layout'); 13 | var pckry = new Packery( container ); 14 | var elem0 = pckry.items[0].element; 15 | var elem1 = pckry.items[1].element; 16 | var elem2 = pckry.items[2].element; 17 | var elem3 = pckry.items[3].element; 18 | 19 | checkItemPosition( elem0, 0, 0, 'first item' ); 20 | checkItemPosition( elem1, 40, 0, '2nd item' ); 21 | checkItemPosition( elem2, 0, 20, '3rd item' ); 22 | assert.equal( container.style.height, '60px', 'height set' ); 23 | 24 | // change size of elems to change layout 25 | elem0.style.width = '18px'; 26 | elem3.style.height = '58px'; 27 | var items = pckry._getItemsForLayout( pckry.items ); 28 | pckry.once( 'layoutComplete', function( completeItems ) { 29 | assert.equal( true, true, 'layoutComplete event did fire' ); 30 | assert.equal( completeItems.length, items.length, 'event-emitted items matches layout items length' ); 31 | assert.strictEqual( completeItems[0], items[0], 'event-emitted items has same first item' ); 32 | var len = completeItems.length - 1; 33 | assert.strictEqual( completeItems[ len ], items[ len ], 'event-emitted items has same last item' ); 34 | checkItemPosition( elem1, 20, 0, '2nd item' ); 35 | checkItemPosition( elem2, 40, 0, '3nd item' ); 36 | 37 | setTimeout( checkHorizontal ); 38 | }); 39 | 40 | pckry.layout(); 41 | assert.equal( container.style.height, '80px', 'height set' ); 42 | 43 | function checkHorizontal() { 44 | // disable transition 45 | pckry.options.transitionDuration = 0; 46 | pckry.options.horizontal = true; 47 | pckry.layout(); 48 | checkItemPosition( elem0, 0, 0, 'horizontal, first item' ); 49 | checkItemPosition( elem1, 0, 20, 'horizontal, 2nd item' ); 50 | checkItemPosition( elem2, 0, 60, 'horizontal, 2nd item' ); 51 | assert.equal( container.style.width, '60px', 'width set' ); 52 | 53 | done(); 54 | } 55 | }); 56 | -------------------------------------------------------------------------------- /test/unit/prepend.js: -------------------------------------------------------------------------------- 1 | QUnit.test( 'prepend', function( assert ) { 2 | var container = document.querySelector('#prepend'); 3 | var pckry = new Packery( container ); 4 | var itemElemA = pckry.items[0].element; 5 | var itemElemB = pckry.items[1].element; 6 | var itemElemC = gimmeAnItemElement(); 7 | itemElemC.style.background = 'orange'; 8 | var itemElemD = gimmeAnItemElement(); 9 | itemElemD.style.background = 'magenta'; 10 | 11 | var done = assert.async(); 12 | var ticks = 0; 13 | 14 | pckry.on( 'layoutComplete', function() { 15 | assert.ok( true, 'layoutComplete triggered' ); 16 | ticks++; 17 | if ( ticks == 2 ) { 18 | assert.ok( true, '2 layoutCompletes triggered' ); 19 | done(); 20 | } 21 | }); 22 | 23 | 24 | var fragment = document.createDocumentFragment(); 25 | fragment.appendChild( itemElemC ); 26 | fragment.appendChild( itemElemD ); 27 | container.insertBefore( fragment, container.firstChild ); 28 | pckry.prepended([ itemElemC, itemElemD ]); 29 | 30 | assert.equal( pckry.items[0].element, itemElemC, 'item C is first' ); 31 | assert.equal( pckry.items[1].element, itemElemD, 'item D is second' ); 32 | assert.equal( pckry.items[2].element, itemElemA, 'item A is third' ); 33 | assert.equal( pckry.items[3].element, itemElemB, 'item B is fourth' ); 34 | 35 | }); 36 | -------------------------------------------------------------------------------- /test/unit/remove.js: -------------------------------------------------------------------------------- 1 | QUnit.test( 'remove', function( assert ) { 2 | var done = assert.async(); 3 | var container = document.querySelector('#add-remove'); 4 | // packery starts with 4 items 5 | var pckry = new Packery( container, { 6 | itemSelector: '.item' 7 | }); 8 | // remove two items 9 | var w2Elems = container.querySelectorAll('.w2'); 10 | pckry.on( 'removeComplete', function( removedItems ) { 11 | assert.equal( true, true, 'removeComplete event did fire' ); 12 | assert.equal( removedItems.length, w2Elems.length, 'remove elems length matches 2nd argument length' ); 13 | for ( var i=0, len = removedItems.length; i < len; i++ ) { 14 | assert.equal( removedItems[i].element, w2Elems[i], 'removedItems element matches' ); 15 | } 16 | assert.equal( container.children.length, 2, 'elements removed from DOM' ); 17 | assert.equal( container.querySelectorAll('.w2').length, 0, 'matched elements were removed' ); 18 | done(); 19 | }); 20 | 21 | pckry.remove( w2Elems ); 22 | assert.equal( pckry.items.length, 2, 'items removed from Packery instance' ); 23 | 24 | }); 25 | -------------------------------------------------------------------------------- /test/unit/stamped.js: -------------------------------------------------------------------------------- 1 | QUnit.test( 'stamped1', function( assert ) { 2 | var container = document.querySelector('#stamped1'); 3 | var stamps = container.querySelectorAll('.stamp'); 4 | var pckry = new Packery( container, { 5 | itemSelector: '.item', 6 | stamp: stamps 7 | }); 8 | 9 | assert.equal( pckry.stamps.length, 2, '2 stamped elements' ); 10 | var elem0 = pckry.items[0].element; 11 | assert.equal( elem0.style.left, '0px', '1st item left' ); 12 | assert.equal( elem0.style.top, '0px', '1st item top' ); 13 | var elem1 = pckry.items[1].element; 14 | assert.equal( elem1.style.left, '52px', '2nd item left' ); 15 | assert.equal( elem1.style.top, '0px', '2nd item top' ); 16 | var elem2 = pckry.items[2].element; 17 | assert.equal( elem2.style.left, '52px', '3rd item left' ); 18 | assert.equal( elem2.style.top, '20px', '3rd item top' ); 19 | var elem3 = pckry.items[3].element; 20 | assert.equal( elem3.style.left, '13px', '4th item left' ); 21 | assert.equal( elem3.style.top, '35px', '4th item top' ); 22 | 23 | assert.equal( container.style.height, '75px', 'container height' ); 24 | 25 | // unstamp first stamp 26 | pckry.unstamp( stamps[1] ); 27 | assert.equal( pckry.stamps.length, 1, 'element was unstamped' ); 28 | // stamp it back 29 | pckry.stamp( stamps[1] ); 30 | assert.equal( pckry.stamps.length, 2, 'element was stamped back' ); 31 | 32 | }); 33 | 34 | QUnit.test( 'stamped2, items are stamped', function( assert ) { 35 | var container = document.querySelector('#stamped2'); 36 | var stamps = container.querySelectorAll('.stamp'); 37 | var pckry = new Packery( container, { 38 | itemSelector: '.item', 39 | stamp: stamps 40 | }); 41 | 42 | var done = assert.async(); 43 | var layoutItems = pckry._getItemsForLayout( pckry.items ); 44 | 45 | assert.equal( layoutItems.length, 7, '7 layout items' ); 46 | var elem0 = layoutItems[0].element; 47 | assert.equal( elem0.style.left, '28px', '1st item left' ); 48 | assert.equal( elem0.style.top, '0px', '1st item top' ); 49 | var elem3 = layoutItems[3].element; 50 | assert.equal( elem3.style.left, '0px', '4th item left' ); 51 | assert.equal( elem3.style.top, '28px', '4th item top' ); 52 | var elem4 = layoutItems[4].element; 53 | assert.equal( elem4.style.left, '20px', '5th item left' ); 54 | assert.equal( elem4.style.top, '40px', '5th item top' ); 55 | 56 | // unplacing 57 | pckry.unstamp( stamps ); 58 | layoutItems = pckry._getItemsForLayout( pckry.items ); 59 | assert.equal( layoutItems.length, 9, '9 layout items' ); 60 | assert.equal( pckry.stamps.length, 0, '0 stamps items' ); 61 | 62 | pckry.on( 'layoutComplete', function() { 63 | var elem0 = pckry.items[0].element; 64 | assert.equal( elem0.style.left, '0px', '1st item left' ); 65 | assert.equal( elem0.style.top, '0px', '1st item top' ); 66 | var elem4 = pckry.items[4].element; 67 | assert.equal( elem4.style.left, '0px', '5th item left' ); 68 | assert.equal( elem4.style.top, '20px', '5th item top' ); 69 | done(); 70 | }); 71 | 72 | pckry.layout(); 73 | }); 74 | 75 | QUnit.test( 'stamped3, stamp with selector string ', function( assert ) { 76 | var container3 = document.querySelector('#stamped3'); 77 | var pckry3 = new Packery( container3, { 78 | itemSelector: '.item', 79 | stamp: '.stamp' 80 | }); 81 | 82 | assert.equal( pckry3.stamps.length, 2, '2 stamped elements' ); 83 | 84 | assert.equal( pckry3.stamps.length, 2, '2 stamped elements' ); 85 | var elem0 = pckry3.items[0].element; 86 | assert.equal( elem0.style.left, '0px', '1st item left' ); 87 | assert.equal( elem0.style.top, '0px', '1st item top' ); 88 | var elem1 = pckry3.items[1].element; 89 | assert.equal( elem1.style.left, '52px', '2nd item left' ); 90 | assert.equal( elem1.style.top, '0px', '2nd item top' ); 91 | var elem2 = pckry3.items[2].element; 92 | assert.equal( elem2.style.left, '52px', '3rd item left' ); 93 | assert.equal( elem2.style.top, '20px', '3rd item top' ); 94 | var elem3 = pckry3.items[3].element; 95 | assert.equal( elem3.style.left, '13px', '4th item left' ); 96 | assert.equal( elem3.style.top, '35px', '4th item top' ); 97 | 98 | assert.equal( container3.style.height, '75px', 'container height' ); 99 | 100 | var container4 = document.querySelector('#stamped4'); 101 | var pckry4 = new Packery( container4, { 102 | itemSelector: '.item', 103 | stamp: 'foobar' 104 | }); 105 | 106 | assert.ok( pckry4._isLayoutInited, 'bad selector didnt cause error' ); 107 | }); 108 | 109 | QUnit.test( 'stamped with borders', function( assert ) { 110 | var container = document.querySelector('#stamped-borders'); 111 | var pckry = new Packery( container, { 112 | itemSelector: '.item', 113 | stamp: '.stamp' 114 | }); 115 | 116 | var elem0 = pckry.items[0].element; 117 | var elem1 = pckry.items[1].element; 118 | var elem2 = pckry.items[2].element; 119 | 120 | assert.equal( elem0.style.left, '50px', '1st item left' ); 121 | assert.equal( elem1.style.left, '50px', '2nd item left' ); 122 | assert.equal( elem2.style.top, '30px', '3rd item top' ); 123 | 124 | }); 125 | -------------------------------------------------------------------------------- /test/unit/sub-pixel-fit.js: -------------------------------------------------------------------------------- 1 | QUnit.test( 'sub-pixel fit', function( assert ) { 2 | 3 | var pckry = new Packery( '#sub-pixel-fit', { 4 | itemSelector: '.item', 5 | transitionDuration: 0 6 | }); 7 | 8 | function getItemsTotalY() { 9 | var y = 0; 10 | for ( var i=0, len = pckry.items.length; i < len; i++ ) { 11 | var item = pckry.items[i]; 12 | y += item.rect.y; 13 | } 14 | return y; 15 | } 16 | 17 | // iterate over multiple container widths 18 | for ( var containerWidth = 290; containerWidth < 310; containerWidth++ ) { 19 | pckry.element.style.width = containerWidth + 'px'; 20 | pckry.layout(); 21 | assert.equal( 0, getItemsTotalY(), 'items fit at ' + containerWidth + 'px' ); 22 | } 23 | 24 | // set grid sizer and do it again 25 | pckry.options.columnWidth = '.grid-sizer'; 26 | 27 | for ( containerWidth = 290; containerWidth < 310; containerWidth++ ) { 28 | pckry.element.style.width = containerWidth + 'px'; 29 | pckry.layout(); 30 | assert.equal( 0, getItemsTotalY(), 'items fit with columnWidth at ' + containerWidth + 'px' ); 31 | } 32 | 33 | }); 34 | -------------------------------------------------------------------------------- /test/unit/test-packer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Packer tests 3 | */ 4 | 5 | ( function() { 6 | 7 | QUnit.module('Packer'); 8 | 9 | var Packer = Packery.Packer; 10 | var Rect = Packery.Rect; 11 | 12 | QUnit.test( 'basics', function( assert ) { 13 | assert.equal( typeof Packer === 'function', true, 'Packery is a function' ); 14 | }); 15 | 16 | QUnit.test( 'packing', function( assert ) { 17 | var packr = new Packer( 30, 100 ); 18 | 19 | // 112 20 | // 352 21 | // 344 22 | // xxx 23 | // xxx 24 | 25 | var rect1 = new Rect({ width: 20, height: 10 }); 26 | var rect2 = new Rect({ width: 10, height: 20 }); 27 | var rect3 = new Rect({ width: 10, height: 20 }); 28 | var rect4 = new Rect({ width: 20, height: 10 }); 29 | var rect5 = new Rect({ width: 10, height: 10 }); 30 | 31 | packr.pack( rect1 ); 32 | packr.pack( rect2 ); 33 | packr.pack( rect3 ); 34 | packr.pack( rect4 ); 35 | packr.pack( rect5 ); 36 | 37 | assert.equal( rect1.x, 0, 'rect1.x top left' ); 38 | assert.equal( rect1.y, 0, 'rect1.y top left' ); 39 | assert.equal( rect2.x, 20, 'rect2.x top right' ); 40 | assert.equal( rect2.y, 0, 'rect2.y top right' ); 41 | assert.equal( rect3.x, 0, 'rect3.x bottom left' ); 42 | assert.equal( rect3.y, 10, 'rect3.y bottom left' ); 43 | assert.equal( rect4.x, 10, 'rect4.x bottom right' ); 44 | assert.equal( rect4.y, 20, 'rect4.y bottom right' ); 45 | assert.equal( rect5.x, 10, 'rect5.x packed in center' ); 46 | assert.equal( rect5.y, 10, 'rect5.y packed in center' ); 47 | 48 | // bottom space is open 49 | assert.equal( packr.spaces.length, 1, 'one space open' ); 50 | var space = packr.spaces[0]; 51 | assert.equal( space.width, 30, 'space.width' ); 52 | assert.equal( space.height, 70, 'space.height' ); 53 | assert.equal( space.x, 0, 'space.x' ); 54 | assert.equal( space.y, 30, 'space.y' ); 55 | 56 | }); 57 | QUnit.test( 'packing with a placed', function( assert ) { 58 | var packr = new Packer( 30, 100 ); 59 | 60 | // 225 61 | // 311 62 | // 34x 63 | // x4x 64 | // xxx 65 | // xxx 66 | 67 | var rect1 = new Rect({ 68 | width: 20, 69 | height: 10, 70 | x: 10, 71 | y: 10 72 | }); 73 | var rect2 = new Rect({ width: 20, height: 10 }); 74 | var rect3 = new Rect({ width: 10, height: 20 }); 75 | var rect4 = new Rect({ width: 10, height: 20 }); 76 | var rect5 = new Rect({ width: 10, height: 10 }); 77 | 78 | packr.placed( rect1 ); 79 | packr.pack( rect2 ); 80 | packr.pack( rect3 ); 81 | packr.pack( rect4 ); 82 | packr.pack( rect5 ); 83 | 84 | assert.equal( rect2.x, 0, 'rect2.x top left' ); 85 | assert.equal( rect2.y, 0, 'rect2.y top left' ); 86 | assert.equal( rect3.x, 0, 'rect3.x left side' ); 87 | assert.equal( rect3.y, 10, 'rect3.y left side' ); 88 | assert.equal( rect4.x, 10, 'rect4.x bottom center' ); 89 | assert.equal( rect4.y, 20, 'rect4.y bottom center' ); 90 | assert.equal( rect5.x, 20, 'rect5.x packed in top right' ); 91 | assert.equal( rect5.y, 0, 'rect5.y packed in top right' ); 92 | 93 | assert.equal( packr.spaces.length, 3, '3 spaces left' ); 94 | 95 | }); 96 | 97 | QUnit.test( 'packing horizontal', function( assert ) { 98 | 99 | function checkRect( rect, x, y ) { 100 | assert.equal( rect.x, x, 'x: ' + x ); 101 | assert.equal( rect.y, y, 'y: ' + y ); 102 | } 103 | 104 | var packr = new Packer( 100, 30, 'rightwardTopToBottom' ); 105 | 106 | // 133xx 107 | // 154xx 108 | // 224xx 109 | 110 | var rect1 = new Rect({ width: 10, height: 20 }); 111 | var rect2 = new Rect({ width: 20, height: 10 }); 112 | var rect3 = new Rect({ width: 20, height: 10 }); 113 | var rect4 = new Rect({ width: 10, height: 20 }); 114 | var rect5 = new Rect({ width: 10, height: 10 }); 115 | 116 | packr.pack( rect1 ); 117 | packr.pack( rect2 ); 118 | packr.pack( rect3 ); 119 | packr.pack( rect4 ); 120 | packr.pack( rect5 ); 121 | 122 | checkRect( rect1, 0, 0 ); 123 | checkRect( rect2, 0, 20 ); 124 | checkRect( rect3, 10, 0 ); 125 | checkRect( rect4, 20, 10 ); 126 | checkRect( rect5, 10, 10 ); 127 | 128 | // bottom space is open 129 | assert.equal( packr.spaces.length, 1, 'one space open' ); 130 | var space = packr.spaces[0]; 131 | assert.equal( space.width, 70, 'space.width' ); 132 | assert.equal( space.height, 30, 'space.height' ); 133 | checkRect( space, 30, 0 ); 134 | 135 | }); 136 | 137 | })(); 138 | -------------------------------------------------------------------------------- /test/unit/test-rect.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Rect tests 3 | **/ 4 | 5 | ( function() { 6 | 7 | var Rect = Packery.Rect; 8 | 9 | QUnit.module('Rect'); 10 | 11 | QUnit.test( 'Rect defaults', function( assert ) { 12 | var rect = new Rect(); 13 | assert.equal( rect.x, 0, 'rect.x = 0' ); 14 | assert.equal( rect.y, 0, 'rect.y = 0' ); 15 | assert.equal( rect.width, 0, 'rect.width = 0' ); 16 | assert.equal( rect.height, 0, 'rect.height = 0' ); 17 | }); 18 | 19 | QUnit.test( 'set properties with initial argument object', function( assert ) { 20 | var rect = new Rect({ 21 | x: 40, 22 | y: 390, 23 | width: 103, 24 | height: -4 25 | }); 26 | assert.equal( rect.x, 40, 'x' ); 27 | assert.equal( rect.y, 390, 'y' ); 28 | assert.equal( rect.width, 103, 'width' ); 29 | assert.equal( rect.height, -4, 'default height property' ); 30 | }); 31 | 32 | QUnit.test( 'contains', function( assert ) { 33 | 34 | var rectA = new Rect({ 35 | x: 10, 36 | y: 30, 37 | width: 100, 38 | height: 400 39 | }); 40 | 41 | var rectB = new Rect({ 42 | x: 40, 43 | y: 60, 44 | width: 10, 45 | height: 20 46 | }); 47 | 48 | assert.strictEqual( rectA.contains( rectB ), true, 'A clearly contains B' ); 49 | 50 | rectB = new Rect({ 51 | x: 500, 52 | y: 40, 53 | width: 40, 54 | height: 20 55 | }); 56 | 57 | assert.strictEqual( rectA.contains( rectB ), false, 'A clearly does not contain B' ); 58 | 59 | rectB = new Rect({ 60 | x: 20, 61 | y: 40 62 | }); 63 | 64 | assert.strictEqual( rectA.contains( rectB ), true, 65 | 'A contains B, which has no width or height' ); 66 | 67 | rectB = new Rect({ 68 | x: 20, 69 | y: 50, 70 | width: 60, 71 | height: 150 72 | }); 73 | 74 | assert.strictEqual( rectA.contains( rectB ), true, 'B is at upper left corner of A' ); 75 | 76 | rectB = new Rect({ 77 | x: rectA.x, 78 | y: rectA.y, 79 | width: rectA.width, 80 | height: rectA.height 81 | }); 82 | 83 | assert.strictEqual( rectA.contains( rectB ), true, 'A contains B. B is equal to A' ); 84 | 85 | rectB = new Rect({ 86 | x: rectA.x - 20, 87 | y: rectA.y, 88 | width: rectA.width, 89 | height: rectA.height 90 | }); 91 | 92 | assert.strictEqual( rectA.contains( rectB ), false, 93 | 'A does not contain B. B same size A, but offset' ); 94 | 95 | }); 96 | 97 | 98 | QUnit.test( 'overlaps', function( assert ) { 99 | 100 | var rectA = new Rect({ 101 | x: 100, 102 | y: 50, 103 | width: 300, 104 | height: 200 105 | }); 106 | 107 | var rectB = new Rect({ 108 | x: 150, 109 | y: 100, 110 | width: 100, 111 | height: 50 112 | }); 113 | 114 | assert.strictEqual( rectA.overlaps( rectB ), true, 'B is inside A, A overlaps B' ); 115 | assert.strictEqual( rectB.overlaps( rectA ), true, 'B is inside A, B overlaps A' ); 116 | 117 | rectB.x = 50; 118 | 119 | assert.strictEqual( rectA.overlaps( rectB ), true, 120 | 'B overlaps left edge of A, A overlaps B' ); 121 | assert.strictEqual( rectB.overlaps( rectA ), true, 122 | 'B overlaps left edge of A, B overlaps A' ); 123 | 124 | rectB.y = 25; 125 | 126 | assert.strictEqual( rectA.overlaps( rectB ), true, 127 | 'B overlaps left top corner of A, A overlaps B' ); 128 | assert.strictEqual( rectB.overlaps( rectA ), true, 129 | 'B overlaps left top corner of A, B overlaps A' ); 130 | 131 | rectB.x = 0; 132 | rectB.y = 0; 133 | 134 | assert.strictEqual( rectA.overlaps( rectB ), false, 135 | 'B bottom right corner meets A top left corner, A DOES NOT overlap B' ); 136 | assert.strictEqual( rectB.overlaps( rectA ), false, 137 | 'B bottom right corner meets A top left corner, B DOES NOT overlap A' ); 138 | 139 | rectB.x = rectA.x - rectB.width; 140 | rectB.y = rectA.y; 141 | rectB.height = rectA.height; 142 | 143 | assert.strictEqual( rectA.overlaps( rectB ), false, 144 | 'B is completely adjacent to A, A DOES NOT overlap B' ); 145 | assert.strictEqual( rectB.overlaps( rectA ), false, 146 | 'B is completely adjacent to A, B DOES NOT overlap A' ); 147 | 148 | }); 149 | 150 | })(); 151 | --------------------------------------------------------------------------------