Packery is a JavaScript library that makes it easy to create dynamic, responsive, and flexible layouts of items in a container. It’s particularly well-suited for layouts where items have varying sizes and you want them to fit neatly together without gaps. Think of layouts like a Pinterest board, an online portfolio, or a responsive image gallery. Packery uses a bin-packing algorithm to efficiently arrange items, making it performant even with a large number of elements. Unlike grid-based layouts, Packery adapts gracefully to different screen sizes and item dimensions, ensuring an optimal presentation across devices.
Packery differs from other layout libraries like Masonry in its approach to bin-packing. While Masonry uses a column-based approach, Packery uses a more sophisticated algorithm that results in denser and often more aesthetically pleasing layouts, especially when dealing with irregularly sized items. Other libraries might focus on specific use-cases (like grids), while Packery offers flexibility to handle a wider range of layout scenarios. Its strength lies in handling dynamic content where item sizes are not known in advance or can change. Libraries such as Gridster focus on drag-and-drop functionalities which are not the primary function of Packery.
Packery can be easily integrated into your project using several methods:
<script>
tag in your HTML file, replacing <VERSION>
with the latest version number (check the Packery website for the current version):<script src="https://unpkg.com/packery@<VERSION>/dist/packery.pkgd.min.js"></script>
npm install packery
Then, import it into your JavaScript file using a module bundler like Webpack or Parcel.
yarn add packery
Packery has a minimal dependency: it requires the existence of a querySelector
method, which is widely supported across modern browsers. If you’re supporting very old browsers, you may need to include a polyfill for older browsers lacking this functionality. However, for most modern development this is not usually a concern. No other external libraries are required.
The simplest way to use Packery is to instantiate the Packery
class with a container element. This element will hold the items you want to arrange. Here’s a basic example:
<div id="packery-container">
<div class="item">Item 1</div>
<div class="item">Item 2</div>
<div class="item">Item 3</div>
<!-- More items... -->
</div>
<script>
const container = document.querySelector('#packery-container');
const packery = new Packery( container, {
// options...
;
})</script>
This code selects the container element with the ID “packery-container” and creates a Packery instance. The second argument is an optional options object (covered in the next section). The items within the container will be automatically laid out by Packery. Note that the item
class is used here, but you can use any class name, as long as it’s consistent in your CSS and JavaScript.
Packery
ConstructorThe Packery
constructor takes two arguments:
element
(required): A DOM element or a selector string representing the container element where the layout will be created. This is where Packery will arrange the items.
options
(optional): An object containing various configuration options. This allows customization of the layout’s behavior, such as column width, item selector, and animation settings (discussed further below).
const packery = new Packery( '#myContainer', {
//options
; } )
The Packery
constructor returns a Packery
instance object which provides methods for manipulating and interacting with the layout.
Packery arranges child elements within your container. By default, Packery assumes all direct child elements of the container are items. You can specify different items using the itemSelector
option (see Essential Options and Configuration).
Packery does not inherently understand the content or semantics of each item. It only considers their dimensions in calculating the layout. You’ll use CSS to style items individually. You can add data attributes to your item elements to influence behavior or provide additional context for your own JavaScript interactions. For instance:
<div class="item" data-width="2" data-height="1">Item A</div>
This allows you to indicate that “Item A” should occupy a space twice as wide as a default item. You can access this data within your custom JavaScript functions.
Several options allow you to customize Packery’s behavior:
itemSelector
: A selector string that specifies which child elements within the container should be considered layout items. The default is >*
.columnWidth
: The width of each column in the layout. This can be a number (in pixels), a string (e.g., “100px”), or a DOM element.rowHeight
: Similar to columnWidth
, but specifies the height of each row.percentPosition
: Boolean; if true, positions items using percentages. This is useful for responsive layouts. Defaults to false
.gutter
: The spacing (in pixels) between items. This can be a single number for uniform spacing or an object with horizontal
and vertical
properties for different horizontal and vertical spacing.transitionDuration
: The duration (in milliseconds) of the animation when items are repositioned. Set to 0
to disable animation.isFitWidth
: Boolean; if true, the container will resize to fit its contents horizontally. Defaults to false.Example using options:
const packery = new Packery( '#myContainer', {
itemSelector: '.grid-item',
columnWidth: 200,
gutter: 10,
percentPosition: true,
transitionDuration: 0 // disables animation
; })
Remember to consult the Packery documentation for the most up-to-date list of available options and their detailed explanations.
Packery provides methods to dynamically add and remove items from the layout.
appended
method to add new items to the layout. This method accepts either a single DOM element or an array of DOM elements. Packery will automatically re-layout the items after addition.const newItem = document.createElement('div');
.className = 'item';
newItem.innerHTML = 'New Item';
newItem.appended( newItem ); // Add single item
packery
const newItems = [item1, item2, item3];
.appended( newItems ); // Add multiple items packery
remove
method to remove items from the layout. This method accepts a single DOM element. Packery will re-layout the remaining items.const itemToRemove = document.querySelector('.item-to-remove');
.remove(itemToRemove); packery
prepended
method. This functions similarly to appended
.const newItem = document.createElement('div');
.className = 'item';
newItem.innerHTML = 'New Item';
newItem.prepended( newItem ); packery
While Packery automatically handles positioning, you can manually adjust item positions and sizes. Modifying an item’s size will trigger a layout update. For precise control, use the getItemElements
method to get the underlying elements for specific items.
layout
method to update the Packery layout:const item = packery.getItemElements()[0]; //Get the first item
.style.width = '300px';
item.layout(); packery
getItemElements()
to gain insights or potentially perform custom animations.Packery triggers several events that you can listen for to respond to changes in the layout. These events are dispatched on the Packery instance.
layoutComplete
: Fired after a layout update is complete.arrangeComplete
: Fired when items are arranged.itemTransitionStart
: Fired when an item starts its transition animation.itemTransitionEnd
: Fired when an item finishes its transition animation.packery:dragItemLayout
: Fired during drag and drop operations to handle layout changes.Example of listening for the layoutComplete
event:
.on('layoutComplete', function() {
packeryconsole.log('Layout complete!');
; })
Remember to use packery.off()
to remove event listeners if necessary.
Packery provides several methods for working with item layouts:
getItemElements()
: Returns an array of the item elements currently in the layout.getItem(element)
: Returns the Packery item object for a given element.getLayoutRect(item)
: Returns the layout rectangle (x, y, width, height) of a given item.layout()
: Forces Packery to re-calculate and update the layout.While Packery doesn’t directly handle filtering and sorting, you can combine it with other JavaScript libraries or techniques to achieve this. You can hide and show items using CSS classes and then call layout()
to update the Packery layout, effectively filtering items. For sorting, you would manipulate the DOM order of your items and then call layout()
to refresh the layout. This approach gives you full control over how filtering and sorting are implemented.
Packery primarily uses a bin-packing layout algorithm. It doesn’t offer distinct “modes” in the way some grid systems might (e.g., masonry, fitting, etc.). The core algorithm adapts to the sizes of the items you provide, aiming for the most efficient arrangement. However, the appearance of the layout is highly influenced by the configuration options you set (columnWidth, rowHeight, gutter, etc.), allowing you to achieve various visual effects.
The columnWidth
and rowHeight
options are central to controlling the layout. These options determine the grid-like structure that Packery uses as a basis for placement.
columnWidth
: This defines the preferred width of each column in the layout. If you set columnWidth
, Packery will attempt to place items within columns of this width. You can specify a numeric value (pixels), a string value (e.g., “100px”), or a DOM element whose width will be used.
rowHeight
: This works similarly to columnWidth
, defining the preferred height of each row. If not specified, Packery will infer row heights based on the items.
If neither columnWidth
nor rowHeight
is defined, Packery will attempt to determine an optimal layout based on the sizes of the items.
While Packery arranges items based on their dimensions, you control the dimensions themselves using CSS. You can set fixed sizes, percentages, or use flexible units like vw
or vh
to create responsive item sizes. Remember to call packery.layout()
after making any changes to item sizes.
For greater control over item aspect ratios, consider using CSS techniques like padding-top
with a percentage value. This can create the appearance of aspect ratio constraints without requiring explicit width and height settings. For example, to maintain a 16:9 aspect ratio:
.item {
position: relative;
padding-bottom: 56.25%; /* 9/16 = 0.5625 */
}.item > img {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
The gutter
option controls the spacing between items. You can provide a single numeric value (pixels) for uniform horizontal and vertical spacing. For more granular control, provide an object:
const packery = new Packery( '#grid', {
gutter: 10, // Uniform 10px gutter
//or
gutter: { horizontal: 20, vertical: 10 } //Different horizontal and vertical gutters
; })
This allows you to have different spacing between columns and rows.
Packery itself isn’t directly tied to specific breakpoints. Its responsiveness comes from using CSS media queries to change item sizes, columnWidth
, rowHeight
, or other options based on screen size. Combine Packery with CSS media queries to adapt the layout to different screen sizes:
/* Styles for screens wider than 768px */
@media (min-width: 768px) {
#packery-container {
column-width: 200px;
}.item {
width: 100%; /*adjusting item size for bigger screens*/
}
}
/* Styles for screens smaller than 768px */
@media (max-width: 767px) {
#packery-container {
column-width: 150px;
}.item {
width: 100%; /*adjusting item size for smaller screens*/
} }
Remember to call packery.layout()
after resizing the window or triggering a media query change, to ensure the layout updates accordingly. For more complex scenarios, consider using a CSS framework with built-in breakpoint utilities.
Beyond the core options, consider exploring these:
isFitWidth
: Setting this to true
makes the container resize horizontally to fit its contents. Useful for fluid layouts that adapt to the available width.percentPosition
: Setting to true
uses percentages for item positioning instead of pixels. Makes responsive layout easier.originLeft
/ originTop
: These options (boolean) control whether items are positioned from the left or top edge, useful for creating layouts that flow differently.stamp
/ unstamp
: Allow you to add ‘stamped’ elements that are part of the layout but are not considered layout items themselves (e.g., static headers or footers). These are helpful for reserving space for elements that won’t be repositioned. Refer to the official documentation for usage.Experimenting with these advanced options provides fine-grained control over layout behavior. Remember to consult the Packery documentation for the most up-to-date information on all options and their functionalities.
Packery works well in conjunction with other JavaScript libraries, particularly Isotope. Isotope is a filtering and sorting library that can be combined with Packery to create highly interactive and dynamic layouts. Isotope uses Packery (or other layout engines) as its underlying layout engine. Using Isotope allows you to filter and sort items, and Packery handles the re-layout efficiently.
To use Packery with Isotope:
layoutMode: 'packery'
and the Packery options you want to use.$('#container').isotope({
layoutMode: 'packery',
packery: {
columnWidth: 200,
gutter: 10
}; })
Isotope then handles filtering and sorting, and Packery manages the layout after each change.
Packery provides smooth animation during layout changes. The transitionDuration
option controls the animation speed. Setting it to 0
disables animations. For more sophisticated animations, consider using CSS transitions or animation libraries like GSAP (GreenSock Animation Platform) to create custom animations synchronized with Packery’s layout updates. You can listen for Packery events like itemTransitionStart
and itemTransitionEnd
to trigger additional animations or effects.
Packery itself doesn’t provide a built-in templating engine. To create custom item renderers, you’ll need to use a templating library (like Handlebars, Mustache, or a simpler approach using JavaScript string manipulation) and integrate it into your workflow. Create your items using your templating engine, add them to the Packery container using the appended
method, and call packery.layout()
to update the layout.
For large datasets, optimizing Packery’s performance is crucial. Consider these techniques:
packery.layout()
. Batch updates when possible.console.log()
statements to track the state of variables and the flow of your code.Remember to carefully read the error messages in your browser’s developer console, as they often provide valuable clues to resolve issues. Debugging often involves a combination of careful code review, inspecting your HTML and CSS, and using the browser’s developer tools.
The Packery constructor creates a new Packery instance.
new Packery( element [, options] )
element
(required): A DOM element or selector string representing the container element.options
(optional): An object containing configuration options (see Options section below).The constructor returns a Packery instance object.
bindResize()
: Binds a resize event listener to the window, triggering a layout update when the window resizes. It’s automatically called by the constructor if you don’t disable it via options.
destroy()
: Removes Packery from the element, unbinding events, and cleaning up resources.
getItemElements()
: Returns an array of the item elements currently in the layout.
getPackery()
: Returns the Packery instance itself (useful for chaining or accessing methods in certain contexts).
layout()
: Forces Packery to recalculate and update the layout. Call this after adding, removing, or resizing items.
on(eventName, handler)
: Binds an event listener to the Packery instance. (See Events section below).
off(eventName, handler)
: Removes an event listener from the Packery instance.
prepend(elements)
: Prepends elements to the layout. elements
can be a single element or an array of elements.
remove(elements)
: Removes elements from the layout. elements
can be a single element or an array of elements.
reloadItems()
: Reloads the items from the container, useful if the contents of the container change dynamically (without using prepend
or append
).
render()
: Renders the layout. Usually called automatically, but useful to call after significant changes.
getSize()
: Returns the size (width and height) of the Packery container.
stamp(elements)
: Stamps elements into the layout. Stamped elements are part of the layout but are not considered layout items themselves; they reserve space but won’t be repositioned.
unstamp(elements)
: Removes the stamped status of elements.
updateLayout()
: Updates the layout and triggers layoutComplete event. This method is now deprecated; use layout()
instead.
Packery emits several custom events on its instance. You can listen for these using the on()
method.
layoutComplete
: Fired when a layout update is complete.arrangeComplete
: Fired when items are arranged.itemTransitionStart
: Fired when an item’s transition animation starts.itemTransitionEnd
: Fired when an item’s transition animation ends.packery:dragItemLayout
: Fired during drag-and-drop operations (if integrated with a drag-and-drop library) to handle layout changes.Example of listening for the layoutComplete
event:
.on( 'layoutComplete', function( data ) {
packeryconsole.log( 'layout is complete', data );
; })
These options are passed as the second argument to the Packery constructor.
columnWidth
: Number, string, or DOM element. The width of each column.rowHeight
: Number or string. The height of each row.itemSelector
: String. Selector string that specifies the item elements within the container. Defaults to >*
.gutter
: Number or object. The spacing between items. Can be a single number (for uniform spacing) or an object { horizontal: x, vertical: y }
.percentPosition
: Boolean. Whether to use percentages for item positioning. Defaults to false
.transitionDuration
: Number. The duration of transition animations in milliseconds. Defaults to a value defined in Packery.isFitWidth
: Boolean. Whether to resize the container to fit its contents horizontally. Defaults to false
.originLeft
: Boolean. Whether to start positioning items from the left. Defaults to true
.originTop
: Boolean. Whether to start positioning items from the top. Defaults to true
.isHorizontal
: Boolean. Whether to lay out horizontally (rows) instead of vertically (columns) - not recommended to use.Remember to consult the official Packery documentation for the most current and detailed API reference, as specifics might change between versions.
Packery excels at creating clean and efficient grid layouts, even with items of varying sizes. Here’s a basic example demonstrating a simple grid:
<div id="grid">
<div class="grid-item">Item 1</div>
<div class="grid-item">Item 2</div>
<div class="grid-item">Item 3</div>
<div class="grid-item wide">Item 4 (wider)</div>
<div class="grid-item">Item 5</div>
</div>
<script>
const grid = new Packery( '#grid', {
columnWidth: 100,
gutter: 10
;
} )</script>
In this example, columnWidth
sets a base column width, and gutter
adds spacing between items. The .wide
class could be used with CSS to make certain items twice the width of standard items. Packery automatically arranges the items to fill the available space efficiently.
While Packery doesn’t have a specific “masonry” mode, you can easily achieve a masonry-like effect by setting appropriate columnWidth
and letting Packery’s bin-packing algorithm handle the layout.
<div id="masonry-grid">
<!-- Items of various heights -->
</div>
<script>
new Packery( '#masonry-grid', {
columnWidth: 100,
gutter: 10
;
} )</script>
The key here is to have items with varying heights; Packery will arrange them in a way that resembles a traditional masonry layout. Experiment with different columnWidth
values to fine-tune the appearance.
Packery is ideal for building responsive image galleries. Combine it with CSS media queries and percentage-based sizing to create galleries that adapt to different screen sizes:
<div id="image-gallery">
<img src="image1.jpg" alt="Image 1">
<img src="image2.jpg" alt="Image 2">
<!-- More images -->
</div>
<script>
const gallery = new Packery( '#image-gallery', {
itemSelector: 'img',
percentPosition: true, // Important for responsive layouts
columnWidth: '.image-item' //Use a class selector for width
;
} )</script>
Here, percentPosition: true
is crucial; it ensures that item positions are calculated as percentages, making the layout responsive. A class selector .image-item
could be applied to the images, controlling their dimensions. Adjust the CSS based on screen size using media queries.
Packery handles dynamic updates gracefully. Use the appended
and prepend
methods to add items, and remove
to delete them. Remember to call packery.layout()
after each update to refresh the layout.
// Add a new item
const newItem = document.createElement('div');
.className = 'grid-item';
newItem.innerHTML = 'New Item';
newItem.appended( newItem );
grid.layout();
grid
// Remove an item
const itemToRemove = document.querySelector('.remove-me');
.remove( itemToRemove );
grid.layout(); grid
This shows how to add and remove items, with grid.layout()
ensuring the layout is recalculated.
Packery itself doesn’t directly handle filtering and sorting. You need to combine it with another library (like Isotope) or manage this yourself by manipulating the DOM order of items and calling packery.layout()
.
A basic example of filtering (without a library) involves showing/hiding elements based on a criteria and then calling layout()
:
// Example Filter: hide items with class 'hidden'
const hiddenItems = document.querySelectorAll('.hidden');
.forEach(item => item.style.display = 'none');
hiddenItems.layout();
grid
// Example Filter: show them back
.forEach(item => item.style.display = 'block');
hiddenItems.layout(); grid
For more complex filtering and sorting, using Isotope is strongly recommended. Isotope provides a robust API for managing such operations seamlessly with Packery. Remember to consult Isotope’s documentation for details.