לרוב הדפדפנים יש גישה למצלמה של המשתמש.
בדפדפנים רבים יש עכשיו אפשרות לגשת לקלט וידאו ואודיו מהמשתמש. עם זאת, בהתאם לדפדפן, יכול להיות שמדובר בחוויה דינמית מלאה בתוך הדף, או שהיא יכולה להיות מועברת לאפליקציה אחרת במכשיר של המשתמש. בנוסף, לא לכל מכשיר יש מצלמה. אז איך אפשר ליצור חוויה שמשתמשת בתמונה שנוצרה על ידי משתמש ותעבוד טוב בכל מקום?
מתחילים בפשטות ובאופן הדרגתי
אם אתם רוצים לשפר את חוויית המשתמש בהדרגה, עליכם להתחיל עם משהו שעובד בכל מקום. הדרך הקלה ביותר היא פשוט לבקש מהמשתמש קובץ שהוקלט מראש.
בקשה לכתובת URL
זו האפשרות עם התמיכה הטובה ביותר, אבל היא פחות משביעת רצון. מבקשים מהמשתמש לתת לכם כתובת URL, ואז משתמשים בה. אם רוצים רק להציג את התמונה, הקוד הזה עובד בכל מקום. יוצרים אלמנט img
, מגדירים את src
וזהו.
עם זאת, אם רוצים לבצע פעולות מניפולציה כלשהן בתמונה, העניינים קצת יותר מורכבים. CORS מונע גישה לעצמים הפיקסלים בפועל, אלא אם השרת מגדיר את הכותרות המתאימות ומסמן את התמונה כ-crossorigin. הדרך המעשית היחידה לעקוף את הבעיה הזו היא להפעיל שרת proxy.
קלט קובץ
אפשר גם להשתמש ברכיב קלט פשוט של קובץ, כולל מסנן 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 המשמעות היא שהמשתמש לא יוכל יותר לבחור תמונה קיימת. במקום זאת, אפליקציית המצלמה של המערכת תופעל ישירות.
גרירה ושחרור
אם אתם כבר מוסיפים את האפשרות להעלות קובץ, יש כמה דרכים פשוטות לשפר את חוויית המשתמש.
האפשרות הראשונה היא להוסיף לדף יעד להעברה (drop target) שמאפשר למשתמש לגרור קובץ מהמחשב או מאפליקציה אחרת.
<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
.
באמצעות המאפיין dropEffect
, בורר האירועים dragover
מאפשר לסמן למשתמש מה יקרה כשהוא יגרור וירפה את הקובץ.
האפשרות לגרור ולשחרר קיימת כבר הרבה זמן, והיא נתמכת היטב בדפדפנים המובילים.
הדבקה מהלוח
הדרך האחרונה להוסיף קובץ תמונה קיים היא מהלוח. הקוד לכך פשוט מאוד, אבל קשה יותר לשפר את חוויית המשתמש.
<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
נוסף).
החלק הקשה ב-Clipboard 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
שמכיל נתונים מהמצלמה. לאחר מכן תוכלו לצרף אותו לאלמנט <video>
ולהפעיל אותו כדי להציג תצוגה מקדימה בזמן אמת, או לצרף אותו ל-<canvas>
כדי לקבל קובץ snapshot.
<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>
לבד, המידע הזה לא כל כך שימושי. כל מה שאפשר לעשות הוא לקחת את נתוני הסרטון ולהפעיל אותם. אם רוצים לקבל תמונה, צריך לבצע קצת עבודה נוספת.
צילום תמונת מצב
האפשרות הטובה ביותר לקבלת תמונה היא לצייר פריים מהסרטון על קנבס.
בניגוד ל-Web Audio API, אין ממשק 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>
אחרי שנתונים מהמצלמה מאוחסנים ב-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()
, הדפדפן יבקש מהמשתמש להעניק לאתר הרשאה למצלמה.
משתמשים לא אוהבים לקבל בקשות גישה למכשירים חזקים במחשב שלהם, ולרוב הם יחסמו את הבקשה או יתעלמו ממנה אם הם לא יבינו את ההקשר שבו היא נוצרה. מומלץ לבקש גישה למצלמה רק כשצריך. אחרי שהמשתמש ייתן גישה, לא תופיע בקשה נוספת. עם זאת, אם המשתמש ידחה את הגישה, לא תוכלו לקבל אותה שוב, אלא אם הוא ישנה את הגדרות ההרשאות של המצלמה באופן ידני.
תאימות
מידע נוסף על הטמעה בדפדפנים לנייד ולמחשב:
מומלץ גם להשתמש ב-shim adapter.js כדי להגן על האפליקציות מפני שינויים במפרט של WebRTC ומפני הבדלים בתחילית.