nikc.org
nikc.org

Reputation: 16962

CSS transitions between absolute and relative positioning

Is it possible to combine position: relative and position: absolute with smooth CSS-transitions?

I'm creating a small widget (I call it a Deck), which I wan't to have a collapsed and expanded state. All well so far, this is working fine.

Switching between the two states naturally warrants a transition animation. This is working too, but not the way I would like it to. I Want to use CSS-transitions, instead of using absolute positioning and JavaScript, like I am currently.

The current scenario is: in expanded state the cards in the deck are always positioned absolutely, their position being calculated on the fly as they are added to the deck. When collapsing, the first four are stacked in a cascading manner and the rest on top of the fourth card. Visually mimicking a pile or stack.

The problem with this approach, is that I can't rely on normal layout flow for positioning the cards, which sucks for many reasons. If I use position: relative for the cards in expanded state, they flow nicely one after another. But the transition to collapsed state is not being animated - simply snapping from one position to the other in an instant. This is of course logical since without absolute positioning in the first place, the browser doesn't know whence from the transition should start.

In my perfect world, adding the class collapsed to div.deck-container would animate the cards into their collapsed positions and vice versa, but it seems this isn't possible. Please someone tell me I'm wrong.

$('button#toggler').click(function() {
  $('div.deck-container').toggleClass('collapsed');
});
div.deck-container {
  position: relative;
}

div.deck-container li {
  display: inline-block;
  position: relative;
  transition: all 0.5s ease-in-out;

  border: 1px solid black;
  padding: 3px;
  background-color: #fff;
}

div.deck-container.collapsed li {
  position: absolute;
  left: 9px;
  top: 6px;
}

div.deck-container.collapsed li:first-child {
  left: 0;
  top: 0px;
}

div.deck-container.collapsed li:nth-child(2) {
  left: 3px;
  top: 2px;
}

div.deck-container.collapsed li:nth-child(3) {
  left: 6px;
  top: 4px;
}

button {
  position: absolute;
  top: 50px;
  left: 50px;
}
<div class="deck-container">
  <ul>
    <li>Card 1</li>
    <li>Card 2</li>
    <li>Card 3</li>
    <li>Card 4</li>
    <li>Card 5</li>
  </ul>
</div>

<button id="toggler">Toggle state</button>

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

Upvotes: 43

Views: 107237

Answers (2)

nd_macias
nd_macias

Reputation: 812

Maybe you could use translate instead:

$('button#toggler').click(function() {
  $('div.deck-container').toggleClass('collapsed');
});
div.deck-container li {
  background-color: #fff;
  border: 1px solid black;
  padding: 3px;
  display: inline-block;
  transition: all 0.5s ease-in-out;
}

div.deck-container.collapsed li:first-child {
  transform: translate(0, 0);
}

div.deck-container.collapsed li:nth-child(2) {
  transform: translate(-100%, 2px);
}

div.deck-container.collapsed li:nth-child(3) {
  transform: translate(-200%, 4px);
}

button {
  position: absolute;
  top: 50px;
  left: 50px;
}
<div class="deck-container">
  <ul>
    <li>Card 1</li>
    <li>Card 2</li>
    <li>Card 3</li>
  </ul>
</div>

<button id="toggler">Toggle state</button>

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

Upvotes: 20

scumah
scumah

Reputation: 6393

No, you can't animate the position property. There are only a number of css properties you can animate, and most of them have numbers or colors as values (With some exceptions). You can see this list in the w3c css transitions especification.

Anyway, since you can animate top and left properties, you could change your markup a little to achieve the effect.

I just set the original position to absolute and positioned those elements. Then, when toggling the class, only top and left attributes change, so the transition works.

$('button#toggler').click(function() {
  $('div.deck-container').toggleClass('collapsed');
});
div.deck-container {
  position: relative;
}

div.deck-container li {
  background-color: #fff;
  position: absolute;
  border: 1px solid black;
  padding: 3px;
  display: inline-block;
  transition: all 0.5s ease-in-out;
}

div.deck-container li {
  left: 160px;
  top: 0px;
}

div.deck-container li:first-child {
  left: 0px;
  top: 0px;
}

div.deck-container li:nth-child(2) {
  left: 40px;
  top: 0px;
}

div.deck-container li:nth-child(3) {
  left: 80px;
  top: 0px;
}

div.deck-container li:nth-child(4) {
  left: 120px;
  top: 0px;
}

div.deck-container.collapsed li {
  left: 12px;
  top: 8px;
}

div.deck-container.collapsed li:first-child {
  left: 0;
  top: 0px;
}

div.deck-container.collapsed li:nth-child(2) {
  left: 3px;
  top: 2px;
}

div.deck-container.collapsed li:nth-child(3) {
  left: 6px;
  top: 4px;
}

div.deck-container.collapsed li:nth-child(4) {
  left: 9px;
  top: 6px;
}

button {
  position: absolute;
  top: 50px;
  left: 50px;
}
<div class="deck-container">
  <ul>
    <li>Card 1</li>
    <li>Card 2</li>
    <li>Card 3</li>
    <li>Card 4</li>
    <li>Card 5</li>
  </ul>
</div>

<button id="toggler">Toggle state</button>

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

Upvotes: 51

Related Questions