upgrade lightbox (#342)

* upgrade lightbox

* Draggable image [wip]

* Resolve conversation

* remove transition

* remove transition only for move

---------

Co-authored-by: ilya-bkv <i.yablokov@ccmp.me>
This commit is contained in:
Arkadzi Rakouski 2023-12-21 16:56:19 +03:00 committed by GitHub
parent 87b6e44eb2
commit dd4dc967bb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 132 additions and 12 deletions

View File

@ -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;
}
}

View File

@ -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 (
<div class={clsx(styles.Lightbox, props.class)} onClick={closeLightbox}>
<div
class={clsx(styles.Lightbox, props.class)}
onClick={closeLightbox}
ref={(el) => (lightboxRef.current = el)}
>
<span class={styles.close} onClick={closeLightbox}>
<Icon name="close-white" class={styles.icon} />
</span>
@ -59,9 +128,11 @@ export const Lightbox = (props: Props) => {
<img
class={styles.image}
src={props.image}
style={lightboxStyle()}
alt={''}
alt={props.imageAlt || ''}
onClick={(event) => event.stopPropagation()}
onWheel={handleWheelZoom}
style={lightboxStyle()}
onMouseDown={onMouseDown}
/>
</div>
)

View File

@ -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 (
<div class={clsx(styles.Swiper, styles.articleMode, { [styles.mobileView]: isMobileView() })}>
<div class={styles.container} ref={(el) => (swiperMainContainer.current = el)}>
@ -94,7 +110,7 @@ export const ImageSwiper = (props: Props) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
<swiper-slide lazy="true" virtual-index={index()}>
<div class={styles.image}>
<div class={styles.image} onClick={handleImageClick}>
<Image src={slide.url} alt={slide.title} width={800} />
</div>
</swiper-slide>
@ -178,6 +194,10 @@ export const ImageSwiper = (props: Props) => {
<div class={styles.body} innerHTML={props.images[slideIndex()].body} />
</Show>
</div>
<Show when={selectedImage()}>
<Lightbox image={selectedImage()} onClose={handleLightboxClose} />
</Show>
</div>
)
}

View File

@ -188,6 +188,7 @@
img {
max-height: 100%;
width: auto;
cursor: zoom-in;
}
}