Muuri is a JavaScript layout engine that allows you to build all kinds of layouts (no kidding!) and make them responsive, sortable, filterable, draggable and/or animated. Comparing to what's out there Muuri is a combination of Packery, Masonry, Isotope and Sortable. Wanna see it in action? Check out the demo on the website.
Features
You can build pretty amazing layouts without a single line of JavaScript these days. However, sometimes (rarely though) CSS just isn't enough, and that's where Muuri comes along. At it's very core Muuri is a layout engine which is limited only by your imagination. You can seriously build any kind of layout, asynchronously in web workers if you wish.
Custom layouts aside, you might need to sprinkle some flare (animation) and/or interactivity (filtering / sorting / drag & drop) on your layout (be it CSS or JS based). Stuff gets complex pretty fast and most of us probably reach for existing libraries to handle the complexity at that point. This is why most of these extra features are built into Muuri's core, so you don't have to go hunting for additional libraries or re-inventing the wheel for the nth time.
The long-term goal of Muuri is to provide a simple (and as low-level as possible) API for building amazing layouts with unmatched performance and most of the complexity abstracted away.
Install via npm:
npm install muuri
Or download:
Or link directly:
<script src="https://cdn.jsdelivr.net/npm/muuri@0.9.5/dist/muuri.min.js"></script>
Muuri uses Web Animations to handle all the animations by default. If you need to use Muuri on a browser that does not support Web Animations you need to use a polyfill.
Install via npm:
npm install web-animations-js
Or download:
Or link directly:
<script src="https://cdn.jsdelivr.net/npm/web-animations-js@2.3.2/web-animations.min.js"></script>
<div class="grid">
<div class="item">
<div class="item-content">
<!-- Safe zone, enter your custom markup -->
This can be anything.
<!-- Safe zone ends -->
</div>
</div>
<div class="item">
<div class="item-content">
<!-- Safe zone, enter your custom markup -->
<div class="my-custom-content">
Yippee!
</div>
<!-- Safe zone ends -->
</div>
</div>
</div>
overflow: auto;
or overflow: scroll;
to the grid element. Muuri's calculation logic does not account for that and you will see some item jumps when dragging starts. Always use a wrapper element for the grid element where you set the auto
/scroll
overflow values..grid {
position: relative;
}
.item {
display: block;
position: absolute;
width: 100px;
height: 100px;
margin: 5px;
z-index: 1;
background: #000;
color: #fff;
}
.item.muuri-item-dragging {
z-index: 3;
}
.item.muuri-item-releasing {
z-index: 2;
}
.item.muuri-item-hidden {
z-index: 0;
}
.item-content {
position: relative;
width: 100%;
height: 100%;
}
The bare minimum configuration is demonstrated below. You must always provide the grid element (or a selector so Muuri can fetch the element for you), everything else is optional.
var grid = new Muuri('.grid');
You can view this little tutorial demo here. After that you might want to check some other demos as well.
Muuri
is a constructor function and should be always instantiated with the new
keyword. For the sake of clarity, we refer to a Muuri instance as grid throughout the documentation.
Syntax
Muuri( element, [options] )
Parameters
null
.Default options
The default options are stored in Muuri.defaultOptions
object, which in it's default state contains the following configuration:
{
// Initial item elements
items: '*',
// Default show animation
showDuration: 300,
showEasing: 'ease',
// Default hide animation
hideDuration: 300,
hideEasing: 'ease',
// Item's visible/hidden state styles
visibleStyles: {
opacity: '1',
transform: 'scale(1)'
},
hiddenStyles: {
opacity: '0',
transform: 'scale(0.5)'
},
// Layout
layout: {
fillGaps: false,
horizontal: false,
alignRight: false,
alignBottom: false,
rounding: false
},
layoutOnResize: 150,
layoutOnInit: true,
layoutDuration: 300,
layoutEasing: 'ease',
// Sorting
sortData: null,
// Drag & Drop
dragEnabled: false,
dragContainer: null,
dragHandle: null,
dragStartPredicate: {
distance: 0,
delay: 0
},
dragAxis: 'xy',
dragSort: true,
dragSortHeuristics: {
sortInterval: 100,
minDragDistance: 10,
minBounceBackAngle: 1
},
dragSortPredicate: {
threshold: 50,
action: 'move',
migrateAction: 'move'
},
dragRelease: {
duration: 300,
easing: 'ease',
useDragContainer: true
},
dragCssProps: {
touchAction: 'none',
userSelect: 'none',
userDrag: 'none',
tapHighlightColor: 'rgba(0, 0, 0, 0)',
touchCallout: 'none',
contentZooming: 'none'
},
dragPlaceholder: {
enabled: false,
createElement: null,
onCreate: null,
onRemove: null
},
dragAutoScroll: {
targets: [],
handle: null,
threshold: 50,
safeZone: 0.2,
speed: Muuri.AutoScroller.smoothSpeed(1000, 2000, 2500),
sortDuringScroll: true,
smoothStop: false,
onStart: null,
onStop: null
},
// Classnames
containerClass: 'muuri',
itemClass: 'muuri-item',
itemVisibleClass: 'muuri-item-shown',
itemHiddenClass: 'muuri-item-hidden',
itemPositioningClass: 'muuri-item-positioning',
itemDraggingClass: 'muuri-item-dragging',
itemReleasingClass: 'muuri-item-releasing',
itemPlaceholderClass: 'muuri-item-placeholder'
}
You can modify the default options easily:
Muuri.defaultOptions.showDuration = 400;
Muuri.defaultOptions.dragSortPredicate.action = 'swap';
This is how you would use the options:
// Minimum configuration.
var gridA = new Muuri('.grid-a');
// Providing some options.
var gridB = new Muuri('.grid-b', {
items: '.item',
});
The initial item elements, which should be children of the grid element. All elements that are not children of the grid element (e.g. if they are not in the DOM yet) will be appended to the grid element. You can provide an array of elements, NodeList, HTMLCollection or a selector (string). If you provide a selector Muuri uses it to filter the current child elements of the container element and sets them as initial items. By default all current child elements of the provided grid element are used as initial items.
'*'
.Examples
// Use specific items.
var grid = new Muuri(elem, {
items: [elemA, elemB, elemC],
});
// Use node list.
var grid = new Muuri(elem, {
items: elem.querySelectorAll('.item'),
});
// Use selector.
var grid = new Muuri(elem, {
items: '.item',
});
Show animation duration in milliseconds. Set to 0
to disable show animation.
300
.Examples
var grid = new Muuri(elem, {
showDuration: 600,
});
Show animation easing. Accepts any valid Animation easing value.
'ease'
.Examples
var grid = new Muuri(elem, {
showEasing: 'cubic-bezier(0.215, 0.61, 0.355, 1)',
});
Hide animation duration in milliseconds. Set to 0
to disable hide animation.
300
.Examples
var grid = new Muuri(elem, {
hideDuration: 600,
});
Hide animation easing. Accepts any valid Animation easing value.
'ease'
.Examples
var grid = new Muuri(elem, {
hideEasing: 'cubic-bezier(0.215, 0.61, 0.355, 1)',
});
The styles that will be applied to all visible items. These styles are also used for the show/hide animations which means that you have to have the same style properties in visibleStyles and hiddenStyles options. Be sure to define all style properties camel cased and without vendor prefixes (Muuri prefixes the properties automatically where needed).
{
opacity: 1,
transform: 'scale(1)'
}
Examples
var grid = new Muuri(elem, {
visibleStyles: {
opacity: 1,
transform: 'rotate(45deg)',
},
hiddenStyles: {
opacity: 0,
transform: 'rotate(-45deg)',
},
});
The styles that will be applied to all hidden items. These styles are also used for the show/hide animations which means that you have to have the same style properties in visibleStyles and hiddenStyles options. Be sure to define all style properties camel cased and without vendor prefixes (Muuri prefixes the properties automatically where needed).
{
opacity: 0,
transform: 'scale(0.5)'
}
Examples
var grid = new Muuri(elem, {
visibleStyles: {
opacity: 1,
transform: 'rotate(45deg)',
},
hiddenStyles: {
opacity: 0,
transform: 'rotate(-45deg)',
},
});
Define how the items will be positioned. Muuri ships with a configurable layout algorithm which is used by default. It's pretty flexible and suitable for most common situations (lists, grids and even bin packed grids). If that does not fit the bill you can always provide your own layout algorithm (it's not as scary as it sounds).
Muuri supports calculating the layout both synchronously and asynchronously. By default (if you use the default layout algorithm) Muuri will use two shared web workers to compute the layouts asynchronously. In browsers that do not support web workers Muuri will fallback to synchronous layout calculations.
{
fillGaps: false,
horizontal: false,
alignRight: false,
alignBottom: false,
rounding: false
}
Provide an object to configure the default layout algorithm with the following properties
false
.true
the algorithm goes through every item in order and places each item to the first available free slot, even if the slot happens to be visually before the previous element's slot. Practically this means that the items might not end up visually in order, but there will be less gaps in the grid.false
.true
the grid works in landscape mode (grid expands to the right). Use for horizontally scrolling sites. When false
the grid works in "portrait" mode and expands downwards.false
.true
the items are aligned from right to left.false
.true
the items are aligned from the bottom up.false
.true
the item dimensions are rounded to a precision of two decimals for the duration of layout calculations. This procedure stabilizes the layout calculations quite a lot, but also causes a hit on performance. Use only if you see your layout behaving badly, which might happen sometimes (hopefully never) when using relative dimension values.Provide a function to use a custom layout algorithm
When you provide a custom layout function Muuri calls it whenever calculation of layout is necessary. Before calling the layout function Muuri always calculates the current width and height of the grid element and also creates an array of all the items that are part of the layout currently (all active items).
The layout function always receives the following arguments:
Muuri.Item
instances. A new array instance is created for each layout so there's no harm in manipulating this if you need to (or using it as is for the layout data object).If the layout function's calculations are asynchronous you can optionally return a cancel function, which Muuri will call if there is a new layout request before the current layout has finished it's calculations.
The layout object, which needs to be provided to the callback, must include the following properties.
layoutId
provided by Muuri).items
array here which is provided by Muuri (in case you haven't mutated it). This array items must be identical to the array of items provided by Muuri.[a, b]
this should be [aLeft, aTop, bLeft, bTop]
. You have to calculate the left
and top
position for each item in the provided items array in the same order the items are provided.layoutStart
event is emitted. E.g. {width: '100%', height: '200px', minWidth: '200px'}
.box-sizing
CSS property is set to border-box
the element's borders are included in the dimensions. E.g. if you set {width: '100px', width: '100px'}
here and the element has a 5px
border and box-sizing
is set to border-box
, then the layout's effective width
and height
(as perceived by Muuri) will be 90px
. So remember to take that into account and add the borders to the dimensions when necessary. If this sounds complicated then just don't set borders directly to the grid element or make sure that grid element's box-sizing
is set to content-box
(which is the default value).Note that you can add additional properties to the layout object if you wish, e.g. the default layout algorithm also stores the layout's width and height (in pixels) to the layout object.
Examples
// Customize the default layout algorithm.
var grid = new Muuri(elem, {
layout: {
fillGaps: true,
horizontal: true,
alignRight: true,
alignBottom: true,
rounding: true,
},
});
// Build your own layout algorithm.
var grid = new Muuri(elem, {
layout: function (grid, layoutId, items, width, height, callback) {
var layout = {
id: layoutId,
items: items,
slots: [],
styles: {},
};
// Calculate the slots asynchronously. Note that the timeout is here only
// as an example and does not help at all in the calculations. You should
// offload the calculations to web workers if you want real benefits.
// Also note that doing asynchronous layout is completely optional and you
// can call the callback function synchronously also.
var timerId = window.setTimeout(function () {
var item,
m,
x = 0,
y = 0,
w = 0,
h = 0;
for (var i = 0; i < items.length; i++) {
item = items[i];
x += w;
y += h;
m = item.getMargin();
w = item.getWidth() + m.left + m.right;
h = item.getHeight() + m.top + m.bottom;
layout.slots.push(x, y);
}
w += x;
h += y;
// Set the CSS styles that should be applied to the grid element.
layout.styles.width = w + 'px';
layout.styles.height = h + 'px';
// When the layout is fully computed let's call the callback function and
// provide the layout object as it's argument.
callback(layout);
}, 200);
// If you are doing an async layout you _can_ (if you want to) return a
// function that cancels this specific layout calculations if it's still
// processing/queueing when the next layout is requested.
return function () {
window.clearTimeout(timerId);
};
},
});
Should Muuri automatically trigger layout
method on window resize? Set to false
to disable. When a number or true
is provided Muuri will automatically position the items every time window is resized. The provided number (true
is transformed to 0
) equals to the amount of time (in milliseconds) that is waited before items are positioned after each window resize event.
150
.Examples
// No layout on resize.
var grid = new Muuri(elem, {
layoutOnResize: false,
});
// Layout on resize (instantly).
var grid = new Muuri(elem, {
layoutOnResize: true,
});
// Layout on resize (with 200ms debounce).
var grid = new Muuri(elem, {
layoutOnResize: 200,
});
Should Muuri trigger layout
method automatically on init?
true
.Examples
var grid = new Muuri(elem, {
layoutOnInit: false,
});
The duration for item's layout animation in milliseconds. Set to 0
to disable.
300
.Examples
var grid = new Muuri(elem, {
layoutDuration: 600,
});
The easing for item's layout animation. Accepts any valid Animation easing value.
'ease'
.Examples
var grid = new Muuri(elem, {
layoutEasing: 'cubic-bezier(0.215, 0.61, 0.355, 1)',
});
The sort data getter functions. Provide an object where the key is the name of the sortable attribute and the function returns a value (from the item) by which the items can be sorted.
null
.Examples
var grid = new Muuri(elem, {
sortData: {
foo: function (item, element) {
return parseFloat(element.getAttribute('data-foo'));
},
bar: function (item, element) {
return element.getAttribute('data-bar').toUpperCase();
},
},
});
// Refresh sort data whenever an item's data-foo or data-bar changes
grid.refreshSortData();
// Sort the grid by foo and bar.
grid.sort('foo bar');
Should items be draggable?
false
.Examples
var grid = new Muuri(elem, {
dragEnabled: true,
});
The element the dragged item should be appended to for the duration of the drag. If set to null
(which is also the default value) the grid element will be used.
null
.Examples
var grid = new Muuri(elem, {
dragContainer: document.body,
});
The element within the item element that should be used as the drag handle. This should be a CSS selector which will be fed to element.querySelector()
as is to obtain the handle element when the item is instantiated. If no valid element is found or if this is null
Muuri will use the item element as the handle.
null
.Examples
var grid = new Muuri(elem, {
dragHandle: '.handle',
});
A function that determines when the item should start moving when the item is being dragged. By default uses the built-in start predicate which has some configurable options.
{
distance: 0,
delay: 0
}
If an object is provided the default start predicate handler will be used. You can define the following properties:
0
.0
.If you provide a function you can customize the drag start logic as you please. When the user starts to drag an item this predicate function will be called until you return true
or false
. If you return true
the item will begin to move whenever the item is dragged. If you return false
the item will not be moved at all. Note that after you have returned true
or false
this function will not be called until the item is released and dragged again.
The predicate function receives two arguments:
Examples
// Configure the default predicate
var grid = new Muuri(elem, {
dragStartPredicate: {
distance: 10,
delay: 100,
},
});
// Provide your own predicate
var grid = new Muuri(elem, {
dragStartPredicate: function (item, e) {
// Start moving the item after the item has been dragged for one second.
if (e.deltaTime > 1000) {
return true;
}
},
});
// Pro tip: provide your own predicate and fall back to the default predicate.
var grid = new Muuri(elem, {
dragStartPredicate: function (item, e) {
// If this is final event in the drag process, let's prepare the predicate
// for the next round (do some resetting/teardown). The default predicate
// always needs to be called during the final event if there's a chance it
// has been triggered during the drag process because it does some necessary
// state resetting.
if (e.isFinal) {
Muuri.ItemDrag.defaultStartPredicate(item, e);
return;
}
// Prevent first item from being dragged.
if (grid.getItems()[0] === item) {
return false;
}
// For other items use the default drag start predicate.
return Muuri.ItemDrag.defaultStartPredicate(item, e);
},
});
Force items to be moved only vertically or horizontally when dragged. Set to 'x'
for horizontal movement and to 'y'
for vertical movement. By default items can be dragged both vertically and horizontally.
'xy'
.'x'
, 'y'
, 'xy'
.Examples
// Move items only horizontally when dragged.
var grid = new Muuri(elem, {
dragAxis: 'x',
});
// Move items only vertically when dragged.
var grid = new Muuri(elem, {
dragAxis: 'y',
});
Should the items be sorted during drag? A simple boolean will do just fine here.
Alternatively you can do some advanced stuff and control within which grids a specific item can be sorted and dragged into. To do that you need to provide a function which receives the dragged item as its first argument and should return an array of grid instances. An important thing to note here is that you need to return all the grid instances you want the dragged item to sort within, even the current grid instance. If you return an empty array the dragged item will not cause sorting at all.
true
.Examples
// Disable drag sorting.
var grid = new Muuri(elem, {
dragSort: false,
});
// Multigrid drag sorting.
var gridA = new Muuri(elemA, { dragSort: getAllGrids });
var gridB = new Muuri(elemB, { dragSort: getAllGrids });
var gridC = new Muuri(elemC, { dragSort: getAllGrids });
function getAllGrids(item) {
return [gridA, gridB, gridC];
}
Defines various heuristics so that sorting during drag would be smoother and faster.
{
sortInterval: 100,
minDragDistance: 10,
minBounceBackAngle: 1
}
You can define the following properties:
100
.dragSortPredicate
function is called.10
.dragSortPredicate
can be called.1
.minDragDistance
and needs it to be set to 3
at minimum to be enabled at all.Examples
var grid = new Muuri(elem, {
dragEnabled: true,
dragSortHeuristics: {
sortInterval: 10,
minDragDistance: 5,
minBounceBackAngle: Math.PI / 2,
},
});
// Pro tip: If you want drag sorting happening only on release set a really
// long sortInterval. A bit of a hack, but works.
var grid = new Muuri(elem, {
dragEnabled: true,
dragSortHeuristics: {
sortInterval: 3600000, // 1 hour
},
});
Defines the logic for the sort procedure during dragging an item.
{
threshold: 50,
action: 'move',
migrateAction: 'move'
}
If an object is provided the default sort predicate handler will be used. You can define the following properties:
50
.1
- 100
.'move'
.'move'
, 'swap'
.'move'
.'move'
, 'swap'
.Alternatively you can provide your own callback function where you can define your own custom sort logic. The callback function receives two arguments:
The callback should return a falsy value if sorting should not occur. If, however, sorting should occur the callback should return an object containing the following properties:
'move'
.'move'
or 'swap'
.Examples
// Customize the default predicate.
var grid = new Muuri(elem, {
dragSortPredicate: {
threshold: 90,
action: 'swap',
},
});
// Provide your own predicate.
var grid = new Muuri(elem, {
dragSortPredicate: function (item, e) {
if (e.deltaTime < 1000) return false;
return {
index: Math.round(e.deltaTime / 1000) % 2 === 0 ? -1 : 0,
action: 'swap',
};
},
});
// Pro tip: use the default predicate as fallback in your custom predicate.
var grid = new Muuri(elem, {
dragSortPredicate: function (item, e) {
if (item.classList.contains('no-sort')) return false;
return Muuri.ItemDrag.defaultSortPredicate(item, {
action: 'swap',
threshold: 75,
});
},
});
{
duration: 300,
easing: 'ease',
useDragContainer: true
}
You can define the following properties:
300
.0
to disable.'ease'
.true
.true
the item element will remain within the dragContainer
for the duration of the release process. Otherwise the item element will be inserted within the grid element (if not already inside it) at the beginning of the release process.Examples
var grid = new Muuri(elem, {
dragRelease: {
duration: 600,
easing: 'ease-out',
useDragContainer: false,
},
});
Drag specific CSS properties that Muuri sets to the draggable item elements. Muuri automatically prefixes the properties before applying them to the element. touchAction
property is required to be always defined, but the other properties are optional and can be omitted by setting their value to an empty string if you want to e.g. define them via CSS only.
{
touchAction: 'none',
userSelect: 'none',
userDrag: 'none',
tapHighlightColor: 'rgba(0, 0, 0, 0)',
touchCallout: 'none',
contentZooming: 'none'
}
You can define the following properties:
'none'
.'none'
.'none'
.'rgba(0, 0, 0, 0)'
.'none'
.'none'
.Examples
// Only set the required touch-action CSS property via the options if you for
// example want to set the other props via CSS instead.
var grid = new Muuri(elem, {
dragEnabled: true,
dragCssProps: {
touchAction: 'pan-y',
userSelect: '',
userDrag: '',
tapHighlightColor: '',
touchCallout: '',
contentZooming: '',
},
});
If you want a placeholder item to appear for the duration of an item's drag & drop procedure you can enable and configure it here. The placeholder animation duration is fetched from the grid's layoutDuration
option and easing from the grid's layoutEasing
option. Note that a special placeholder class is given to all drag placeholders and is customizable via itemPlaceholderClass option.
{
enabled: false,
createElement: null,
onCreate: null,
onRemove: null
}
You can define the following properties:
false
.null
.div
element is created when a placeholder is summoned.null
.null
.Examples
// This example showcases how to pool placeholder elements
// for better performance and memory efficiency.
var phPool = [];
var phElem = document.createElement('div');
var grid = new Muuri(elem, {
dragEnabled: true,
dragPlaceholder: {
enabled: true,
createElement(item) {
return phPool.pop() || phElem.cloneNode();
},
onCreate(item, element) {
// If you want to do something after the
// placeholder is fully created, here's
// the place to do it.
},
onRemove(item, element) {
phPool.push(element);
},
},
});
If you want to trigger scrolling on any element during dragging you can enable and configure it here. By default this feature is disabled. When you use this feature it is highly recommended that you create a fixed
positioned element right under document.body
and use that as the dragContainer
for all the dragged items. If you don't do this and a dragged item's parent is auto-scrolled, the dragged item will potentially grow the scrolled element's scroll area to infinity unintentionally.
{
targets: [],
handle: null,
threshold: 50,
safeZone: 0.2,
speed: Muuri.AutoScroller.smoothSpeed(1000, 2000, 2500),
sortDuringScroll: true,
smoothStop: false,
onStart: null,
onStop: null
}
You can define the following properties:
[]
.item
instance as it's argument and which should return an array of scroll targets (elements and/or objects). This way you can provide different configurations for different items.Muuri.AutoScroller.AXIS_X | Muuri.AutoScroller.AXIS_Y
.Muuri.AutoScroller.AXIS_X
.Muuri.AutoScroller.AXIS_Y
.0
.priority
comes in. Here you can manually tell Muuri which element to prefer over another in these scenarios. The element with highest priority always wins the fight, in matches with equal priority we determine the winner by the amount of overlap.null
.null
.null
) the dragged element's dimensions and offsets are used. However, you can provide a function which should return an object containing the handle's client offsets in pixels (left
and top
) and dimensions in pixels (width
and height
). The function receives the following arguments:Muuri.AutoScroller.pointerHandle(pointerSize)
utility method if you want to use the pointer (instead of the element) as the handle.50
.0
the scrolling will start when the dragged element reaches the scrollable element's edge. Do note that Muuri dynamically adjusts the scroll element's edge for the calculations (when needed).0.2
.0
and 1
. So in practice, if you set this to e.g 0.5
the safe zone would be 50% of the scrollable element's width and/or height.Muuri.AutoScroller.smoothSpeed(1000, 2000, 2500)
.number
or dynamic speed with a function
. The function is called before every scroll operation and should return the speed (number
, pixels per second) for the next scroll operation. The function receives three arguments:Muuri.Item
instance.Muuri.AutoScroller.LEFT
, Muuri.AutoScroller.RIGHT
, Muuri.AutoScroller.UP
, Muuri.AutoScroller.DOWN
.handle
option) current distance from the edge of the scroll element. E.g, if direction
is Muuri.AutoScroller.RIGHT
then distance is scrollElement.getBoundingClientRect().right - handleRect.right
, and if direction
is Muuri.AutoScroller.LEFT
then distance is handleRect.left - scrollElement.getBoundingClientRect().left
. Can be a negative value too.requestAnimationFrame
's delta time (in milliseconds).true
it means that the associated drag item does not satisfy the threshold anymore. You should now start decreasing the speed towards 0
to allow the item to come to rest smoothly.Muuri.AutoScroller.smoothSpeed()
for dynamic speed that provides a smooth scrolling experience. When executed it creates and returns a speed function which you can directly provide for speed
option. The method requires three arguments (in the following order):0
or less.true
.false
.false
, scrolling will stop immediately. If you set this to true
scrolling will continue until speed drops to 0
. When this option is enabled
you must handle decelerating the speed to 0
manually within speed
function, so do not enable this option if you use a static speed value. The default speed
function handles the deceleration automatically.null
.Muuri.Item
instance.Muuri.AutoScroller.LEFT
, Muuri.AutoScroller.RIGHT
, Muuri.AutoScroller.UP
, Muuri.AutoScroller.DOWN
.null
.Muuri.Item
instance.Muuri.AutoScroller.LEFT
, Muuri.AutoScroller.RIGHT
, Muuri.AutoScroller.UP
, Muuri.AutoScroller.DOWN
.Examples
// Create a fixed drag container for the dragged items, this is done with JS
// just for example's purposes.
var dragContainer = document.createElement('div');
dragContainer.style.position = 'fixed';
dragContainer.style.left = '0px';
dragContainer.style.top = '0px';
dragContainer.style.zIndex = 1000;
document.body.appendChild(dragContainer);
var grid = new Muuri(elem, {
dragEnabled: true,
dragContainer: dragContainer,
dragAutoScroll: {
targets: [
// Scroll window on both x-axis and y-axis.
{ element: window, priority: 0 },
// Scroll scrollElement (can be any scrollable element) on y-axis only,
// and prefer it over window in conflict scenarios.
{ element: scrollElement, priority: 1, axis: Muuri.AutoScroller.AXIS_Y },
],
// Let's use the dragged item element as the handle.
handle: null,
// Start auto-scroll when the distance from scroll target's edge to dragged
// item is 40px or less.
threshold: 40,
// Make sure the inner 10% of the scroll target's area is always "safe zone"
// which does not trigger auto-scroll.
safeZone: 0.1,
// Let's define smooth dynamic speed.
// Max speed: 2000 pixels per second
// Acceleration: 2700 pixels per second
// Deceleration: 3200 pixels per second.
speed: Muuri.AutoScroller.smoothSpeed(2000, 2700, 3200),
// Let's not sort during scroll.
sortDuringScroll: false,
// Enable smooth stop.
smoothStop: true,
// Finally let's log some data when auto-scroll starts and stops.
onStart: function (item, scrollElement, direction) {
console.log('AUTOSCROLL STARTED', item, scrollElement, direction);
},
onStop: function (item, scrollElement, direction) {
console.log('AUTOSCROLL STOPPED', item, scrollElement, direction);
},
},
});
Grid element's class name.
'muuri'
.Examples
var grid = new Muuri(elem, {
containerClass: 'foo',
});
Item element's class name.
'muuri-item'
.Examples
var grid = new Muuri(elem, {
itemClass: 'foo-item',
});
Visible item's class name.
'muuri-item-shown'
.Examples
var grid = new Muuri(elem, {
itemVisibleClass: 'foo-item-shown',
});
Hidden item's class name.
'muuri-item-hidden'
.Examples
var grid = new Muuri(elem, {
itemHiddenClass: 'foo-item-hidden',
});
This class name will be added to the item element for the duration of positioning.
'muuri-item-positioning'
.Examples
var grid = new Muuri(elem, {
itemPositioningClass: 'foo-item-positioning',
});
This class name will be added to the item element for the duration of drag.
'muuri-item-dragging'
.Examples
var grid = new Muuri(elem, {
itemDraggingClass: 'foo-item-dragging',
});
This class name will be added to the item element for the duration of release.
'muuri-item-releasing'
.Examples
var grid = new Muuri(elem, {
itemReleasingClass: 'foo-item-releasing',
});
This class name will be added to the drag placeholder element.
'muuri-item-placeholder'
.Examples
var grid = new Muuri(elem, {
itemPlaceholderClass: 'foo-item-placeholder',
});
Get the grid element.
Returns — element
Examples
var elem = grid.getElement();
Get a single grid item by element or by index. Target can also be a Muuri.Item
instance in which case the function returns the item if it exists within related Muuri
instance. If nothing is found with the provided target, null
is returned.
Parameters
Returns — Muuri.Item / null
null
if no item is found.Examples
// Get first item in grid.
var itemA = grid.getItem(0);
// Get item by element reference.
var itemB = grid.getItem(someElement);
Get all items in the grid. Optionally you can provide specific targets (indices or elements).
Parameters
Returns — array
Examples
// Get all items, both active and inactive.
var allItems = grid.getItems();
// Get all active items.
var activeItems = grid.getItems().filter(function (item) {
return item.isActive();
});
// Get all positioning items.
var positioningItems = grid.getItems().filter(function (item) {
return item.isPositioning();
});
// Get the first item.
var firstItem = grid.getItems(0)[0];
// Get specific items by their elements.
var items = grid.getItems([elemA, elemB]);
Update the cached dimensions of the instance's items. By default all the items are refreshed, but you can also provide an array of target items as the first argument if you want to refresh specific items. Note that all hidden items are not refreshed by default since their display
property is 'none'
and their dimensions are therefore not readable from the DOM. However, if you do want to force update hidden item dimensions too you can provide true
as the second argument, which makes the elements temporarily visible while their dimensions are being read.
Parameters
true
to read dimensions of hidden items too (and make them visible for the duration of the reading).false
.Returns — Muuri
Examples
// Refresh dimensions of all items.
grid.refreshItems();
// Refresh dimensions of specific items.
grid.refreshItems([0, someElem, someItem]);
// Refresh dimensions of specific items and force read their
// dimensions even if they are hidden. Note that this has performance cost.
grid.refreshItems([0, someElem, someHiddenItem], true);
Refresh the sort data of the grid's items.
Parameters
Returns — Muuri
Examples
// Refresh the sort data for every item.
grid.refreshSortData();
// Refresh the sort data for specific items.
grid.refreshSortData([0, someElem, someItem]);
Synchronize the item elements in the DOM to match the order of the items in the grid. This comes handy if you need to keep the DOM structure matched with the order of the items. Note that if an item's element is not currently a child of the grid element (if it is dragged for example) it is ignored and left untouched. The reason why item elements are not kept in sync automatically is that there's rarely a need for that as they are absolutely positioned elements.
Returns — Muuri
Examples
// Let's say we have to move the first item in the grid as the last.
grid.move(0, -1);
// Now the order of the item elements in the DOM is not in sync anymore
// with the order of the items in the grid. We can sync the DOM with
// synchronize method.
grid.synchronize();
Calculate item positions and move items to their calculated positions, unless they are already positioned correctly. The grid's height/width (depends on the layout algorithm) is also adjusted according to the position of the items.
Parameters
false
.Returns — Muuri
Examples
// Layout items.
grid.layout();
// Layout items instantly (without animations).
grid.layout(true);
// Layout all items and define a callback that will be called
// after all items have been animated to their positions.
grid.layout(function (items, hasLayoutChanged) {
// If hasLayoutChanged is `true` it means that there has been another layout
// call before this layout had time to finish positioning all the items.
console.log('layout done!');
});
Add new items by providing the elements you wish to add to the grid and optionally provide the index where you want the items to be inserted into. All elements that are not already children of the grid element will be automatically appended to the grid element. If an element has it's CSS display property set to none it will be marked as inactive during the initiation process. As long as the item is inactive it will not be part of the layout, but it will retain it's index. You can activate items at any point with grid.show()
method. This method will automatically call grid.layout()
if one or more of the added elements are visible. If only hidden items are added no layout will be called. All the new visible items are positioned without animation during their first layout.
Parameters
undefined
) Muuri will automatically detect from each element's display
style if the item should be active (visible) or inactive (hidden) on init. If the element's display
style is none
then the item will be inactive on init. However, you can also provide a boolean here to force the item to be active (true
) or inactive (false
) on init.undefined
.-1
will insert the items to the end of the list while 0
will insert the items to the beginning. Note that the DOM elements are always just appended to the grid element regardless of the index value. You can use the grid.synchronize()
method to arrange the DOM elements to the same order as the items.-1
.grid.layout()
is called at the end of this method. With this argument you can control the layout call. You can disable the layout completely with false
, or provide a callback function for the layout method, or provide the string 'instant'
to make the layout happen instantly without any animations.true
.Returns — array
Examples
// Add two new items to the end.
var newItemsA = grid.add([elemA, elemB]);
// Add two new items to the beginning.
var newItemsB = grid.add([elemA, elemB], { index: 0 });
// Skip the automatic layout.
var newItemsC = grid.add([elemA, elemB], { layout: false });
Remove items from the grid.
Parameters
false
.grid.layout()
is called at the end of this method. With this argument you can control the layout call. You can disable the layout completely with false
, or provide a callback function for the layout method, or provide the string 'instant'
to make the layout happen instantly without any animations.true
.Returns — array
Examples
// Remove the first item, but keep the element in the DOM.
var removedItemsA = grid.remove(grid.getItems(0));
// Remove items and the associated elements.
var removedItemsB = grid.remove([itemA, itemB], { removeElements: true });
// Skip the layout.
var removedItemsC = grid.remove([itemA, itemB], { layout: false });
Show the targeted items.
Parameters
false
.false
and the show animations will begin immediately.true
.grid.layout()
is called at the end of this method. With this argument you can control the layout call. You can disable the layout completely with false
, or provide a callback function for the layout method, or provide the string 'instant'
to make the layout happen instantly without any animations.true
.Returns — Muuri
Examples
// Show items with animation (if any).
grid.show([itemA, itemB]);
// Show items instantly without animations.
grid.show([itemA, itemB], { instant: true });
// Show items with callback (and with animations if any).
grid.show([itemA, itemB], {
onFinish: function (items) {
console.log('items shown!');
},
});
Hide the targeted items.
Parameters
false
.false
and the hide animations will begin immediately.true
.grid.layout()
is called at the end of this method. With this argument you can control the layout call. You can disable the layout completely with false
, or provide a callback function for the layout method, or provide the string 'instant'
to make the layout happen instantly without any animations.true
.Returns — Muuri
Examples
// Hide items with animation.
grid.hide([itemA, itemB]);
// Hide items instantly without animations.
grid.hide([itemA, itemB], { instant: true });
// Hide items and call the callback function after
// all items are hidden.
grid.hide([itemA, itemB], {
onFinish: function (items) {
console.log('items hidden!');
},
});
Filter items. Expects at least one argument, a predicate, which should be either a function or a string. The predicate callback is executed for every item in the grid. If the return value of the predicate is truthy the item in question will be shown and otherwise hidden. The predicate callback receives the item instance as it's argument. If the predicate is a string it is considered to be a selector and it is checked against every item element in the grid with the native element.matches() method. All the matching items will be shown and others hidden.
Parameters
false
.false
and the visibility animations will begin immediately.true
.grid.layout()
is called at the end of this method. With this argument you can control the layout call. You can disable the layout completely with false
, or provide a callback function for the layout method, or provide the string 'instant'
to make the layout happen instantly without any animations.true
.Returns — Muuri
Examples
// Show all items that have the attribute "data-foo".
grid.filter(function (item) {
return item.getElement().hasAttribute('data-foo');
});
// Or simply just...
grid.filter('[data-foo]');
// Show all items that have a class foo.
grid.filter('.foo');
Sort items. There are three ways to sort the items. The first is simply by providing a function as the comparer which works almost identically to native array sort. The only difference is that the sort is always stable. Alternatively you can sort by the sort data you have provided in the grid's options. Just provide the sort data key(s) as a string (separated by space) and the items will be sorted based on the provided sort data keys. Lastly you have the opportunity to provide a presorted array of items which will be used to sync the internal items array in the same order.
Parameters
grid._items
(retrievable safely via grid.getItems()
), only change the order of items. Muuri does not validate the array of items you provide due to performance reasons.true
. Note that this option has no effect when you provide a pre-sorted array of items.false
.grid.layout()
is called at the end of this method. With this argument you can control the layout call. You can disable the layout completely with false
, or provide a callback function for the layout method, or provide the string 'instant'
to make the layout happen instantly without any animations.true
.Returns — Muuri
Examples
// Sort items by data-id attribute value (ascending).
grid.sort(function (itemA, itemB) {
var aId = parseInt(itemA.getElement().getAttribute('data-id'));
var bId = parseInt(itemB.getElement().getAttribute('data-id'));
return aId - bId;
});
// Sort items with a presorted array of items.
grid.sort(grid.getItems().reverse());
// Sort items using the sort data keys (ascending).
grid.sort('foo bar');
// Sort items using the sort data keys (descending).
grid.sort('foo bar', { descending: true });
// Sort items using the sort data keys. Sort some keys
// ascending and some keys descending.
grid.sort('foo bar:desc');
Move an item to another position in the grid.
Parameters
'move'
: moves item in place of another item.'swap'
: swaps position of items.'move'
.grid.layout()
is called at the end of this method. With this argument you can control the layout call. You can disable the layout completely with false
, or provide a callback function for the layout method, or provide the string 'instant'
to make the layout happen instantly without any animations.true
.Returns — Muuri
Examples
// Move elemA to the index of elemB.
grid.move(elemA, elemB);
// Move the first item in the grid as the last.
grid.move(0, -1);
// Swap positions of elemA and elemB.
grid.move(elemA, elemB, { action: 'swap' });
// Swap positions of the first and the last item.
grid.move(0, -1, { action: 'swap' });
Move an item into another grid.
Parameters
document.body
.grid.layout()
is called for the sending grid at the end of this method. With this argument you can control the layout call. You can disable the layout completely with false
, or provide a callback function for the layout method, or provide the string 'instant'
to make the layout happen instantly without any animations.true
.grid.layout()
is called for the receiving grid at the end of this method. With this argument you can control the layout call. You can disable the layout completely with false
, or provide a callback function for the layout method, or provide the string 'instant'
to make the layout happen instantly without any animations.true
.Returns — Muuri
Examples
// Move the first item of gridA as the last item of gridB.
// The sent item will be appended to document.body.
gridA.send(0, gridB, -1);
// Move the first item of gridA as the last item of gridB.
// The sent item will be appended to someElem.
gridA.send(0, gridB, -1, {
appendTo: someElem,
});
// Do something after the item has been sent and the layout
// processes have finished.
gridA.send(0, gridB, -1, {
layoutSender: function (isAborted, items) {
// Do your thing here...
},
layoutReceiver: function (isAborted, items) {
// Do your other thing here...
},
});
Bind an event listener.
Parameters
Returns — Muuri
Examples
grid.on('layoutEnd', function (items) {
console.log(items);
});
Unbind an event listener.
Parameters
Returns — Muuri
Examples
function onLayoutEnd(items) {
console.log(items);
}
// Start listening to some event.
grid.on('layoutEnd', onLayoutEnd);
/// ...sometime later -> unbind listener.
grid.off('layoutEnd', onLayoutEnd);
Destroy the grid.
Parameters
false
.Returns — Muuri
Examples
// Destroy the instance, but keep
// item element in the DOM.
grid.destroy();
// Destroy the instance and remove
// the item elements from the DOM.
grid.destroy(true);
Triggered after item elements are synchronized via grid.synchronize()
.
Examples
grid.on('synchronize', function () {
console.log('Synced!');
});
Triggered when the the layout procedure begins. More specifically, this event is emitted right after new layout has been generated, internal item positions updated and grid element's dimensions updated. After this event is emitted the items in the layout will be positioned to their new positions. So if you e.g. want to react to grid element dimension changes this is a good place to do that.
Arguments
instant
flag or not.Examples
grid.on('layoutStart', function (items, isInstant) {
console.log(items, isInstant);
});
Triggered after the layout procedure has finished, successfully. Note that if you abort a layout procedure by calling grid.layout()
before items have finished positioning, this event will not be emitted for the aborted layout procedure. In such a case layoutAbort
will be emitted instead.
Arguments
Examples
grid.on('layoutEnd', function (items) {
console.log(items);
// For good measure you might want to filter out all the non-active items,
// because it's techniclly possible that some of the items are
// destroyed/hidden when we receive this event.
var activeItems = items.filter(function (item) {
return item.isActive();
});
});
Triggered if you start a new layout process (grid.layout()
) while the current layout process is still busy positioning items. Note that this event is not triggered if you start a new layout process while the layout is being computed and the items have not yet started positioning.
Arguments
Examples
grid.on('layoutAbort', function (items) {
console.log(items);
// For good measure you might want to filter out all the non-active items,
// because it's techniclly possible that some of the items are destroyed or
// hidden when we receive this event.
var activeItems = items.filter(function (item) {
return item.isActive();
});
});
Triggered after grid.add()
is called.
Arguments
Examples
grid.on('add', function (items) {
console.log(items);
});
Triggered after grid.remove()
is called.
Arguments
Examples
grid.on('remove', function (items, indices) {
console.log(items, indices);
});
Triggered after grid.show()
is called, just before the items are shown.
Arguments
Examples
grid.on('showStart', function (items) {
console.log(items);
});
Triggered after grid.show()
is called, after the items are shown.
Arguments
grid.hide()
to some of the items that are currently being shown, those items will be omitted from this argument.Examples
grid.on('showEnd', function (items) {
console.log(items);
});
Triggered after grid.hide()
is called, just before the items are hidden.
Arguments
Examples
grid.on('hideStart', function (items) {
console.log(items);
});
Triggered after grid.hide()
is called, after the items are hidden.
Arguments
grid.show()
to some of the items that are currently being hidden, those items will be omitted from this argument.Examples
grid.on('hideEnd', function (items) {
console.log(items);
});
Triggered after grid.filter()
is called.
Arguments
Examples
grid.on('filter', function (shownItems, hiddenItems) {
console.log(shownItems);
console.log(hiddenItems);
});
Triggered after grid.sort()
is called.
Arguments
Examples
grid.on('sort', function (currentOrder, previousOrder) {
console.log(currentOrder);
console.log(previousOrder);
});
Triggered after grid.move()
is called or when the grid is sorted during drag. Note that this is event not triggered when an item is dragged into another grid.
Arguments
Examples
grid.on('move', function (data) {
console.log(data);
});
Triggered for the originating grid in the end of the send process (after grid.send()
is called or when an item is dragged into another grid). Note that this event is called before the item's layout starts.
Arguments
Examples
grid.on('send', function (data) {
console.log(data);
});
Triggered for the originating grid in the beginning of the send process (after grid.send()
is called or when an item is dragged into another grid). This event is highly useful in situations where you need to manipulate the sent item (freeze it's dimensions for example) before it is appended to it's temporary layout container as defined in send method options.
Arguments
Examples
grid.on('beforeSend', function (data) {
console.log(data);
});
Triggered for the receiving grid in the end of the send process (after grid.send()
is called or when an item is dragged into another grid). Note that this event is called before the item's layout starts.
Arguments
Examples
grid.on('receive', function (data) {
console.log(data);
});
Triggered for the receiving grid in the beginning of the send process (after grid.send()
is called or when an item is dragged into another grid). This event is highly useful in situations where you need to manipulate the received item (freeze it's dimensions for example) before it is appended to it's temporary layout container as defined in send method options.
Arguments
Examples
grid.on('beforeReceive', function (data) {
console.log(data);
});
Triggered in the beginning of the drag start process when dragging of an item begins. This event is highly useful in situations where you need to manipulate the dragged item (freeze it's dimensions for example) before it is appended to the dragContainer.
Arguments
Examples
grid.on('dragInit', function (item, event) {
console.log(event);
console.log(item);
});
Triggered in the end of the drag start process when dragging of an item begins.
Arguments
Examples
grid.on('dragStart', function (item, event) {
console.log(event);
console.log(item);
});
Triggered every time a dragged item is moved. Note that Muuri has an automatic throttling system which makes sure that this event is triggered at maximum once in an animation frame.
Arguments
Examples
grid.on('dragMove', function (item, event) {
console.log(event);
console.log(item);
});
Triggered when any of the scroll parents of a dragged item is scrolled.
Arguments
Examples
grid.on('dragScroll', function (item, event) {
console.log(event);
console.log(item);
});
Triggered when dragged item is released and the drag process ends.
Arguments
Examples
grid.on('dragEnd', function (item, event) {
console.log(event);
console.log(item);
});
Triggered when a dragged item is released (always after dragEnd
event).
Arguments
Examples
grid.on('dragReleaseStart', function (item) {
console.log(item);
});
Triggered after released item has finished it's position animation.
Arguments
Examples
grid.on('dragReleaseEnd', function (item) {
console.log(item);
});
Triggered after grid is destroyed.
Examples
grid.on('destroy', function () {
console.log('Muuri is no more...');
});
Get the grid instance the item belongs to.
Returns — Muuri
Examples
var grid = item.getGrid();
Get the item element.
Returns — element
Examples
var elem = item.getElement();
Get item element's cached width (in pixels). The returned value includes the element's paddings and borders.
Returns — number
Examples
var width = item.getWidth();
Get item element's cached height (in pixels). The returned value includes the element's paddings and borders.
Returns — number
Examples
var height = item.getHeight();
Get item element's cached margins (in pixels).
Returns — object
Examples
var margin = item.getMargin();
Get item element's cached position (in pixels, relative to the grid element).
Returns — object
Examples
var position = item.getPosition();
Check if the item is currently active. Only active items are considered to be part of the layout.
Returns — boolean
Examples
var isActive = item.isActive();
Check if the item is currently visible.
Returns — boolean
Examples
var isVisible = item.isVisible();
Check if the item is currently animating to visible.
Returns — boolean
Examples
var isShowing = item.isShowing();
Check if the item is currently animating to hidden.
Returns — boolean
Examples
var isHiding = item.isHiding();
Check if the item is currently being positioned.
Returns — boolean
Examples
var isPositioning = item.isPositioning();
Check if the item is currently being dragged.
Returns — boolean
Examples
var isDragging = item.isDragging();
Check if the item is currently being released.
Returns — boolean
Examples
var isReleasing = item.isReleasing();
Check if the item is destroyed.
Returns — boolean
Examples
var isDestroyed = item.isDestroyed();
Created and maintained by Niklas Rämö.
Copyright © 2015 Haltu Oy. Licensed under the MIT license.