JavaScript: Collapsible containers with rotation support
The is a continuation of a previous article where we were experimenting with expanding and collapsing HTML containers and how to implement a smooth transition in both directions.
What needed changing?
One outstanding issue was that in the case of the viewport (browser) being resized, or a mobile device being rotated, the initially computed values for the container height were no longer accurate, breaking the implementation.
This is because in a narrower viewport the HTML contents of the container will take up more vertical space. If we were initially transitioning between 100px and 400px, then a narrow screen might require transitioning instead between 150px and 600px.
The solution is to use any resize event as a trigger for re-computing the required height values for any collapsible containers on the page.
Demonstration
This is the first paragraph of text that will always be visible. A clickable element will be appended to the bottom of the container to show/hide more content. Alternatively, you could make the entire container clickable, but for now we're gone with the toggler.
The second and subsequent paragraphs (or lists or other elements) will remain in the HTML, but hidden until requested.
When the toggler is clicked, JavaScript toggles an open class on the parent container, which in turn shows and hides the additional content.
Again, the only gotcha is that the first child of the container must be a P.
To test the changes you will need to resize or rotate your browser viewport so that the content width changes. What you should see is the container height expanding or contracting as necesssary for the same amount of content to remain visible.
That includes keeping the content expanded if it was already expanded.
HTML markup
The HTML markup is essentially the same as our previous examples:
<div class="collapsible slow boxed">
<p>... text to be visible on page load ...</p>
<p>...</p>
<p>...</p>
<p>...</p>
</div>
CSS for this example
We haven't had to change anything here:
<style>
.boxed {
border: 1px solid #ccc;
padding: 1em 2em;
}
.collapsible.slow {
position: relative;
overflow: hidden;
padding-bottom: 0.5em;
transition: height 0.5s ease-out;
}
.collapsible.slow > * {
display: none;
}
.collapsible.slow > p:first-child,
.collapsible.slow.open > *,
.collapsible.slow.ready > * {
display: revert;
}
.collapsible.slow > .toggler {
position: absolute;
left: 0;
bottom: 0;
width: 100%;
background: #fff;
text-align: center;
cursor: pointer;
}
.collapsible.slow > .toggler::after {
content: "\25bc";
}
.collapsible.slow.open > .toggler::after {
content: "\25b2";
}
</style>
JavaScript code for this example
Here we have added a new function setTransitionHeights which is called on page load, and then again any time the viewport is resized.
In theory this causes the containers to expand and contract in the browser, but in practice this happens too fast to be seen.
<script>
window.addEventListener("DOMContentLoaded", (e) => {
// Original JavaScript code by Chirp Internet: www.chirpinternet.eu
// Please acknowledge use of this code by including this header.
const getContainerHeight = (el) => window.getComputedStyle(el).getPropertyValue("height");
const setTransitionHeights = (el) => {
let containerWasOpen = el.classList.contains("open");
el.style.height = null;
el.classList.remove("open", "ready");
el.dataset.initial = getContainerHeight(el);
el.classList.add("open");
el.dataset.final = getContainerHeight(el);
el.classList.remove("open");
if(containerWasOpen) {
el.classList.add("open");
el.style.height = el.dataset.final;
} else {
el.style.height = el.dataset.initial;
}
el.classList.add("ready");
};
document.querySelectorAll(".collapsible.slow").forEach(current => {
let toggler = document.createElement("div");
toggler.className = "toggler";
current.appendChild(toggler);
setTransitionHeights(current);
toggler.addEventListener("click", (e) => {
current.style.height = current.classList.toggle("open") ? current.dataset.final : current.dataset.initial;
});
});
window.addEventListener("resize", (e) => {
document.querySelectorAll(".collapsible.slow").forEach(current => {
setTransitionHeights(current);
});
});
});
</script>
This of course leads to a gazilion resize events and related actions if the viewport size changes slowly, such as by dragging, but only one or two for a device rotation. Some rate-limiting may be in order in a future version...
Feel free to use this code in your projects and let us know if you have any comments or questions using the feedback option below.
Related Articles - Text Manipulation
- HTML Forcing INPUT text to uppercase
- JavaScript HTML content that expands on click
- JavaScript Collapsible containers with rotation support
- PHP Truncating Text
- PHP Word Wrapping
- PHP Passing variables to JavaScript
- PHP What happened with htmlspecialchars?