Last active
June 30, 2021 07:55
-
-
Save mvsde/b7e4cdcf560b4ef288958076d23aacd0 to your computer and use it in GitHub Desktop.
JavaScript Collapse
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
/** | |
* Modern browsers support the `once` option for event listeners: | |
* https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#parameters | |
*/ | |
module.exports = function(target, type, callback) { | |
let eventWrapper = function(event) { | |
target.removeEventListener(type, eventWrapper); | |
callback(event); | |
}; | |
target.addEventListener(type, eventWrapper); | |
}; |
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
<div class="js-collapse"> | |
<button aria-controls="panel" class="js-collapse-button">Button</button> | |
<div aria-hidden="true" id="panel" class="js-collapse-content">Content</div> | |
</div> |
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
/* COLLAPSE | |
* ========================================================================== */ | |
/* DEPENDENCIES | |
* ====================================================== */ | |
const Util = require('./utilities.js'); | |
const addEventListenerOnce = require('./add-event-listener-once.js'); | |
/* COLLAPSE | |
* ====================================================== */ | |
let Collapse = function(options) { | |
let container = options.containerElement; | |
let buttonSelector = options.buttonSelector || '.js-collapse-button'; | |
let contentSelector = options.contentSelector || '.js-collapse-content'; | |
let activeClass = options.activeClass || 'is-active'; | |
let collapsingClass = options.collapsingClass || 'is-collapsing'; | |
// Abort if no container element was provided | |
if (!container) { | |
throw new Error('Collapse Error: Missing container element.'); | |
} | |
// Get elements | |
let button = container.querySelector(buttonSelector); | |
let content = container.querySelector(contentSelector); | |
// Get and set states | |
let isActive = content.classList.contains(activeClass); | |
let isCollapsing = false; | |
// Hide the content | |
this.hide = function() { | |
// Abort if content is already collapsing or not visible | |
if (isCollapsing || !isActive) { | |
return; | |
} | |
isCollapsing = true; | |
// Set explicit content height | |
content.style.height = content.offsetHeight + 'px'; | |
// Force reflow | |
Util.reflow(content); | |
// Set classes and attributes | |
button.classList.remove(activeClass); | |
content.classList.remove(activeClass); | |
content.classList.add(collapsingClass); | |
content.setAttribute('aria-hidden', true); | |
// Remove content height | |
content.style.height = null; | |
let complete = function() { | |
content.classList.remove(collapsingClass); | |
isActive = false; | |
isCollapsing = false; | |
}; | |
// Check if browser supports transition event | |
if (!Util.supportsTransitionEnd) { | |
complete(); | |
return; | |
} | |
// Add event listener once to transition end | |
addEventListenerOnce(content, 'transitionend', function() { | |
complete(); | |
}); | |
}; | |
// Show the content | |
this.show = function() { | |
// Abort if content is already collapsing or visible | |
if (isCollapsing || isActive) { | |
return; | |
} | |
// Set collapsing state | |
isCollapsing = true; | |
// Get height by first showing the content, | |
// then retrieving the height and hiding the content again | |
content.style.display = 'block'; | |
let contentHeight = content.offsetHeight; | |
content.style.display = null; | |
// Set classes and attributes | |
content.classList.add(collapsingClass); | |
// Force reflow | |
Util.reflow(content); | |
button.classList.add(activeClass); | |
content.setAttribute('aria-hidden', false); | |
// Set explicit content height | |
content.style.height = contentHeight + 'px'; | |
let complete = function() { | |
// Set classes | |
content.classList.remove(collapsingClass); | |
content.classList.add(activeClass); | |
// Reset content height | |
content.style.height = null; | |
isActive = true; | |
isCollapsing = false; | |
}; | |
// Check if browser supports transition event | |
if (!Util.supportsTransitionEnd) { | |
complete(); | |
return; | |
} | |
// Add event listener once to transition end | |
addEventListenerOnce(content, 'transitionend', function() { | |
complete(); | |
}); | |
}; | |
// Toggle content | |
this.toggle = function() { | |
// Abort early if content is already collapsing | |
if (isCollapsing) { | |
return; | |
} | |
// Decide whether to show or to hide content | |
if (isActive) { | |
this.hide(); | |
} else { | |
this.show(); | |
} | |
}; | |
// Initialize button click event | |
this.init = function() { | |
let context = this; | |
button.addEventListener('click', function(event) { | |
event.preventDefault(); | |
context.toggle(); | |
}); | |
}; | |
}; | |
/* INITIALIZE COLLAPSE | |
* ====================================================== */ | |
let jsCollapse = document.querySelectorAll('.js-collapse'); | |
let jsCollapseLength = jsCollapse.length; | |
for (let i = 0; i < jsCollapseLength; i++) { | |
new Collapse({ | |
containerElement: jsCollapse[i], | |
buttonSelector: '.js-collapse-button', | |
contentSelector: '.js-collapse-content', | |
activeClass: 'is-active', | |
collapsingClass: 'is-collapsing' | |
}).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
/* UTILITIES | |
* | |
* Small helper functions | |
* ========================================================================== */ | |
module.exports = { | |
// Reflow by retrieving element height | |
reflow: function(element) { | |
return element.offsetHeight; | |
}, | |
// Check if browser supports transition end event | |
supportsTransitionEnd: function() { | |
let testElement = document.createElement('div'); | |
let transitionEndEventNames = { | |
WebkitTransition: 'webkitTransitionEnd', | |
MozTransition: 'transitionend', | |
OTransition: 'oTransitionEnd otransitionend', | |
transition: 'transitionend' | |
}; | |
for (let name in transitionEndEventNames) { | |
if (testElement.style[name] !== undefined) { | |
return transitionEndEventNames[name]; | |
} | |
} | |
return false; | |
} | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment