Matt
Matt

Reputation: 105

Animate element to its new position in DOM

I have a container, with an unknown amount of cards in (ranging from 3 to 8).

|  --   --   --  |
| CARD CARD CARD |
|  --   --   --  |

And when one is clicked, all others disappear and the card clicked needs to move to the left. For example, if the middle card is clicked:

Cards disappear

|       --       |
|      CARD      |
|       --       |

Card moves to left (with an animation)

|  --            |
| CARD           |
|  --            |

The cards are able to disappear but I feel my solution isn't great. I cannot move the card to the left.

Here's my solution for the hiding of the cards:

    cards.forEach(card => {
        card.addEventListener('click', () => {
            cards.forEach(card2 => {
                if(card2 != card){
                    card2.style.animation = 'fadeOut 0.2s linear forwards';
                }
            })
        })
    })

But I am very unsure how to move the card. Here's the css for the card container and card itself.

.cards{
    height: 100%;
    width: 100%;
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    grid-gap: 50px;
    position: relative;
}
.card{
    height: 100%;
    width: 100%;
    position: relative;
}

Is this possible with a css animation?

Upvotes: 3

Views: 3865

Answers (2)

LJD
LJD

Reputation: 496

Here is a pretty solid working solution:

var cards = document.querySelectorAll(".card");

function transitionOpacity(){ 
  cards.forEach( el => {
    if(el.id != this.id){
      el.classList.add("invisible"); 
    }
  });
}

function transitionWidth(){  
  cards.forEach( el => {
    if(el.classList.contains("invisible")){
      el.classList.add("thin"); 
    }
  });
}

cards.forEach( el => { 
  el.addEventListener("click", transitionOpacity);
  el.addEventListener("transitionend", transitionWidth);
});
#main{
  width: 100%;
  height: 300px;
  background-color: grey; 
  box-sizing: border-box;
}

.card{
  height: 100%;
  background-color: white;
  margin: 2.5%;
  width: 20%;
  float: left;
  opacity: 1;
  transition: all .5s;
}

.invisible{
  opacity: 0; 
}

.thin{
  width: 0; 
  margin-left: 0;
  margin-right: 0;
}
<div id = "main">
  <div id = "1" class = "card"></div>
  <div id = "2" class = "card"></div>
  <div id = "3" class = "card"></div>
  <div id = "4" class = "card"></div>
</div>

Step 1: Add create html tags Step 2: Create CSS Step 3: Add all elements with card class to array Step 4: Add event listener to all cards to listen for clicks. Step 5: Add event listener to all cards to listen for the end of a transition Step 6: Check ID of clicked element. Hide elements that weren't clicked Step 7: Check classList of all cards that were made invisible and now make those cards shrink

Upvotes: 2

Roko C. Buljan
Roko C. Buljan

Reputation: 206131

Animate element to its new position

To animate an element to its new position follow this simple steps:

  1. Calculate the current X, Y position of the element relative to the parent
  2. Hide the other elements with display: none
  3. Since the element has a new position now that the other elements are gone - snap (move) it immediately back where it was using CSS translate: transform(x, y) to the old X and Y.
  4. Animate with CSS transition to 0, 0 translate: transform(0, 0), which is the position this element actually occupies after the other elements were hided from the view.

Here's an example of the above list with an extra step to first fade-out the other elements before hiding them, and only than - animate the element:

const animateCards = (card) => {
  const fxHide = `display: none;`;
  const fxFade = `transition: opacity 0.3s; opacity: 0;`;
  const fxMove = `transform: translate(${card.offsetLeft}px, ${card.offsetTop}px);`;
  const fxAnim = `transition: transform 0.5s; transform: translate(0, 0);`;
  let other; // Just to catch a sibling element as reference

  cards.forEach(el => {
    if (el === card) return;
    if (!other) other = el;
    el.addEventListener("transitionend", () => el.style.cssText += fxHide, {once: true});
    el.style.cssText += fxFade;
  });

  other.addEventListener("transitionend", () => {
    card.style.cssText += fxMove;
    setTimeout(() => card.style.cssText += fxAnim, 0);
  }, {once: true});
};

const cards = document.querySelectorAll(".card");
cards.forEach(el => el.addEventListener('click', () => animateCards(el)));
.cards {
  height: 100%;
  width: 100%;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-gap: 50px;
  position: relative;
}

.card {
  height: 100%;
  position: relative;
  cursor: pointer;
}
<div class="cards">
  <div class="card" style="background-color:#0bf;">1</div>
  <div class="card" style="background-color:#f0b;">2</div>
  <div class="card" style="background-color:#bf0;">3</div>
</div>

Upvotes: 5

Related Questions