Is it possible to animate Flexbox inserts & removes?
Solution 1
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:
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.
Solution 2
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
Solution 3
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',
}
)
}
}
Solution 4
I accidentally got it to work in a simple way.
Basically, you set width:0;flex-grow:1
and of course add transition:all 2s;
and that's it. It's a curious hack.
Solution 5
Another adaptation to @skyline3000 and @chris-nicola's solutions: http://jsfiddle.net/2gbPg/2/
You can simply animate max-width
(or max-height
as appropriate), animating to 0
when removing, and 100%
when inserting.
mmaclaurin
I run an exploratory prototyping lab at eBay. We work at the bleeding edge of UI technology: HTML5, CSS3, WebGL We also work on the convergence of online and offline commerce: - mobile - large touchscreen installations & kiosks - gamifying commerce ecosystems I do write code, too. I used to work at Microsoft (research, xbox) and Apple (MacOS, Newton) and ran a digital consulting studio for 7 years. My career is all about creating great places and teams to do great work.
Updated on July 18, 2022Comments
-
mmaclaurin almost 2 years
When I remove an item from a flexbox, the remaining items "snap" into their new positions immediately rather than animating.
Conceptually, since the items are changing their positions, I would expect the transitions to apply.
I have set the transition property on all involved elements (the flexbox and the children)
Is there any way to animate edits (adds & deletes) to a flexbox? This is actually a showstopper for me and the one missing piece with flexbox.
-
Ricardo Gomes over 10 yearswhere there is content in your middle div, this doesn't quite work as well
-
skyline3000 over 10 yearsYou could easily wrap the content and set overflow to be hidden during the transition so that it works nicely.
-
T J almost 10 yearsi can't find any transition in the demo... it works just like normal flex... and the
transitionEnd
handler in the demo is not triggering which means no transitions are being applied... -
Chris Nicola almost 10 yearsYeah same problem. If this was working it no longer is.
-
Pranay Soni almost 10 yearsWhat To Do For Animate Justify-Content from center to flex-start
-
Chris Nicola almost 10 yearsI'm not exactly sure I understand what you mean, can you post an example?
-
ierdna about 7 yearsis it possible to do this with a vertical layout? i.e. flex-flow: column;
-
DasBeasto over 6 yearsThis only seems to work if theres no content, which seems...unlikely. (ex. put some text in the div)
-
John Balvin Arias almost 6 yearsIt does not work neither when the element it's an image
-
D_S_X almost 3 yearsThis is a pretty neat solution. One thing i'm not able to understand is that you are calling
moveCards
aftercard.parentNode.removeChild(card)
. Still the repositioning of remaining elements is not done until animation starts. Shouldn't the elements be first re-positioned immediately in flex container (due to element removal) and then transition should start? I wonder why this is able to work like it does! -
Maurici Abad almost 3 years@D_S_X Yes. The elements move into the new position, and then the animation moves it back to where it was to the current position. There's a small gap between the element removal and the animation starting, but js is so fast and the browser optimized that it looks fluid.
-
D_S_X almost 3 yearsUnderstood. But why does
.animate
move them back and then start animating? We are translating them and not changing theirleft
/right
values, so if they have moved to new positions after element removal, shouldn't they be translated from those new positions? -
WorldOfEmre over 2 yearswidth trick worked for me thanks!!
-
Just a coder about 2 yearscan this work for grid?
-
Liero about 2 yearsit should, yes..