iT邦幫忙

2024 iThome 鐵人賽

DAY 6
0

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>
  );

DEMO

https://codesandbox.io/p/devbox/day19-webcam-fun-mxrldn


上一篇
[Day18]_Adding-Up-Times-with-Reduce
下一篇
[Day20]_Speech-Detection
系列文
React30——用 React 探索 JavaScript30 的魅力30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言