Skip to content

Instantly share code, notes, and snippets.

@geotrev
Created May 2, 2018 23:10
Show Gist options
  • Save geotrev/a2d367f3996359e910cb6bfb41c1da39 to your computer and use it in GitHub Desktop.
Save geotrev/a2d367f3996359e910cb6bfb41c1da39 to your computer and use it in GitHub Desktop.
Lightbox Class
// Example:
//
// Include a link anywhere in the document:
// <a data-lightbox-button href='#' id="lightbox-1">lightbox 1</a>
//
// Then include the lightbox itself at the end of your <body> content:
// <div data-lightbox-overlay data-lightbox-1>
// <div class='lightbox-content' aria-labelledby="lightbox-heading-1" role="dialog">
// <header>
// <h2 id="lightbox-heading-1">Lightbox Heading</h2>
// <a data-lightbox-close href='#'>x</a>
// </header>
// <section>
// <p>This is some body content 1</p>
// <button>This is a button</button>
// </section>
// <footer>
// <a data-lightbox-close href='#'>OK</a>
// <a data-lightbox-close href='#'>Cancel</a>
// </footer>
// </div>
// </div>
document.addEventListener('DOMContentLoaded', () => {
new Lightbox()
})
class Utils {
getFocusableElements(container) {
const tags = [ "a", "button", "input", "object", "select", "textarea", "[tabindex]" ]
let focusables = [], selectors = []
tags.map(tag => {
focusables.push(`${tag}:not(.is-visually-hidden)`)
})
focusables.map(selector => {
return selectors.push(`${container} ${selector}`)
})
const nodes = document.querySelectorAll(selectors.join(", "))
return Array.apply(null, nodes)
}
trapFocus(container, title, stop = false) {
const children = this.getFocusableElements(container),
firstChild = children[0],
lastChild = children[children.length - 1]
if (stop) {
title.removeEventListener('keydown', (e) => this.handleFocus(e, lastChild, false))
lastChild.removeEventListener('keydown', (e) => this.handleFocus(e, firstChild, false))
firstChild.removeEventListener('keydown', (e) => this.handleFocus(e, lastChild, true))
return
}
title.addEventListener('keydown', (e) => this.handleFocus(e, lastChild, true))
firstChild.addEventListener('keydown', (e) => this.handleFocus(e, lastChild, true))
lastChild.addEventListener('keydown', (e) => this.handleFocus(e, firstChild, false))
}
handleFocus(e, element, shift) {
let shiftKey = shift === true ? e.shiftKey : !e.shiftKey
if (shiftKey && e.which === 9) {
e.preventDefault()
element.focus()
}
}
}
class Lightbox extends Utils {
constructor() {
super()
this.getSelectors()
this.getComponent()
}
getSelectors() {
this.closeButtonAttr = '[data-lightbox-close]'
this.noScrollClass = 'no-scroll';
this.visibilityAttr = 'data-lightbox-visible'
this.lightboxes = document.querySelectorAll('[data-lightbox-overlay]')
this.lightboxButtons = document.querySelectorAll('[data-lightbox-button]')
this.closeButtons = document.querySelectorAll(this.closeButtonAttr)
this.body = document.body
this.html = document.querySelector('html')
}
getComponent() {
if (this.lightboxButtons.length === 0) return
this.lightboxes.forEach(lightbox => {
lightbox.setAttribute('aria-hidden', 'true')
})
this.lightboxButtons.forEach(button => {
button.addEventListener('click', (e) => {
e.preventDefault()
this.renderLightbox(button)
})
})
}
renderLightbox(button) {
this.stopScroll()
const lightboxAttr = `[data-${button.id}]`,
lightbox = document.querySelector(lightboxAttr),
lightboxContent = lightbox.children[0],
lightboxHeader = document.querySelector(`${lightboxAttr} h2`),
lightboxCloseButtons = document.querySelectorAll(`${lightboxAttr} ${this.closeButtonAttr}`)
this.setupLightbox(lightbox, lightboxAttr, lightboxHeader)
lightboxCloseButtons.forEach(closeButton => {
closeButton.addEventListener('click', (e) => {
e.preventDefault()
this.closeLightbox(lightbox, lightboxAttr, lightboxHeader, button)
})
})
}
setupLightbox(container, attr, title) {
this.getLightboxOffset(container)
this.trapFocus(attr, title)
this.watchChildren(container, attr, title)
container.setAttribute(this.visibilityAttr, "")
container.setAttribute('aria-hidden', 'false')
title.setAttribute('tabindex', '-1')
title.focus()
}
closeLightbox(container, attr, title, button) {
container.removeAttribute(this.visibilityAttr)
container.setAttribute('aria-hidden', 'true')
container.removeEventListener('resize', () => this.getOffsetValue(container))
container.removeEventListener('click', () => this.trapFocus(attr, title))
this.trapFocus(attr, title, true)
this.returnFocus(button)
this.restoreScroll()
}
watchChildren(container, attr, title) {
container.addEventListener('click', () => this.trapFocus(attr, title))
}
returnFocus(button) {
button.setAttribute('tabindex', '-1')
button.focus()
button.setAttribute('tabindex', null)
}
getLightboxOffset(container) {
this.getOffsetValue(container)
window.addEventListener('resize', () => this.getOffsetValue(container))
}
getOffsetValue(container) {
let scrollPosition = Math.round(document.body.scrollTop || window.pageYOffset);
container.style.top = `${scrollPosition}px`
}
restoreScroll() {
this.body.classList.remove(this.noScrollClass)
this.html.classList.remove(this.noScrollClass)
}
stopScroll() {
this.body.classList.add(this.noScrollClass)
this.html.classList.add(this.noScrollClass)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment