Home > OS >  Why is this slideUp function not smooth?
Why is this slideUp function not smooth?

Time:12-28

I want to implement a vanilla JS version of the jQuery slideUp()and slideDown() function.

My idea was to use CSS transition for the height property together with incrementing/decrementing the height using requestAnimationFrame().

I tried it with the following code, and slideDown is working as expected, but slideUp is not smoothly collapsing, instead, it is just suddenly gone.

Here is how it looks:

enter image description here

Here is a jsfiddle.

  function toggleSlide(element) {
  // Check if the element is currently visible
  if (element.style.display== '' || element.style.display== 'none') {
    // If the element is not visible, slide it down
    slideDown(element);
  } else {
    // If the element is visible, slide it up
    slideUp(element);
  }
}


function slideDown(element) {

  // If the element is not visible, set the display and height properties
  if (element.style.display !== 'block') {
    element.style.display = 'block';
    element.style.height = 0;
  }
  
    // Get the height of the element's content
  const contentHeight = element.scrollHeight;

  // Set a variable to keep track of the element's height
  let currentHeight = 0;

  // Start an animation loop
  function animate() {
    // Increase the element's height by 10px
    currentHeight  = 10;

    // Update the element's height
    element.style.height = `${currentHeight}px`;

    // If the element's height is less than the content height, request another animation frame
    if (currentHeight < contentHeight) {
      requestAnimationFrame(animate);
    }
  }

  // Start the animation
  animate();
}

function slideUp(element) {
  // Get the height of the element's content
  const contentHeight = element.scrollHeight;
  // Set a variable to keep track of the element's height
  let currentHeight = contentHeight;

  // Start an animation loop
  function animate() {
    // Decrease the element's height by 10px
    currentHeight -= 10;

    // Update the element's height
    element.style.height = `${currentHeight}px`;
    
    // If the element's height is greater than 0, request another animation frame
    if (currentHeight > 0) {
   
      requestAnimationFrame(animate);
    } else {
      // If the animation is complete, hide the element
      element.style.display = 'none';
    }
    
  }

  // Start the animation
  animate();
}
 .my-element {
        background-color: #ddd;
        
        transition: height 0.5s;
        overflow: hidden;
        display:none;
      }
   <button onclick="toggleSlide(document.querySelector('.my-element'))">Slide Down</button>

    <!-- Create the element that we will slide down -->
    <div >
      <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. In et tellus in quam convallis dictum. Maecenas eget odio eget arcu accumsan porttitor. Ut elementum volutpat orci, sed tincidunt ipsum dictum at. Maecenas auctor tempus diam quis gravida. Aliquam at ultricies leo. Etiam euismod, nisi ac blandit placerat, diam turpis vestibulum diam, at ultricies turpis orci at elit. Mauris auctor dictum dolor, quis pretium nisi ultricies in. Aenean sollicitudin, quam non euismod porta, enim nisl laoreet velit, vel venenatis dui dui vel lacus. Aliquam erat volutpat. Donec et elit ut ipsum elementum volutpat. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Praesent a nisi at est eleifend ullamcorper. Aenean porta, erat ac sagittis fermentum, leo magna tincidunt lectus, ac semper risus est et diam. Suspendisse auctor ipsum quam, id porta dui dictum sed. Vivamus ac diam non elit placerat iaculis vel at velit. Aliquam vel tincidunt velit. Duis imperdiet, dolor eu placerat luctus, ipsum quam laoreet dui, non suscipit nulla tellus et arcu. Donec dictum, massa non bibendum varius, leo urna condimentum diam, nec suscipit elit turpis vel turpis. Aenean ac nunc quis nisl tempus tincidunt a eu metus.</p>
    </div>

Why is slideUp not collapsing smoothly?

CodePudding user response:

The CSS Transition is the one taking effect before the toggle animation ends. Removing it solves the issue since your functions are taking care of the animating durations

function toggleSlide(element) {
  // Check if the element is currently visible
  if (element.style.display== '' || element.style.display== 'none') {
    // If the element is not visible, slide it down
    slideDown(element);
  } else {
    // If the element is visible, slide it up
    slideUp(element);
  }
}


function slideDown(element) {

  // If the element is not visible, set the display and height properties
  if (element.style.display !== 'block') {
    element.style.display = 'block';
    element.style.height = 0;
  }
  
    // Get the height of the element's content
  const contentHeight = element.scrollHeight;

  // Set a variable to keep track of the element's height
  let currentHeight = 0;

  // Start an animation loop
  function animate() {
    // Increase the element's height by 10px
    currentHeight  = 10;

    // Update the element's height
    element.style.height = `${currentHeight}px`;

    // If the element's height is less than the content height, request another animation frame
    if (currentHeight < contentHeight) {
      requestAnimationFrame(animate);
    }
  }

  // Start the animation
  animate();
}

function slideUp(element) {
  // Get the height of the element's content
  const contentHeight = element.scrollHeight;
  // Set a variable to keep track of the element's height
  let currentHeight = contentHeight;

  // Start an animation loop
  function animate() {
    // Decrease the element's height by 10px
    currentHeight -= 10;

    // Update the element's height
    element.style.height = `${currentHeight}px`;
    
    // If the element's height is greater than 0, request another animation frame
    if (currentHeight > 0) {
   
      requestAnimationFrame(animate);
    } else {
      // If the animation is complete, hide the element
      element.style.display = 'none';
    }
    
  }

  // Start the animation
  animate();
}
.my-element {
        background-color: #ddd;
        /* Remove the transition */
        overflow: hidden;
        display:none;
      }
<button onclick="toggleSlide(document.querySelector('.my-element'))">Slide Down</button>

    <!-- Create the element that we will slide down -->
    <div >
      <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. In et tellus in quam convallis dictum. Maecenas eget odio eget arcu accumsan porttitor. Ut elementum volutpat orci, sed tincidunt ipsum dictum at. Maecenas auctor tempus diam quis gravida. Aliquam at ultricies leo. Etiam euismod, nisi ac blandit placerat, diam turpis vestibulum diam, at ultricies turpis orci at elit. Mauris auctor dictum dolor, quis pretium nisi ultricies in. Aenean sollicitudin, quam non euismod porta, enim nisl laoreet velit, vel venenatis dui dui vel lacus. Aliquam erat volutpat. Donec et elit ut ipsum elementum volutpat. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Praesent a nisi at est eleifend ullamcorper. Aenean porta, erat ac sagittis fermentum, leo magna tincidunt lectus, ac semper risus est et diam. Suspendisse auctor ipsum quam, id porta dui dictum sed. Vivamus ac diam non elit placerat iaculis vel at velit. Aliquam vel tincidunt velit. Duis imperdiet, dolor eu placerat luctus, ipsum quam laoreet dui, non suscipit nulla tellus et arcu. Donec dictum, massa non bibendum varius, leo urna condimentum diam, nec suscipit elit turpis vel turpis. Aenean ac nunc quis nisl tempus tincidunt a eu metus.</p>
    </div>

CodePudding user response:

As Barmar stated, one should either use requestAnimationFrame() or CSS transitions. Cypherjac represents the solution of just using requestAnimationFrame() here comes the alternative CSS transitions solution:

See jsfiddle or

const button = document.querySelector('button')

button.addEventListener('click', (e) => {
    const container = document.querySelector('div');
    const active = container.style.height !== '' && container.style.height !== '0px';
            
    if(active){
        container.style.height = "0px"
      container.addEventListener('transitionend', () => {
          container.style.display = 'none';
      }, {once: true})
      return;
    }
      
    container.style.display = 'block';
    container.style.height = "auto"
    const height = container.clientHeight   "px"
    container.style.height = "0px"
     
    setTimeout(() => {
        container.style.height = height          
    }, 0) 
    
})
.box {
    transition: height .5s ease;
    overflow  : hidden;
    height:0;
}
<div >
    I'm an unknown content height element. I'm an unknown content height element. I'm an unknown content height element. I'm an unknown content height element. I'm an unknown content height element. I'm an unknown content height element. I'm an unknown content height element. I'm an unknown content height element. I'm an unknown content height element. I'm an unknown content height element. I'm an unknown content height element. I'm an unknown content height element. I'm an unknown content height element. I'm an unknown content height element. I'm an unknown content height element. 
</div>
<button>Slide Toggle</button>

The part with setting display to none/block could be omitted. Only added, as I assume for most scenarios, its useful to have it completly removed from flow when toggled.

The addEventListener:transitioned is necessary, so the display is set after the height has been changed to 0 and CSS transition has taken place.

The setTimeout is necessary, because otherwise, the height would be directly set to height and CSS would not get the change to animate.

  • Related