|
|
<!DOCTYPE html> |
|
|
<html lang="en"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
<title>Images to PDF</title> |
|
|
<style> |
|
|
body { |
|
|
font-family: Arial, sans-serif; |
|
|
margin: 50px; |
|
|
text-align: center; |
|
|
touch-action: none; |
|
|
} |
|
|
#imageContainer { |
|
|
display: flex; |
|
|
flex-direction: column; |
|
|
align-items: center; |
|
|
gap: 10px; |
|
|
margin: 20px 0; |
|
|
padding: 10px; |
|
|
border: 1px solid #ccc; |
|
|
} |
|
|
.imageWrapper { |
|
|
max-width: 200px; |
|
|
cursor: move; |
|
|
position: relative; |
|
|
user-select: none; |
|
|
} |
|
|
.imageWrapper img { |
|
|
max-width: 100%; |
|
|
height: auto; |
|
|
border: 1px solid #ddd; |
|
|
border-radius: 5px; |
|
|
} |
|
|
.imageWrapper.dragging { |
|
|
opacity: 0.5; |
|
|
} |
|
|
.imageWrapper.over { |
|
|
border: 2px dashed #4CAF50; |
|
|
} |
|
|
button { |
|
|
padding: 10px 20px; |
|
|
font-size: 16px; |
|
|
cursor: pointer; |
|
|
background-color: #4CAF50; |
|
|
color: white; |
|
|
border: none; |
|
|
border-radius: 5px; |
|
|
} |
|
|
button:hover { |
|
|
background-color: #45a049; |
|
|
} |
|
|
</style> |
|
|
</head> |
|
|
<body> |
|
|
<h1>Images to PDF Converter</h1> |
|
|
<input type="file" id="imageInput" accept="image/*" multiple> |
|
|
<p>Drag and drop (desktop) or tap and move (mobile) images to reorder them for the PDF.</p> |
|
|
<div id="imageContainer"></div> |
|
|
<button onclick="generatePDF()">Download as PDF</button> |
|
|
|
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script> |
|
|
<script> |
|
|
const { jsPDF } = window.jspdf; |
|
|
const imageInput = document.getElementById('imageInput'); |
|
|
const imageContainer = document.getElementById('imageContainer'); |
|
|
let images = []; |
|
|
|
|
|
|
|
|
imageInput.addEventListener('change', (event) => { |
|
|
images = []; |
|
|
imageContainer.innerHTML = ''; |
|
|
const files = Array.from(event.target.files); |
|
|
|
|
|
files.forEach((file) => { |
|
|
const reader = new FileReader(); |
|
|
reader.onload = (e) => { |
|
|
const img = new Image(); |
|
|
img.src = e.target.result; |
|
|
img.onload = () => { |
|
|
images.push({ src: e.target.result, width: img.width, height: img.height }); |
|
|
renderImages(); |
|
|
}; |
|
|
}; |
|
|
reader.readAsDataURL(file); |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
function renderImages() { |
|
|
imageContainer.innerHTML = ''; |
|
|
images.forEach((image, index) => { |
|
|
const wrapper = document.createElement('div'); |
|
|
wrapper.className = 'imageWrapper'; |
|
|
wrapper.draggable = true; |
|
|
wrapper.dataset.index = index; |
|
|
|
|
|
const img = new Image(); |
|
|
img.src = image.src; |
|
|
wrapper.appendChild(img); |
|
|
|
|
|
|
|
|
wrapper.addEventListener('dragstart', (e) => { |
|
|
wrapper.classList.add('dragging'); |
|
|
e.dataTransfer.setData('text/plain', index); |
|
|
}); |
|
|
|
|
|
wrapper.addEventListener('dragend', () => { |
|
|
wrapper.classList.remove('dragging'); |
|
|
clearOverStyles(); |
|
|
}); |
|
|
|
|
|
wrapper.addEventListener('dragover', (e) => { |
|
|
e.preventDefault(); |
|
|
wrapper.classList.add('over'); |
|
|
}); |
|
|
|
|
|
wrapper.addEventListener('dragleave', () => { |
|
|
wrapper.classList.remove('over'); |
|
|
}); |
|
|
|
|
|
wrapper.addEventListener('drop', (e) => { |
|
|
e.preventDefault(); |
|
|
const draggedIndex = parseInt(e.dataTransfer.getData('text/plain')); |
|
|
const dropIndex = parseInt(wrapper.dataset.index); |
|
|
reorderImages(draggedIndex, dropIndex); |
|
|
renderImages(); |
|
|
}); |
|
|
|
|
|
|
|
|
wrapper.addEventListener('touchstart', (e) => { |
|
|
e.preventDefault(); |
|
|
wrapper.classList.add('dragging'); |
|
|
startTouchReorder(e, wrapper, index); |
|
|
}); |
|
|
|
|
|
wrapper.addEventListener('touchmove', (e) => { |
|
|
e.preventDefault(); |
|
|
handleTouchMove(e, wrapper); |
|
|
}); |
|
|
|
|
|
wrapper.addEventListener('touchend', (e) => { |
|
|
e.preventDefault(); |
|
|
wrapper.classList.remove('dragging'); |
|
|
endTouchReorder(wrapper, index); |
|
|
clearOverStyles(); |
|
|
}); |
|
|
|
|
|
imageContainer.appendChild(wrapper); |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
let touchStartY = 0; |
|
|
let targetIndex = null; |
|
|
|
|
|
function startTouchReorder(event, wrapper, index) { |
|
|
const touch = event.touches[0]; |
|
|
touchStartY = touch.clientY; |
|
|
targetIndex = index; |
|
|
} |
|
|
|
|
|
function handleTouchMove(event, wrapper) { |
|
|
const touch = event.touches[0]; |
|
|
const touchY = touch.clientY; |
|
|
|
|
|
|
|
|
const elements = document.elementsFromPoint(touch.clientX, touchY); |
|
|
const targetWrapper = elements.find(el => el.classList.contains('imageWrapper') && el !== wrapper); |
|
|
|
|
|
|
|
|
clearOverStyles(); |
|
|
|
|
|
if (targetWrapper) { |
|
|
targetWrapper.classList.add('over'); |
|
|
} |
|
|
} |
|
|
|
|
|
function endTouchReorder(wrapper, startIndex) { |
|
|
const overElement = document.querySelector('.imageWrapper.over'); |
|
|
if (overElement) { |
|
|
const dropIndex = parseInt(overElement.dataset.index); |
|
|
reorderImages(startIndex, dropIndex); |
|
|
renderImages(); |
|
|
} |
|
|
} |
|
|
|
|
|
function clearOverStyles() { |
|
|
document.querySelectorAll('.imageWrapper.over').forEach(el => el.classList.remove('over')); |
|
|
} |
|
|
|
|
|
function reorderImages(draggedIndex, dropIndex) { |
|
|
if (draggedIndex !== dropIndex) { |
|
|
const [draggedImage] = images.splice(draggedIndex, 1); |
|
|
images.splice(dropIndex, 0, draggedImage); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function generatePDF() { |
|
|
if (images.length === 0) { |
|
|
alert('Please upload at least one image.'); |
|
|
return; |
|
|
} |
|
|
|
|
|
const doc = new jsPDF(); |
|
|
const pageWidth = doc.internal.pageSize.getWidth(); |
|
|
const pageHeight = doc.internal.pageSize.getHeight(); |
|
|
const margin = 10; |
|
|
const maxImgWidth = pageWidth - 2 * margin; |
|
|
const maxImgHeight = pageHeight - 2 * margin; |
|
|
|
|
|
images.forEach((image, index) => { |
|
|
if (index > 0) { |
|
|
doc.addPage(); |
|
|
} |
|
|
|
|
|
|
|
|
let imgWidth = image.width; |
|
|
let imgHeight = image.height; |
|
|
const ratio = Math.min(maxImgWidth / imgWidth, maxImgHeight / imgHeight); |
|
|
imgWidth = imgWidth * ratio; |
|
|
imgHeight = imgHeight * ratio; |
|
|
|
|
|
|
|
|
const x = (pageWidth - imgWidth) / 2; |
|
|
const y = (pageHeight - imgHeight) / 2; |
|
|
|
|
|
doc.addImage(image.src, 'JPEG', x, y, imgWidth, imgHeight); |
|
|
}); |
|
|
|
|
|
doc.save('images.pdf'); |
|
|
} |
|
|
</script> |
|
|
</body> |
|
|
</html> |