-
-
Save asenchi/3a339f7574cc722c5185265ca13c0afb to your computer and use it in GitHub Desktop.
image viewer
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Fullscreen Image Viewer</title> | |
<style> | |
body { | |
font-family: Arial, sans-serif; | |
} | |
#imageDisplay { | |
max-width: 80%; | |
max-height: 78vh; | |
height: auto; | |
margin: 20px auto; | |
border: 5px dashed #ddd; | |
padding: 10px; | |
cursor: pointer; | |
} | |
.btn { | |
display: inline-block; | |
padding: 10px; | |
background-color: #007bff; | |
color: white; | |
cursor: pointer; | |
margin: 10px; | |
} | |
.btn.danger { | |
background-color: #dc3545; | |
} | |
#buttons { | |
position: sticky; | |
top: 0; | |
display: block; | |
background: white; | |
user-select: none; | |
} | |
#container { | |
width: 80%; | |
margin: auto; | |
text-align: center; | |
} | |
#thumbnails { | |
display: flex; | |
overflow-x: auto; | |
padding: 10px; | |
margin-top: 20px; | |
justify-content: center; | |
} | |
.thumbnail { | |
margin: 0 5px; | |
cursor: pointer; | |
} | |
.thumbnail img { | |
width: 100px; | |
height: auto; | |
border: 2px solid transparent; | |
} | |
.thumbnail img.active { | |
border-color: blue; | |
} | |
[hidden] { | |
display: none !important; | |
} | |
</style> | |
</head> | |
<body> | |
<div id="container"> | |
<div> | |
<input type="file" id="imageInput" multiple accept="image/*" style="display: none;"> | |
<label for="imageInput" class="btn">Select Images</label> | |
</div> | |
<div id="buttons" hidden> | |
<button class="btn" onclick="previousImage()">Previous</button> | |
<button class="btn" onclick="nextImage()">Next</button> | |
<button class="btn danger" onclick="removeCurrentImage()">Remove Current Image</button> | |
</div> | |
<img id="imageDisplay" onclick="toggleFullscreen()"> | |
<div id="imageIndex"></div> | |
<div id="thumbnails"></div> | |
</div> | |
<script> | |
let images = []; | |
let currentIndex = -1; // Start before the first index | |
let touchstartX = 0; | |
let touchendX = 0; | |
function checkSwipeDirection() { | |
if (touchstartX - touchendX > 100) nextImage(); | |
if (touchendX - touchstartX > 100) previousImage(); | |
} | |
document.addEventListener("touchstart", e => { | |
touchstartX = e.changedTouches[0].screenX; | |
}); | |
document.addEventListener("touchend", e => { | |
touchendX = e.changedTouches[0].screenX; | |
checkSwipeDirection(); | |
}); | |
document.getElementById("imageInput").addEventListener("change", handleFiles); | |
document.getElementById("container").addEventListener("paste", handlePaste); | |
document.addEventListener("dragover", preventDefault); | |
document.addEventListener("drop", handleDrop); | |
document.addEventListener("keydown", handleKeydown); | |
function handleFiles(e) { | |
const files = e.target.files; | |
for (let i = 0; i < files.length; i++) { | |
if (files[i].type.startsWith("image/")) { | |
const fileReader = new FileReader(); | |
fileReader.onload = (e) => { | |
images.push(e.target.result); | |
if (i === 0) displayImage(images.length - 1); // Display the last image added | |
addThumbnail(e.target.result, images.length - 1); | |
document.getElementById("buttons").removeAttribute("hidden"); | |
}; | |
fileReader.onerror = (e) => console.log(e.target.error); | |
fileReader.readAsDataURL(files[i]); | |
} | |
} | |
} | |
function handlePaste(e) { | |
const files = e.clipboardData.files; | |
handleFiles({ target: { files } }); | |
} | |
function preventDefault(e) { | |
e.preventDefault(); | |
} | |
function handleDrop(e) { | |
e.preventDefault(); | |
if (!e.dataTransfer) { | |
console.log("DataTransfer is null, this drop event cannot be processed."); | |
return; | |
} | |
const files = e.dataTransfer.files; | |
if (files && files.length > 0) { | |
// Handle files from the user's computer | |
handleFiles({ target: { files } }); | |
} else { | |
// Handle images dragged from other web pages | |
const htmlContent = e.dataTransfer.getData("text/html"); | |
const match = htmlContent && htmlContent.match(/<img [^>]*src="([^"]+)"[^>]*>/i); | |
if (match && match[1]) { | |
images.push(match[1]); | |
displayImage(images.length - 1); // Display the last image added | |
addThumbnail(match[1], images.length - 1); | |
} | |
} | |
} | |
function removeThumbnail(index) { | |
const thumbnailsContainer = document.getElementById("thumbnails"); | |
thumbnailsContainer.removeChild(thumbnailsContainer.children[index]); | |
} | |
function displayImage(index) { | |
const nothingToDisplay = images.length === 0; | |
if (nothingToDisplay) { | |
document.getElementById("buttons").setAttribute("hidden", ""); | |
} else { | |
document.getElementById("buttons").removeAttribute("hidden"); | |
} | |
currentIndex = nothingToDisplay ? -1 : index; | |
document.getElementById("imageDisplay").src = nothingToDisplay ? "" : images[index]; | |
document.getElementById("imageIndex").innerText = nothingToDisplay ? "" : `Image ${index + 1} of ${images.length}`; | |
updateThumbnailActiveState(); | |
} | |
function addThumbnail(imageSrc, index) { | |
const thumbnailWrapper = document.createElement("div"); | |
const thumbnailsContainer = document.getElementById("thumbnails"); | |
thumbnailWrapper.classList.add("thumbnail"); | |
thumbnailWrapper.addEventListener("click", () => displayImage([...thumbnailsContainer.children].indexOf(thumbnailWrapper))); | |
const img = document.createElement("img"); | |
img.src = imageSrc; | |
thumbnailWrapper.appendChild(img); | |
thumbnailsContainer.appendChild(thumbnailWrapper); | |
updateThumbnailActiveState(); | |
} | |
function updateThumbnailActiveState() { | |
const thumbnails = document.querySelectorAll(".thumbnail img"); | |
thumbnails.forEach((img, idx) => { | |
img.classList.remove("active"); | |
if (idx === currentIndex) { | |
img.classList.add("active"); | |
} | |
}); | |
} | |
function nextImage() { | |
if (currentIndex === -1) { | |
return; | |
} | |
displayImage((currentIndex + 1) % images.length); | |
} | |
function previousImage() { | |
if (currentIndex === -1) { | |
return; | |
} | |
displayImage((currentIndex - 1 + images.length) % images.length); | |
} | |
function handleKeydown(e) { | |
if (e.key === "ArrowRight") { | |
nextImage(); | |
} else if (e.key === "ArrowLeft") { | |
previousImage(); | |
} else if (e.key === "Escape") { | |
exitFullscreen(); | |
} else if (e.key === "Delete") { | |
removeCurrentImage(); | |
} else if (e.key === "Enter") { | |
toggleFullscreen(); | |
} | |
} | |
function removeCurrentImage() { | |
if (currentIndex === -1) { | |
return; | |
} | |
images.splice(currentIndex, 1); | |
removeThumbnail(currentIndex); | |
displayImage(currentIndex % images.length); | |
} | |
function toggleFullscreen() { | |
const imgDisplay = document.getElementById("imageDisplay"); | |
if (!document.fullscreenElement) { | |
if (currentIndex === -1) { | |
return; | |
} | |
imgDisplay.requestFullscreen().catch(err => console.log(err)); | |
} else { | |
if (document.exitFullscreen) { | |
document.exitFullscreen(); | |
} | |
} | |
} | |
function exitFullscreen() { | |
if (document.fullscreenElement) { | |
document.exitFullscreen(); | |
} | |
} | |
// load existing images when the page loads from the leftover thumbnails | |
document.addEventListener("DOMContentLoaded", () => { | |
let activeIndex = 0; | |
const thumbnails = document.querySelectorAll(".thumbnail img"); | |
thumbnails.forEach((img, idx) => { | |
images.push(img.src); | |
img.addEventListener("click", () => displayImage(idx)); | |
if (img.classList.contains("active")) { | |
activeIndex = idx; | |
} | |
}); | |
displayImage(activeIndex); | |
}); | |
// change the image when the user uses scroll wheel | |
document.getElementById("imageDisplay").addEventListener("wheel", e => { | |
e.preventDefault(); | |
if (e.deltaY > 0) { | |
nextImage(); | |
} else { | |
previousImage(); | |
} | |
}); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment