เบราว์เซอร์ส่วนใหญ่เข้าถึงกล้องของผู้ใช้ได้
ปัจจุบันเบราว์เซอร์หลายรุ่นสามารถเข้าถึงอินพุตวิดีโอและเสียงจากผู้ใช้ได้แล้ว อย่างไรก็ตาม ประสบการณ์การใช้งานอาจเป็นแบบไดนามิกและแสดงในหน้าเว็บอย่างเต็มรูปแบบ หรืออาจมอบสิทธิ์ให้แอปอื่นในอุปกรณ์ของผู้ใช้ ทั้งนี้ขึ้นอยู่กับเบราว์เซอร์ นอกจากนี้ อุปกรณ์บางเครื่องก็ไม่มีกล้องด้วย คุณจะสร้างประสบการณ์ที่ใช้รูปภาพที่ผู้ใช้สร้างขึ้นซึ่งใช้งานได้ทุกที่ได้อย่างไร
เริ่มต้นแบบง่ายและค่อยเป็นค่อยไป
หากต้องการปรับปรุงประสบการณ์การใช้งานให้ดียิ่งขึ้นเรื่อยๆ คุณต้องเริ่มต้นด้วยสิ่งที่ใช้งานได้ทุกที่ สิ่งที่ง่ายที่สุดคือขอให้ผู้ใช้ส่งไฟล์ที่บันทึกไว้ล่วงหน้า
ขอ URL
ตัวเลือกนี้รองรับได้ดีที่สุดแต่ให้ประสบการณ์การใช้งานน้อยที่สุด ขอให้ผู้ใช้ระบุ URL แล้วใช้ URL นั้น สำหรับการแค่แสดงรูปภาพ การดำเนินการนี้จะใช้งานได้ทุกที่ สร้างองค์ประกอบ img
ตั้งค่า src
แล้วเสร็จ
อย่างไรก็ตาม หากต้องการดัดแปลงรูปภาพด้วยวิธีใดก็ตาม กระบวนการจะซับซ้อนขึ้นเล็กน้อย CORS ป้องกันไม่ให้คุณเข้าถึงพิกเซลจริง เว้นแต่เซิร์ฟเวอร์จะตั้งค่าส่วนหัวที่เหมาะสมและคุณทําเครื่องหมายรูปภาพเป็น crossorigin วิธีแก้ปัญหาที่ใช้งานได้จริงเพียงวิธีเดียวคือการใช้พร็อกซีเซิร์ฟเวอร์
การป้อนไฟล์
นอกจากนี้ คุณยังใช้องค์ประกอบอินพุตไฟล์แบบง่ายได้ ซึ่งรวมถึงตัวกรอง accept
ที่ระบุว่าคุณต้องการเฉพาะไฟล์รูปภาพ
<input type="file" accept="image/*" />
วิธีนี้ใช้งานได้ในทุกแพลตฟอร์ม ส่วนในเดสก์ท็อป การแจ้งเตือนจะแจ้งให้ผู้ใช้ อัปโหลดไฟล์รูปภาพจากระบบไฟล์ ใน Chrome และ Safari บน iOS และ Android วิธีการนี้จะให้ผู้ใช้เลือกแอปที่จะใช้จับภาพ รวมถึงตัวเลือกในการถ่ายภาพด้วยกล้องโดยตรงหรือเลือกไฟล์รูปภาพที่มีอยู่
จากนั้นจะแนบข้อมูลดังกล่าวกับ <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.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 คุณจึงต้องใช้วิธีแฮ็กเล็กน้อยเพื่อจับภาพจากกล้องของผู้ใช้
ขั้นตอนมีดังนี้
- สร้างออบเจ็กต์ภาพพิมพ์แคนวาสที่จะเก็บเฟรมจากกล้อง
- รับสิทธิ์เข้าถึงสตรีมกล้อง
- แนบกับองค์ประกอบวิดีโอ
- เมื่อต้องการจับเฟรมที่แม่นยำ ให้เพิ่มข้อมูลจากองค์ประกอบวิดีโอไปยังออบเจ็กต์แคนวาสโดยใช้
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>
เมื่อมีข้อมูลจากกล้องที่เก็บอยู่ในแคนวาสแล้ว คุณสามารถ ทำอะไรได้มากมาย การดำเนินการที่คุณทำได้มีดังนี้
- อัปโหลดไปยังเซิร์ฟเวอร์โดยตรง
- จัดเก็บในเครื่อง
- ใช้เอฟเฟ็กต์แปลกๆ กับรูปภาพ
เคล็ดลับ
หยุดสตรีมจากกล้องเมื่อไม่จำเป็น
คุณควรหยุดใช้กล้องเมื่อไม่ต้องการแล้ว ซึ่งไม่เพียงช่วยประหยัดแบตเตอรี่และกำลังประมวลผลเท่านั้น แต่ยังช่วยให้ผู้ใช้มั่นใจในการใช้งานแอปพลิเคชันของคุณด้วย
หากต้องการหยุดการเข้าถึงกล้อง ให้เรียกใช้ 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 และความแตกต่างของคำนำหน้า