easyinspection-mask
Version:
Web Components para captura de fotos com máscaras SVG do EasyInspection
389 lines (335 loc) • 12.3 kB
JavaScript
// 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);
});
});
}