UNPKG

muuri

Version:

Responsive, sortable, filterable and draggable layouts

1,247 lines (1,010 loc) 109 kB
<h1 align="center"> <a id="muuri" href="#muuri" aria-hidden="true"><img src="https://muuri.dev/muuri-logo.svg" alt="Muuri" width="400" /></a> </h1> 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](http://packery.metafizzy.co/), [Masonry](http://masonry.desandro.com/), [Isotope](http://isotope.metafizzy.co/) and [Sortable](https://github.com/RubaXa/Sortable). Wanna see it in action? Check out the [demo](https://muuri.dev/) on the website. **Features** - Fully customizable layout - Asynchronous layout calculations in web workers - Drag & drop (even between grids) - Auto-scrolling during drag - Nested grids - Fast animations - Filtering - Sorting <h2><a id="table-of-contents" href="#table-of-contents" aria-hidden="true">#</a> Table of contents</h2> - [Motivation](#motivation) - [Getting started](#getting-started) - [API](#api) - [Grid constructor](#grid-constructor) - [Grid options](#grid-options) - [Grid methods](#grid-methods) - [Grid events](#grid-events) - [Item methods](#item-methods) - [Credits](#credits) - [License](#license) <h2><a id="motivation" href="#motivation" aria-hidden="true">#</a> Motivation</h2> 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. <h2><a id="getting-started" href="#getting-started" aria-hidden="true">#</a> Getting started</h2> <h3><a id="getting-started-1" href="#getting-started-1" aria-hidden="true">#</a> 1. Get Muuri</h3> Install via [npm](https://www.npmjs.com/package/muuri): ```bash npm install muuri ``` Or download: - [muuri.js](https://cdn.jsdelivr.net/npm/muuri@0.9.5/dist/muuri.js) - for development (not minified, with comments). - [muuri.min.js](https://cdn.jsdelivr.net/npm/muuri@0.9.5/dist/muuri.min.js) - for production (minified, no comments). Or link directly: ```html <script src="https://cdn.jsdelivr.net/npm/muuri@0.9.5/dist/muuri.min.js"></script> ``` <h3><a id="getting-started-2" href="#getting-started-2" aria-hidden="true">#</a> 2. Get Web Animations Polyfill (if needed)</h3> Muuri uses [Web Animations](https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API) 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](https://github.com/web-animations/web-animations-js). Install via [npm](https://www.npmjs.com/package/web-animations-js): ```bash npm install web-animations-js ``` Or download: - [web-animations.min.js](https://cdn.jsdelivr.net/npm/web-animations-js@2.3.2/web-animations.min.js) Or link directly: ```html <script src="https://cdn.jsdelivr.net/npm/web-animations-js@2.3.2/web-animations.min.js"></script> ``` <h3><a id="getting-started-3" href="#getting-started-3" aria-hidden="true">#</a> 3. Add the markup</h3> - Every grid must have a container element (referred as the _grid element_ from now on). - Grid items must always consist of at least two elements. The outer element is used for positioning the item and the inner element (first direct child) is used for animating the item's visibility (show/hide methods). You can insert any markup you wish inside the inner item element. - Note that the class names in the below example are not required by Muuri at all, they're just there for example's sake. ```html <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> ``` <h3><a id="getting-started-4" href="#getting-started-4" aria-hidden="true">#</a> 4. Add the styles</h3> - The grid element must be "positioned" meaning that it's CSS position property must be set to _relative_, _absolute_ or _fixed_. Also note that Muuri automatically resizes the grid element's width/height depending on the area the items cover and the layout algorithm configuration. - The item elements must have their CSS position set to _absolute_. - The item elements must not have any CSS transitions or animations applied to them, because they might conflict with Muuri's internal animation engine. However, the grid element can have transitions applied to it if you want it to animate when it's size changes after the layout operation. - You can control the gaps between the items by giving some margin to the item elements. - One last thing. Never ever set `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. ```css .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%; } ``` <h3><a id="getting-started-5" href="#getting-started-5" aria-hidden="true">#</a> 5. Fire it up</h3> 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. ```javascript var grid = new Muuri('.grid'); ``` You can view this little tutorial demo [here](https://codepen.io/niklasramo/pen/wpwNjK). After that you might want to check some [other demos](https://codepen.io/collection/AWopag/) as well. <h2><a id="api" href="#api" aria-hidden="true">#</a> API</h2> <h3><a id="grid-constructor" href="#grid-constructor" aria-hidden="true">#</a> Grid constructor</h3> `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** - **element** &nbsp;&mdash;&nbsp; _element_ / _string_ - Default value: `null`. - You can provide the element directly or use a selector (string) which uses [querySelector()](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector) internally. - **options** &nbsp;&mdash;&nbsp; _object_ - Optional. Check out the [detailed options reference](#grid-options). **Default options** The default options are stored in `Muuri.defaultOptions` object, which in it's default state contains the following configuration: ```javascript { // 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: ```javascript Muuri.defaultOptions.showDuration = 400; Muuri.defaultOptions.dragSortPredicate.action = 'swap'; ``` This is how you would use the options: ```javascript // Minimum configuration. var gridA = new Muuri('.grid-a'); // Providing some options. var gridB = new Muuri('.grid-b', { items: '.item', }); ``` <h3><a id="grid-options" href="#grid-options" aria-hidden="true">#</a> Grid options</h3> - [items](#grid-option-items) - [showDuration](#grid-option-showduration) - [showEasing](#grid-option-showeasing) - [hideDuration](#grid-option-hideduration) - [hideEasing](#grid-option-hideeasing) - [visibleStyles](#grid-option-visiblestyles) - [hiddenStyles](#grid-option-hiddenstyles) - [layout](#grid-option-layout) - [layoutOnResize](#grid-option-layoutonresize) - [layoutOnInit](#grid-option-layoutoninit) - [layoutDuration](#grid-option-layoutduration) - [layoutEasing](#grid-option-layouteasing) - [sortData](#grid-option-sortdata) - [dragEnabled](#grid-option-dragenabled) - [dragContainer](#grid-option-dragcontainer) - [dragHandle](#grid-option-draghandle) - [dragStartPredicate](#grid-option-dragstartpredicate) - [dragAxis](#grid-option-dragaxis) - [dragSort](#grid-option-dragsort) - [dragSortHeuristics](#grid-option-dragsortheuristics) - [dragSortPredicate](#grid-option-dragsortpredicate) - [dragRelease](#grid-option-dragrelease) - [dragCssProps](#grid-option-dragcssprops) - [dragPlaceholder](#grid-option-dragplaceholder) - [dragAutoScroll](#grid-option-dragautoscroll) - [containerClass](#grid-option-containerclass) - [itemClass](#grid-option-itemclass) - [itemVisibleClass](#grid-option-itemvisibleclass) - [itemHiddenClass](#grid-option-itemhiddenclass) - [itemPositioningClass](#grid-option-itempositioningclass) - [itemDraggingClass](#grid-option-itemdraggingclass) - [itemReleasingClass](#grid-option-itemreleasingclass) - [itemPlaceholderClass](#grid-option-itemplaceholderclass) <h3><a id="grid-option-items" href="#grid-option-items" aria-hidden="true">#</a> <i>option</i>: items</h3> 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](https://developer.mozilla.org/en-US/docs/Web/API/NodeList), [HTMLCollection](https://developer.mozilla.org/en-US/docs/Web/API/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. - Default value: `'*'`. - Accepted types: array (of elements), [NodeList](https://developer.mozilla.org/en-US/docs/Web/API/NodeList), [HTMLCollection](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCollection), string, null. **Examples** ```javascript // 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', }); ``` <h3><a id="grid-option-showduration" href="#grid-option-showduration" aria-hidden="true">#</a> <i>option</i>: showDuration</h3> Show animation duration in milliseconds. Set to `0` to disable show animation. - Default value: `300`. - Accepted types: number. **Examples** ```javascript var grid = new Muuri(elem, { showDuration: 600, }); ``` <h3><a id="grid-option-showeasing" href="#grid-option-showeasing" aria-hidden="true">#</a> <i>option</i>: showEasing</h3> Show animation easing. Accepts any valid [Animation easing](https://developer.mozilla.org/en-US/docs/Web/API/AnimationEffectTimingProperties/easing) value. - Default value: `'ease'`. - Accepted types: string. **Examples** ```javascript var grid = new Muuri(elem, { showEasing: 'cubic-bezier(0.215, 0.61, 0.355, 1)', }); ``` <h3><a id="grid-option-hideduration" href="#grid-option-hideduration" aria-hidden="true">#</a> <i>option</i>: hideDuration</h3> Hide animation duration in milliseconds. Set to `0` to disable hide animation. - Default value: `300`. - Accepted types: number. **Examples** ```javascript var grid = new Muuri(elem, { hideDuration: 600, }); ``` <h3><a id="grid-option-hideeasing" href="#grid-option-hideeasing" aria-hidden="true">#</a> <i>option</i>: hideEasing</h3> Hide animation easing. Accepts any valid [Animation easing](https://developer.mozilla.org/en-US/docs/Web/API/AnimationEffectTimingProperties/easing) value. - Default value: `'ease'`. - Accepted types: string. **Examples** ```javascript var grid = new Muuri(elem, { hideEasing: 'cubic-bezier(0.215, 0.61, 0.355, 1)', }); ``` <h3><a id="grid-option-visiblestyles" href="#grid-option-visiblestyles" aria-hidden="true">#</a> <i>option</i>: visibleStyles</h3> 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). - Default value: ```javascript { opacity: 1, transform: 'scale(1)' } ``` - Accepted types: object. **Examples** ```javascript var grid = new Muuri(elem, { visibleStyles: { opacity: 1, transform: 'rotate(45deg)', }, hiddenStyles: { opacity: 0, transform: 'rotate(-45deg)', }, }); ``` <h3><a id="grid-option-hiddenstyles" href="#grid-option-hiddenstyles" aria-hidden="true">#</a> <i>option</i>: hiddenStyles</h3> 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). - Default value: ```javascript { opacity: 0, transform: 'scale(0.5)' } ``` - Accepted types: object. **Examples** ```javascript var grid = new Muuri(elem, { visibleStyles: { opacity: 1, transform: 'rotate(45deg)', }, hiddenStyles: { opacity: 0, transform: 'rotate(-45deg)', }, }); ``` <h3><a id="grid-option-layout" href="#grid-option-layout" aria-hidden="true">#</a> <i>option</i>: layout</h3> 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. - Default value: ```javascript { fillGaps: false, horizontal: false, alignRight: false, alignBottom: false, rounding: false } ``` - Accepted types: function, object. **Provide an _object_ to configure the default layout algorithm with the following properties** - **fillGaps** &nbsp;&mdash;&nbsp; _boolean_ - Default value: `false`. - When `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. - **horizontal** &nbsp;&mdash;&nbsp; _boolean_ - Default value: `false`. - When `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. - **alignRight** &nbsp;&mdash;&nbsp; _boolean_ - Default value: `false`. - When `true` the items are aligned from right to left. - **alignBottom** &nbsp;&mdash;&nbsp; _boolean_ - Default value: `false`. - When `true` the items are aligned from the bottom up. - **rounding** &nbsp;&mdash;&nbsp; _boolean_ - Default value: `false`. - When `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: - **grid** &nbsp;&mdash;&nbsp; _Muuri_ - The grid instance that requested the layout. - **layoutId** &nbsp;&mdash;&nbsp; _number_ - Automatically generated unique id for the layout which is used to keep track of the layout requests and to make sure that the correct layout gets applied at correct time. - **items** &nbsp;&mdash;&nbsp; _array_ - Array of `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). - **width** &nbsp;&mdash;&nbsp; _number_ - Current width (in pixels) of the grid element (excluding borders, but including padding). - **height** &nbsp;&mdash;&nbsp; _number_ - Current height (in pixels) of the grid element (excluding borders, but including padding). - **callback** &nbsp;&mdash;&nbsp; _function_ - When the layout is calculated and ready to be applied you need to call this callback function and provide a _layout object_ as it's argument. Note that if another layout is requesteded while the current layout is still being calculated (asynchronously) this layout will be ignored. 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. - **id** &nbsp;&mdash;&nbsp; _number_ - The layout's unique id (must be the `layoutId` provided by Muuri). - **items** &nbsp;&mdash;&nbsp; _array_ - Array of the active item instances that are part of the layout. You can pass the same `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. - **slots** &nbsp;&mdash;&nbsp; _array_ - Array of the item positions (numbers). E.g. if the items were `[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. - **styles** &nbsp;&mdash;&nbsp; _object / null_ - Here you can optionally define all the layout related CSS styles that should be applied to the grid element _just_ before the `layoutStart` event is emitted. E.g. `{width: '100%', height: '200px', minWidth: '200px'}`. - It's important to keep in mind here that if the grid element's `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** ```javascript // Customize the default layout algorithm. var grid = new Muuri(elem, { layout: { fillGaps: true, horizontal: true, alignRight: true, alignBottom: true, rounding: true, }, }); ``` ```javascript // 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); }; }, }); ``` <h3><a id="grid-option-layoutonresize" href="#grid-option-layoutonresize" aria-hidden="true">#</a> <i>option</i>: layoutOnResize</h3> 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. - Default value: `150`. - Accepted types: boolean, number. **Examples** ```javascript // No layout on resize. var grid = new Muuri(elem, { layoutOnResize: false, }); ``` ```javascript // Layout on resize (instantly). var grid = new Muuri(elem, { layoutOnResize: true, }); ``` ```javascript // Layout on resize (with 200ms debounce). var grid = new Muuri(elem, { layoutOnResize: 200, }); ``` <h3><a id="grid-option-layoutoninit" href="#grid-option-layoutoninit" aria-hidden="true">#</a> <i>option</i>: layoutOnInit</h3> Should Muuri trigger `layout` method automatically on init? - Default value: `true`. - Accepted types: boolean. **Examples** ```javascript var grid = new Muuri(elem, { layoutOnInit: false, }); ``` <h3><a id="grid-option-layoutduration" href="#grid-option-layoutduration" aria-hidden="true">#</a> <i>option</i>: layoutDuration</h3> The duration for item's layout animation in milliseconds. Set to `0` to disable. - Default value: `300`. - Accepted types: number. **Examples** ```javascript var grid = new Muuri(elem, { layoutDuration: 600, }); ``` <h3><a id="grid-option-layouteasing" href="#grid-option-layouteasing" aria-hidden="true">#</a> <i>option</i>: layoutEasing</h3> The easing for item's layout animation. Accepts any valid [Animation easing](https://developer.mozilla.org/en-US/docs/Web/API/AnimationEffectTimingProperties/easing) value. - Default value: `'ease'`. - Accepted types: string. **Examples** ```javascript var grid = new Muuri(elem, { layoutEasing: 'cubic-bezier(0.215, 0.61, 0.355, 1)', }); ``` <h3><a id="grid-option-sortdata" href="#grid-option-sortdata" aria-hidden="true">#</a> <i>option</i>: sortData</h3> 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. - Default value: `null`. - Accepted types: object, null. **Examples** ```javascript 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'); ``` <h3><a id="grid-option-dragenabled" href="#grid-option-dragenabled" aria-hidden="true">#</a> <i>option</i>: dragEnabled</h3> Should items be draggable? - Default value: `false`. - Accepted types: boolean. **Examples** ```javascript var grid = new Muuri(elem, { dragEnabled: true, }); ``` <h3><a id="grid-option-dragcontainer" href="#grid-option-dragcontainer" aria-hidden="true">#</a> <i>option</i>: dragContainer</h3> 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. - Default value: `null`. - Accepted types: element, null. **Examples** ```javascript var grid = new Muuri(elem, { dragContainer: document.body, }); ``` <h3><a id="grid-option-draghandle" href="#grid-option-draghandle" aria-hidden="true">#</a> <i>option</i>: dragHandle</h3> 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. - Default value: `null`. - Accepted types: string, null. **Examples** ```javascript var grid = new Muuri(elem, { dragHandle: '.handle', }); ``` <h3><a id="grid-option-dragstartpredicate" href="#grid-option-dragstartpredicate" aria-hidden="true">#</a> <i>option</i>: dragStartPredicate</h3> 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. - Default value: ```javascript { distance: 0, delay: 0 } ``` - Accepted types: function, object. If an object is provided the default start predicate handler will be used. You can define the following properties: - **distance** &nbsp;&mdash;&nbsp; _number_ - Default value: `0`. - How many pixels the user must drag before the drag procedure starts and the item starts moving. - **delay** &nbsp;&mdash;&nbsp; _number_ - Default value: `0`. - How long (in milliseconds) the user must drag before the dragging starts. 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: - **item** &nbsp;&mdash;&nbsp; _Muuri.Item_ - The item that's being dragged. - **event** &nbsp;&mdash;&nbsp; _object_ - Muuri.Dragger event data. **Examples** ```javascript // Configure the default predicate var grid = new Muuri(elem, { dragStartPredicate: { distance: 10, delay: 100, }, }); ``` ```javascript // 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; } }, }); ``` ```javascript // 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); }, }); ``` <h3><a id="grid-option-dragaxis" href="#grid-option-dragaxis" aria-hidden="true">#</a> <i>option</i>: dragAxis</h3> 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. - Default value: `'xy'`. - Accepted types: string. - Allowed values: `'x'`, `'y'`, `'xy'`. **Examples** ```javascript // Move items only horizontally when dragged. var grid = new Muuri(elem, { dragAxis: 'x', }); ``` ```javascript // Move items only vertically when dragged. var grid = new Muuri(elem, { dragAxis: 'y', }); ``` <h3><a id="grid-option-dragsort" href="#grid-option-dragsort" aria-hidden="true">#</a> <i>option</i>: dragSort</h3> 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. - Default value: `true`. - Accepted types: boolean, function. **Examples** ```javascript // Disable drag sorting. var grid = new Muuri(elem, { dragSort: false, }); ``` ```javascript // 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]; } ``` <h3><a id="grid-option-dragsortheuristics" href="#grid-option-dragsortheuristics" aria-hidden="true">#</a> <i>option</i>: dragSortHeuristics</h3> Defines various heuristics so that sorting during drag would be smoother and faster. - Default value: ```javascript { sortInterval: 100, minDragDistance: 10, minBounceBackAngle: 1 } ``` - Accepted types: object. You can define the following properties: - **sortInterval** &nbsp;&mdash;&nbsp; _number_ - Default value: `100`. - Defines the amount of time the dragged item must be still before `dragSortPredicate` function is called. - **minDragDistance** &nbsp;&mdash;&nbsp; _number_ - Default value: `10`. - Defines how much (in pixels) the item must be dragged before `dragSortPredicate` can be called. - **minBounceBackAngle** &nbsp;&mdash;&nbsp; _number_ - Default value: `1`. - Defines the minimum angle (in radians) of the delta vector between the last movement vector and the current movement vector that is required for the dragged item to be allowed to be sorted to it's previous index. The problem this heuristic is trying to solve is the scenario where you drag an item over a much bigger item and the bigger item moves, but it's still overlapping the dragged item after repositioning. Now when you move the dragged item again another sort is triggered and the bigger item moves back to it's previous position. This bouncing back and forth can go on for quite a while and it looks quite erratic. The fix we do here is that, by default, we disallow an item to be moved back to it's previous position, unless it's drag direction changes enough. And what is enough? That's what you can define here. Note that this option works in tandem with `minDragDistance` and needs it to be set to `3` at minimum to be enabled at all. **Examples** ```javascript var grid = new Muuri(elem, { dragEnabled: true, dragSortHeuristics: { sortInterval: 10, minDragDistance: 5, minBounceBackAngle: Math.PI / 2, }, }); ``` ```javascript // 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 }, }); ``` <h3><a id="grid-option-dragsortpredicate" href="#grid-option-dragsortpredicate" aria-hidden="true">#</a> <i>option</i>: dragSortPredicate</h3> Defines the logic for the sort procedure during dragging an item. - Default value: ```javascript { threshold: 50, action: 'move', migrateAction: 'move' } ``` - Accepted types: function, object. If an object is provided the default sort predicate handler will be used. You can define the following properties: - **threshold** &nbsp;&mdash;&nbsp; _number_ - Default value: `50`. - Allowed values: `1` - `100`. - How many percent the intersection area between the dragged item and the compared item should be from the maximum potential intersection area between the items before sorting is triggered. - **action** &nbsp;&mdash;&nbsp; _string_ - Default value: `'move'`. - Allowed values: `'move'`, `'swap'`. - Should the dragged item be _moved_ to the new position or should it _swap_ places with the item it overlaps when the drag occurs within the same grid? - **migrateAction** &nbsp;&mdash;&nbsp; _string_ - Default value: `'move'`. - Allowed values: `'move'`, `'swap'`. - Should the dragged item be _moved_ to the new position or should it _swap_ places with the item it overlaps when the dragged item is moved to another grid? Alternatively you can provide your own callback function where you can define your own custom sort logic. The callback function receives two arguments: - **item** &nbsp;&mdash;&nbsp; _Muuri.Item_ - The item that's being dragged. - **event** &nbsp;&mdash;&nbsp; _object_ - Muuri.Dragger event data. 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: - **index** &nbsp;&mdash;&nbsp; _number_ - The index where the item should be moved to. - **grid** &nbsp;&mdash;&nbsp; _Muuri_ - The grid where the item should be moved to. - Defaults to the item's current grid. - Optional. - **action** &nbsp;&mdash;&nbsp; _string_ - The movement method. - Default value: `'move'`. - Allowed values: `'move'` or `'swap'`. - Optional. **Examples** ```javascript // Customize the default predicate. var grid = new Muuri(elem, { dragSortPredicate: { threshold: 90, action: 'swap', }, }); ``` ```javascript // 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', }; }, }); ``` ```javascript // 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, }); }, }); ``` <h3><a id="grid-option-dragrelease" href="#grid-option-dragrelease" aria-hidden="true">#</a> <i>option</i>: dragRelease</h3> - Default value: ```javascript { duration: 300, easing: 'ease', useDragContainer: true } ``` - Accepted types: object. You can define the following properties: - **duration** &nbsp;&mdash;&nbsp; _number_ - Default value: `300`. - The duration for item's drag release animation. Set to `0` to disable. - **easing** &nbsp;&mdash;&nbsp; _string_ - Default value: `'ease'`. - The easing for item's drag release animation. Accepts any valid [Animation easing](https://developer.mozilla.org/en-US/docs/Web/API/AnimationEffectTimingProperties/easing) value. - **useDragContainer** &nbsp;&mdash;&nbsp; _boolean_ - Default value: `true`. - If `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** ```javascript var grid = new Muuri(elem, { dragRelease: { duration: 600, easing: 'ease-out', useDragContainer: false, }, }); ``` <h3><a id="grid-option-dragcssprops" href="#grid-option-dragcssprops" aria-hidden="true">#</a> <i>option</i>: dragCssProps</h3> 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. - Default value: ```javascript { touchAction: 'none', userSelect: 'none', userDrag: 'none', tapHighlightColor: 'rgba(0, 0, 0, 0)', touchCallout: 'none', contentZooming: 'none' } ``` - Accepted types: object. You can define the following properties: - **touchAction** &nbsp;&mdash;&nbsp; _string_ - Default value: `'none'`. - https://developer.mozilla.org/en-US/docs/Web/CSS/touch-action - **userSelect** &nbsp;&mdash;&nbsp; _string_ - Default value: `'none'`. - https://developer.mozilla.org/en-US/docs/Web/CSS/user-select - Optional. - **userDrag** &nbsp;&mdash;&nbsp; _string_ - Default value: `'none'`. - http://help.dottoro.com/lcbixvwm.php - Optional. - **tapHighlightColor** &nbsp;&mdash;&nbsp; _string_ - Default value: `'rgba(0, 0, 0, 0)'`. - https://developer.mozilla.org/en-US/docs/Web/CSS/-webkit-tap-highlight-color - Optional. - **touchCallout** &nbsp;&mdash;&nbsp; _string_ - Default value: `'none'`. - https://developer.mozilla.org/en-US/docs/Web/CSS/-webkit-touch-callout - Optional. - **contentZooming** &nbsp;&mdash;&nbsp; _string_ - Default value: `'none'`. - https://developer.mozilla.org/en-US/docs/Web/CSS/-ms-content-zooming - Optional. **Examples** ```javascript // 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: '', }, }); ``` <h3><a id="grid-option-dragplaceholder" href="#grid-option-dragplaceholder" aria-hidden="true">#</a> <i>option</i>: dragPlaceholder</h3> 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](#itemplaceholderclass-) option. - Default value: ```javascript { enabled: false, createElement: null, onCreate: null, onRemove: null } ``` - Accepted types: object. You can define the following properties: - **enabled** &nbsp;&mdash;&nbsp; _boolean_ - Default value: `false`. - Is the placeholder enabled? - **createElement** &nbsp;&mdash;&nbsp; _function / null_ - Default value: `null`. - If defined, this method will be used to create the DOM element that is used for the placeholder. By default a new `div` element is created when a placeholder is summoned. - **onCreate** &nbsp;&mdash;&nbsp; _function / null_ - Default value: `null`. - An optional callback that will be called after a placeholder is created for an item. - **onRemove** &nbsp;&mdash;&nbsp; _function / null_ - Default value: `null`. - An optional callback that will be called after a placeholder is removed from the grid. **Examples** ```javascript // 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); }, }, }); ``` <h3><a id="grid-option-dragautoscroll" href="#grid-option-dragautoscroll" aria-hidden="true">#</a> <i>option</i>: dragAutoScroll</h3> 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. - Default value: ```javascript { targets: [], handle: null, threshold: 50, safeZone: 0.2, speed: Muuri.AutoScroller.smoothSpeed(1000, 2000, 2500), sortDuringScroll: true, smoothStop: false, onStart: null, onStop: null } ``` - Accepted types: object. You can define the following properties: - **targets** &nbsp;&mdash;&nbsp; _array / function_ - Default value: `[]`. - Define the DOM elements that should be scrolled during drag. As long as this array is empty there will be no scrolling during drag. To keep it simple you can just provide an array of elements here, in which case Muuri attempts to scroll the elements both vertically and horizontally when possible. If you want more fine-grained control, e.g. scroll an element only on specific axis or prioritize some element over another (handy for cases when there are overlapping elements), you can provide an array of scroll targets (objects). Finally, you can also provide a function which receives the dragged `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. - **scrollTarget** &nbsp;&mdash;&nbsp; _object_ - **element** &nbsp;&mdash;&nbsp; _element_ / _window_ - The DOM element to scroll. - Required. - **axis** &nbsp;&mdash;&nbsp; _number_ - Optional. Defaults to scrolling both axes: `Muuri.AutoScroller.AXIS_X | Muuri.AutoScroller.AXIS_Y`. - To scroll only x-axis: `Muuri.AutoScroller.AXIS_X`. - To scroll only y-axis: `Muuri.AutoScroller.AXIS_Y`. - **priority** &nbsp;&mdash;&nbsp; _number_ - Default: `0`. - A dragged item can only scroll one element horizontally and one element vertically simultaneously. This is an artificial limit to fend off unnecesary complexity, and to avoid awkward situations. In the case where the dragged item overlaps multiple scrollable elements simultaneously and exceeds their scroll thresholds we pick the one that the dragged item overlaps most. However, that's not always the best choice. This is where `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. - Optional. - **threshold** &nbsp;&mdash;&nbsp; _number / null_ - Default: `null`. - If defined (a number is provided), this value will override the default threshold for _this scroll target_. Otherwise the default threshold will be used. - Optional. - **handle** &nbsp;&mdash;&nbsp; _function / null_ - Default value: `null`. - This property defines size and position of the handle (the rectangle that is compared against the scroll element's threshold). By default (when `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: - **item** &nbsp;&mdash;&nbsp; _Muuri.Item_ - **itemClientX** &nbsp;&mdash;&nbsp; _number_ - **itemClientY** &nbsp;&mdash;&nbsp; _number_ - **itemWidth** &nbsp;&mdash;&nbsp; _number_ - **itemHeight** &nbsp;&mdash;&nbsp; _number_ - **pointerClientX** &nbsp;&mdash;&nbsp; _number_ - **pointerClientY** &nbsp;&mdash;&nbsp; _number_ - Tip: Use `Muuri.AutoScroller.pointerHandle(pointerSize)` utility method if you want to use the pointer (instead of the element) as the handle. - **threshold** &nbsp;&mdash;&nbsp; _number_ - Default value: `50`. - Defines the distance (in pixels) from the edge of the scrollable element when scrolling should start, in pixels. If this value is `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). - **safeZone** &nbsp;&mdash;&nbsp; _number_ - Default value: `0.2`. - Defines the size of the minimum "safe zone" space, an area in the center of the scrollable element that will be guaranteed not trigger scrolling regardless of threshold size and the dragged item's size. This