JSFiddle - React, Tailwind, and code Playground
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Pan-Zoom Demo</title>
</head>
<body>
<div class="debug-panel" id="debug-panel">
<div>Scale: <span id="debug-scale" class="debug-value">100</span>%</div>
<div>TranslateX: <span id="debug-x" class="debug-value">0</span>px</div>
<div>TranslateY: <span id="debug-y" class="debug-value">0</span>px</div>
<div>isPanning: <span id="debug-panning" class="debug-value">false</span></div>
<div>Mouse X: <span id="debug-mouse-x" class="debug-value">0</span></div>
<div>Mouse Y: <span id="debug-mouse-y" class="debug-value">0</span></div>
<div>Delta X: <span id="debug-delta-x" class="debug-value">0</span></div>
<div>Delta Y: <span id="debug-delta-y" class="debug-value">0</span></div>
</div>
<div class="container" id="container">
<div class="content-wrapper" id="wrapper">
<iframe src="about:blank" id="demo-iframe"></iframe>
</div>
</div>
</body>
</html>
CSS
body {
margin: 0;
padding: 0;
font-family: Arial, sans-serif;
overflow: hidden;
background: #222;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
.container {
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
cursor: default;
}
.container:active {
cursor: grabbing;
}
.content-wrapper {
position: relative;
transform-origin: 0 0;
background-color: white;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.2);
width: 600px;
height: 400px;
}
iframe {
width: 100%;
height: 100%;
border: none;
}
.debug-panel {
position: fixed;
top: 10px;
left: 10px;
background: rgba(0,0,0,0.8);
color: white;
padding: 12px;
border-radius: 4px;
font-family: monospace;
font-size: 14px;
line-height: 1.5;
z-index: 1000;
min-width: 200px;
}
.debug-value {
color: #4cff4c;
font-weight: bold;
}
JavaScript
(function() {
// DOM Elements
const container = document.getElementById('container');
const wrapper = document.getElementById('wrapper');
const iframe = document.getElementById('demo-iframe');
// Debug elements
const debugScale = document.getElementById('debug-scale');
const debugX = document.getElementById('debug-x');
const debugY = document.getElementById('debug-y');
const debugPanning = document.getElementById('debug-panning');
const debugMouseX = document.getElementById('debug-mouse-x');
const debugMouseY = document.getElementById('debug-mouse-y');
const debugDeltaX = document.getElementById('debug-delta-x');
const debugDeltaY = document.getElementById('debug-delta-y');
// State variables
let translateX = 0;
let translateY = 0;
let scale = 100;
let isPanning = false;
let lastMouseX = 0;
let lastMouseY = 0;
let deltaX = 0;
let deltaY = 0;
// Constants
const ZOOM_SPEED = 5;
const MIN_SCALE = 10;
const MAX_SCALE = 500;
// Update debug info
function updateDebug() {
debugScale.textContent = scale;
debugX.textContent = translateX.toFixed(2);
debugY.textContent = translateY.toFixed(2);
debugPanning.textContent = isPanning;
debugMouseX.textContent = lastMouseX.toFixed(2);
debugMouseY.textContent = lastMouseY.toFixed(2);
debugDeltaX.textContent = deltaX.toFixed(2);
debugDeltaY.textContent = deltaY.toFixed(2);
}
// Apply transform to wrapper
function applyTransform() {
const scaleValue = scale / 100;
wrapper.style.transform = `scale(${scaleValue}) translate(${translateX}px, ${translateY}px)`;
updateDebug();
}
// Start panning
function startPanning(x, y) {
isPanning = true;
lastMouseX = x;
lastMouseY = y;
document.body.style.cursor = 'grabbing';
updateDebug();
}
// Update panning
function updatePanning(x, y) {
if (!isPanning) return;
const scaleValue = scale / 100;
deltaX = x - lastMouseX;
deltaY = y -...