التقاط صورة من المستخدم

يمكن لمعظم المتصفّحات الوصول إلى كاميرا المستخدم.

تتمتع العديد من المتصفحات الآن بإمكانية الوصول إلى إدخالات الفيديو والصوت من المستخدم. ومع ذلك، قد تكون التجربة ديناميكية ومضمّنة بالكامل استنادًا إلى المتصفّح أو قد يتم تفويضها إلى تطبيق آخر على جهاز المستخدم. علاوة على ذلك، لا تحتوي كل الأجهزة على كاميرا. إذًا كيف يمكنك إنشاء تجربة تستخدم صورة من إنشاء المستخدم تعمل بشكل جيد في كل مكان؟

البدء ببساطة وبشكل تدريجي

إذا كنت ترغب في تحسين تجربتك تدريجيًا، فأنت بحاجة إلى البدء بشيء يعمل في كل مكان. أسهل شيء تفعله هو ببساطة مطالبة المستخدم بملف مسجل مسبقًا.

طلب عنوان URL

هذا هو أفضل خيار متاح ولكنه الأقل إرضاءً. اطلب من المستخدم أن يعطيك عنوان URL، ثم استخدمه. يعمل هذا الخيار في كل مكان لعرض الصورة فقط. أنشئ عنصر img، واضبط عنصر src وبذلك تكون قد انتهيت.

أمّا إذا أردت معالجة الصورة بأيّ شكل من الأشكال، فتكون الأمور أكثر تعقيدًا. تمنع دالة CORS من الوصول إلى وحدات البكسل الفعلية ما لم يضبط الخادم العناوين المناسبة وتضع علامة على الصورة باعتبارها من مصادر متعددة، لأنّ الطريقة العملية الوحيدة لذلك هي تشغيل خادم وكيل.

إدخال الملف

يمكنك أيضًا استخدام عنصر إدخال ملف بسيط، بما في ذلك فلتر accept الذي يشير إلى أنّك لا تريد سوى ملفات الصور.

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

تعمل هذه الطريقة على جميع الأنظمة الأساسية. على سطح المكتب، ستطالب المستخدم بتحميل ملف صورة من نظام الملفات. في Chrome وSafari على نظامي التشغيل iOS وAndroid، ستمنح هذه الطريقة المستخدم اختيار التطبيق الذي يستخدمه لالتقاط الصورة، بما في ذلك خيار التقاط صورة مباشرة من الكاميرا أو اختيار ملف صورة موجود.

قائمة على Android تتضمّن خياران: التقاط الصورة والملفات قائمة iOS مع ثلاثة خيارات: التقاط صورة ومكتبة الصور و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، يعني هذا أن المستخدم لن يكون لديه خيار اختيار صورة حالية بعد الآن. سيتم تشغيل تطبيق كاميرا النظام مباشرةً.

السحب والإفلات

إذا كنت تضيف إمكانية تحميل ملف، فهناك طريقتان بسيطتان يمكنك من خلالهما تحسين تجربة المستخدم.

الطريقة الأولى هي إضافة هدف إفلات إلى صفحتك يسمح للمستخدم بسحب ملف من سطح المكتب أو من تطبيق آخر.

<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 آخر إلى الآن.)

الجزء الصعب في واجهة برمجة تطبيقات الحافظة هو أنه، للحصول على الدعم الكامل عبر المتصفحات، يجب أن يكون العنصر المستهدف قابلاً للتحديد وقابل للتعديل. يتطابق كل من <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()"

الوصول إلى الكاميرا بشكل تفاعلي

الآن بعد أن غطيت قواعدك، حان الوقت لتحسينها تدريجيًا!

يمكن للمتصفّحات الحديثة الوصول مباشرةً إلى الكاميرات، ما يتيح لك إنشاء تجارب متكاملة تمامًا مع صفحة الويب، بحيث لا يحتاج المستخدم إلى مغادرة المتصفّح مطلقًا.

الحصول على إذن بالوصول إلى الكاميرا

يمكنك الوصول إلى الكاميرا والميكروفون مباشرةً باستخدام واجهة برمجة تطبيقات في مواصفات WebRTC المسماة getUserMedia(). سيؤدي ذلك إلى مطالبة المستخدم بالوصول إلى الميكروفونات والكاميرات المتصلة.

دعم getUserMedia() جيد جدًا، ولكنه غير متاح حتى الآن في كل مكان. وعلى وجه الخصوص، لا يتوفر هذا الإصدار في الإصدار 10 من Safari أو الإصدارات الأقدم، والذي لا يزال حتى وقت كتابة هذا التقرير أحدث إصدار ثابت. ومع ذلك، أعلنت شركة Apple أنها ستكون متاحة في الإصدار 11 من Safari.

ومع ذلك، يسهل اكتشاف الدعم.

const supported = 'mediaDevices' in navigator;

عند استدعاء getUserMedia()، تحتاج إلى تمرير كائن يصف نوع الوسائط التي تريدها. وتسمى هذه الاختيارات القيود. هناك العديد من القيود المحتملة التي يمكن أن تنطبق، مثل ما إذا كنت تفضّل استخدام الكاميرا الأمامية أو الخلفية، وما إذا كنت تريد تشغيل الصوت أم لا ودقتك المفضّلة للبث.

للحصول على البيانات من الكاميرا، تحتاج إلى قيد واحد فقط، وهو video: true.

وفي حال نجاحها، ستعرض واجهة برمجة التطبيقات عنصر 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>

في حد ذاته، هذا ليس مفيدًا إلى هذا الحد. كل ما يمكنك فعله هو الحصول على بيانات الفيديو وإعادة تشغيلها. إذا كنت ترغب في الحصول على صورة، فعليك القيام ببعض الإجراءات الإضافية.

أخذ لقطة

أفضل خيار معتمد للحصول على صورة هو رسم إطار من الفيديو إلى لوحة.

على عكس Web Audio API، ما مِن واجهة برمجة تطبيقات مخصّصة لمعالجة البث المباشر للفيديوهات على الويب، لذا عليك الاعتماد على بعض أساليب الاختراق لتصوير لقطة من كاميرا المستخدم.

تتم العملية على النحو التالي:

  1. أنشئ كائن لوحة ليحمل الإطار من الكاميرا
  2. الوصول إلى بث الكاميرا
  3. إرفاقه بعنصر فيديو
  4. عندما تريد التقاط إطار دقيق، أضِف البيانات من عنصر الفيديو إلى كائن لوحة باستخدام 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() منح إذن الوصول إلى الموقع الإلكتروني للكاميرا.

يكره المستخدمون طلب الوصول إلى أجهزة قوية على أجهزتهم، ويحظرون الطلب كثيرًا، أو سيتجاهلونه إذا لم يفهموا السياق الذي تم إنشاء الطلب من أجله. من الأفضل أن تطلب الوصول إلى الكاميرا عند الحاجة أولاً. بمجرد منح المستخدم الوصول، لن يُطلب منه مرة أخرى. ومع ذلك، إذا رفض المستخدم إذن الوصول إلى الكاميرا، لن تتمكّن من الحصول عليه مرة أخرى ما لم يغيِّر إعدادات أذونات الكاميرا يدويًا.

التوافق

مزيد من المعلومات حول تنفيذ المتصفح المتوافق مع الأجهزة الجوّالة وأجهزة الكمبيوتر المكتبي:

نقترح أيضًا استخدام دعم adapter.js لحماية التطبيقات من تغييرات مواصفات WebRTC والاختلافات في البادئات.

إضافة ملاحظات