Convert Map & Lightbox to vue components
This commit is contained in:
1 parent
d29403d9c5
commit
4ae0e46a7d
6 files changed
+1192
-1187
No files matched your search
@@ -0,0 +1,585 @@
|
||||
<script>
|
||||
import AppIcon from '@components/AppIcon';
|
||||
import { getStyleProperty } from '@scripts/common';
|
||||
|
||||
/* lightbox (https://github.com/lokesh/lightbox2) converted to a vue component and improved to support videos */
|
||||
|
||||
export default {
|
||||
components: {
|
||||
AppIcon
|
||||
},
|
||||
props: {
|
||||
alwaysShowNavOnTouchDevices: {type: Boolean, default: false},
|
||||
positionFromTop: {type: Number, default: 50},
|
||||
wrapAround: {type: Boolean, default: false},
|
||||
disableScrolling: {type: Boolean, default: false},
|
||||
sanitizeTitle: {type: Boolean, default: false},
|
||||
hasVideo: {type: Boolean, default: true},
|
||||
maxWidth: {type: Number, default: null},
|
||||
maxHeight: {type: Number, default: null}
|
||||
},
|
||||
emits: ['media-change', 'closing'],
|
||||
data() {
|
||||
/*
|
||||
fadeDuration/imageFadeDuration/resizeDuration read --trans-quick/--trans-slow
|
||||
directly rather than being passed down as props from Project.vue: they're
|
||||
declared on :root (_common.scss), so they're available immediately.
|
||||
No need to wait on Project.vue's own $el to mount first, and no risk of
|
||||
a parent-to-child prop update lagging behind the first time this opens.
|
||||
*/
|
||||
const fadeDuration = parseFloat(getStyleProperty('--trans-quick'));
|
||||
const resizeDuration = parseFloat(getStyleProperty('--trans-slow'));
|
||||
|
||||
return {
|
||||
album: [],
|
||||
currentImageIndex: 0,
|
||||
gMouseDownOffsetX: 0,
|
||||
gMouseDownOffsetY: 0,
|
||||
resizeTimer: null,
|
||||
containerPadding: null,
|
||||
imageBorderWidth: null,
|
||||
videoBorderWidth: null,
|
||||
fadeDuration,
|
||||
imageFadeDuration: fadeDuration,
|
||||
resizeDuration
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.setVisible(this.$refs.overlay, false);
|
||||
this.setVisible(this.$refs.lightboxEl, false);
|
||||
|
||||
this.containerPadding = this.getBoxMetrics(this.$refs.container, 'padding');
|
||||
this.imageBorderWidth = this.getBoxMetrics(this.$refs.image, 'border');
|
||||
this.videoBorderWidth = this.getBoxMetrics(this.$refs.video, 'border');
|
||||
|
||||
this.$refs.nav.addEventListener('wheel', this.onWheel, {passive: false});
|
||||
this.$refs.nav.addEventListener('mousedown', this.onDragStart);
|
||||
window.addEventListener('mouseup', this.onDragEnd);
|
||||
|
||||
this.enable();
|
||||
},
|
||||
beforeUnmount() {
|
||||
this.disable();
|
||||
if(this.resizeTimer) clearTimeout(this.resizeTimer);
|
||||
window.removeEventListener('mouseup', this.onDragEnd);
|
||||
window.removeEventListener('resize', this.sizeOverlay);
|
||||
window.removeEventListener('mousemove', this.onDragMove);
|
||||
},
|
||||
methods: {
|
||||
enable() {
|
||||
document.body.addEventListener('click', this.onBodyClick);
|
||||
},
|
||||
disable() {
|
||||
document.body.removeEventListener('click', this.onBodyClick);
|
||||
},
|
||||
onBodyClick(event) {
|
||||
const link = event.target.closest('a[data-lightbox], area[data-lightbox]');
|
||||
if(!link) return;
|
||||
event.preventDefault();
|
||||
this.start(link);
|
||||
},
|
||||
start(link) {
|
||||
this.sizeOverlay();
|
||||
this.album = [];
|
||||
let imageNumber = 0;
|
||||
const setName = link.getAttribute('data-lightbox');
|
||||
|
||||
const links = [...document.querySelectorAll(`${link.tagName}[data-lightbox="${CSS.escape(setName)}"]`)];
|
||||
links.forEach((item, index) => {
|
||||
this.addToAlbum(item);
|
||||
if(item === link) imageNumber = index;
|
||||
});
|
||||
|
||||
this.fade(this.$refs.overlay, true, this.fadeDuration);
|
||||
this.fade(this.$refs.lightboxEl, true, this.fadeDuration);
|
||||
|
||||
if(this.disableScrolling) document.body.classList.add('lb-disable-scrolling');
|
||||
|
||||
window.addEventListener('resize', this.sizeOverlay);
|
||||
this.changeImage(imageNumber);
|
||||
},
|
||||
end(dispose = false) {
|
||||
this.disableKeyboardNav();
|
||||
this.$refs.video?.pause();
|
||||
this.$refs.video?.removeAttribute('src');
|
||||
this.$refs.container?.classList.remove('lb-video-nav', 'moveable', 'moving');
|
||||
window.removeEventListener('resize', this.sizeOverlay);
|
||||
window.removeEventListener('mousemove', this.onDragMove);
|
||||
|
||||
if(dispose) {
|
||||
this.album = [];
|
||||
}
|
||||
else {
|
||||
this.fade(this.$refs.lightboxEl, false, this.fadeDuration);
|
||||
this.fade(this.$refs.overlay, false, this.fadeDuration);
|
||||
this.$emit('closing');
|
||||
}
|
||||
|
||||
if(this.disableScrolling) document.body.classList.remove('lb-disable-scrolling');
|
||||
},
|
||||
addToAlbum(link) {
|
||||
const img = link.querySelector('img');
|
||||
this.album.push({
|
||||
alt: link.getAttribute('data-alt') || '',
|
||||
link: link.getAttribute('href'),
|
||||
title: link.getAttribute('data-title') || link.getAttribute('title') || '',
|
||||
orientation: parseInt(link.getAttribute('data-orientation') || '0', 10),
|
||||
type: link.getAttribute('data-type') || 'image',
|
||||
id: link.getAttribute('data-id'),
|
||||
width: parseInt(img?.getAttribute('width') || '0', 10),
|
||||
height: parseInt(img?.getAttribute('height') || '0', 10),
|
||||
set: link.getAttribute('data-lightbox') || ''
|
||||
});
|
||||
},
|
||||
hasMediaAfterCurrent() {
|
||||
return this.currentImageIndex < this.album.length - 1;
|
||||
},
|
||||
refreshAlbum() {
|
||||
const current = this.album[this.currentImageIndex];
|
||||
if(!current?.set) return;
|
||||
|
||||
const links = [...document.querySelectorAll(`a[data-lightbox="${CSS.escape(current.set)}"], area[data-lightbox="${CSS.escape(current.set)}"]`)];
|
||||
if(!links.length) return;
|
||||
|
||||
const existingKeys = new Set(this.album.map((media) => this.getMediaKey(media)));
|
||||
links.forEach((link) => {
|
||||
const key = this.getLinkMediaKey(link);
|
||||
if(existingKeys.has(key)) return;
|
||||
|
||||
this.addToAlbum(link);
|
||||
existingKeys.add(key);
|
||||
});
|
||||
|
||||
this.updateNav();
|
||||
},
|
||||
getMediaKey(media) {
|
||||
return `${media.set}:${media.id}`;
|
||||
},
|
||||
getLinkMediaKey(link) {
|
||||
return `${link.getAttribute('data-lightbox') || ''}:${link.getAttribute('data-id')}`;
|
||||
},
|
||||
getMaxSizes(mediaType) {
|
||||
let maxWidth = window.innerWidth - this.containerPadding.left - this.containerPadding.right;
|
||||
let maxHeight = window.innerHeight - this.containerPadding.top - this.containerPadding.bottom - this.positionFromTop;
|
||||
const border = mediaType === 'image' ? this.imageBorderWidth : this.videoBorderWidth;
|
||||
maxWidth -= border.left + border.right;
|
||||
maxHeight -= border.top + border.bottom;
|
||||
maxHeight -= this.getDataContainerHeight(maxWidth + this.containerPadding.left + this.containerPadding.right + border.left + border.right);
|
||||
|
||||
return {
|
||||
maxWidth: Math.max(maxWidth, 1),
|
||||
maxHeight: Math.max(maxHeight, 1)
|
||||
};
|
||||
},
|
||||
getDataContainerHeight(width = null) {
|
||||
if(!this.$refs.dataContainer) return 0;
|
||||
|
||||
const currentWidth = this.$refs.dataContainer.style.width;
|
||||
if(width !== null) this.$refs.dataContainer.style.width = `${width}px`;
|
||||
const height = Math.ceil(this.$refs.dataContainer.getBoundingClientRect().height || this.$refs.dataContainer.offsetHeight || 0);
|
||||
this.$refs.dataContainer.style.width = currentWidth;
|
||||
|
||||
return height;
|
||||
},
|
||||
getMediaSize(media, maxWidth, maxHeight) {
|
||||
if(media.width <= maxWidth && media.height <= maxHeight) {
|
||||
return {
|
||||
width: media.width,
|
||||
height: media.height
|
||||
};
|
||||
}
|
||||
|
||||
const widthRatio = media.width / maxWidth;
|
||||
const heightRatio = media.height / maxHeight;
|
||||
|
||||
if(widthRatio > heightRatio) {
|
||||
return {
|
||||
width: maxWidth,
|
||||
height: Math.round(media.height / widthRatio)
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
width: Math.round(media.width / heightRatio),
|
||||
height: maxHeight
|
||||
};
|
||||
},
|
||||
fitSizeWithDataContainer(size, mediaType) {
|
||||
const border = mediaType === 'image' ? this.imageBorderWidth : this.videoBorderWidth;
|
||||
const maxOuterHeight = Math.max(window.innerHeight - this.positionFromTop, 1);
|
||||
let fittedSize = size;
|
||||
|
||||
for(let i = 0; i < 5; i++) {
|
||||
const containerWidth = fittedSize.width + this.containerPadding.left + this.containerPadding.right + border.left + border.right;
|
||||
const containerHeight = fittedSize.height + this.containerPadding.top + this.containerPadding.bottom + border.top + border.bottom;
|
||||
const dataHeight = this.getDataContainerHeight(containerWidth);
|
||||
const overflow = Math.ceil(containerHeight + dataHeight - maxOuterHeight);
|
||||
if(overflow <= 0 || fittedSize.height <= 1) break;
|
||||
|
||||
const height = Math.max(fittedSize.height - overflow, 1);
|
||||
fittedSize = {
|
||||
width: Math.max(Math.round(fittedSize.width * (height / fittedSize.height)), 1),
|
||||
height
|
||||
};
|
||||
}
|
||||
|
||||
return fittedSize;
|
||||
},
|
||||
updateSize(index) {
|
||||
const media = this.album[index];
|
||||
const maxSizes = this.getMaxSizes(media.type);
|
||||
const maxWidth = this.maxWidth ? Math.min(this.maxWidth, maxSizes.maxWidth) : maxSizes.maxWidth;
|
||||
const maxHeight = this.maxHeight ? Math.min(this.maxHeight, maxSizes.maxHeight) : maxSizes.maxHeight;
|
||||
const size = this.fitSizeWithDataContainer(this.getMediaSize(media, maxWidth, maxHeight), media.type);
|
||||
|
||||
const target = media.type === 'video' ? this.$refs.video : this.$refs.image;
|
||||
target.width = size.width;
|
||||
target.height = size.height;
|
||||
this.sizeContainer(size.width, size.height, media.type);
|
||||
},
|
||||
changeImage(index) {
|
||||
const media = this.album[index];
|
||||
if(!media) return;
|
||||
|
||||
this.updateDetails(media, false);
|
||||
this.hideElements([this.$refs.dataContainer]);
|
||||
this.disableKeyboardNav();
|
||||
this.fade(this.$refs.overlay, true, this.fadeDuration);
|
||||
this.fade(this.$refs.loader, true, 200);
|
||||
this.hideElements([this.$refs.image, this.$refs.video, this.$refs.nav, this.$refs.prev, this.$refs.next]);
|
||||
this.resetImageTransform();
|
||||
this.$refs.outerContainer.classList.add('animating');
|
||||
this.$refs.container.classList.remove('moveable', 'moving', 'lb-video-nav');
|
||||
this.currentImageIndex = index;
|
||||
|
||||
this.$emit('media-change', media);
|
||||
|
||||
if(media.type === 'video') {
|
||||
this.$refs.image.removeAttribute('src');
|
||||
this.$refs.container.classList.add('lb-video-nav');
|
||||
this.$refs.video.onloadedmetadata = () => {
|
||||
media.width = this.$refs.video.videoWidth;
|
||||
media.height = this.$refs.video.videoHeight;
|
||||
this.$refs.video.onloadedmetadata = null;
|
||||
this.updateSize(index);
|
||||
};
|
||||
this.$refs.video.src = media.link;
|
||||
} else {
|
||||
this.$refs.video.pause();
|
||||
this.$refs.video.removeAttribute('src');
|
||||
this.$refs.image.onload = () => {
|
||||
this.$refs.image.alt = media.alt;
|
||||
let width = this.$refs.image.naturalWidth;
|
||||
let height = this.$refs.image.naturalHeight;
|
||||
if(Math.abs(media.orientation) === 90 && width > height) {
|
||||
const tmp = width;
|
||||
width = height;
|
||||
height = tmp;
|
||||
}
|
||||
media.width = width;
|
||||
media.height = height;
|
||||
this.$refs.image.onload = null;
|
||||
this.updateSize(index);
|
||||
};
|
||||
this.$refs.image.src = media.link;
|
||||
}
|
||||
},
|
||||
sizeOverlay() {
|
||||
if(this.resizeTimer) clearTimeout(this.resizeTimer);
|
||||
if(!this.album.length) return;
|
||||
|
||||
this.resizeTimer = window.setTimeout(() => {
|
||||
const current = this.album[this.currentImageIndex];
|
||||
if(!current) return;
|
||||
if(current.type === 'image') this.changeImage(this.currentImageIndex);
|
||||
else this.updateSize(this.currentImageIndex);
|
||||
}, 200);
|
||||
},
|
||||
sizeContainer(width, height, mediaType = 'image') {
|
||||
const border = mediaType === 'image' ? this.imageBorderWidth : this.videoBorderWidth;
|
||||
const newWidth = width + this.containerPadding.left + this.containerPadding.right + border.left + border.right;
|
||||
const newHeight = height + this.containerPadding.top + this.containerPadding.bottom + border.top + border.bottom;
|
||||
const dataHeight = this.getDataContainerHeight(newWidth);
|
||||
|
||||
this.$refs.outerContainer.style.transition = `width ${this.resizeDuration}ms, height ${this.resizeDuration}ms`;
|
||||
this.$refs.outerContainer.style.width = `${newWidth}px`;
|
||||
this.$refs.outerContainer.style.height = `${newHeight + dataHeight}px`;
|
||||
this.$refs.container.style.height = `${newHeight}px`;
|
||||
|
||||
window.setTimeout(() => {
|
||||
this.$refs.overlay.focus();
|
||||
this.showImage();
|
||||
this.$refs.outerContainer.style.transition = '';
|
||||
}, this.resizeDuration);
|
||||
},
|
||||
showImage() {
|
||||
this.fade(this.$refs.loader, false, 0);
|
||||
if(this.hasVideo && this.album[this.currentImageIndex].type === 'video') this.fade(this.$refs.video, true, this.imageFadeDuration);
|
||||
else this.fade(this.$refs.image, true, this.imageFadeDuration);
|
||||
|
||||
this.updateNav();
|
||||
this.updateDetails();
|
||||
this.preloadNeighboringImages();
|
||||
this.enableKeyboardNav();
|
||||
},
|
||||
updateNav() {
|
||||
this.setVisible(this.$refs.nav, true);
|
||||
this.setVisible(this.$refs.prev, false);
|
||||
this.setVisible(this.$refs.next, false);
|
||||
|
||||
const alwaysShowNav = ('ontouchstart' in window) && this.alwaysShowNavOnTouchDevices;
|
||||
if(this.album.length <= 1) return;
|
||||
|
||||
if(this.wrapAround) {
|
||||
this.setVisible(this.$refs.prev, true);
|
||||
this.setVisible(this.$refs.next, true);
|
||||
} else {
|
||||
if(this.currentImageIndex > 0) this.setVisible(this.$refs.prev, true);
|
||||
if(this.currentImageIndex < this.album.length - 1) this.setVisible(this.$refs.next, true);
|
||||
}
|
||||
|
||||
if(alwaysShowNav) {
|
||||
this.$refs.prev.style.opacity = '1';
|
||||
this.$refs.next.style.opacity = '1';
|
||||
} else {
|
||||
this.$refs.prev.style.opacity = '';
|
||||
this.$refs.next.style.opacity = '';
|
||||
}
|
||||
},
|
||||
updateDetails(media = this.album[this.currentImageIndex], show = true) {
|
||||
if(!media) return;
|
||||
|
||||
if(media.title) {
|
||||
if(this.sanitizeTitle) this.$refs.caption.textContent = media.title;
|
||||
else this.$refs.caption.innerHTML = media.title;
|
||||
if(show) this.fade(this.$refs.caption, true, 200);
|
||||
else this.setVisible(this.$refs.caption, true);
|
||||
} else {
|
||||
this.$refs.caption.textContent = '';
|
||||
this.setVisible(this.$refs.caption, false);
|
||||
}
|
||||
|
||||
if(show) {
|
||||
this.fade(this.$refs.closeButton, true, 200);
|
||||
this.$refs.outerContainer.classList.remove('animating');
|
||||
this.fade(this.$refs.dataContainer, true, this.resizeDuration);
|
||||
} else {
|
||||
this.setVisible(this.$refs.closeButton, true);
|
||||
this.setVisible(this.$refs.dataContainer, false);
|
||||
this.$refs.dataContainer.style.transition = '';
|
||||
this.$refs.dataContainer.style.opacity = '0';
|
||||
}
|
||||
},
|
||||
preloadNeighboringImages() {
|
||||
const next = this.album[this.currentImageIndex + 1];
|
||||
const prev = this.album[this.currentImageIndex - 1];
|
||||
if(next && next.type === 'image') {
|
||||
const preloadNext = new Image();
|
||||
preloadNext.src = next.link;
|
||||
}
|
||||
if(prev && prev.type === 'image') {
|
||||
const preloadPrev = new Image();
|
||||
preloadPrev.src = prev.link;
|
||||
}
|
||||
},
|
||||
enableKeyboardNav() {
|
||||
this.disableKeyboardNav();
|
||||
this.$refs.lightboxEl.addEventListener('keyup', this.keyboardAction);
|
||||
this.$refs.overlay.addEventListener('keyup', this.keyboardAction);
|
||||
},
|
||||
disableKeyboardNav() {
|
||||
this.$refs.lightboxEl?.removeEventListener('keyup', this.keyboardAction);
|
||||
this.$refs.overlay?.removeEventListener('keyup', this.keyboardAction);
|
||||
},
|
||||
keyboardAction(event) {
|
||||
switch(event.key) {
|
||||
case 'Escape':
|
||||
event.stopPropagation();
|
||||
this.end();
|
||||
break;
|
||||
case 'ArrowLeft':
|
||||
if(this.currentImageIndex !== 0) this.changeImage(this.currentImageIndex - 1);
|
||||
else if(this.wrapAround && this.album.length > 1) this.changeImage(this.album.length - 1);
|
||||
break;
|
||||
case 'ArrowRight':
|
||||
if(this.currentImageIndex !== this.album.length - 1) this.changeImage(this.currentImageIndex + 1);
|
||||
else if(this.wrapAround && this.album.length > 1) this.changeImage(0);
|
||||
break;
|
||||
}
|
||||
},
|
||||
onCloseKeyup(event) {
|
||||
if(event.key === 'Enter' || event.key === ' ') this.end();
|
||||
},
|
||||
onPrevClick() {
|
||||
if(this.currentImageIndex === 0) this.changeImage(this.album.length - 1);
|
||||
else this.changeImage(this.currentImageIndex - 1);
|
||||
},
|
||||
onNextClick() {
|
||||
if(this.currentImageIndex === this.album.length - 1) this.changeImage(0);
|
||||
else this.changeImage(this.currentImageIndex + 1);
|
||||
},
|
||||
onOuterContainerClick(event) {
|
||||
if(event.target === this.$refs.outerContainer) this.end();
|
||||
},
|
||||
onLightboxClick(event) {
|
||||
if(event.target === this.$refs.lightboxEl) this.end();
|
||||
},
|
||||
onWheel(event) {
|
||||
const media = this.album[this.currentImageIndex];
|
||||
if(!media || media.type === 'video') return;
|
||||
event.preventDefault();
|
||||
|
||||
const rect = this.$refs.image.getBoundingClientRect();
|
||||
const oldTransform = this.getImageTransform();
|
||||
const oldZoom = oldTransform.scale;
|
||||
const maxZoom = Math.max(media.width / Math.max(this.$refs.image.width, 1), media.height / Math.max(this.$refs.image.height, 1), 1);
|
||||
const newZoom = Math.min(Math.max(oldZoom + (-Math.sign(event.deltaY) / 10), 1), maxZoom);
|
||||
|
||||
const imageCenterX = rect.left + rect.width / 2 - oldTransform.translateX;
|
||||
const imageCenterY = rect.top + rect.height / 2 - oldTransform.translateY;
|
||||
const cursorX = event.clientX - imageCenterX;
|
||||
const cursorY = event.clientY - imageCenterY;
|
||||
const zoomRatio = newZoom / oldZoom;
|
||||
const transform = this.clampImageTransform({
|
||||
scale: newZoom,
|
||||
translateX: cursorX - zoomRatio * (cursorX - oldTransform.translateX),
|
||||
translateY: cursorY - zoomRatio * (cursorY - oldTransform.translateY)
|
||||
});
|
||||
|
||||
this.$refs.container.classList.toggle('moveable', newZoom > 1);
|
||||
this.setImageTransform(transform);
|
||||
},
|
||||
onDragStart(event) {
|
||||
const scale = parseFloat(this.$refs.image.style.getPropertyValue('--scale') || '1');
|
||||
if(scale <= 1) return;
|
||||
|
||||
this.gMouseDownOffsetX = event.clientX - parseFloat(this.$refs.image.style.getPropertyValue('--translate-x') || '0');
|
||||
this.gMouseDownOffsetY = event.clientY - parseFloat(this.$refs.image.style.getPropertyValue('--translate-y') || '0');
|
||||
this.$refs.container.classList.add('moving');
|
||||
window.addEventListener('mousemove', this.onDragMove);
|
||||
},
|
||||
onDragMove(event) {
|
||||
const zoom = parseFloat(this.$refs.image.style.getPropertyValue('--scale') || '1');
|
||||
const transform = this.clampImageTransform({
|
||||
scale: zoom,
|
||||
translateX: event.clientX - this.gMouseDownOffsetX,
|
||||
translateY: event.clientY - this.gMouseDownOffsetY
|
||||
});
|
||||
|
||||
this.setImageTransform(transform);
|
||||
},
|
||||
onDragEnd() {
|
||||
window.removeEventListener('mousemove', this.onDragMove);
|
||||
this.$refs.container?.classList.remove('moving');
|
||||
},
|
||||
getBoxMetrics(element, type) {
|
||||
const styles = getComputedStyle(element);
|
||||
return {
|
||||
top: parseInt(styles[`${type}-top-width`], 10) || 0,
|
||||
right: parseInt(styles[`${type}-right-width`], 10) || 0,
|
||||
bottom: parseInt(styles[`${type}-bottom-width`], 10) || 0,
|
||||
left: parseInt(styles[`${type}-left-width`], 10) || 0
|
||||
};
|
||||
},
|
||||
resetImageTransform() {
|
||||
this.setImageTransform({scale: 1, translateX: 0, translateY: 0});
|
||||
},
|
||||
getImageTransform() {
|
||||
return {
|
||||
scale: parseFloat(this.$refs.image.style.getPropertyValue('--scale') || '1'),
|
||||
translateX: parseFloat(this.$refs.image.style.getPropertyValue('--translate-x') || '0'),
|
||||
translateY: parseFloat(this.$refs.image.style.getPropertyValue('--translate-y') || '0')
|
||||
};
|
||||
},
|
||||
clampImageTransform(transform) {
|
||||
const maxTranslateX = (transform.scale - 1) * this.$refs.image.width / 2;
|
||||
const maxTranslateY = (transform.scale - 1) * this.$refs.image.height / 2;
|
||||
|
||||
return {
|
||||
scale: transform.scale,
|
||||
translateX: Math.max(Math.min(transform.translateX, maxTranslateX), -maxTranslateX),
|
||||
translateY: Math.max(Math.min(transform.translateY, maxTranslateY), -maxTranslateY)
|
||||
};
|
||||
},
|
||||
setImageTransform(transform) {
|
||||
if(!this.$refs.image) return;
|
||||
this.$refs.image.style.setProperty('--scale', String(transform.scale));
|
||||
this.$refs.image.style.setProperty('--translate-x', `${transform.translateX}px`);
|
||||
this.$refs.image.style.setProperty('--translate-y', `${transform.translateY}px`);
|
||||
},
|
||||
hideElements(elements) {
|
||||
elements.forEach((element) => {
|
||||
this.setVisible(element, false);
|
||||
});
|
||||
},
|
||||
setVisible(element, visible) {
|
||||
if(!element) return;
|
||||
element.style.visibility = visible ? 'visible' : 'hidden';
|
||||
element.style.pointerEvents = visible ? '' : 'none';
|
||||
},
|
||||
fade(element, show, duration, done) {
|
||||
if(!element) return;
|
||||
|
||||
const safeDuration = duration || 0;
|
||||
element.style.transition = `opacity ${safeDuration}ms`;
|
||||
if(show) {
|
||||
this.setVisible(element, true);
|
||||
requestAnimationFrame(() => {
|
||||
element.style.opacity = element === this.$refs.overlay ? '0.8' : '1';
|
||||
});
|
||||
} else {
|
||||
element.style.opacity = '0';
|
||||
element.style.pointerEvents = 'none';
|
||||
window.setTimeout(() => {
|
||||
this.setVisible(element, false);
|
||||
}, safeDuration);
|
||||
}
|
||||
|
||||
if(typeof done === 'function') {
|
||||
window.setTimeout(done, safeDuration);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Teleport to="body">
|
||||
<div id="lightboxOverlay" ref="overlay" tabindex="-1" class="lightboxOverlay" @click="end()"></div>
|
||||
<div id="lightbox" ref="lightboxEl" tabindex="-1" class="lightbox" @click="onLightboxClick">
|
||||
<div class="lb-outerContainer" ref="outerContainer" @click.stop="onOuterContainerClick">
|
||||
<div class="lb-container" ref="container">
|
||||
<img class="lb-image" ref="image" src="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" alt="" />
|
||||
<video class="lb-video" ref="video" controls autoplay></video>
|
||||
<div class="lb-nav" ref="nav">
|
||||
<div class="lb-prev-area">
|
||||
<a class="lb-prev" ref="prev" aria-label="Previous image" href="" role="button" @click.prevent="onPrevClick">
|
||||
<AppIcon :icon="'prev'" />
|
||||
</a>
|
||||
</div>
|
||||
<div class="lb-next-area">
|
||||
<a class="lb-next" ref="next" aria-label="Next image" href="" role="button" @click.prevent="onNextClick"><AppIcon :icon="'next'" /></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="lb-loader" ref="loader" @click.prevent="end()">
|
||||
<a class="lb-cancel" ref="cancel" href="#">
|
||||
<AppIcon :icon="'cancel'" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="lb-dataContainer desktop" ref="dataContainer" @click="end()">
|
||||
<div class="lb-data">
|
||||
<div class="lb-details">
|
||||
<span class="lb-caption" ref="caption"></span>
|
||||
</div>
|
||||
<div class="lb-closeContainer">
|
||||
<a class="lb-close" ref="closeButton" href="#" role="button" @click.prevent.stop="end()" @keyup="onCloseKeyup">
|
||||
<AppIcon :icon="'close'" :classes="'fa-lg'" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
</template>
|
||||
Reference in new issue
Block a user