Is it possible to animate Flexbox inserts & removes?

55,124

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:

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.

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.

See it working

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.

Share:
55,124
mmaclaurin
Author by

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 &amp; 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, 2022

Comments

  • mmaclaurin
    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
    Ricardo Gomes over 10 years
    where there is content in your middle div, this doesn't quite work as well
  • skyline3000
    skyline3000 over 10 years
    You could easily wrap the content and set overflow to be hidden during the transition so that it works nicely.
  • T J
    T J almost 10 years
    i 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
    Chris Nicola almost 10 years
    Yeah same problem. If this was working it no longer is.
  • Pranay Soni
    Pranay Soni almost 10 years
    What To Do For Animate Justify-Content from center to flex-start
  • Chris Nicola
    Chris Nicola almost 10 years
    I'm not exactly sure I understand what you mean, can you post an example?
  • ierdna
    ierdna about 7 years
    is it possible to do this with a vertical layout? i.e. flex-flow: column;
  • DasBeasto
    DasBeasto over 6 years
    This only seems to work if theres no content, which seems...unlikely. (ex. put some text in the div)
  • John Balvin Arias
    John Balvin Arias almost 6 years
    It does not work neither when the element it's an image
  • D_S_X
    D_S_X almost 3 years
    This is a pretty neat solution. One thing i'm not able to understand is that you are calling moveCards after card.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
    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
    D_S_X almost 3 years
    Understood. But why does .animate move them back and then start animating? We are translating them and not changing their left/right values, so if they have moved to new positions after element removal, shouldn't they be translated from those new positions?
  • WorldOfEmre
    WorldOfEmre over 2 years
    width trick worked for me thanks!!
  • Just a coder
    Just a coder about 2 years
    can this work for grid?
  • Liero
    Liero about 2 years
    it should, yes..