Is it possible to animate Flexbox inserts & removes?
I've fixed up @skyline3000's demo based on this example from Treehouse. Not sure if this will break again if browsers change but this seems to be the intended way to animate flex size changes:
http://jsfiddle.net/2gbPg/2/
Also I used jQuery but it technically isn't required.
.flexed {
background: grey;
/* The border seems to cause drawing artifacts on transition. Probably a browser bug. */
/* border: 1px solid black; */
margin: 5px;
height: 100px;
flex-grow: 1;
transition: flex-grow 1000ms linear;
}
.removed {
/* Setting this to zero breaks the transition */
flex-grow: 0.00001;
}
One thing to note about the CSS is you can't transition to a flex-grow
of zero, it won't transition it will just disappear. You need to just put a very small value. Also there seems to be an artifacting bug when drawing borders so I've used a background in this case.
Remember that the Flexible Box Model and Grid Layout specifications are changing constantly, even the properties and valid values. The browser implementations are far from complete as well. That being said, you can transition on the flex
property so that the elements transition smoothly, then just listen for TransitionEnd
to finally remove the node from the DOM tree.
Here is an example JSFiddle, running in Chrome 21: http://jsfiddle.net/5kJjM/ (click the middle div)
var node = document.querySelector('#remove-me');
node.addEventListener('click', function(evt) {
this.classList.add('clicked');
}, false);
node.addEventListener('webkitTransitionEnd', function(evt) {
document.querySelector('#flexbox').removeChild(this);
}, false);
#flexbox {
display: -webkit-flex;
-webkit-flex-flow: row;
}
.flexed {
border: 1px solid black;
height: 100px;
-webkit-flex: 1 0 auto;
-webkit-transition: -webkit-flex 250ms linear;
}
.clicked {
-webkit-flex: 0 0 auto;
}
<div id="flexbox">
<div class="flexed"></div>
<div class="flexed" id="remove-me"></div>
<div class="flexed"></div>
</div>
Edit: To further clarify, when you remove a node, you should set its flex to 0, then remove it from the DOM. When adding a node, add it in with flex: 0, then transition it to flex:1
I've done a codepen that animates the elements when you remove one, take a look: https://codepen.io/MauriciAbad/pen/yLbrpey
HTML
<div class="container">
<div></div>
<div></div>
... more elements ...
</div>
CSS
.container{
display: flex;
flex-wrap: wrap;
}
.container > * {
transform-origin: left top;
}
TypeScript
If you want the JavaScript just remove the : Anything
from the function's signature and the interface at the top.
interface FlexItemInfo {
element: Element
x: number
y: number
width: number
height: number
}
const container = document.querySelector('.container')
for (const item of container.children) {
item.addEventListener('click', () => {
removeFlexItem(container, item)
})
}
function removeFlexItem(container: Element, item: Element): void {
const oldFlexItemsInfo = getFlexItemsInfo(container)
container.removeChild(item)
const newFlexItemsInfo = getFlexItemsInfo(container)
aminateFlexItems(oldFlexItemsInfo, newFlexItemsInfo)
}
function getFlexItemsInfo(container: Element): FlexItemInfo[] {
return Array.from(container.children).map((item) => {
const rect = item.getBoundingClientRect()
return {
element: item,
x: rect.left,
y: rect.top,
width: rect.right - rect.left,
height: rect.bottom - rect.top,
}
})
}
function aminateFlexItems(
oldFlexItemsInfo: FlexItemInfo[],
newFlexItemsInfo: FlexItemInfo[]
): void {
for (const newFlexItemInfo of newFlexItemsInfo) {
const oldFlexItemInfo = oldFlexItemsInfo.find(
(itemInfo) => itemInfo.element === newFlexItemInfo.element
)
const translateX = oldFlexItemInfo.x - newFlexItemInfo.x
const translateY = oldFlexItemInfo.y - newFlexItemInfo.y
const scaleX = oldFlexItemInfo.width / newFlexItemInfo.width
const scaleY = oldFlexItemInfo.height / newFlexItemInfo.height
newFlexItemInfo.element.animate(
[
{
transform: `translate(${translateX}px, ${translateY}px) scale(${scaleX}, ${scaleY})`,
},
{ transform: 'none' },
],
{
duration: 250,
easing: 'ease-out',
}
)
}
}