UNPKG

easyinspection-mask

Version:

Web Components para captura de fotos com máscaras SVG do EasyInspection

389 lines (335 loc) 12.3 kB
// Importação dinâmica dos SVGs import square from "./masks/square.svg"; import chassiAndMotorMask from "./masks/chassi-and-motor.svg"; import odometerMask from "./masks/odometer.svg"; import smallVehicleFrontMask from "./masks/small-vehicle-front.svg"; import smallVehicleSideLeftMask from "./masks/small-vehicle-side-left.svg"; import smallVehicleSideRightMask from "./masks/small-vehicle-side-right.svg"; import motocycleFrontMask from "./masks/motocycle-front.svg"; import motocycleSideLeftMask from "./masks/motocycle-side-left.svg"; import motocycleSideRightMask from "./masks/motocycle-side-right.svg"; import bigVehicleFrontMask from "./masks/big-vehicle-front.svg"; import busSideLeftMask from "./masks/bus-side-left.svg"; import busideRightMask from "./masks/bus-side-right.svg"; import truckSideLeftMask from "./masks/truck-side-left.svg"; import truckideRightMask from "./masks/truck-side-right.svg"; export default function captureFullscreenPhoto(options = {}) { const { maskName = "small-vehicle-front", width = "80%", marginSize = 20, styles = {}, labels = { captureButton: "Capturar", cancelButton: "Cancelar", }, } = options; return new Promise((resolve, reject) => { // Criar o container fullscreen const container = document.createElement("div"); container.className = "fullscreen-camera-container"; // Aplicar estilos ao container Object.assign(container.style, { position: "fixed", top: "0", left: "0", width: "100%", height: "100%", backgroundColor: "#000", zIndex: "9999", display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", overflow: "hidden", ...styles.container, }); // Criar o elemento de vídeo const video = document.createElement("video"); video.autoplay = true; video.playsInline = true; // Aplicar estilos ao vídeo Object.assign(video.style, { width: "100%", height: "100%", objectFit: "cover", ...styles.video, }); // Criar o container da máscara const maskContainer = document.createElement("div"); maskContainer.className = "mask-container"; // Aplicar estilos ao container da máscara Object.assign(maskContainer.style, { position: "absolute", top: "0", left: "0", width: "100%", height: "100%", pointerEvents: "none", ...styles.maskContainer, }); // Criar o elemento da máscara const maskElement = document.createElement("div"); maskElement.className = "mask-element"; // Aplicar estilos à máscara Object.assign(maskElement.style, { width: "100%", height: "100%", ...styles.maskElement, }); // Adicionar a máscara ao container maskContainer.appendChild(maskElement); maskElement.appendChild(video); // Criar o container dos botões const buttonsContainer = document.createElement("div"); buttonsContainer.className = "buttons-container"; // Aplicar estilos ao container dos botões Object.assign(buttonsContainer.style, { position: "absolute", bottom: "30px", left: "0", width: "100%", display: "flex", justifyContent: "center", gap: "20px", padding: "0 20px", boxSizing: "border-box", zIndex: "10", ...styles.buttonsContainer, }); // Criar o botão de cancelar const cancelButton = document.createElement("button"); cancelButton.textContent = labels.cancelButton; // Aplicar estilos ao botão de cancelar Object.assign(cancelButton.style, { padding: "12px 24px", backgroundColor: "rgba(255, 255, 255, 0.3)", color: "white", border: "none", borderRadius: "50px", fontSize: "16px", cursor: "pointer", ...styles.cancelButton, }); // Adicionar evento de clique ao botão de cancelar cancelButton.addEventListener("click", () => { cleanup(); reject(new Error("Captura cancelada pelo usuário")); }); // Criar o botão de captura const captureButton = document.createElement("button"); captureButton.className = "capture-button"; // Aplicar estilos ao botão de captura Object.assign(captureButton.style, { width: "70px", height: "70px", backgroundColor: "white", border: "3px solid rgba(255, 255, 255, 0.5)", borderRadius: "50%", cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center", ...styles.captureButton, }); // Criar o círculo interno do botão de captura const captureButtonInner = document.createElement("div"); // Aplicar estilos ao círculo interno Object.assign(captureButtonInner.style, { width: "54px", height: "54px", backgroundColor: "#f44336", borderRadius: "50%", ...styles.captureButtonInner, }); captureButton.appendChild(captureButtonInner); // Adicionar evento de clique ao botão de captura captureButton.addEventListener("click", () => { const photo = capturePhoto(); cleanup(); resolve(photo); }); // Adicionar os botões ao container buttonsContainer.appendChild(cancelButton); buttonsContainer.appendChild(captureButton); // Criar o canvas (oculto) const canvas = document.createElement("canvas"); canvas.style.display = "none"; // Montar a estrutura // container.appendChild(video); container.appendChild(maskContainer); container.appendChild(buttonsContainer); container.appendChild(canvas); // Adicionar ao DOM document.body.appendChild(container); // Iniciar a câmera let stream = null; let maskDimensions = { x: 0, y: 0, width: 0, height: 0 }; // Função para aplicar a máscara SVG function applyMask() { const maskPath = getMaskPath(maskName); const squareMaskPath = getMaskPath("square"); // Aplicar a máscara usando CSS maskElement.style.maskImage = `url('${squareMaskPath}'), url('${maskPath}')`; maskElement.style.maskSize = `cover, ${width}`; maskElement.style.maskRepeat = "no-repeat"; maskElement.style.maskPosition = "center, center"; maskElement.style.maskComposite = "subtract"; // Para navegadores WebKit maskElement.style.webkitMaskImage = `url('${squareMaskPath}'), url('${maskPath}')`; maskElement.style.webkitMaskSize = `cover, ${width}`; maskElement.style.webkitMaskRepeat = "no-repeat"; maskElement.style.webkitMaskPosition = "center, center"; maskElement.style.webkitMaskComposite = "source-out"; // Calcular as dimensões da máscara calculateMaskDimensions(); } // Função para calcular as dimensões da máscara function calculateMaskDimensions() { const rect = maskContainer.getBoundingClientRect(); // Diferentes máscaras podem ter diferentes proporções let maskWidthRatio = 0.7; // Padrão let maskHeightRatio = 0.7; // Padrão // Ajustar as proporções com base no tipo de máscara if (maskName.includes("vehicle")) { if (maskName.includes("front")) { maskWidthRatio = 0.8; maskHeightRatio = 0.6; } else if (maskName.includes("side")) { maskWidthRatio = 0.9; maskHeightRatio = 0.5; } } else if (maskName.includes("motocycle")) { maskWidthRatio = 0.6; maskHeightRatio = 0.8; } else if (maskName === "odometer") { maskWidthRatio = 0.5; maskHeightRatio = 0.3; } else if (maskName === "chassi-motor") { maskWidthRatio = 0.7; maskHeightRatio = 0.4; } const maskWidth = rect.width * maskWidthRatio; const maskHeight = rect.height * maskHeightRatio; maskDimensions = { x: (rect.width - maskWidth) / 2, y: (rect.height - maskHeight) / 2, width: maskWidth, height: maskHeight, containerWidth: rect.width, containerHeight: rect.height, }; } // Função para capturar a foto function capturePhoto() { if (!stream) return null; // Configurar o canvas com as dimensões do vídeo canvas.width = video.videoWidth; canvas.height = video.videoHeight; const ctx = canvas.getContext("2d"); // Desenhar o frame atual do vídeo no canvas (sem a máscara) ctx.drawImage(video, 0, 0, canvas.width, canvas.height); // Calcular as dimensões para o recorte const videoAspectRatio = video.videoWidth / video.videoHeight; const containerAspectRatio = maskDimensions.containerWidth / maskDimensions.containerHeight; let cropX, cropY, cropWidth, cropHeight; if (videoAspectRatio > containerAspectRatio) { // Vídeo mais largo que o contêiner const scale = video.videoHeight / maskDimensions.containerHeight; cropWidth = maskDimensions.width * scale; cropHeight = maskDimensions.height * scale; cropX = (video.videoWidth - maskDimensions.containerWidth * scale) / 2 + maskDimensions.x * scale - marginSize; cropY = maskDimensions.y * scale - marginSize; } else { // Vídeo mais alto que o contêiner const scale = video.videoWidth / maskDimensions.containerWidth; cropWidth = maskDimensions.width * scale; cropHeight = maskDimensions.height * scale; cropX = maskDimensions.x * scale - marginSize; cropY = (video.videoHeight - maskDimensions.containerHeight * scale) / 2 + maskDimensions.y * scale - marginSize; } // Ajustar para incluir a margem cropWidth += marginSize * 2; cropHeight += marginSize * 2; // Garantir que as dimensões de recorte estejam dentro dos limites do canvas cropX = Math.max(0, cropX); cropY = Math.max(0, cropY); cropWidth = Math.min(canvas.width - cropX, cropWidth); cropHeight = Math.min(canvas.height - cropY, cropHeight); // Criar um novo canvas para a imagem recortada const croppedCanvas = document.createElement("canvas"); croppedCanvas.width = cropWidth; croppedCanvas.height = cropHeight; const croppedCtx = croppedCanvas.getContext("2d"); croppedCtx.drawImage( canvas, cropX, cropY, cropWidth, cropHeight, 0, 0, cropWidth, cropHeight ); // Converter para base64 return croppedCanvas.toDataURL("image/jpeg"); } // Função para obter o caminho da máscara function getMaskPath(maskName) { const maskMap = { square: square, "chassi-motor": chassiAndMotorMask, odometer: odometerMask, "small-vehicle-front": smallVehicleFrontMask, "small-vehicle-side-left": smallVehicleSideLeftMask, "small-vehicle-side-right": smallVehicleSideRightMask, "motocycle-front": motocycleFrontMask, "motocycle-side-left": motocycleSideLeftMask, "motocycle-side-right": motocycleSideRightMask, "big-vehicle-front": bigVehicleFrontMask, "bus-side-left": busSideLeftMask, "bus-side-right": busideRightMask, "truck-side-left": truckSideLeftMask, "truck-side-right": truckideRightMask, }; return maskMap[maskName] || ""; } // Função para limpar recursos function cleanup() { if (stream) { stream.getTracks().forEach((track) => track.stop()); stream = null; } if (container && container.parentNode) { container.parentNode.removeChild(container); } } // Iniciar a câmera navigator.mediaDevices .getUserMedia({ video: { facingMode: "environment" }, audio: false, }) .then((mediaStream) => { stream = mediaStream; video.srcObject = stream; // Quando o vídeo estiver pronto video.onloadedmetadata = () => { video.play(); applyMask(); }; }) .catch((error) => { console.error("Erro ao acessar a câmera:", error); reject(error); }); }); }