UNPKG

alpine-tailwind-lightbox

Version:

A simple lightbox for AlpineJS and Tailwind CSS projects.

238 lines (182 loc) 7.11 kB
import html from './template.html' let defaultGroup = null let pluginOptions = {} const lazyLoadedImages = [] export default function lightbox(Alpine) { Alpine.store('lightbox', { show: {}, items: {}, touchStart: null, onTouchStart(event) { if (!event.changedTouches) { this.touchStart = null return } this.touchStart = event.changedTouches[0] }, onTouchEnd(event, group) { if (!event.changedTouches || !this.touchStart) return const { screenX: startX, screenY: startY } = this.touchStart const { screenX: endX, screenY: endY } = event.changedTouches[0] if (!startX || !startY || !endX || !endY) return const horizontalDiff = startX - endX const verticalDiff = startY - endY if (Math.abs(horizontalDiff) < Math.abs(verticalDiff)) return if (horizontalDiff >= 100) { this.next(group) } else if (horizontalDiff <= -100) { this.prev(group) } }, loadImage(item, group) { const index = this.items[group].findIndex(({ id }) => id === item.id) const items = [item, this.nextItem(index, group), this.prevItem(index, group)] items.filter(Boolean).forEach(({ id, url, loaded }) => { if (loaded) return const image = new Image() image.onload = () => { const loadedIndex = this.items[group].findIndex((current) => current.id === id) this.items[group][loadedIndex].loaded = true lazyLoadedImages.push(image) } image.src = url }) }, open(group, urlOrRef) { const item = urlOrRef ? this.items[group] .find((item) => item[typeof urlOrRef === 'object' ? 'el' : 'url'] === urlOrRef) : (this.items[group][0] || null) if (item) { this.show[group] = item this.loadImage(item, group) } }, prevItem(index, group) { return index === 0 ? this.items[group][this.items[group].length - 1] : this.items[group][index - 1] }, nextItem(index, group) { return index === this.items[group].length - 1 ? this.items[group][0] : this.items[group][index + 1] }, navigate(direction, group) { const current = this.items[group].findIndex((item) => item.id === this.show[group].id) const newCurrent = direction === 'prev' ? this.prevItem(current, group) : this.nextItem(current, group) this.show[group] = newCurrent this.loadImage(newCurrent, group) }, prev(group) { this.navigate('prev', group) }, next(group) { this.navigate('next', group) }, }) function createOrUpdateLightbox(items, group, id = null, el = null, modifiers = []) { if (!Array.isArray(items)) items = [items] if (!document.querySelector(`#lightbox-${group}`)) { const template = document.createElement('template') template.innerHTML = html const templateEl = template.content.children[0] templateEl.id = `lightbox-${group}` templateEl.setAttribute('x-data', `{ group: '${group}' }`) const target = pluginOptions.container ? document.querySelector(pluginOptions.container) : document.body target.appendChild(templateEl) setTimeout(() => { if (!templateEl.hasOwnProperty('_x_isShown')) { Alpine.initTree(templateEl) } }, 15) } Alpine.store('lightbox').show[group] ??= null; Alpine.store('lightbox').items[group] ??= []; const existingItems = Alpine.store('lightbox').items items.forEach((config) => { const index = Alpine.store('lightbox').items[group]?.findIndex((item) => item.id === id) const data = mergeConfig(config, group, id, el, modifiers) if (index !== -1 && index !== undefined) { existingItems[group][index] = { ...existingItems[group][index], ...data } } else { data.loaded = !data.lazy existingItems[group].push(data) } }) } Alpine.magic('lightbox', () => { function register(items, group = null) { items = items.map((item) => { if (typeof item === 'string') item = { url: item } item.id = getRandomId() return item }) createOrUpdateLightbox(items, group || getDefaultGroupName()) } register.open = (urlOrRef = null, group = null) => { Alpine.store('lightbox').open(group || getDefaultGroupName(), urlOrRef) } return register }) Alpine.directive('lightbox', (el, { value, modifiers, expression }, { effect, evaluateLater }) => { if (value === 'group') { return } if (!expression) { console.warn('Alpine warn: no url or config expression passed to x-lightbox', el) return } const evaluateConfig = evaluateLater(expression) const id = getRandomId() let hasListener = false effect(() => { evaluateConfig((config) => { Alpine.nextTick(() => { const group = getGroupName(el, config) createOrUpdateLightbox(config, group, id, el, modifiers) if (!hasListener) { el.addEventListener('click', (event) => { event.preventDefault() Alpine.store('lightbox').open(group, el) }) hasListener = true } }) }) }) }) } lightbox.configure = (options) => { pluginOptions = { ...options } return lightbox } const getRandomId = () => (Math.random() + 1).toString(36).substring(2, 15) const getGroupName = (el, config) => { if (el.hasAttribute('x-lightbox:group')) { return el.getAttribute('x-lightbox:group') } if (config.group) return String(config.group) return getDefaultGroupName() } const getDefaultGroupName = () => { defaultGroup ??= getRandomId() return defaultGroup } const mergeConfig = (config, group, id = null, el = null, modifiers = []) => { if (typeof config === 'string') config = { url: config } return { type: 'image', lazy: modifiers.includes('lazy'), autoplay: false, muted: false, id, ...config, el, group, } }