1

In an accordion animated with Animate Plus, when padding is set on an element, the collapsing animation suddenly jumps when closing…

jumping animation

The animation is smooth when padding is not set…

smooth animation

How can I smoothly animate the accordion when padding is set?

My JavaScript code:

const accordions = Array.from(document.querySelectorAll("dl")).map(dl => ({
  element: dl,
  translate: 0
}))

const getButtons = accordion =>
  Array.from(accordion.element.getElementsByTagName("button"), element => ({
    element,
    translate: 0
  }))

const timing = {
  easing: "out-quartic",
  duration: 400
}

const clear = element =>
  Object.values(element.attributes).forEach(({ name }) =>
    element.removeAttribute(name)
  )

const hide = async (accordion, buttons, collapsing) => {
  const objects = buttons.filter(({ translate }) => translate)
  const direction = "reverse"
  rotate(collapsing.previousElementSibling.lastElementChild, direction)
  slide(accordion, objects)
  await fold(collapsing, direction)
  clear(collapsing)
}

const show = (accordion, buttons, expanding) => {
  const button = expanding.previousElementSibling.lastElementChild
  const index = buttons.findIndex(({ element }) => element == button)
  const objects = buttons.slice(index + 1)
  const { height } = expanding.getBoundingClientRect()
  expanding.className = "open"
  rotate(button)
  slide(accordion, objects, height)
  fold(expanding)
}

const slide = (accordion, array, to = 0) => {
  center(accordion, to)
  animate({
    ...timing,
    elements: array.map(({ element }) => element.parentElement),
    transform(index) {
      const object = array[index]
      const from = object.translate
      object.translate = to
      return [`translateY(${from}px)`, to]
    }
  })
}

const center = (accordion, height) => {
  const from = accordion.translate
  const to = Math.round(-height / 2)
  accordion.translate = to
  animate({
    ...timing,
    elements: accordion.element,
    transform: [`translateY(${from}px)`, to]
  })
}

const fold = async (content, direction = "normal") => {
  const scrollHeight = content.scrollHeight
  await animate({
    ...timing,
    direction,
    elements: content,
    opacity: [0, 1],
    maxHeight: ["0px", scrollHeight + "px"],
    transform: ["scaleY(0)", 1]
  })
}
const rotate = ({ lastElementChild: elements }, direction = "normal") =>
  animate({
    elements,
    direction,
    easing: "out-cubic",
    duration: 600,
    transform: ["rotate(0turn)", 0.5]
  })

const toggle = (accordion, buttons) => async ({ target }) => {
  const collapsing = accordion.element.querySelector(".open")
  const expanding = target.parentElement.nextElementSibling
  if (collapsing) await hide(accordion, buttons, collapsing)
  if (collapsing != expanding) show(accordion, buttons, expanding)
}

accordions.forEach(accordion => {
  const buttons = getButtons(accordion)
  buttons.forEach(({ element }) =>
    element.addEventListener("click", toggle(accordion, buttons))
  )
})

import animate from "https://cdn.jsdelivr.net/npm/animateplus@2/animateplus.js"

My full code for an accordion can be found on CodePen:

https://codepen.io/anon/pen/KJjYba

Community
  • 1
  • 1
Tzar
  • 5,132
  • 4
  • 23
  • 57
  • 2
    Perhaps try your own css animation that runs concurrently with the Animate Plus animation: transition: padding duration of Animation Plus animation; – Ted Fitzpatrick Feb 22 '19 at 19:10

3 Answers3

1

Animate the padding in the fold() method; add your desired measurements to paddingBottom and paddingTop properties. Then, you can remove the padding property from the .open class rule in the CSS.

const fold = async (content, direction = "normal") => {
  const scrollHeight = content.scrollHeight;
  await animate({
    ...timing,
    direction,
    elements: content,
    opacity: [0, 1],
    maxHeight: ["0px", scrollHeight + "px"],
    paddingTop: ["0em", "2em"],
    paddingBottom: ["0em", "2em"],
    transform: ["scaleY(0)", 1]
  });
};

CodePen

Ito Pizarro
  • 1,607
  • 1
  • 11
  • 21
1

you'll have to add paddingTop and paddingBottom to the fold function :

const fold = async (content, direction = "normal") => {
  const scrollHeight = content.scrollHeight
    await animate({
        ...timing,
        direction,
        elements: content,
        opacity: [0, 1],
        maxHeight: ['0px', scrollHeight + 'px'],
        transform: ["scaleY(0)", 1],
        paddingTop: ['0em', '2em'], // add this
        paddingBottom: ['0em', '2em'] // add this
    })
}

Code pen

Taki
  • 17,320
  • 4
  • 26
  • 47
  • Check that on subsequent toggles (more than just a single open/close). – Ito Pizarro Feb 22 '19 at 19:58
  • @ItoPizarro i see what you mean, that's another issue, not cause by the solution, it happens without it too. – Taki Feb 22 '19 at 20:07
  • Your change to the `timing.duration` is affecting the timing of concurrent/sequential animations. I arrived at essentially the same solution but left the timing as-is, and it doesn't exhibit that value compounding behavior. – Ito Pizarro Feb 22 '19 at 20:17
  • @ItoPizarro oh, i did that while debugging, i forgot to remove it,it's not mentionned in the snippet in the answer so it's not suppoed to be a part of it, i updated the Pen, thnx for spotting it. – Taki Feb 22 '19 at 20:20
-1

You can set the padding to a new child element. So, wrap the content in an extra div like this:

<dd><div>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua.</div></dd>

And then, instead of styling .open style the div inside:

.open div {
    padding: 2em 0;
}

It doesn't jump using the child.

Teuniz
  • 133
  • 6