Files
BRETT/app/components/ImageLightbox.vue
2026-10-03 23:45:04 +02:00

82 lines
2.0 KiB
Vue

<script setup lang="ts">
import { onKeyStroke } from '@vueuse/core'
defineProps<{
src: string
alt?: string
}>()
const emit = defineEmits<{
close: []
}>()
const scale = ref(1)
const offset = ref({ x: 0, y: 0 })
const dragging = ref(false)
let last = { x: 0, y: 0 }
function onWheel(event: WheelEvent) {
const factor = event.deltaY < 0 ? 1.15 : 1 / 1.15
scale.value = Math.min(10, Math.max(0.2, scale.value * factor))
}
function onPointerDown(event: PointerEvent) {
dragging.value = true
last = { x: event.clientX, y: event.clientY }
;(event.currentTarget as HTMLElement).setPointerCapture(event.pointerId)
}
function onPointerMove(event: PointerEvent) {
if (!dragging.value) return
offset.value = {
x: offset.value.x + event.clientX - last.x,
y: offset.value.y + event.clientY - last.y,
}
last = { x: event.clientX, y: event.clientY }
}
function onPointerUp() {
dragging.value = false
}
function reset() {
scale.value = 1
offset.value = { x: 0, y: 0 }
}
onKeyStroke('Escape', () => emit('close'))
</script>
<template>
<Teleport to="body">
<div
class="fixed inset-0 z-50 flex items-center justify-center overflow-hidden bg-black/90"
@wheel.prevent="onWheel"
@click="emit('close')"
>
<NuxtImg
:src="src"
:alt="alt"
draggable="false"
class="max-h-[95vh] max-w-[95vw] touch-none select-none"
:class="dragging ? 'cursor-grabbing' : 'cursor-grab'"
:style="{ transform: `translate(${offset.x}px, ${offset.y}px) scale(${scale})` }"
@click.stop
@dblclick="reset"
@pointerdown.prevent="onPointerDown"
@pointermove="onPointerMove"
@pointerup="onPointerUp"
@pointercancel="onPointerUp"
/>
<button
type="button"
class="absolute top-4 right-4 rounded-md bg-black/50 p-2 text-white hover:bg-black/70"
@click="emit('close')"
>
<Icon name="ph:x-fill" class="size-5" />
<span class="sr-only">{{ $t('common.close') }}</span>
</button>
</div>
</Teleport>
</template>