Skip to content

Instantly share code, notes, and snippets.

@akingdom
Last active September 19, 2023 08:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save akingdom/272a345e93dc2f107c895a154cc497e1 to your computer and use it in GitHub Desktop.
Save akingdom/272a345e93dc2f107c895a154cc497e1 to your computer and use it in GitHub Desktop.
This is an example of implementing headings with collapsible content. This doesn't currently recalculate height if content changes as I don't need that.
// Implements clickable headings with collapsible content.
// This doesn't currently recalculate height if content changes as I don't need that.
//
// Copyright (C) Andrew Kingdom 2023. all rights reserved.
//
// Heading elements must include class 'heading'.
// Content elements must include class 'collapsibleContent' and be the node sibling immediately after the Headiing.
//
//
// If HTML is in a separate file,
// Refer to this javascript from the HTML file.
const concertina = {
headings: null,
init() {
this.headings = document.querySelectorAll('.heading');
this.headings.forEach((heading) => {
// Keep a reference to the concertina
heading.concertina = this;
// Add a click event listener to each heading div.
heading.addEventListener('click', (event) => { this.handleClick(this, this.getParentWithClass(event.target, 'heading')) });
// Find next sibling containing the class 'collapsibleContent'
var seek = heading.nextElementSibling;
while (seek) {
if (seek.classList.contains('collapsibleContent')) {
heading.collapsibleContent = seek;
break;
}
}
heading.expanded = false;
concertina.toggleContentDiv(heading, heading.collapsibleContent, heading.expanded, 0.0);
},
getParentWithClass(element, className) {
while (!element || !element.classList.contains(className)) {
element = element.parentElement;
}
return element;
},
handleClick(concertina, target) {
// Hide the collapsibleContent div of all non-clicked heading div.
concertina.headings.forEach((heading) => {
if (heading !== target) {
// Contract all 'other' open collapsibleContent div slowly.
heading.expanded = false;
concertina.toggleContentDiv(heading, heading.collapsibleContent, heading.expanded, 0.45);
}
});
// Toggle the clicked heading div's corresponding collapsibleContent div.
target.expanded = !target.expanded;
concertina.toggleContentDiv(target, target.collapsibleContent, target.expanded, 0.45);
},
toggleContentDiv(heading, collapsibleContent, expand, seconds) {
if (expand) {
// Toggle the rotated class on the heading div.
heading.classList.add('rotated');
// Expand the collapsibleContent div slowly.
collapsibleContent.style.transition = 'height ' + seconds + 's ease';
collapsibleContent.offsetHeight; // Force reflow
collapsibleContent.style.height = collapsibleContent.scrollHeight + 'px';
} else { // contract instead
// Toggle the rotated class on the heading div.
heading.classList.remove('rotated');
collapsibleContent.style.transition = 'height ' + seconds + 's ease';
collapsibleContent.offsetHeight; // Force reflow
collapsibleContent.style.height = 0;
}
}
};
window.addEventListener('load', concertina.init());
<html>
<!-- Implements clickable headings with collapsible content. -->
<head>
<style>
/* customisable */
.heading {
position: relative;
cursor: pointer;
background-color:#d0f8ff;
padding: 0.25em 1em;
border-top-style: ridge;
}
.collapsibleContent {
position: relative;
background-color: white;
}
/* required for collapse */
.collapsibleContent {
transition: all 0.3s ease;
overflow: hidden;
}
.collapsibleContent.hidden {
display: none;
}
.heading .triangle {
position: absolute;
top: calc(50% - 5px);
right: 10px;
transform: translateY(-50%);
width: 10px;
height: 10px;
border: solid 2px black;
border-bottom: none;
border-right: none;
transform: rotate(135deg);
transition: all 0.3s ease;
}
.heading.rotated .triangle {
transform: rotate(225deg);
}
</style>
</head>
<body>
<div class="heading">
<h1>Heading 1</h1>
<div class="triangle"></div>
</div>
<div class="collapsibleContent">
<p>This is the content for Heading 1.</p>
<p>This is the content for Heading 1.</p>
<p>This is the content for Heading 1.</p>
<p>This is the content for Heading 1.</p>
<p>This is the content for Heading 1.</p>
<p>This is the content for Heading 1.</p>
<p>This is the content for Heading 1.</p>
<p>This is the content for Heading 1.</p>
<p>This is the content for Heading 1.</p>
<p>This is the content for Heading 1.</p>
<p>This is the content for Heading 1.</p>
<p>This is the content for Heading 1.</p>
<p>This is the content for Heading 1.</p>
</div>
<div class="heading">
<h1>Heading 2</h1>
<div class="triangle"></div>
</div>
<div class="collapsibleContent">
<p>This is the content for Heading 2.</p>
<p>This is the content for Heading 2.</p>
<p>This is the content for Heading 2.</p>
<p>This is the content for Heading 2.</p>
<p>This is the content for Heading 2.</p>
<p>This is the content for Heading 2.</p>
</div>
<div class="heading">
<h1>Heading 3</h1>
<div class="triangle"></div>
</div>
<div class="collapsibleContent">
<p>This is the content for Heading 3.</p>
<p>This is the content for Heading 3.</p>
<p>This is the content for Heading 3.</p>
<p>This is the content for Heading 3.</p>
</div>
<script src="concertina-collapsible.js"></script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment