38a45baad5
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
83 lines
2.3 KiB
Vue
83 lines
2.3 KiB
Vue
<template>
|
|
<div
|
|
ref="canvasEl"
|
|
class="fc-canvas"
|
|
:class="{ 'fc-canvas--zoomed': panZoom.state.scale > 1 }"
|
|
@wheel="panZoom.handlers.onWheel"
|
|
@pointerdown="panZoom.handlers.onPointerDown"
|
|
@pointermove="panZoom.handlers.onPointerMove"
|
|
@pointerup="panZoom.handlers.onPointerUp"
|
|
@click="onCanvasClick"
|
|
>
|
|
<img
|
|
ref="imgEl"
|
|
:src="src" :alt="alt"
|
|
:style="{
|
|
transform: `translate(${panZoom.state.x}px, ${panZoom.state.y}px) scale(${panZoom.state.scale})`
|
|
}"
|
|
draggable="false"
|
|
>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, watch } from 'vue'
|
|
import { usePanZoom } from '../../composables/usePanZoom.js'
|
|
|
|
const props = defineProps({ src: String, alt: String })
|
|
const emit = defineEmits(['close-request'])
|
|
const panZoom = usePanZoom()
|
|
|
|
const canvasEl = ref(null)
|
|
const imgEl = ref(null)
|
|
|
|
// Reset zoom when the src changes (prev/next nav).
|
|
watch(() => props.src, () => panZoom.reset())
|
|
|
|
// Click on the image → toggle zoom (IR/expected behavior). Click on
|
|
// the haze area around the image → request close. We use the image's
|
|
// own bounding rect rather than @click.self because the <img> sets
|
|
// pointer-events: none for the pan-drag pathway, so the click always
|
|
// targets the canvas div regardless of where the cursor was.
|
|
function onCanvasClick(ev) {
|
|
if (panZoom.state.scale > 1) {
|
|
// When zoomed, any click resets zoom — preserve old behavior.
|
|
panZoom.handlers.onClick(ev)
|
|
return
|
|
}
|
|
const img = imgEl.value
|
|
if (!img) return
|
|
const r = img.getBoundingClientRect()
|
|
const inside =
|
|
ev.clientX >= r.left && ev.clientX <= r.right &&
|
|
ev.clientY >= r.top && ev.clientY <= r.bottom
|
|
if (inside) {
|
|
panZoom.handlers.onClick(ev)
|
|
} else {
|
|
emit('close-request')
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.fc-canvas {
|
|
flex: 1;
|
|
position: relative;
|
|
/* Transparent so the viewer's haze shows through to the image area
|
|
instead of a solid panel sitting on top of it. */
|
|
background: transparent;
|
|
overflow: hidden;
|
|
cursor: zoom-in;
|
|
user-select: none;
|
|
display: flex; align-items: center; justify-content: center;
|
|
}
|
|
.fc-canvas--zoomed { cursor: grab; }
|
|
.fc-canvas--zoomed:active { cursor: grabbing; }
|
|
.fc-canvas img {
|
|
max-width: 100%; max-height: 100%;
|
|
transform-origin: center;
|
|
transition: transform 100ms ease-out;
|
|
pointer-events: none;
|
|
}
|
|
</style>
|