SnapToPano / viewer.html
HirCoir's picture
Upload 8 files
ade108c verified
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Visor Panorámico - {{ filename }}</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css" rel="stylesheet">
<style>
body {
margin: 0;
padding: 0;
background: #000;
overflow: hidden;
}
.viewer-container {
position: relative;
width: 100vw;
height: 100vh;
}
.panorama-canvas {
display: block;
cursor: grab;
}
.panorama-canvas:active {
cursor: grabbing;
}
.controls {
position: absolute;
top: 20px;
left: 20px;
z-index: 1000;
display: flex;
gap: 10px;
}
.control-btn {
background: rgba(0,0,0,0.7);
border: none;
color: white;
padding: 10px 15px;
border-radius: 8px;
cursor: pointer;
transition: background 0.3s;
}
.control-btn:hover {
background: rgba(0,0,0,0.9);
}
.info-panel {
position: absolute;
bottom: 20px;
left: 20px;
right: 20px;
background: rgba(0,0,0,0.8);
color: white;
padding: 15px;
border-radius: 8px;
display: flex;
justify-content: space-between;
align-items: center;
}
.loading {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: white;
text-align: center;
}
.zoom-controls {
position: absolute;
top: 20px;
right: 20px;
display: flex;
flex-direction: column;
gap: 5px;
}
.zoom-btn {
background: rgba(0,0,0,0.7);
border: none;
color: white;
width: 40px;
height: 40px;
border-radius: 50%;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
}
.zoom-btn:hover {
background: rgba(0,0,0,0.9);
}
</style>
</head>
<body>
<div class="viewer-container">
<div class="loading" id="loading">
<div class="spinner-border" role="status">
<span class="visually-hidden">Cargando...</span>
</div>
<p class="mt-2">Cargando panorama...</p>
</div>
<canvas id="panoramaCanvas" class="panorama-canvas" style="display: none;"></canvas>
<div class="controls">
<button class="control-btn" onclick="window.close()" title="Cerrar">
<i class="bi bi-x-lg"></i>
</button>
<button class="control-btn" onclick="window.history.back()" title="Volver">
<i class="bi bi-arrow-left"></i>
</button>
<button class="control-btn" onclick="toggleFullscreen()" title="Pantalla completa">
<i class="bi bi-fullscreen"></i>
</button>
<button class="control-btn" onclick="resetView()" title="Reiniciar vista">
<i class="bi bi-arrow-clockwise"></i>
</button>
</div>
<div class="zoom-controls">
<button class="zoom-btn" onclick="zoomIn()" title="Acercar">
<i class="bi bi-plus"></i>
</button>
<button class="zoom-btn" onclick="zoomOut()" title="Alejar">
<i class="bi bi-dash"></i>
</button>
</div>
<div class="info-panel">
<div>
<strong>{{ filename }}</strong>
<br>
<small>Arrastra para rotar • Rueda del mouse para zoom</small>
</div>
<div>
<button class="btn btn-sm btn-outline-light" onclick="downloadImage()">
<i class="bi bi-download"></i> Descargar
</button>
</div>
</div>
</div>
<script>
const canvas = document.getElementById('panoramaCanvas');
const ctx = canvas.getContext('2d');
const loading = document.getElementById('loading');
let img = new Image();
let isDragging = false;
let lastX = 0;
let lastY = 0;
let offsetX = 0;
let offsetY = 0;
let zoom = 1;
let minZoom = 0.5;
let maxZoom = 3;
// Configurar canvas
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
if (img.complete) {
drawPanorama();
}
}
// Cargar imagen
img.onload = function() {
loading.style.display = 'none';
canvas.style.display = 'block';
resizeCanvas();
// Calcular zoom inicial para ajustar la imagen
const scaleX = canvas.width / img.width;
const scaleY = canvas.height / img.height;
zoom = Math.max(scaleX, scaleY);
minZoom = zoom * 0.5;
maxZoom = zoom * 3;
drawPanorama();
};
img.onerror = function() {
loading.innerHTML = '<p>Error al cargar la imagen</p>';
};
// Dibujar panorama
function drawPanorama() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
const scaledWidth = img.width * zoom;
const scaledHeight = img.height * zoom;
// Centrar verticalmente
const centerY = (canvas.height - scaledHeight) / 2 + offsetY;
// Dibujar imagen principal
ctx.drawImage(img, offsetX, centerY, scaledWidth, scaledHeight);
// Repetir horizontalmente para efecto cilíndrico
if (scaledWidth + offsetX < canvas.width) {
ctx.drawImage(img, offsetX + scaledWidth, centerY, scaledWidth, scaledHeight);
}
if (offsetX > 0) {
ctx.drawImage(img, offsetX - scaledWidth, centerY, scaledWidth, scaledHeight);
}
// Limitar desplazamiento vertical
const maxOffsetY = Math.max(0, (scaledHeight - canvas.height) / 2);
offsetY = Math.max(-maxOffsetY, Math.min(maxOffsetY, offsetY));
}
// Eventos de mouse
canvas.addEventListener('mousedown', (e) => {
isDragging = true;
lastX = e.clientX;
lastY = e.clientY;
});
canvas.addEventListener('mousemove', (e) => {
if (isDragging) {
const deltaX = e.clientX - lastX;
const deltaY = e.clientY - lastY;
offsetX += deltaX;
offsetY += deltaY;
// Mantener offset X en rango válido para efecto cilíndrico
const scaledWidth = img.width * zoom;
if (offsetX > scaledWidth) offsetX -= scaledWidth;
if (offsetX < -scaledWidth) offsetX += scaledWidth;
drawPanorama();
lastX = e.clientX;
lastY = e.clientY;
}
});
canvas.addEventListener('mouseup', () => {
isDragging = false;
});
canvas.addEventListener('mouseleave', () => {
isDragging = false;
});
// Zoom con rueda del mouse
canvas.addEventListener('wheel', (e) => {
e.preventDefault();
const zoomFactor = e.deltaY > 0 ? 0.9 : 1.1;
const newZoom = zoom * zoomFactor;
if (newZoom >= minZoom && newZoom <= maxZoom) {
zoom = newZoom;
drawPanorama();
}
});
// Eventos de teclado
document.addEventListener('keydown', (e) => {
switch(e.key) {
case 'Escape':
if (document.fullscreenElement) {
document.exitFullscreen();
}
break;
case 'f':
case 'F':
toggleFullscreen();
break;
case 'r':
case 'R':
resetView();
break;
}
});
// Funciones de control
function zoomIn() {
const newZoom = zoom * 1.2;
if (newZoom <= maxZoom) {
zoom = newZoom;
drawPanorama();
}
}
function zoomOut() {
const newZoom = zoom * 0.8;
if (newZoom >= minZoom) {
zoom = newZoom;
drawPanorama();
}
}
function resetView() {
offsetX = 0;
offsetY = 0;
const scaleX = canvas.width / img.width;
const scaleY = canvas.height / img.height;
zoom = Math.max(scaleX, scaleY);
drawPanorama();
}
function toggleFullscreen() {
if (!document.fullscreenElement) {
document.documentElement.requestFullscreen();
} else {
document.exitFullscreen();
}
}
function downloadImage() {
const link = document.createElement('a');
link.download = '{{ filename }}';
link.href = '{{ url_for("static", filename=image_path) }}';
link.click();
}
// Redimensionar canvas cuando cambie el tamaño de ventana
window.addEventListener('resize', resizeCanvas);
// Cargar imagen
img.src = '{{ url_for("static", filename=image_path) }}';
</script>
</body>
</html>