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:
parent
87b6e44eb2
commit
dd4dc967bb
|
@ -10,6 +10,9 @@
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
z-index: 10000;
|
z-index: 10000;
|
||||||
|
|
||||||
|
animation: 300ms fadeIn;
|
||||||
|
animation-fill-mode: forwards;
|
||||||
|
|
||||||
.image {
|
.image {
|
||||||
max-width: 90%;
|
max-width: 90%;
|
||||||
max-height: 80%;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import { createSignal } from 'solid-js'
|
import { createMemo, createSignal, onCleanup } from 'solid-js'
|
||||||
|
|
||||||
import { useEscKeyDownHandler } from '../../../utils/useEscKeyDownHandler'
|
import { useEscKeyDownHandler } from '../../../utils/useEscKeyDownHandler'
|
||||||
import { Icon } from '../Icon'
|
import { Icon } from '../Icon'
|
||||||
|
@ -8,40 +8,109 @@ import styles from './Lightbox.module.scss'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
class?: string
|
class?: string
|
||||||
|
imageAlt?: string
|
||||||
image: string
|
image: string
|
||||||
onClose: () => void
|
onClose: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const ZOOM_STEP = 1.08
|
const ZOOM_STEP = 1.08
|
||||||
|
const TRANSITION_SPEED = 300
|
||||||
|
|
||||||
export const Lightbox = (props: Props) => {
|
export const Lightbox = (props: Props) => {
|
||||||
const [zoomLevel, setZoomLevel] = createSignal(1)
|
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 = () => {
|
const closeLightbox = () => {
|
||||||
props.onClose()
|
lightboxRef.current?.classList.add(styles.fadeOut)
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
props.onClose()
|
||||||
|
}, 300)
|
||||||
}
|
}
|
||||||
|
|
||||||
const zoomIn = (event) => {
|
const zoomIn = (event) => {
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
|
setTransitionEnabled(true)
|
||||||
setZoomLevel(zoomLevel() * ZOOM_STEP)
|
setZoomLevel(zoomLevel() * ZOOM_STEP)
|
||||||
|
setTimeout(() => setTransitionEnabled(false), TRANSITION_SPEED)
|
||||||
}
|
}
|
||||||
|
|
||||||
const zoomOut = (event) => {
|
const zoomOut = (event) => {
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
|
setTransitionEnabled(true)
|
||||||
setZoomLevel(zoomLevel() / ZOOM_STEP)
|
setZoomLevel(zoomLevel() / ZOOM_STEP)
|
||||||
|
setTimeout(() => setTransitionEnabled(false), TRANSITION_SPEED)
|
||||||
}
|
}
|
||||||
|
|
||||||
const zoomReset = (event) => {
|
const zoomReset = (event) => {
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
setZoomLevel(1)
|
setZoomLevel(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
const lightboxStyle = () => ({
|
const handleWheelZoom = (event) => {
|
||||||
transform: `scale(${zoomLevel()})`,
|
event.preventDefault()
|
||||||
transition: 'transform 0.3s ease',
|
|
||||||
})
|
let scale = zoomLevel()
|
||||||
|
|
||||||
|
scale += event.deltaY * -0.01
|
||||||
|
|
||||||
|
scale = Math.min(Math.max(0.125, scale), 4)
|
||||||
|
|
||||||
|
setZoomLevel(scale * ZOOM_STEP)
|
||||||
|
}
|
||||||
|
|
||||||
useEscKeyDownHandler(closeLightbox)
|
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 (
|
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}>
|
<span class={styles.close} onClick={closeLightbox}>
|
||||||
<Icon name="close-white" class={styles.icon} />
|
<Icon name="close-white" class={styles.icon} />
|
||||||
</span>
|
</span>
|
||||||
|
@ -59,9 +128,11 @@ export const Lightbox = (props: Props) => {
|
||||||
<img
|
<img
|
||||||
class={styles.image}
|
class={styles.image}
|
||||||
src={props.image}
|
src={props.image}
|
||||||
style={lightboxStyle()}
|
alt={props.imageAlt || ''}
|
||||||
alt={''}
|
|
||||||
onClick={(event) => event.stopPropagation()}
|
onClick={(event) => event.stopPropagation()}
|
||||||
|
onWheel={handleWheelZoom}
|
||||||
|
style={lightboxStyle()}
|
||||||
|
onMouseDown={onMouseDown}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { MediaItem } from '../../../pages/types'
|
||||||
import { getImageUrl } from '../../../utils/getImageUrl'
|
import { getImageUrl } from '../../../utils/getImageUrl'
|
||||||
import { Icon } from '../Icon'
|
import { Icon } from '../Icon'
|
||||||
import { Image } from '../Image'
|
import { Image } from '../Image'
|
||||||
|
import { Lightbox } from '../Lightbox'
|
||||||
|
|
||||||
import { SwiperRef } from './swiper'
|
import { SwiperRef } from './swiper'
|
||||||
|
|
||||||
|
@ -23,12 +24,14 @@ type Props = {
|
||||||
const MIN_WIDTH = 540
|
const MIN_WIDTH = 540
|
||||||
|
|
||||||
export const ImageSwiper = (props: Props) => {
|
export const ImageSwiper = (props: Props) => {
|
||||||
const [slideIndex, setSlideIndex] = createSignal(0)
|
|
||||||
const [isMobileView, setIsMobileView] = createSignal(false)
|
|
||||||
const mainSwipeRef: { current: SwiperRef } = { current: null }
|
const mainSwipeRef: { current: SwiperRef } = { current: null }
|
||||||
const thumbSwipeRef: { current: SwiperRef } = { current: null }
|
const thumbSwipeRef: { current: SwiperRef } = { current: null }
|
||||||
const swiperMainContainer: { current: HTMLDivElement } = { current: null }
|
const swiperMainContainer: { current: HTMLDivElement } = { current: null }
|
||||||
|
|
||||||
|
const [slideIndex, setSlideIndex] = createSignal(0)
|
||||||
|
const [isMobileView, setIsMobileView] = createSignal(false)
|
||||||
|
const [selectedImage, setSelectedImage] = createSignal('')
|
||||||
|
|
||||||
const handleSlideChange = () => {
|
const handleSlideChange = () => {
|
||||||
thumbSwipeRef.current.swiper.slideTo(mainSwipeRef.current.swiper.activeIndex)
|
thumbSwipeRef.current.swiper.slideTo(mainSwipeRef.current.swiper.activeIndex)
|
||||||
setSlideIndex(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 (
|
return (
|
||||||
<div class={clsx(styles.Swiper, styles.articleMode, { [styles.mobileView]: isMobileView() })}>
|
<div class={clsx(styles.Swiper, styles.articleMode, { [styles.mobileView]: isMobileView() })}>
|
||||||
<div class={styles.container} ref={(el) => (swiperMainContainer.current = el)}>
|
<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
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
<swiper-slide lazy="true" virtual-index={index()}>
|
<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} />
|
<Image src={slide.url} alt={slide.title} width={800} />
|
||||||
</div>
|
</div>
|
||||||
</swiper-slide>
|
</swiper-slide>
|
||||||
|
@ -178,6 +194,10 @@ export const ImageSwiper = (props: Props) => {
|
||||||
<div class={styles.body} innerHTML={props.images[slideIndex()].body} />
|
<div class={styles.body} innerHTML={props.images[slideIndex()].body} />
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<Show when={selectedImage()}>
|
||||||
|
<Lightbox image={selectedImage()} onClose={handleLightboxClose} />
|
||||||
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -188,6 +188,7 @@
|
||||||
img {
|
img {
|
||||||
max-height: 100%;
|
max-height: 100%;
|
||||||
width: auto;
|
width: auto;
|
||||||
|
cursor: zoom-in;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user