การจับภาพจากผู้ใช้

เบราว์เซอร์ส่วนใหญ่เข้าถึงกล้องของผู้ใช้ได้

เครื่องชั่งน้ำหนักเสื่อ

ขณะนี้เบราว์เซอร์จำนวนมากสามารถเข้าถึงอินพุตวิดีโอและเสียงจากผู้ใช้ได้แล้ว อย่างไรก็ตาม เบราว์เซอร์อาจเป็นประสบการณ์แบบอินไลน์แบบไดนามิกและแบบอินไลน์ หรืออาจมีการมอบสิทธิ์ให้กับแอปอื่นในอุปกรณ์ของผู้ใช้ ทั้งนี้ขึ้นอยู่กับเบราว์เซอร์ ยิ่งไปกว่านั้น อุปกรณ์บางเครื่องไม่มีกล้องด้วย แล้วคุณจะสร้างประสบการณ์ ที่ใช้รูปภาพที่ผู้ใช้สร้างขึ้นและทำงานได้ดีในทุกที่ได้อย่างไร

เริ่มต้นอย่างง่ายๆ และค่อยๆ เติบโต

หากต้องการปรับปรุงประสบการณ์การใช้งานให้ก้าวหน้าขึ้น คุณต้องเริ่มต้นด้วยสิ่งที่สามารถใช้งานได้ในทุกที่ วิธีที่ง่ายที่สุดคือขอไฟล์ที่บันทึกไว้ล่วงหน้าจากผู้ใช้

ขอ URL

ตัวเลือกนี้เป็นตัวเลือกที่ดีที่สุดที่รองรับแต่น่าพอใจน้อยที่สุด ให้ผู้ใช้ให้ URL แก่คุณ แล้วใช้ URL นั้น การแสดงรูปภาพเพียงอย่างเดียวจะใช้งานกับทุกที่ได้ สร้างองค์ประกอบ img ตั้งค่า src เท่านี้ก็เรียบร้อย

แต่ถ้าคุณต้องการปรับแต่งรูปภาพด้วยวิธีใดก็ตาม สถานการณ์จะซับซ้อนขึ้นเล็กน้อย CORS จะทำให้คุณเข้าถึงพิกเซลจริงไม่ได้ เว้นแต่ว่าเซิร์ฟเวอร์จะตั้งส่วนหัวที่เหมาะสมและคุณต้องทำเครื่องหมายรูปภาพเป็น Crossorigin วิธีเดียวที่ใช้ได้คือการเรียกใช้พร็อกซีเซิร์ฟเวอร์

อินพุตไฟล์

นอกจากนี้คุณยังใช้องค์ประกอบอินพุตไฟล์ง่ายๆ รวมถึงตัวกรอง accept ที่บอกว่าคุณต้องการเฉพาะไฟล์ภาพเท่านั้นก็ได้

<input type="file" accept="image/*" />

วิธีนี้ได้ผลในทุกแพลตฟอร์ม ในเดสก์ท็อปจะแจ้งให้ผู้ใช้อัปโหลดไฟล์รูปภาพจากระบบไฟล์ ใน Chrome และ Safari บน iOS และ Android วิธีนี้จะช่วยให้ผู้ใช้เลือกได้ว่าจะใช้แอปใดถ่ายภาพ ซึ่งรวมถึงตัวเลือกเพื่อถ่ายภาพโดยตรงด้วยกล้องหรือเลือกไฟล์ภาพที่มีอยู่

เมนู Android ที่มี 2 ตัวเลือก ได้แก่ การจับภาพและไฟล์ เมนู iOS ที่มี 3 ตัวเลือก ได้แก่ ถ่ายภาพ, คลังภาพ, iCloud

จากนั้นจะแนบข้อมูลกับ <form> หรือจัดการด้วย JavaScript ได้โดยฟังเหตุการณ์ onchange ในองค์ประกอบอินพุต จากนั้นอ่านพร็อพเพอร์ตี้ files ของเหตุการณ์ target

<input type="file" accept="image/*" id="file-input" />
<script>
  const fileInput = document.getElementById('file-input');

  fileInput.addEventListener('change', (e) =>
    doSomethingWithFiles(e.target.files),
  );
</script>

พร็อพเพอร์ตี้ files เป็นออบเจ็กต์ FileList ซึ่งฉันจะพูดถึงเพิ่มเติมในภายหลัง

คุณยังเพิ่มแอตทริบิวต์ capture ลงในองค์ประกอบได้ด้วย ซึ่งจะระบุให้เบราว์เซอร์ทราบว่าคุณต้องการใช้รูปภาพจากกล้อง

<input type="file" accept="image/*" capture />
<input type="file" accept="image/*" capture="user" />
<input type="file" accept="image/*" capture="environment" />

การเพิ่มแอตทริบิวต์ capture ที่ไม่มีค่าจะช่วยให้เบราว์เซอร์ตัดสินใจเลือกกล้องที่จะใช้ ส่วนค่า "user" และ "environment" จะบอกให้เบราว์เซอร์เลือกใช้กล้องหน้าและกล้องหลังตามลำดับ

แอตทริบิวต์ capture ใช้งานได้ใน Android และ iOS แต่จะไม่สนใจในเดสก์ท็อป อย่างไรก็ตาม โปรดทราบว่าบน Android นี่หมายความว่าผู้ใช้จะไม่มีตัวเลือกในการเลือกรูปภาพที่มีอยู่อีกต่อไป แอปกล้องของระบบจะเริ่มทำงานโดยตรงแทน

ลากและวาง

ถ้าคุณได้เพิ่มความสามารถในการอัปโหลดไฟล์อยู่แล้ว มี 2-3 วิธีง่ายๆ ที่คุณสามารถสร้างประสบการณ์ของผู้ใช้ที่สมบูรณ์ยิ่งขึ้น

วิธีแรกคือการเพิ่มเป้าหมายแบบเลื่อนลงลงในหน้าเว็บเพื่อให้ผู้ใช้ลากไฟล์จากเดสก์ท็อปหรือแอปพลิเคชันอื่นได้

<div id="target">You can drag an image file here</div>
<script>
  const target = document.getElementById('target');

  target.addEventListener('drop', (e) => {
    e.stopPropagation();
    e.preventDefault();

    doSomethingWithFiles(e.dataTransfer.files);
  });

  target.addEventListener('dragover', (e) => {
    e.stopPropagation();
    e.preventDefault();

    e.dataTransfer.dropEffect = 'copy';
  });
</script>

คุณสามารถรับออบเจ็กต์ FileList จากพร็อพเพอร์ตี้ dataTransfer.files ของเหตุการณ์ drop เช่นเดียวกับอินพุตไฟล์

ตัวแฮนเดิลเหตุการณ์ dragover ให้คุณส่งสัญญาณให้ผู้ใช้ทราบสิ่งที่จะเกิดขึ้นเมื่อวางไฟล์โดยใช้พร็อพเพอร์ตี้ dropEffect

การลากและวางได้รับการเปิดใช้งานมานานแล้วและได้รับการสนับสนุนเป็นอย่างดีจากเบราว์เซอร์หลักๆ

วางจากคลิปบอร์ด

วิธีสุดท้ายที่จะนำไฟล์ภาพที่มีอยู่มาจากคลิปบอร์ด โค้ดในโค้ดนี้เรียบง่ายมาก แต่ประสบการณ์ของผู้ใช้จะยากขึ้นเล็กน้อย

<textarea id="target">Paste an image here</textarea>
<script>
  const target = document.getElementById('target');

  target.addEventListener('paste', (e) => {
    e.preventDefault();
    doSomethingWithFiles(e.clipboardData.files);
  });
</script>

(e.clipboardData.files เป็นออบเจ็กต์ FileList อีกรายการ)

ส่วนที่ยากสำหรับ API คลิปบอร์ดก็คือ องค์ประกอบเป้าหมายต้องให้เลือกและแก้ไขได้อย่างเต็มรูปแบบเพื่อรองรับข้ามเบราว์เซอร์อย่างเต็มรูปแบบ ทั้ง <textarea> และ <input type="text"> อยู่ในใบเรียกเก็บเงินนี้ เช่นเดียวกับองค์ประกอบที่มีแอตทริบิวต์ contenteditable แต่ข้อความเหล่านี้ออกแบบมา สำหรับแก้ไขข้อความอย่างชัดเจน

การทำให้การดำเนินการนี้เป็นไปอย่างราบรื่นอาจทำได้ยาก หากคุณไม่ต้องการให้ผู้ใช้ป้อนข้อความได้ กลอุบาย เช่น การใส่ข้อมูลที่เลือกลงไปเมื่อคุณคลิกองค์ประกอบอื่นๆ อาจทำให้การช่วยเหลือพิเศษยากขึ้น

การจัดการออบเจ็กต์ FileList

เนื่องจากวิธีข้างต้นส่วนใหญ่สร้าง FileList เราจึงควรพูดถึงวิธีการดังกล่าวสักเล็กน้อย

FileList คล้ายกับ Array คีย์ดังกล่าวมีคีย์ตัวเลขและพร็อพเพอร์ตี้ length แต่ไม่ใช่อาร์เรย์จริง ไม่มีเมธอดอาร์เรย์ เช่น forEach() หรือ pop() และไม่สามารถทำซ้ำได้ แน่นอน คุณสามารถรับ Array จริงได้โดยใช้ Array.from(fileList)

รายการของ FileList คือ File ออบเจ็กต์ ออบเจ็กต์เหล่านี้เหมือนกับออบเจ็กต์ Blob ทุกประการ ยกเว้นว่าจะมีพร็อพเพอร์ตี้ name และ lastModified แบบอ่านอย่างเดียวเพิ่มเติม

<img id="output" />
<script>
  const output = document.getElementById('output');

  function doSomethingWithFiles(fileList) {
    let file = null;

    for (let i = 0; i < fileList.length; i++) {
      if (fileList[i].type.match(/^image\//)) {
        file = fileList[i];
        break;
      }
    }

    if (file !== null) {
      output.src = URL.createObjectURL(file);
    }
  }
</script>

ตัวอย่างนี้จะค้นหาไฟล์แรกที่มีประเภท MIME ของรูปภาพ แต่ก็สามารถจัดการรูปภาพหลายรูปที่เลือกไว้/วาง/วางได้พร้อมกัน

เมื่อเข้าถึงไฟล์แล้ว คุณจะทำอะไรกับไฟล์ได้ ตัวอย่างเช่น

  • วาดเป็นองค์ประกอบ <canvas> เพื่อให้คุณจัดการได้
  • ดาวน์โหลดลงในอุปกรณ์ของผู้ใช้
  • อัปโหลดไปยังเซิร์ฟเวอร์ด้วย fetch()

เข้าถึงกล้องแบบอินเทอร์แอกทีฟ

เมื่อจัดการฐานทัพเรียบร้อยแล้ว ก็ถึงเวลาปรับปรุงอย่างต่อเนื่อง

เบราว์เซอร์ที่ทันสมัยจะเข้าถึงกล้องได้โดยตรง ซึ่งทำให้คุณสามารถสร้างประสบการณ์ที่ผสานรวมกับหน้าเว็บโดยสมบูรณ์ ผู้ใช้จึงไม่ต้องออกจากเบราว์เซอร์เลย

รับสิทธิ์เข้าถึงกล้อง

คุณสามารถเข้าถึงกล้องและไมโครโฟนได้โดยตรงโดยใช้ API ตามข้อกำหนดของ WebRTC ที่ชื่อ getUserMedia() ซึ่งจะแจ้งให้ผู้ใช้ เข้าถึงไมโครโฟนและกล้องที่เชื่อมต่อ

การสนับสนุนสำหรับ getUserMedia() ค่อนข้างดีแต่ยังไม่ครอบคลุมทุกที่ กล่าวอย่างเจาะจงก็คือ ไม่มีใน Safari 10 หรือต่ำกว่า ซึ่งขณะที่เขียนเป็นเวอร์ชันล่าสุดที่มีความเสถียรอยู่ อย่างไรก็ตาม Apple ได้ประกาศว่า แอปจะพร้อมให้บริการใน Safari 11

แต่การตรวจหาการสนับสนุนนั้นง่ายมาก

const supported = 'mediaDevices' in navigator;

เมื่อเรียกใช้ getUserMedia() คุณต้องส่งผ่านออบเจ็กต์ที่อธิบายประเภทสื่อที่ต้องการ ตัวเลือกเหล่านี้เรียกว่า "ข้อจำกัด" มีข้อจำกัดที่เป็นไปได้หลายอย่างที่ครอบคลุมสิ่งต่างๆ เช่น คุณต้องการใช้กล้องหน้าหรือกล้องหลัง ต้องการเสียงแบบใด และความละเอียดที่ต้องการสำหรับสตรีม

อย่างไรก็ตาม หากต้องการรับข้อมูลจากกล้อง คุณต้องมีข้อจำกัดเพียง 1 ข้อ ซึ่งก็คือ video: true

หากทำสำเร็จ API จะแสดงผล MediaStream ที่มีข้อมูลจากกล้อง จากนั้นคุณจะแนบไว้กับองค์ประกอบ <video> แล้วเล่นเพื่อแสดงตัวอย่างแบบเรียลไทม์ หรือแนบไปยัง <canvas> เพื่อรับสแนปชอต

<video id="player" controls autoplay></video>
<script>
  const player = document.getElementById('player');

  const constraints = {
    video: true,
  };

  navigator.mediaDevices.getUserMedia(constraints).then((stream) => {
    player.srcObject = stream;
  });
</script>

โดยไม่ได้มีประโยชน์มากนัก ก็เพียงแค่นำข้อมูลวิดีโอมาเล่น หากต้องการได้รูปภาพ คุณจะต้องทำงานเพิ่มเติมเล็กน้อย

จับภาพสแนปชอต

ตัวเลือกที่ดีที่สุดสำหรับการสร้างรูปภาพคือการวาดกรอบจากวิดีโอลงในผืนผ้าใบ

ไม่มี API การประมวลผลสตรีมสำหรับวิดีโอบนเว็บโดยเฉพาะ ซึ่งแตกต่างจาก Web Audio API คุณจึงต้องใช้การแฮ็กเล็กๆ น้อยๆ เพื่อจับภาพจากกล้องของผู้ใช้

โดยมีขั้นตอนดังนี้

  1. สร้างวัตถุบน Canvas ที่จะหยุดเฟรมจากกล้อง
  2. รับสิทธิ์เข้าถึงสตรีมจากกล้อง
  3. แนบกับองค์ประกอบวิดีโอ
  4. เมื่อต้องการจับภาพเฟรมที่แน่นอน ให้เพิ่มข้อมูลจากองค์ประกอบวิดีโอลงในวัตถุ Canvas โดยใช้ drawImage()
<video id="player" controls autoplay></video>
<button id="capture">Capture</button>
<canvas id="canvas" width="320" height="240"></canvas>
<script>
  const player = document.getElementById('player');
  const canvas = document.getElementById('canvas');
  const context = canvas.getContext('2d');
  const captureButton = document.getElementById('capture');

  const constraints = {
    video: true,
  };

  captureButton.addEventListener('click', () => {
    // Draw the video frame to the canvas.
    context.drawImage(player, 0, 0, canvas.width, canvas.height);
  });

  // Attach the video stream to the video element and autoplay.
  navigator.mediaDevices.getUserMedia(constraints).then((stream) => {
    player.srcObject = stream;
  });
</script>

เมื่อมีข้อมูลจากกล้องที่จัดเก็บไว้ในผืนผ้าใบแล้ว คุณก็สามารถทำสิ่งต่างๆ ได้มากมาย การดำเนินการที่คุณทำได้มีดังนี้

  • อัปโหลดไปยังเซิร์ฟเวอร์โดยตรง
  • จัดเก็บไว้ในเครื่อง
  • ใช้เอฟเฟ็กต์สุดเก๋กับรูปภาพ

จากคนวงใน

หยุดการสตรีมจากกล้องเมื่อไม่จำเป็น

คุณควรหยุดใช้กล้องเมื่อไม่ต้องการใช้งานแล้ว ซึ่งนอกจากจะช่วยประหยัดแบตเตอรี่และเพิ่มประสิทธิภาพในการประมวลผลแล้ว ยังช่วยให้ผู้ใช้มีความมั่นใจในแอปพลิเคชันของคุณอีกด้วย

หากต้องการหยุดเข้าถึงกล้อง ก็แค่เรียกใช้ stop() ในแต่ละแทร็กวิดีโอสำหรับสตรีมที่ getUserMedia() แสดงผล

<video id="player" controls autoplay></video>
<button id="capture">Capture</button>
<canvas id="canvas" width="320" height="240"></canvas>
<script>
  const player = document.getElementById('player');
  const canvas = document.getElementById('canvas');
  const context = canvas.getContext('2d');
  const captureButton = document.getElementById('capture');

  const constraints = {
    video: true,
  };

  captureButton.addEventListener('click', () => {
    context.drawImage(player, 0, 0, canvas.width, canvas.height);

    // Stop all video streams.
    player.srcObject.getVideoTracks().forEach(track => track.stop());
  });

  navigator.mediaDevices.getUserMedia(constraints).then((stream) => {
    // Attach the video stream to the video element and autoplay.
    player.srcObject = stream;
  });
</script>

ขอสิทธิ์ใช้กล้องอย่างมีความรับผิดชอบ

หากผู้ใช้ยังไม่เคยให้สิทธิ์เว็บไซต์เข้าถึงกล้องถ่ายรูป เมื่อคุณเรียกใช้ getUserMedia() เบราว์เซอร์จะแจ้งให้ผู้ใช้ให้สิทธิ์กล้องแก่เว็บไซต์

ผู้ใช้ไม่ชอบการได้รับแจ้งให้เข้าถึงอุปกรณ์ที่มีประสิทธิภาพในเครื่องของตน และมักจะบล็อกคำขอ หรืออาจจะเพิกเฉยต่อคำขอหากไม่เข้าใจบริบทของข้อความแจ้งที่สร้างขึ้น แนวทางปฏิบัติแนะนำคือ ขอเข้าถึงกล้องในครั้งแรกที่จำเป็นเท่านั้น เมื่อผู้ใช้ให้สิทธิ์เข้าถึงแล้ว ระบบจะไม่ถามอีก แต่หากผู้ใช้ปฏิเสธการเข้าถึง คุณจะไม่สามารถเข้าถึงอีกได้ เว้นแต่ผู้ใช้จะเปลี่ยนการตั้งค่าสิทธิ์กล้องด้วยตนเอง

ความเข้ากันได้

ข้อมูลเพิ่มเติมเกี่ยวกับการใช้งานเบราว์เซอร์บนอุปกรณ์เคลื่อนที่และเดสก์ท็อป

นอกจากนี้ เราขอแนะนำให้ใช้ Shim adapter.js เพื่อปกป้องแอปจากการเปลี่ยนแปลงข้อมูลจำเพาะของ WebRTC และความแตกต่างของคำนำหน้าด้วย

ความคิดเห็น