PDF-Forge/src/components/ZoomableContainer.svelte
Kilian Schuettler 0e9f020e26 fixed rendering
2025-02-16 16:35:24 +01:00

163 lines
4.1 KiB
Svelte

<script lang="ts">
import ZoomControls from "../components/ZoomControls.svelte";
import { RawImageData } from "../models/RawImageData";
type ZoomableProps = {
minZoom?: number;
maxZoom?: number;
zoomStep?: number;
img: RawImageData;
height?: string | number;
};
let {
minZoom = 0.5,
maxZoom = 10,
zoomStep = 0.1,
img,
height,
}: ZoomableProps = $props();
let scale = $state(1);
// DOM refs
let container = $state<HTMLElement>();
let image = $state<HTMLElement>();
// Drag state
type DragState = {
startX: number;
startY: number;
scrollLeft: number;
scrollTop: number;
};
let dragState: DragState | undefined = $state({
startX: 0,
startY: 0,
scrollLeft: 0,
scrollTop: 0,
});
// Zoom handlers
function handleZoom(event: WheelEvent) {
if (!container || !image || !event.ctrlKey) return;
event.preventDefault();
const delta = -Math.sign(event.deltaY);
const newScale = Math.min(
Math.max(scale + delta * zoomStep, minZoom),
maxZoom,
);
if (newScale !== scale) {
const rect = image.getBoundingClientRect();
const x = event.clientX;
const y = event.clientY;
const scaleChange = newScale / scale;
container.scrollLeft = x * scaleChange - x + container.scrollLeft;
container.scrollTop = y * scaleChange - y + container.scrollTop;
scale = newScale;
}
}
// Drag handlers
function handleDragStart(event: MouseEvent) {
if (!container) return;
event.preventDefault();
dragState = {
startX: event.pageX - container.offsetLeft,
startY: event.pageY - container.offsetTop,
scrollLeft: container.scrollLeft,
scrollTop: container.scrollTop,
};
}
function handleDragMove(event: MouseEvent) {
if (!container || !dragState) return;
event.preventDefault();
const x = event.pageX - container.offsetLeft;
const y = event.pageY - container.offsetTop;
container.scrollLeft = dragState.scrollLeft - (x - dragState.startX);
container.scrollTop = dragState.scrollTop - (y - dragState.startY);
}
function handleDragEnd(event: MouseEvent) {
event.preventDefault();
dragState = undefined;
}
function resetZoom() {
scale = 1;
if (container) {
container.scrollLeft = 0;
container.scrollTop = 0;
}
}
// Event listener management
$effect(() => {
if (container) {
container.addEventListener("wheel", handleZoom, { passive: false });
return () => container.removeEventListener("wheel", handleZoom);
}
});
</script>
<div class="relative w-full h-full">
<ZoomControls
{scale}
onZoomIn={() => (scale = Math.min(scale + zoomStep, maxZoom))}
onZoomOut={() => (scale = Math.max(scale - zoomStep, minZoom))}
{resetZoom}
/>
<div
class="container"
bind:this={container}
onmousedown={handleDragStart}
onmousemove={handleDragMove}
onmouseup={handleDragEnd}
onmouseleave={handleDragEnd}
role="presentation"
>
<div
class="image-container"
bind:this={image}
style:transform="scale({scale})"
style:height={height + "px"}
>
<img
alt="rendered-page"
src={img.toObjectUrl()}
style:max-height={height + "px"}
/>
</div>
</div>
</div>
<style lang="postcss">
.container {
@apply w-full h-full bg-forge-dark;
overflow: auto;
cursor: grab;
}
.container:active {
cursor: grabbing;
}
.image-container {
transform-origin: 0 0;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
</style>