skip to Main Content

I have a JavaScript function to move different child elements across a parent div, but I want to add a transition so the elements move to their new position in the div instead of changing the position instantly.

As far as I used transitions, for me it only worked with defined properties, like top/bottom or height/width. In this case, I didn’t define any properties, so I wonder if a transition effect is still possible.

Here you can see the children and the parent I set up, and my function for moving the child elements:

function moveElement(elementId, place) {
  let div = document.getElementById('parent')
  let element = document.getElementById(String(elementId))
  let referencenode = div.children[place]
  div.insertBefore(element, referencenode)
}

var children = document.getElementById('parent').children;

document.addEventListener("click", function(event) {
  let id = children.item(2).id;
  moveElement(id, 0)
});
<div id="parent">
  <div id="child1" style="background-color:red" class="child">
    <p>child1</p>
  </div>
  <div id="child2" style="background-color:lightblue" class="child">
    <p>child2</p>
  </div>
  <div id="child3" style="background-color:green" class="child">
    <p>child3</p>
  </div>
  <div id="child4" style="background-color:yellow" class="child">
    <p>child4</p>
  </div>
</div>

since all my attempts of making a working transition failed, I only left the switching function, so they switch places instantly.

I tried using transition-duration, but since no CSS properties were defined, expected nothing happened.

Thanks in advance!

And please no jQuery, since I didn’t ever use it, so if possible I would appreciate vanilla JavaScript 🙂

2

Answers


  1. As the comments suggest, CSS transitions happen on CSS property changes, so some CSS property has to change for a transition to happen. For animating changes in order or DOM elements, you could consider using the First, Last, Invert, Play (FLIP) technique1234 whereby:

    1. First: get the initial position of the element(s).
    2. Last: make the element(s) to their ending state.
    3. Invert: use the information from the previous steps to invert the positional change back to how the element(s) look before the change occurred.
    4. Play: remove the inversion but with transitions, so that the element(s) animate back from inversion back to their new positions.
    function moveElement(elementId, place) {
      let div = document.getElementById('parent')
      let element = document.getElementById(String(elementId))
      
      const children = Array.from(element.parentElement.children);
      let referencenode = children[place]
      
      // Get the child index of the element being moved.
      const elementIndex = children.indexOf(element);
      // Get the list of elements that will move.
      const movingChildren = children.slice(
        elementIndex > place ? place : elementIndex,
        (elementIndex > place ? elementIndex : place) + 1,
      );
      
      // Capture the positions of the elements being moved (First).
      const before = movingChildren.map(
        movingChild => movingChild.getBoundingClientRect(),
      );  
      
      // Do the moving (Last).
      div.insertBefore(element, referencenode);
      
      // Do the animations.
      movingChildren.forEach((child, i) => {
        // Get the moved position.
        const newPosition = child.getBoundingClientRect();
        // `transform` the element back to their old position (Invert).
        child.style.transform = `translate(${before[i].x - newPosition.x}px, ${before[i].y - newPosition.y}px)`;
        
        // Initialize the transition on the next render frame.
        requestAnimationFrame(() => {
          // Clear transitioning.
          const clearTransition = () => {
            child.style.transition = '';
            child.removeEventListener('transitionend', clearTransition);
          };
          child.addEventListener('transitionend', clearTransition);
          
          // Do the transition (Play).
          child.style.transition = 'transform 250ms';
          child.style.transform = '';
        });
      });  
    }
    
    var children = document.getElementById('parent').children;
    
    document.addEventListener("click", function(event) {
      let id = children.item(2).id;
      moveElement(id, 0)
    });
    <div id="parent">
      <div id="child1" style="background-color:red" class="child">
        <p>child1</p>
      </div>
      <div id="child2" style="background-color:lightblue" class="child">
        <p>child2</p>
      </div>
      <div id="child3" style="background-color:green" class="child">
        <p>child3</p>
      </div>
      <div id="child4" style="background-color:yellow" class="child">
        <p>child4</p>
      </div>
    </div>

    1Layout Animation FLIP Technique

    2Animating Layouts with the FLIP Technique

    3FLIP Your Animations

    4Animating the Unanimatable

    Login or Signup to reply.
  2. Rearrange sibling elements with animation

    • Store the current boundingClientRect of all the elements that need to move (basically all the elements from minIndex to maxIndex included)
    • Move the element into the new DOM position index (within the same parent) taking care that: if the index matches the total indexes available, you’ll have to use ParentElement.append() instead of Element.insertBefore()
    • Use the Animation API to translate the elements from the starting boundingClientRect (before the rearrangement) to the current one (0px left and top):
    const moveElement = (elA, iB) => {
      const par = elA.parentElement
      const ch = [...par.children];
      const elB = ch[iB];
      const iA = ch.indexOf(elA);
      const iTot = ch.length - 1;
      const iMin = Math.min(iA, iB, 0);
      const iMax = Math.max(iA, iB, iTot);
      const chMove = ch.slice(iMin, iMax + 1);
      const bcr = chMove.map(el => el.getBoundingClientRect());
    
      // Rearrange
      if (iB === iTot) par.append(elA);
      else  par.insertBefore(elA, elB); 
      
      // Animate elements
      chMove.forEach((el, i) => { 
        const {left: xA, top: yA} = bcr[i];
        const {left: xB, top: yB} = el.getBoundingClientRect();
        el.animate([
          { translate: `${xA - xB}px ${yA - yB}px`},
          { translate: "0px 0px" },
        ], {duration: 280});
      });
    };
    
    const par = document.querySelector("#parent");
    par.addEventListener("click", () => {
      const elA = par.children[2];
      moveElement(elA, 0);
    });
    Click to rearrange + animate
    <div id="parent">
      <p style="background:#0bf;">A</p>
      <p style="background:#fb0;">B</p>
      <p style="background:#f0b;">C</p>
      <p style="background:#bf0;">D</p>
    </div>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search