4

I am working on a website for my history project and I came across a problem. I tried to animate a details tag but nothing seems to be working. Here is the code I used for the opening animation:

@keyframes open {
  0% {
    opacity: 0;
    transform: translateY(-1vw);
  }
  100% {
    opacity: 1;
    margin-left: 0px
  }
}

details[open] summary~* {
  animation: open .5s cubic-bezier(0.785, 0.135, 0.15, 0.86);
}
<details>
  <summary>Cfare do te mesosh gjate ketij materiali:</summary>
  <ul>
    <li>
      <p>Shqiperia gjate luftes se pare boterore</p>
    </li>
    <li>
      <p>Shqiperia mes lufterave boterore</p>
    </li>
    <li>
      <p>Shqiperia gjate luftes se dyte boterore</p>
    </li>
  </ul>
</details>

It is basically a simple fade in animation and I would love to do a fade out one as well!

Thanks in advance!

Michael Haddad
  • 4,085
  • 7
  • 42
  • 82

1 Answers1

10

Updated answer:

The older version was a bit hacky. This is a better approach.

When you open the details element, its content - by definition - becomes visible. This is why every animation you apply is visible. However, when you close the details element, its content - by definition - becomes invisible, immediately. So even though every animation you try to apply will actually occur, it will simply not be visible.

So we need to tell the browser to suspend hiding the elements, apply the animation, and only when it's over, to hide the element. Let's use some JS for that.

We need to capture when the user toggles the details element. Supposedly, the relevant event would be toggle. However, this event is fired only after the browser hides everything. So we will actually go with the click event, which fires before the hiding happens.

const details = document.querySelector("details");
details.addEventListener("click", (e) => {
  if (details.hasAttribute("open")) { // since it's not closed yet, it's open!
    e.preventDefault(); // stop the default behavior, meaning - the hiding
    details.classList.add("closing"); // add a class which applies the animation in CSS
  }
});

// when the "close" animation is over
details.addEventListener("animationend", (e) => {
  if (e.animationName === "close") {
    details.removeAttribute("open"); // close the element
    details.classList.remove("closing"); // remove the animation
  }
});
@keyframes open {
  0% { opacity: 0 }
  100% { opacity: 1 }
}

/* closing animation */
@keyframes close {
  0% { opacity: 1 }
  100% { opacity: 0 }
}

details[open] summary~* {
  animation: open .5s
}

/* closing class */
details.closing summary~* {
  animation: close .5s
}
<details>
  <summary>Summary</summary>
  <p>
    Details
  </p>
</details>

Old answer:

const details = document.querySelector("details");
details.addEventListener("click", (e) => {
  if (details.hasAttribute("open")) { // since it's not closed yet, it's open!
    e.preventDefault(); // stop the default behavior, meaning - the hiding
    details.classList.add("closing"); // add a class that applies the animation in CSS
    setTimeout(() => { // only after the animation finishes, continue
      details.removeAttribute("open"); // close the element
      details.classList.remove("closing");
    }, 400);
  }
});
@keyframes open {
  0% { opacity: 0 }
  100% { opacity: 1 }
}

/* closing animation */
@keyframes close {
  0% { opacity: 1 }
  100% { opacity: 0 }
}

details[open] summary~* {
  animation: open .5s
}

/* closing class */
details.closing summary~* {
  animation: close .5s
}
<details>
  <summary>Summary</summary>
  <p>
    Details
  </p>
</details>

Note that time durations are not guaranteed to be accurate, not in CSS and not in JS. This is why I have set the JS timeout to be just a little bit lower than the CSS animation duration (400ms vs 500ms), so we are sure the animation ends just a bit after we're hiding the elements, so we don't get any flickering.

Michael Haddad
  • 4,085
  • 7
  • 42
  • 82
  • 1
    OP did not mention Javascript can be used for answer, still good solution. – codemonkey Nov 14 '21 at 00:02
  • This is definetly doable with css only. Using an attribute selector. – NVRM Nov 14 '21 at 02:44
  • @NVRM The only selector I can imagine would presumably work is `details:not([open]) summary~*`. However, it will *not* work, because an animation set in it will play only *after* the element will be closed, and thus will not be visible. If you can make it work, or have another selector in mind, I will be very interested to read about it, ao please post it in an answer. However, my approach works, so even if it's not the most elegant, it is somewhat helpful, and in my opinion - does not deserve a downvote. – Michael Haddad Nov 14 '21 at 08:55
  • See https://stackoverflow.com/a/66430685/2494754 and https://stackoverflow.com/a/62322817/2494754 – NVRM Nov 14 '21 at 23:00
  • 3
    @NVRM - in both links you provided there is no *fade-out* effect when the details get closed, which is what the OP is asking for. All animations happen only when *opening* the details, but not when closing... – Michael Haddad Nov 14 '21 at 23:13
  • @MisterJojo Thank you for your comment. 1. In my answer, I explained the problem with using `toggle` and my reasoning for selecting `click` instead. If you could specify which aspect of my explanation you disagree with, I would be able to address it. 2. Using `removeAttribute` with boolean attributes is a [common practice](https://stackoverflow.com/a/9201499/1925272). – Michael Haddad Apr 16 '23 at 22:57