Mimicking Bootstrap’s Collapse with Vanilla Javascript

David Atchley
Apr 22, 2018 · 5 min read
Photo by Evan Kirby from UnSplash

A few days back I was having a conversation with a friend who was doing a side project that used Bootstrap. We were talking about the dependency on jQuery for the interactive components and he mentioned he only really used the .collapse feature and didn’t really need anything else.

I mentioned that if that was all he needed, then he could put that together fairly simply with vanilla JavaScript and CSS, and drop jQuery. He wasn’t quite sure how to go about that, so I put together a little example to show him one way he could get similar functionality.

I figured since it might have been useful to him, it might be useful to others. So, here’s how I approached implementing this little feature. Hope it’s useful to you.

How does Bootstrap do it?

Before we dive into what we’re building, let’s see what Bootstrap’s functionality offers first.

Bootstrap’s collapse is basically a series of CSS classes that setup a collapsed and un-collapsed state for elements. The primary classes are as follows:

.collapse — this class basically sets display to none and effectively hides the element.

.show — this class just sets the display back to block for the element.

Bootstrap can trigger the transition via JavaScript , like so:

<!-- target element to collapse/expand -->
<div class="collapse">
/* your content here */
</div>
<script>
$('.collapse').collapse('toggle')
</script>

Or, automatically hook it up via data attributes.

<!-- data-* attributes set this button up as a trigger -->
<button data-toggle="collapse" data-target=".collapse">Click</button>
<!-- target element to collapse/expand -->
<div class="collapse">
/* your content here */
</div>

Bootstrap also provides a temp state, .collapsing , which is applied to the target element during the transition and manages the class changes via JavaScript and the transitionend event, along with some inline styles for height that are calculated for the target element.

Our implementation

In our implementation, we’ll mimic the data-* attributes to have things work automatically on page load, while also creating a collapse() function that can toggle the collapsed/expanded state programmatically.

We’ll also keep it simple and just use a .collapse and .show state, allowing us to take advantage of max-height for the transition using pure CSS, rather than dealing with calculating target element heights and applying inline styles.

First, let’s create some classes for the collapsed and uncollapsed state for elements. We won’t use display: none in ours, as you can’t transition the display property without introducing some trickery to make that work. However, we can transition the height of an element using the max-height property, which will make this more straight-forward (though not without it’s own caveats).

Here’s our styles (using SCSS):

*, ::after, ::before {
box-sizing: border-box;
}
.collapse {
display: block;
max-height: 0px;
overflow: hidden;
transition: max-height .5s cubic-bezier(0, 1, 0, 1);
&.show {
max-height: 99em;
transition: max-height .5s ease-in-out;
}
}

We apply border-box to all the elements to ensure padding and border are included in the browser’s calculation for height. We then setup a .collapse class that will collapse an element to 0 height via the max-height attribute. And a .show class that will set the max-height to some arbitrarily large value to allow the target element to grow via the transition.

The cubic-bezier() transition is there to account for handling timing of the transition between the target element’s height and the rather large max-height we set to allow it to expand. Without it, the transition could seem immediate or delayed depending on the difference between the element’s actual height and the large max-height we set.

Now let’s create a collapse() function that will work similarly to the Bootstrap $().collapse() method. In our case, we’ll pass in a selector to determine what elements to collapse/expand, and a second argument that tells it what to do. Here’s how it can be called:

collapse(<selector>, 'toggle') — will toggle the state of the element(s) described by the selector.

collapse(<selector>, 'show') — will expand the element(s) described by the selector by adding the .show class to them.

collapse(<selector>, 'hide') — will collapse the element(s) described by the selector by removing the .show class from them.

Here’s the implementation:

// map our commands to the classList methods
const fnmap = {
'toggle': 'toggle',
'show': 'add',
'hide': 'remove'
};
const collapse = (selector, cmd) => {
const targets = Array.from(document.querySelectorAll(selector));
targets.forEach(target => {
target.classList[fnmap[cmd]]('show');
});
}

Here we grab all the DOM elements matching the selector as an actual Array and loop through them applying the appropriate action on the element’s classList to add or remove the .show class.

To mimic Bootstrap’s data-toggle="collapse" attribute triggers, we’ll simply find all such trigger elements on the page and setup a click event listener to watch for clicks on those triggers. When a trigger is clicked, we’ll use our collapse() function from above to apply the toggle command to the target element.

The target element is identified via the data-target attribute on the trigger element. We expect it to contain a valid selector that matches one or more collapsible elements. Collapsible elements will be any element with the .collapse class applied to them.

Here’s the implementation:

// Grab all the trigger elements on the page
const triggers = Array.from(document.querySelectorAll('[data-toggle="collapse"]'));
// Listen for click events, but only on our triggers
window.addEventListener('click', (ev) => {
const elm = ev.target;
if (triggers.includes(elm)) {
const selector = elm.getAttribute('data-target');
collapse(selector, 'toggle');
}
}, false);

Running this code will automatically setup listeners for any trigger elements on the page and toggle the collapse/expand on their target elements.

Here’s the full implementation, which shows two triggers that expand/collapse a single element and another that toggles multiple elements at once.

Summary

Feel free to fork the code and play around with the implementation. The goal was to create something simple and small that could handle the collapse functionality my friend needed without a lot of complexity and overhead. Obviously, there are multiple ways to skin this cat, and I’d love to hear what others have done.

Hopefully it’s helpful to those out there using Bootstrap’s CSS for their base styling or looking to find more ways to use vanilla JavaScript to minimize dependencies on larger libraries like jQuery when they’re not utilizing a bulk of the features.

If you enjoyed this, please give it some claps and share it with others! Thanks to Kurt Schwind for the conversation and idea.

DailyJS

JavaScript news and opinion.