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