最新文章
合影功能實(shí)現(xiàn)
設(shè)計(jì)一個(gè)具有合影功能的網(wǎng)頁(yè),允許用戶拍照、合并多張照片并下載最終合影。
設(shè)計(jì)思路:
使用攝像頭捕獲實(shí)時(shí)視頻
實(shí)現(xiàn)拍照功能并展示縮略圖
提供照片合并功能
實(shí)現(xiàn)下載最終合影功能
添加簡(jiǎn)潔美觀的UI設(shè)計(jì)
實(shí)現(xiàn)方案:
使用HTML5的
getUserMedia
API訪問攝像頭利用Canvas進(jìn)行圖像捕獲和處理
使用Flexbox布局創(chuàng)建響應(yīng)式界面
下面是完整的實(shí)現(xiàn)代碼:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>在線合影制作工具</title> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> <style> * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; } body { background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%); min-height: 100vh; display: flex; justify-content: center; align-items: center; padding: 20px; color: #333; } .container { width: 100%; max-width: 1200px; background: rgba(255, 255, 255, 0.9); border-radius: 20px; box-shadow: 0 15px 30px rgba(0, 0, 0, 0.2); overflow: hidden; display: flex; flex-direction: column; } header { background: linear-gradient(to right, #4a00e0, #8e2de2); color: white; padding: 20px; text-align: center; } header h1 { font-size: 2.5rem; margin-bottom: 10px; } header p { font-size: 1.1rem; opacity: 0.9; } .content { display: flex; flex-wrap: wrap; padding: 20px; gap: 20px; } .camera-section { flex: 1; min-width: 300px; background: white; border-radius: 15px; padding: 20px; box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1); } .preview-section { flex: 1; min-width: 300px; background: white; border-radius: 15px; padding: 20px; box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1); display: flex; flex-direction: column; } .section-title { font-size: 1.5rem; margin-bottom: 15px; color: #4a00e0; display: flex; align-items: center; gap: 10px; } .section-title i { background: #4a00e0; color: white; width: 36px; height: 36px; border-radius: 50%; display: flex; align-items: center; justify-content: center; } .camera-container { width: 100%; height: 300px; background: #f0f0f0; border-radius: 10px; overflow: hidden; margin-bottom: 20px; position: relative; display: flex; justify-content: center; align-items: center; } #video { width: 100%; height: 100%; object-fit: cover; } .controls { display: flex; gap: 10px; flex-wrap: wrap; } .btn { padding: 12px 24px; border: none; border-radius: 50px; font-size: 1rem; font-weight: 600; cursor: pointer; display: flex; align-items: center; gap: 8px; transition: all 0.3s ease; } .btn-primary { background: linear-gradient(to right, #4a00e0, #8e2de2); color: white; box-shadow: 0 4px 10px rgba(74, 0, 224, 0.3); } .btn-primary:hover { transform: translateY(-3px); box-shadow: 0 6px 15px rgba(74, 0, 224, 0.4); } .btn-success { background: linear-gradient(to right, #00b09b, #96c93d); color: white; box-shadow: 0 4px 10px rgba(0, 176, 155, 0.3); } .btn-success:hover { transform: translateY(-3px); box-shadow: 0 6px 15px rgba(0, 176, 155, 0.4); } .btn-danger { background: linear-gradient(to right, #ff416c, #ff4b2b); color: white; box-shadow: 0 4px 10px rgba(255, 65, 108, 0.3); } .btn-danger:hover { transform: translateY(-3px); box-shadow: 0 6px 15px rgba(255, 65, 108, 0.4); } .captured-photos { display: flex; gap: 15px; margin: 20px 0; flex-wrap: wrap; min-height: 120px; } .photo-thumb { width: 100px; height: 100px; border-radius: 10px; overflow: hidden; position: relative; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); border: 3px solid #8e2de2; } .photo-thumb img { width: 100%; height: 100%; object-fit: cover; } .photo-thumb .remove { position: absolute; top: 5px; right: 5px; background: rgba(255, 255, 255, 0.8); border-radius: 50%; width: 22px; height: 22px; display: flex; align-items: center; justify-content: center; cursor: pointer; color: #ff416c; font-size: 14px; } .result-container { flex: 1; background: #f8f9fa; border-radius: 10px; display: flex; justify-content: center; align-items: center; margin-top: 20px; overflow: hidden; min-height: 300px; } #result { max-width: 100%; max-height: 100%; display: none; } .placeholder { color: #6c757d; text-align: center; padding: 20px; } .placeholder i { font-size: 3rem; margin-bottom: 15px; color: #adb5bd; } .download-btn { margin-top: 20px; align-self: center; padding: 12px 30px; font-size: 1.1rem; } .instructions { background: #eef2ff; border-left: 4px solid #4a00e0; padding: 15px; border-radius: 0 8px 8px 0; margin: 20px 0; } .instructions h3 { margin-bottom: 10px; color: #4a00e0; } .instructions ol { padding-left: 20px; } .instructions li { margin-bottom: 8px; } footer { text-align: center; padding: 20px; background: #f8f9fa; color: #6c757d; font-size: 0.9rem; } @media (max-width: 768px) { .content { flex-direction: column; } header h1 { font-size: 2rem; } } .flash { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background-color: white; opacity: 0; pointer-events: none; } @keyframes flash { 0% { opacity: 0; } 50% { opacity: 0.8; } 100% { opacity: 0; } } </style> </head> <body> <div> <header> <h1><i class="fas fa-camera"></i> 在線合影制作工具</h1> <p>拍攝照片,合并多張圖片,創(chuàng)建完美的合影</p> </header> <div> <div> <h2><i class="fas fa-video"></i> 拍照區(qū)域</h2> <div> <div id="flash"></div> <video id="video" autoplay playsinline></video> <div id="camera-placeholder"> <p>正在請(qǐng)求攝像頭權(quán)限...</p> </div> </div> <div> <button id="capture-btn" class="btn btn-primary"> <i class="fas fa-camera"></i> 拍照 </button> <button id="reset-btn" class="btn btn-danger"> <i class="fas fa-trash-alt"></i> 重置所有照片 </button> </div> <div> <h3>使用說明:</h3> <ol> <li>點(diǎn)擊"允許"按鈕授權(quán)訪問您的攝像頭</li> <li>調(diào)整位置,點(diǎn)擊"拍照"按鈕拍攝照片</li> <li>拍攝多張照片后,點(diǎn)擊"合并照片"創(chuàng)建合影</li> <li>最后點(diǎn)擊"下載合影"保存最終圖片</li> </ol> </div> <h3><i class="fas fa-images"></i> 已拍攝照片</h3> <div id="photos-container"> <div> <i class="fas fa-image"></i> <p>尚未拍攝照片</p> </div> </div> <button id="merge-btn" class="btn btn-success"> <i class="fas fa-object-group"></i> 合并照片 </button> </div> <div> <h2><i class="fas fa-file-image"></i> 合影預(yù)覽</h2> <div> <div id="result-placeholder"> <i class="fas fa-users"></i> <p>合并后的合影將顯示在這里</p> </div> <img id="result" alt="合并后的合影"> </div> <button id="download-btn" class="btn btn-primary download-btn"> <i class="fas fa-download"></i> 下載合影 </button> </div> </div> <footer> <p>? 2023 在線合影工具 | 使用HTML5技術(shù)實(shí)現(xiàn) | 兼容Chrome、Firefox、Edge等現(xiàn)代瀏覽器</p> </footer> </div> <script> document.addEventListener('DOMContentLoaded', function() { // 獲取DOM元素 const video = document.getElementById('video'); const captureBtn = document.getElementById('capture-btn'); const resetBtn = document.getElementById('reset-btn'); const mergeBtn = document.getElementById('merge-btn'); const downloadBtn = document.getElementById('download-btn'); const photosContainer = document.getElementById('photos-container'); const resultImg = document.getElementById('result'); const resultPlaceholder = document.getElementById('result-placeholder'); const flash = document.getElementById('flash'); const cameraPlaceholder = document.getElementById('camera-placeholder'); // 存儲(chǔ)拍攝的照片 let capturedPhotos = []; // 訪問攝像頭 async function initCamera() { try { const stream = await navigator.mediaDevices.getUserMedia({ video: { width: { ideal: 1280 }, height: { ideal: 720 }, facingMode: 'user' } }); video.srcObject = stream; cameraPlaceholder.style.display = 'none'; } catch (err) { console.error("攝像頭訪問錯(cuò)誤:", err); cameraPlaceholder.innerHTML = `<p style="color:red;">攝像頭訪問失敗: ${err.message}</p>`; } } // 拍照功能 function capturePhoto() { const canvas = document.createElement('canvas'); canvas.width = video.videoWidth; canvas.height = video.videoHeight; const ctx = canvas.getContext('2d'); ctx.drawImage(video, 0, 0, canvas.width, canvas.height); // 添加閃光效果 flash.style.animation = 'flash 0.5s'; setTimeout(() => { flash.style.animation = ''; }, 500); // 生成數(shù)據(jù)URL const dataURL = canvas.toDataURL('image/png'); // 添加到照片數(shù)組 capturedPhotos.push(dataURL); // 更新照片縮略圖 updatePhotoThumbnails(); } // 更新照片縮略圖 function updatePhotoThumbnails() { if (capturedPhotos.length === 0) { photosContainer.innerHTML = ` <div> <i class="fas fa-image"></i> <p>尚未拍攝照片</p> </div> `; return; } photosContainer.innerHTML = ''; capturedPhotos.forEach((photo, index) => { const thumbDiv = document.createElement('div'); thumbDiv.className = 'photo-thumb'; const img = document.createElement('img'); img.src = photo; img.alt = `照片 ${index + 1}`; const removeBtn = document.createElement('div'); removeBtn.className = 'remove'; removeBtn.innerHTML = '<i class="fas fa-times"></i>'; removeBtn.onclick = () => removePhoto(index); thumbDiv.appendChild(img); thumbDiv.appendChild(removeBtn); photosContainer.appendChild(thumbDiv); }); } // 移除照片 function removePhoto(index) { capturedPhotos.splice(index, 1); updatePhotoThumbnails(); } // 重置所有照片 function resetPhotos() { if (capturedPhotos.length === 0) return; if (confirm('確定要?jiǎng)h除所有照片嗎?')) { capturedPhotos = []; updatePhotoThumbnails(); resultImg.style.display = 'none'; resultPlaceholder.style.display = 'flex'; } } // 合并照片 function mergePhotos() { if (capturedPhotos.length === 0) { alert('請(qǐng)先拍攝至少一張照片!'); return; } // 計(jì)算畫布尺寸 const maxWidth = 800; const thumbWidth = Math.floor(maxWidth / Math.min(capturedPhotos.length, 4)); const thumbHeight = Math.floor(thumbWidth * 0.75); const rows = Math.ceil(capturedPhotos.length / 4); const canvas = document.createElement('canvas'); canvas.width = Math.min(capturedPhotos.length, 4) * thumbWidth; canvas.height = rows * thumbHeight; const ctx = canvas.getContext('2d'); ctx.fillStyle = '#ffffff'; ctx.fillRect(0, 0, canvas.width, canvas.height); // 繪制所有照片 let col = 0, row = 0; for (let i = 0; i < capturedPhotos.length; i++) { const img = new Image(); img.src = capturedPhotos[i]; ctx.drawImage( img, col * thumbWidth, row * thumbHeight, thumbWidth, thumbHeight ); // 繪制照片邊框 ctx.strokeStyle = 'rgba(142, 45, 226, 0.5)'; ctx.lineWidth = 2; ctx.strokeRect( col * thumbWidth, row * thumbHeight, thumbWidth, thumbHeight ); col++; if (col >= 4) { col = 0; row++; } } // 添加標(biāo)題 ctx.fillStyle = '#4a00e0'; ctx.font = 'bold 24px Arial'; ctx.textAlign = 'center'; ctx.fillText('美好回憶', canvas.width / 2, 30); // 添加日期 const now = new Date(); const dateStr = now.toLocaleDateString(); ctx.fillStyle = '#6c757d'; ctx.font = '16px Arial'; ctx.fillText(dateStr, canvas.width / 2, canvas.height - 20); // 顯示結(jié)果 resultImg.src = canvas.toDataURL('image/png'); resultImg.style.display = 'block'; resultPlaceholder.style.display = 'none'; } // 下載合影 function downloadResult() { if (!resultImg.src || resultImg.style.display === 'none') { alert('請(qǐng)先合并照片!'); return; } const link = document.createElement('a'); link.download = `合影-${new Date().getTime()}.png`; link.href = resultImg.src; link.click(); } // 事件監(jiān)聽 captureBtn.addEventListener('click', capturePhoto); resetBtn.addEventListener('click', resetPhotos); mergeBtn.addEventListener('click', mergePhotos); downloadBtn.addEventListener('click', downloadResult); // 初始化攝像頭 initCamera(); // 初始更新縮略圖 updatePhotoThumbnails(); }); </script> </body> </html>
功能說明
這個(gè)合影工具具有以下功能:
攝像頭訪問:請(qǐng)求用戶攝像頭權(quán)限并顯示實(shí)時(shí)視頻
拍照功能:點(diǎn)擊"拍照"按鈕可捕獲當(dāng)前視頻幀為照片
照片管理:
顯示所有拍攝照片的縮略圖
可刪除單張照片
可重置所有照片
合影制作:
將多張照片合并為一張合影
自動(dòng)排列照片(每行最多4張)
添加標(biāo)題和日期水印
下載功能:將合并后的合影下載為PNG圖片
使用說明
打開頁(yè)面后允許瀏覽器訪問您的攝像頭
調(diào)整位置,點(diǎn)擊"拍照"按鈕拍攝照片
可拍攝多張照片(顯示在下方區(qū)域)
點(diǎn)擊"合并照片"按鈕創(chuàng)建合影
點(diǎn)擊"下載合影"保存最終圖片
這個(gè)應(yīng)用使用了現(xiàn)代Web API(getUserMedia, Canvas)實(shí)現(xiàn),不需要任何外部庫(kù),在大多數(shù)現(xiàn)代瀏覽器中都能正常工作。
注意:由于安全限制,該頁(yè)面需要在HTTPS環(huán)境或localhost下運(yùn)行才能訪問攝像頭。