April 16th, 2013: Version 2.0 released. There may be bugs and we are still browser testing. Please report any bugs you find through issues.
Inspired heavily by the jQuery Masonry plugin, Shapeshift is a plugin which will dynamically arrange a collection of elements into a column grid system similar to Pinterest. What sets it apart is the ability to drag and drop items within the grid while still maintaining a logical index position for each item. This allows for the grid to be rendered exactly the same every time Shapeshift is used, as long as the child elements are in the correct order.
-
Drag and Drop Rearrange items within a container or even drag items between multiple Shapeshift enabled containers. Dragging elements around will physically change their index position within their parent container. When a page reloads, as long as the child elements are placed in the correct order then the grid will look exactly the same.
-
Works on Touch Devices Shapeshift uses jQuery UI Draggable/Droppable for help with the drag and drop system. Luckily there is already a plugin called jQuery Touch Punch which provides touch support for jQuery UI D/D. It can be found in the vendor folder.
-
Multiwidth Elements A new feature in 2.0 is the ability to add elements that can span across multiple columns as long as their width is correctly set through CSS.
-
Responsive Grid Enabled by default, Shapeshift will listen for window resize events and arrange the elements within it according to the space provided by their parent container.
A big thanks to all of our contributors!
Shapeshift is maintained by We The Media, inc.
Got a project that you are using shapeshift on? Let us know and we will happily throw a link to your page here!
Shapeshift requires the latest version of jQuery, and drag and drop feature (enabled by default) requires jQuery UI Draggable/Droppable libraries. It also requires jQuery Touch Punch to work on touch devices.
Shapeshift arranges child elements by absolutely positioning them in their parent container which must be set to "position: relative". The container does not have to be a div and can be substituted for any element that can have child elements, such as an unordered list. The container also must have a width specified.
<div class="container"></div>
.container {
position: relative;
width: 100%;
}
By default all child elements within the parent container will be Shapeshifted. Just make sure that they are set to "position: absolute" in your CSS file. The children also must have a height/width specified.
<div class="container">
<div>Child Element 1</div>
<div>Child Element 2</div>
<div>Child Element 3</div>
<div>Child Element 4</div>
...
</div>
.container {
position: relative;
width: 100%;
}
.container div {
height: 50px;
position: absolute;
width: 50px;
}
Shapeshift relies on a column grid system, this means that every time Shapeshift is initialized on a container it will determine the column width based on the width of the first child in that container. If no column width is specified on a child element then Shapeshift will assume it will use it to set the single column width for the grid.
To make a child element multiwidth, simply add the data attribute "data-ss-colspan=X", where X is the amount of columns it should span. Shapeshift does not automatically set their width though so the childs width must already be set to the correct width. The calculated width must be set to: "single column width * columns to span + the gutter space in between".
For example, assuming the default gutter value of 10px, multiwidth elements can be created as such:
<div class="container">
<div>Spans 1 Column</div>
<div data-ss-colspan="2">Spans 2 Columns</div>
<div data-ss-colspan="3">Spans 3 Columns</div>
<div data-ss-colspan="4">Spans 4 Columns</div>
...
</div>
.container {
position: relative;
width: 100%;
}
.container div {
height: 120px;
position: absolute;
width: 80px;
}
.container div[data-ss-colspan="2"] { width: 170px; }
.container div[data-ss-colspan="3"] { width: 260px; }
.container div[data-ss-colspan="4"] { width: 350px; }
Now that we have our setup complete, simply call .shapeshift() on the parent element. It will, by default, select all the children in the parent element to be rearranged.
$('.container').shapeshift();
Customize your grid even further. All of these are the default options and more in depth information can be found further down the page.
$('.container').shapeshift
# The Basics
selector: "*"
# Features
enableDrag: true
enableCrossDrop: true
enableResize: true
enableTrash: false
# Grid Properties
align: "center"
colWidth: null
columns: null
minColumns: 1
autoHeight: true
maxHeight: null
minHeight: 100
gutterX: 10
gutterY: 10
paddingX: 10
paddingY: 10
# Animation
animated: true
animateOnInit: false
animationSpeed: 225
animationThreshold: 100
# Drag/Drop Options
dragClone: false
deleteClone: true
dragRate: 100
dragWhitelist: "*"
crossDropWhitelist: "*"
cutoffStart: null
cutoffEnd: null
handle: false
# Customize CSS
cloneClass: "ss-cloned-child"
activeClass: "ss-active-child"
draggedClass: "ss-dragged-child"
placeholderClass: "ss-placeholder-child"
originalContainerClass: "ss-original-container"
currentContainerClass: "ss-current-container"
previousContainerClass: "ss-previous-container"
Option | Description | Type | Acceptable Values | Default |
---|---|---|---|---|
Selector | Use a CSS selector to specify which child elements should be Shapeshifted. | String | Any CSS selector, such as ".amelia" or "#pond" | "*" |
Option | Description | Default |
---|---|---|
enableDrag | Allows for the child items to be dragged in the container and to other containers that have drop enabled. See Drag and Drop options for more customization. | true |
enableCrossDrop | Allows for children to be dropped from *other* containers into this one. | true |
enableResize | Shapeshift will listen for the window resize event and rearrange the child elements if the parent container has also changed. | true |
enableTrash | When an item is dropped into a container that has trash enabled, it will destroy the dropped element. | false |
Option | Description | Acceptable Values | Default |
---|---|---|---|
align | Align / justify the grid. | "left", "center", "right" | "center" |
colWidth | Manually set the column width. Column width is automatically determined by Shapeshift, however it is required to be set if the container has no initial children to calculate it from. | Any Integer >= 1 | 1 |
columns | Force the grid to have a specific number of columns. Setting this to null will automatically determine the maximum columns for the width of the container. | Any Integer >= 1 | null |
minColumns | This will prevent the grid from ever going below a set number of columns. If using multiwidth then this must be set to the highest colspan child element. | Any Integer >= 1 | 1 |
autoHeight | Automatically sets the height of the container according to the height of the contents within it. If set to false, then the "height" option must also be specified. | true, false | true |
maxHeight | If "autoHeight" is turned on, maxHeight will never allow the container height to go above this number. | Any Integer >= 1 | null |
minHeight | If "autoHeight" is turned on, minHeight will never allow the container height to go below this number. | Any Integer >= 1 | 100 |
gutterX | The number of pixels horizontally between each column. | Any Integer >= 0 | 10 |
gutterY | The number of pixels vertically between each element. | Any Integer >= 0 | 10 |
paddingX | Sets the horizontal padding of the grid between the left and right sides of the container. | Any Integer >= 0 | 10 |
paddingY | Sets the vertical padding of the grid between the top and bottom sides of the container. | Any Integer >= 0 | 10 |
Option | Description | Acceptable Values | Default |
---|---|---|---|
animated | When children shift around via the resize or drag and drop features, they will animate into place. | true, false | true |
animateOnInit | Animates the children into position upon page load. | true, false | false |
animationSpeed | The speed at which the children will animate into place. | Any Integer >= 0 | 225 |
animationThreshold | If there are too many elements on a page then it can get very laggy during animation. If the number of children exceed this threshold then they will not animate when changing positions. | Any Integer >= 0 | 100 |
Option | Description | Acceptable Values | Default |
---|---|---|---|
dragClone | When an element is dragged it will create a clone instead. | true, false | false |
deleteClone | If a cloned item is dropped into its original container, delete the clone that was made. | true, false | true |
dragRate | The number of milliseconds that Shapeshift will attempt to find a target pisition for a dragged item. | Any Integer >= 0 | 100 |
dragRate | The number of milliseconds that Shapeshift will attempt to find a target pisition for a dragged item. | Any Integer >= 0 | 100 |
dragWhitelist | A CSS selector specifying the elements which can be dragged. | Any CSS selector, such as ".river" or "#song" | "*" |
crossDropWhitelist | A CSS selector specifying the elements which can be dropped into this container from *other* containers. | Any CSS selector, such as ".martha" or "#jones" | "*" |
cutoffStart | Items cannot be dragged to an index position below this number. | Any Integer >= 0 | null |
cutoffEnd | Items cannot be dragged to an index position past this number. | Any Integer >= 0 | null |
handle | If specified, restricts dragging from starting unless the mousedown occurs on the specified element(s). | Any CSS selector, such as ".jack" or "#harkness" | false |
Certain elements will have CSS classes attached to them for specific events. Customize those CSS classes if needed.
Option | Affected Element | Description | Default |
---|---|---|---|
activeClass | Child Elements | Every active Shapeshift child item will have this class applied to them. | ss-active-child |
cloneClass | Cloned Child Element | If the "dragClone" option is used, this is the CSS class applied to the clone that is created. | ss-cloned-child |
draggedClass | Dragged Child Element | The class applied to an element while it is being dragged. | ss-dragged-child |
placeholderClass | Placeholder Element | When an item is dragged, a placeholder element is created to show the new target position. | ss-placeholder-child |
originalContainerClass | Container Element | When an item is dragged, this is the class applied to the container it originated from. | ss-original-container |
currentContainerClass | Container Element | When an item is dragged, this is the class applied to the container it currently is in. | ss-current-container |
previousContainerClass | Container Element | When an item is dragged between containers, this is the class applied to the container it was previously in. | ss-previous-container |
Changes to the grid will trigger several different events on the container element and important objects will be returned with it. Here are a list of events that can be listened to, with some examples following.
Event Name | Triggered When | Triggered On | Variables Returned |
---|---|---|---|
ss-rearranged | When an item is dropped into the container it originated from. | original container element | selected element |
ss-removed | When an item is dropped into a container it didn't originate from. | original container element | selected element |
ss-added | When an item is dropped into a container it didn't originate from. | new container element | selected element |
ss-trashed | When an item is dropped into a container that has trash enabled and therefore is removed from the DOM. | trash enabled container element | selected element |
ss-drop-complete | When an item is dropped into a container, this gets called when it has stopped moving to its new position. | new container element | none |
ss-arranged | When an item is dragged around in a container, arranged is triggered every time items are shifted. | current container element | none |
When an item has begun being dragged, it will trigger the "ss-event-dragged" on the container element. You can then write out some code to be fired off when that event occurs. The object that was just selected is also passed back to you. For example,
$containers = $(".ss-container")
$containers.on "ss-rearranged", (e, selected) ->
console.log "This container:", $(this)
console.log "Has rearranged this item:", $(selected)
console.log "Into this position:", $(selected).index()
$containers.on "ss-removed", (e, selected) ->
console.log "This item:", $(selected)
console.log "Has been removed from this container:", $(this)
$containers.on "ss-added", (e, selected) ->
console.log "This item:", $(selected)
console.log "Has been added to this container:", $(this)
$containers.on "ss-trashed", (e, selected) ->
console.log "This item:", $(selected)
console.log "Has been removed from the DOM"
$containers.on "ss-drop-complete", (e) ->
console.log "This container:", $(this)
console.log "Has finished rearrangement after a drop."
$containers.on "ss-arranged", (e) ->
console.log "This container:", $(this)
console.log "Has just rearranged items but no drop has occurred."
If you add, remove, hide, or show elements through your own code then you may need to rearrange the items into their new positions. Triggering "ss-rearrange" on the target container will do so.
$(".ss-container").trigger("ss-rearrange")
Simply trigger the event "ss-destroy" on the container.
$(".ss-container").trigger("ss-destroy")
Feel like you've got an idea on how to optimize the code and want to share it? We are totally open to new changes, however this is one of the first publically available plugins that I am offering and therefore do not have an exact process on pull requests. Feel free to fork the project all you want, but be aware any pull requests that are made may take a while to get implemented (if at all).