Thorben Croisé
Thorben Croisé

Reputation: 12870

Use transition on flexbox order

Is there any way to have a transition on the order of flex-box items?

In other words, can I have this (details in this fiddle)

#container {
    display: flex;
}
#container:hover div:last-child {
    order: -1;
}

animated (the element getting the new position assumes it's position over time), please?

Upvotes: 40

Views: 38485

Answers (6)

CodeNStuff
CodeNStuff

Reputation: 314

I came across this discussion while dealing with flexbox-order-animation myself. All proposals here either require a library or are clunky b/c of the integer issue.

With SO I found a vanilla JS solution based on Element.animate(), which as of time of today has a good support. You can see a sample implementation in the code snippet included in this post.

Upvotes: 0

Valery
Valery

Reputation: 755

So here is what I created based on the above:

// Some simple logic that will "change the order of elements"
function shuffleThem() {
  var items = [...document.getElementsByClassName('item')];
  items.sort(() => Math.random() < 0.5 ? -1 : 1);
  items.forEach((item, index) => {
    item.style.setProperty('--order', index);
  })
}
.container {
  position: relative;
}
.placeholder {
  /*
  A .placeholder will push the .container's height
  while its .item is absolutely positioned inside.
  */
  background: red;
  width: 10rem;
  height: 3rem;
}
.item {
  /*
  All items are initially rendered at the top of the .container
  and are later transformed/translated to their ordered positions.
  */
  position: absolute;
  top: 0;
  width: 10rem;
  height: 3rem;
  
  transition: transform 0.5s ease;
  transform: translateY(calc(var(--order) * 100%));
    
  text-align: center;
  line-height: 3rem;
}
<button onclick="shuffleThem()">Shufle 🔀</button>

<br /><hr /><br />

<p>Content before the .container</p>

<div class="container">
  <div class="placeholder"></div>
  <div class="placeholder"></div>
  <div class="placeholder"></div>
  <div class="placeholder"></div>
  <div class="placeholder"></div>
  
  <div class="item" style="--order: 0; background: #FFBE66">A</div>
  <div class="item" style="--order: 1; background: #66FF9F">B</div>
  <div class="item" style="--order: 2; background: #FFFFFF">C</div>
  <div class="item" style="--order: 3; background: #FF8166">D</div>
  <div class="item" style="--order: 4; background: #70C1FF">E</div>
</div>

<p>Content after the .container</p>

Not saying it fits all cases but it did suit mine.

  • One major requirement for this approach is that you know (or determine) the heights of your items so that you can have placeholders for them.
  • A major flaw in the approach is that you cannot use the wrapping abilities of flexbox. You are either reordering vertically (as in the example) or horizontally.

Here is the same thing as a CodePen.

Upvotes: 4

neiya
neiya

Reputation: 3142

I am not really answering the question because I am not using the order property.

But I wanted to do something similar to what you expect, and finally decided to :

  • In HTML, add a data-order attribute to the elements
  • Add the CSS properties for each element position
  • Change the data-order using Javascript
  • Using CSS transitions for the interpolation

setInterval(changeOrder, 3000);

function changeOrder() {
  const allSlides = document.querySelectorAll(".single-slide");
  const previous = "1";
  const current = "2";
  const next = "3";

  for (const slide of allSlides) {
    const order = slide.getAttribute("data-order");

    switch (order) {
      case current:
        slide.setAttribute("data-order", previous);
        break;
      case next:
        slide.setAttribute("data-order", current);
        break;
      case previous:
        slide.setAttribute("data-order", next);
        break;
    }
  }
}
.all-slides {
  display: flex;
  width: 80vw;
  margin: 0 auto;
  perspective: 500px;
  height: 500px;
}

.single-slide {
  padding: 30px 20px;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  width: 30%;
  position: absolute;
  background-color: white;
  transition: 2s ease;
  box-shadow: 0px 5px 10px lightgrey;
}

/* Left slide*/
.single-slide[data-order="1"] {
  left: 10vw;
  transform: translate(-50%) scale(0.8, 0.8);
  z-index: 1;
  opacity: 0.7;
}

/* Middle slide */
.single-slide[data-order="2"] {
  left: 40vw;
  transform: translate(-50%);
  z-index: 3;
  opacity: 1;
}

/* Right slide*/
.single-slide[data-order="3"] {
  left: 90vw;
  transform: translate(-120%) scale(0.8, 0.8);
  z-index: 2;
  opacity: 0.7;
}

.single-slide:nth-child(2) {
  order: 3;
}

.single-slide:nth-child(1) {
  order: 2;
}

.single-slide:nth-child(3) {
  order: 1;
}
<div class="all-slides">
  <div class="single-slide" data-order="2">
    <h3>First slide </h3>
    <p>Some text</p>
  </div>
  <div class="single-slide" data-order="3">
    <h3>Second slide</h3>
    <p>Some other text</p>
  </div>
  <div class="single-slide" data-order="1">
    <h3>Third slide</h3>
    <p>Yet some other text</p>
  </div>
</div>

This could be useful if you want to animate a slider (or anything else), but want to keep the order of the elements in the HTML for accessibility purposes, which is one of the useful usage of the order property. See https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Flexible_Box_Layout/Ordering_Flex_Items#The_order_property_and_accessibility

Upvotes: 34

Tom Anthony
Tom Anthony

Reputation: 931

This question is old now, but I recently tested this, using this Fiddle (adapted from the one posted by Jason in a comment): http://jsfiddle.net/aqrxcd1u/ (code below).

In both Chrome and Firefox this partially animates, in that the order transitions one at a time from the current value to the target value. Meaning it doesn't go from 5->1 but instead goes 5->4->3->2->1.

In desktop Safari it still goes 5->1 directly.

#container {
    display: flex;
}
#container div {
    width: 100px;
    height: 100px;
    margin-right: 10px;
    background-color: red;    
}
#container div:nth-child(even) {
    background-color: blue;
}
}
#container div:last-child {
    order: 5;
    transition: order 1s;
}
#container:hover div:last-child {
    order: -1 !important;
}
<div id="container">
    <div style="order: 1">Element 1A</div>
    <div style="order: 2">Element 2B</div>
    <div style="order: 3">Element 3C</div>
    <div style="order: 4">Element 4D</div>
    <div style="order: 5">Element 5E</div>
</div>

Upvotes: 6

Nguyễn Đức Long
Nguyễn Đức Long

Reputation: 154

As Emil stated, it only animates as an integer. However, I am thinking of a hack:

  • Put the element you want to display in a wrapper with 1/10 of the height, set the wrapper overflow: visible
  • Then put 9 spacing element between these wrappers with the same height.
  • Put order and transition on all of them.
  • Change order of a wrapper and watch it 'transitioning'.

Sadly, it's ugly and only work in Firefox. Here is what I tested in Angular

Upvotes: 2

Emil
Emil

Reputation: 2059

Sadly no: the order attribute is animatable, but only as integers. That means that for each step/frame of the animation it will interpolate the value by flooring to the neareast integer. So items will only ever show up in the slot that the computed integer value results in, never in-between in any smooth sort of motion way.

It's technically still an animation: the calculated integer position should still follow the timing function and keyframe rules of the animation, it's just that the items "jump" from position to position as they change.

See https://developer.mozilla.org/en-US/docs/Web/CSS/integer#Interpolation

Upvotes: 29

Related Questions