Last active
September 19, 2023 08:39
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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()); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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