accordion animation

main
alexanderroese 2024-06-22 16:07:24 +02:00
parent ec6f74299b
commit 2837a974f0
1 changed files with 115 additions and 116 deletions

View File

@ -108,122 +108,121 @@
<script> <script>
class Accordion { class Accordion {
constructor(el) { constructor(el) {
// Store the <details> element // Store the <details> element
this.el = el; this.el = el;
// Store the <summary> element // Store the <summary> element
this.summary = el.querySelector('summary'); this.summary = el.querySelector('summary');
// Store the <div class="content"> element // Store the <div class="content"> element
this.content = el.querySelector('.accordion__content'); this.content = el.querySelector('.accordion__content');
// Store the animation object (so we can cancel it if needed) // Store the animation object (so we can cancel it if needed)
this.animation = null; this.animation = null;
// Store if the element is closing // Store if the element is closing
this.isClosing = false; this.isClosing = false;
// Store if the element is expanding // Store if the element is expanding
this.isExpanding = false; this.isExpanding = false;
// Detect user clicks on the summary element // Detect user clicks on the summary element
this.summary.addEventListener('click', (e) => this.onClick(e)); this.summary.addEventListener('click', (e) => this.onClick(e));
} }
onClick(e) { onClick(e) {
// Stop default behaviour from the browser // Stop default behaviour from the browser
e.preventDefault(); e.preventDefault();
// Add an overflow on the <details> to avoid content overflowing // Add an overflow on the <details> to avoid content overflowing
this.el.style.overflow = 'hidden'; this.el.style.overflow = 'hidden';
// Check if the element is being closed or is already closed // Check if the element is being closed or is already closed
if (this.isClosing || !this.el.open) { if (this.isClosing || !this.el.open) {
this.open(); this.open();
// Check if the element is being openned or is already open // Check if the element is being openned or is already open
} else if (this.isExpanding || this.el.open) { } else if (this.isExpanding || this.el.open) {
this.shrink(); this.shrink();
} }
} }
shrink() { shrink() {
// Set the element as "being closed" // Set the element as "being closed"
this.isClosing = true; this.isClosing = true;
// Store the current height of the element // Store the current height of the element
const startHeight = `${this.el.offsetHeight}px`; const startHeight = `${this.el.offsetHeight}px`;
// Calculate the height of the summary // Calculate the height of the summary
const endHeight = `${this.summary.offsetHeight}px`; const endHeight = `${this.summary.offsetHeight}px`;
// If there is already an animation running // If there is already an animation running
if (this.animation) { if (this.animation) {
// Cancel the current animation // Cancel the current animation
this.animation.cancel(); this.animation.cancel();
} }
// Start a WAAPI animation // Start a WAAPI animation
this.animation = this.el.animate({ this.animation = this.el.animate({
// Set the keyframes from the startHeight to endHeight // Set the keyframes from the startHeight to endHeight
height: [startHeight, endHeight] height: [startHeight, endHeight]
}, { }, {
duration: 200, duration: 200,
easing: 'linear' easing: 'linear'
}); });
// When the animation is complete, call onAnimationFinish() // When the animation is complete, call onAnimationFinish()
this.animation.onfinish = () => this.onAnimationFinish(false); this.animation.onfinish = () => this.onAnimationFinish(false);
// If the animation is cancelled, isClosing variable is set to false // If the animation is cancelled, isClosing variable is set to false
this.animation.oncancel = () => this.isClosing = false; this.animation.oncancel = () => this.isClosing = false;
} }
open() { open() {
// Apply a fixed height on the element // Apply a fixed height on the element
this.el.style.height = `${this.el.offsetHeight}px`; this.el.style.height = `${this.el.offsetHeight}px`;
// Force the [open] attribute on the details element // Force the [open] attribute on the details element
this.el.open = true; this.el.open = true;
// Wait for the next frame to call the expand function // Wait for the next frame to call the expand function
window.requestAnimationFrame(() => this.expand()); window.requestAnimationFrame(() => this.expand());
} }
expand() { expand() {
// Set the element as "being expanding" // Set the element as "being expanding"
this.isExpanding = true; this.isExpanding = true;
// Get the current fixed height of the element // Get the current fixed height of the element
const startHeight = `${this.el.offsetHeight}px`; const startHeight = `${this.el.offsetHeight}px`;
// Calculate the open height of the element (summary height + content height) // Calculate the open height of the element (summary height + content height)
const endHeight = `${this.summary.offsetHeight + this.content.offsetHeight}px`; const endHeight = `${this.summary.offsetHeight + this.content.offsetHeight}px`;
// If there is already an animation running // If there is already an animation running
if (this.animation) { if (this.animation) {
// Cancel the current animation // Cancel the current animation
this.animation.cancel(); this.animation.cancel();
} }
// Start a WAAPI animation // Start a WAAPI animation
this.animation = this.el.animate({ this.animation = this.el.animate({
// Set the keyframes from the startHeight to endHeight // Set the keyframes from the startHeight to endHeight
height: [startHeight, endHeight] height: [startHeight, endHeight]
}, { }, {
duration: 300, duration: 200,
easing: 'linear' easing: 'linear'
}); });
// When the animation is complete, call onAnimationFinish() // When the animation is complete, call onAnimationFinish()
this.animation.onfinish = () => this.onAnimationFinish(true); this.animation.onfinish = () => this.onAnimationFinish(true);
// If the animation is cancelled, isExpanding variable is set to false // If the animation is cancelled, isExpanding variable is set to false
this.animation.oncancel = () => this.isExpanding = false; this.animation.oncancel = () => this.isExpanding = false;
} }
onAnimationFinish(open) { onAnimationFinish(open) {
// Set the open attribute based on the parameter // Set the open attribute based on the parameter
this.el.open = open; this.el.open = open;
// Clear the stored animation // Clear the stored animation
this.animation = null; this.animation = null;
// Reset isClosing & isExpanding // Reset isClosing & isExpanding
this.isClosing = false; this.isClosing = false;
this.isExpanding = false; this.isExpanding = false;
// Remove the overflow hidden and the fixed height // Remove the overflow hidden and the fixed height
this.el.style.height = this.el.style.overflow = ''; this.el.style.height = this.el.style.overflow = '';
} }
} }
document.querySelectorAll('details').forEach((el) => { document.querySelectorAll('details').forEach((el) => {
new Accordion(el); new Accordion(el);
}); });
</script> </script>
{% schema %} {% schema %}