לרוב הדפדפנים יש גישה למצלמה של המשתמש.
לדפדפנים רבים יש עכשיו אפשרות לגשת לקלט וידאו ואודיו מהמשתמש. עם זאת, בהתאם לדפדפן, יכול להיות שמדובר בחוויה דינמית מלאה ובחוויה מוטבעת, או שאפשר להעניק אותה לאפליקציה אחרת במכשיר של המשתמש. בנוסף, לא לכל מכשיר יש מצלמה. אז איך אפשר ליצור חוויה שמשתמשת בתמונה שנוצרה על ידי משתמש שפועלת היטב בכל מקום?
מתחילים בפשטות ובאופן הדרגתי
אם אתם רוצים לשפר את חוויית המשתמש בהדרגה, עליכם להתחיל עם משהו שעובד בכל מקום. הדבר הקל ביותר לעשות הוא פשוט לבקש מהמשתמש קובץ שהוקלט מראש.
בקשה לכתובת 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>
אחרי שנתונים מהמצלמה מאוחסנים בקנבס, אפשר לעשות איתם הרבה דברים. תוכל:
- להעלות אותו ישירות לשרת
- לשמור אותם באופן מקומי
- להוסיף אפקטים מיוחדים לתמונה
טיפים
להפסיק את הסטרימינג מהמצלמה כשאין צורך
מומלץ להפסיק להשתמש במצלמה כשאין בה יותר צורך. הפעולה הזו לא רק תחסוך בסוללה ובכוח העיבוד, אלא גם תעניק למשתמשים אמון באפליקציה.
כדי להפסיק את הגישה למצלמה, אפשר פשוט להפעיל את 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 ומפני הבדלים בתחילית.