accordion animation
parent
ec6f74299b
commit
2837a974f0
|
@ -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 %}
|
||||||
|
|
Loading…
Reference in New Issue