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

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

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

เริ่มต้นแบบง่ายและค่อยเป็นค่อยไป

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

ขอ 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 วิธี

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

<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 คืออะไร

FileList คล้ายกับ Array ตัวแปรนี้มีคีย์ตัวเลขและพร็อพเพอร์ตี้ length แต่ไม่ได้เป็นอาร์เรย์ ไม่มีเมธอดอาร์เรย์ เช่น forEach() หรือ pop() และไม่สามารถวนซ้ำได้ แน่นอนว่าคุณจะได้รับอาร์เรย์จริงโดยใช้ 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() คุณต้องส่งออบเจ็กต์ที่อธิบายประเภทสื่อที่ต้องการ ตัวเลือกเหล่านี้เรียกว่าข้อจำกัด มีข้อจำกัดหลายประการที่อาจเกิดขึ้น ซึ่งครอบคลุมถึงเรื่องต่างๆ เช่น คุณต้องการใช้กล้องหน้าหรือกล้องหลัง ต้องการเสียงไหม และความละเอียดที่ต้องการสำหรับสตรีม

แต่หากต้องการข้อมูลจากกล้อง คุณต้องใช้ข้อจำกัดเพียงข้อเดียวเท่านั้น ซึ่งก็คือ video: true

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

<video id="player" controls playsinline 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. สร้างออบเจ็กต์ภาพพิมพ์แคนวาสที่จะยึดเฟรมจากกล้อง
  2. รับสิทธิ์เข้าถึงสตรีมจากกล้อง
  3. แนบกับองค์ประกอบวิดีโอ
  4. เมื่อต้องการจับเฟรมที่แม่นยำ ให้เพิ่มข้อมูลจากองค์ประกอบวิดีโอไปยังออบเจ็กต์แคนวาสโดยใช้ drawImage()
<video id="player" controls playsinline 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>

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

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

เคล็ดลับ

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

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

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

<video id="player" controls playsinline 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() เบราว์เซอร์จะแจ้งให้ผู้ใช้ให้สิทธิ์เข้าถึงกล้องแก่เว็บไซต์

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

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

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

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

ความคิดเห็น