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 -...