diff --git a/src/components/_shared/Lightbox/Lightbox.module.scss b/src/components/_shared/Lightbox/Lightbox.module.scss index 6f99e359..de7910d2 100644 --- a/src/components/_shared/Lightbox/Lightbox.module.scss +++ b/src/components/_shared/Lightbox/Lightbox.module.scss @@ -10,6 +10,9 @@ justify-content: center; z-index: 10000; + animation: 300ms fadeIn; + animation-fill-mode: forwards; + .image { max-width: 90%; max-height: 80%; @@ -72,3 +75,28 @@ } } } + +.fadeOut { + animation: 300ms fadeOut; + animation-fill-mode: backwards; +} + +@keyframes fadeIn { + 0% { + opacity: 0; + } + + 100% { + opacity: 1; + } +} + +@keyframes fadeOut { + 0% { + opacity: 1; + } + + 100% { + opacity: 0; + } +} diff --git a/src/components/_shared/Lightbox/Lightbox.tsx b/src/components/_shared/Lightbox/Lightbox.tsx index 56092245..89c7d94b 100644 --- a/src/components/_shared/Lightbox/Lightbox.tsx +++ b/src/components/_shared/Lightbox/Lightbox.tsx @@ -1,5 +1,5 @@ import { clsx } from 'clsx' -import { createSignal } from 'solid-js' +import { createMemo, createSignal, onCleanup } from 'solid-js' import { useEscKeyDownHandler } from '../../../utils/useEscKeyDownHandler' import { Icon } from '../Icon' @@ -8,40 +8,109 @@ import styles from './Lightbox.module.scss' type Props = { class?: string + imageAlt?: string image: string onClose: () => void } const ZOOM_STEP = 1.08 +const TRANSITION_SPEED = 300 + export const Lightbox = (props: Props) => { const [zoomLevel, setZoomLevel] = createSignal(1) + const [translateX, setTranslateX] = createSignal(0) + const [translateY, setTranslateY] = createSignal(0) + const [transitionEnabled, setTransitionEnabled] = createSignal(false) + + const lightboxRef: { + current: HTMLElement + } = { + current: null, + } const closeLightbox = () => { - props.onClose() + lightboxRef.current?.classList.add(styles.fadeOut) + + setTimeout(() => { + props.onClose() + }, 300) } const zoomIn = (event) => { event.stopPropagation() + setTransitionEnabled(true) setZoomLevel(zoomLevel() * ZOOM_STEP) + setTimeout(() => setTransitionEnabled(false), TRANSITION_SPEED) } + const zoomOut = (event) => { event.stopPropagation() + setTransitionEnabled(true) setZoomLevel(zoomLevel() / ZOOM_STEP) + setTimeout(() => setTransitionEnabled(false), TRANSITION_SPEED) } + const zoomReset = (event) => { event.stopPropagation() setZoomLevel(1) } - const lightboxStyle = () => ({ - transform: `scale(${zoomLevel()})`, - transition: 'transform 0.3s ease', - }) + const handleWheelZoom = (event) => { + event.preventDefault() + + let scale = zoomLevel() + + scale += event.deltaY * -0.01 + + scale = Math.min(Math.max(0.125, scale), 4) + + setZoomLevel(scale * ZOOM_STEP) + } useEscKeyDownHandler(closeLightbox) + let startX: number = 0 + let startY: number = 0 + let isDragging: boolean = false + + const onMouseDown: (event: MouseEvent) => void = (event) => { + startX = event.clientX - translateX() + startY = event.clientY - translateY() + isDragging = true + event.preventDefault() + } + + const onMouseMove: (event: MouseEvent) => void = (event) => { + if (isDragging) { + setTranslateX(event.clientX - startX) + setTranslateY(event.clientY - startY) + } + } + + const onMouseUp: () => void = () => { + isDragging = false + } + + document.addEventListener('mousemove', onMouseMove) + document.addEventListener('mouseup', onMouseUp) + + onCleanup(() => { + document.removeEventListener('mousemove', onMouseMove) + document.removeEventListener('mouseup', onMouseUp) + }) + + const lightboxStyle = createMemo(() => ({ + transform: `translate(${translateX()}px, ${translateY()}px) scale(${zoomLevel()})`, + transition: transitionEnabled() ? `transform ${TRANSITION_SPEED}ms ease-in-out` : '', + cursor: 'grab', + })) + return ( -
+
(lightboxRef.current = el)} + > @@ -59,9 +128,11 @@ export const Lightbox = (props: Props) => { {''} event.stopPropagation()} + onWheel={handleWheelZoom} + style={lightboxStyle()} + onMouseDown={onMouseDown} />
) diff --git a/src/components/_shared/SolidSwiper/ImageSwiper.tsx b/src/components/_shared/SolidSwiper/ImageSwiper.tsx index 93864056..5364e47b 100644 --- a/src/components/_shared/SolidSwiper/ImageSwiper.tsx +++ b/src/components/_shared/SolidSwiper/ImageSwiper.tsx @@ -7,6 +7,7 @@ import { MediaItem } from '../../../pages/types' import { getImageUrl } from '../../../utils/getImageUrl' import { Icon } from '../Icon' import { Image } from '../Image' +import { Lightbox } from '../Lightbox' import { SwiperRef } from './swiper' @@ -23,12 +24,14 @@ type Props = { const MIN_WIDTH = 540 export const ImageSwiper = (props: Props) => { - const [slideIndex, setSlideIndex] = createSignal(0) - const [isMobileView, setIsMobileView] = createSignal(false) const mainSwipeRef: { current: SwiperRef } = { current: null } const thumbSwipeRef: { current: SwiperRef } = { current: null } const swiperMainContainer: { current: HTMLDivElement } = { current: null } + const [slideIndex, setSlideIndex] = createSignal(0) + const [isMobileView, setIsMobileView] = createSignal(false) + const [selectedImage, setSelectedImage] = createSignal('') + const handleSlideChange = () => { thumbSwipeRef.current.swiper.slideTo(mainSwipeRef.current.swiper.activeIndex) setSlideIndex(mainSwipeRef.current.swiper.activeIndex) @@ -76,6 +79,19 @@ export const ImageSwiper = (props: Props) => { }) }) + const openLightbox = (image) => { + setSelectedImage(image) + } + const handleLightboxClose = () => { + setSelectedImage() + } + + const handleImageClick = (event) => { + const src = event.target.src + + openLightbox(getImageUrl(src)) + } + return (
(swiperMainContainer.current = el)}> @@ -94,7 +110,7 @@ export const ImageSwiper = (props: Props) => { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore -
+
{slide.title}
@@ -178,6 +194,10 @@ export const ImageSwiper = (props: Props) => {
+ + + +
) } diff --git a/src/components/_shared/SolidSwiper/Swiper.module.scss b/src/components/_shared/SolidSwiper/Swiper.module.scss index 4928695e..f4dbccf1 100644 --- a/src/components/_shared/SolidSwiper/Swiper.module.scss +++ b/src/components/_shared/SolidSwiper/Swiper.module.scss @@ -188,6 +188,7 @@ img { max-height: 100%; width: auto; + cursor: zoom-in; } }