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