Day19 是要操作照相機來拍照,甚至可以加上濾鏡
const videoRef = useRef<HTMLVideoElement>(null);
const canvasRef = useRef<HTMLCanvasElement>(null);
const stripRef = useRef<HTMLDivElement>(null);
const snapSound = useRef<HTMLAudioElement>(null);
const [isCameraOn, setIsCameraOn] = useState<boolean>(false);
const [stream, setStream] = useState<MediaStream | null>(null);
const paintToCanvas = (): (() => void) => {
const video = videoRef.current;
const canvas = canvasRef.current;
if (!video || !canvas) return () => {};
const ctx = canvas.getContext("2d");
if (!ctx) return () => {};
const width = 320;
const height = 240;
canvas.width = width;
canvas.height = height;
const interval = setInterval(() => {
if (video.readyState === video.HAVE_ENOUGH_DATA) {
ctx.drawImage(video, 0, 0, width, height);
let pixels = ctx.getImageData(0, 0, width, height);
ctx.putImageData(pixels, 0, 0);
}
}, 16);
return () => clearInterval(interval);
};
const startCamera = (): void => {
navigator.mediaDevices
.getUserMedia({ video: true })
.then((stream: MediaStream) => {
const video = videoRef.current;
if (video) {
video.srcObject = stream;
video.play();
}
setStream(stream);
setIsCameraOn(true);
paintToCanvas();
})
.catch((err: Error) => {
console.error("Error accessing the webcam", err);
});
};
const stopCamera = (): void => {
if (stream) {
stream.getTracks().forEach((track: MediaStreamTrack) => track.stop());
setStream(null);
}
const video = videoRef.current;
if (video) {
video.srcObject = null;
}
setIsCameraOn(false);
};
const toggleCamera = useCallback(() => {
if (isCameraOn) {
stopCamera();
} else {
startCamera();
}
}, [isCameraOn]);
const takePhoto = (): void => {
const sound = snapSound.current;
if (sound) {
sound.currentTime = 0;
sound.play();
}
const canvas = canvasRef.current;
if (!canvas) {
return;
}
const data = canvas.toDataURL("image/jpeg");
const link = document.createElement("a");
link.href = data;
link.setAttribute("download", "people");
link.innerHTML = `<img src="${data}" alt="Handsome Man" class="w-24 p-2 pb-6 shadow bg-white" style="transform: rotate(${
Math.random() * 20 - 10
}deg);" />`;
const strip = stripRef.current;
if (strip) {
strip.insertBefore(link, strip.firstChild);
}
};
return (
<div className="min-h-screen bg-yellow-400 py-8 px-4 text-xs">
<div className="max-w-sm mx-auto bg-white rounded-sm shadow-lg overflow-hidden relative">
<div className="p-4 space-y-4">
<button
type="button"
onClick={toggleCamera}
className="px-4 py-2 mr-2 bg-green-500 text-white rounded hover:bg-green-600 transition"
>
{isCameraOn ? "Turn Off Camera" : "Turn On Camera"}
</button>
<button
type="button"
onClick={takePhoto}
className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition"
disabled={!isCameraOn}
>
Take Photo
</button>
<div className="space-y-2">
{["Red", "Green", "Blue"].map((color) => (
<div key={color} className="flex items-center space-x-2">
<label htmlFor={`${color.toLowerCase()}min`} className="w-24">
{color} Min:
</label>
<input
type="range"
min="0"
max="255"
name={`${color.toLowerCase()}min`}
className="flex-grow"
/>
<label htmlFor={`${color.toLowerCase()}max`} className="w-24">
{color} Max:
</label>
<input
type="range"
min="0"
max="255"
name={`${color.toLowerCase()}max`}
className="flex-grow"
/>
</div>
))}
</div>
</div>
<canvas ref={canvasRef} className="w-full"></canvas>
<video
ref={videoRef}
className="absolute bottom-5 right-5 w-40"
></video>
<div ref={stripRef} className="p-8 overflow-x-auto whitespace-nowrap">
{/* Photos will be inserted here */}
</div>
</div>
<audio
ref={snapSound}
src="https://github.com/wesbos/JavaScript30/raw/refs/heads/master/19%20-%20Webcam%20Fun/snap.mp3"
hidden
></audio>
</div>
);