تجربة قياس عمليات التنقّل البسيطة

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

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

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

ينظر فريق Chrome في هذا التحدي منذ بعض الوقت، ويسعى إلى وضع تعريف موحّد لما يُقصد بـ "التنقّل السلس"، وكيفية قياس "مؤشرات أداء الويب الأساسية" لهذا الغرض، وذلك بالطريقة نفسها التي يتم بها قياس المواقع الإلكترونية التي تم تنفيذها باستخدام بنية المواقع الإلكترونية التقليدية متعددة الصفحات (MPA). مع أنّ هذه الميزة لا تزال في مراحلها الأولى، أصبح الفريق جاهزًا الآن لتوفير الميزات التي تم تنفيذها حتى الآن على نطاق أوسع ليتمكّن أصحاب المواقع الإلكترونية من تجربتها. سيتيح ذلك للمواقع الإلكترونية تقديم ملاحظات حول النهج الذي تم اتّباعه حتى الآن.

ما المقصود بالتنقل البسيط؟

لقد توصّلنا إلى التعريف التالي للتنقّل السلس:

  • يبدأ التنقّل بإجراء من جانب المستخدِم.
  • يؤدي التنقّل إلى تغيير عنوان URL ظاهر للمستخدم وتغيير في السجلّ.
  • يؤدي التنقّل إلى تغيير في نموذج DOM.

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

كيف ينفِّذ Chrome التنقلات البسيطة؟

بعد تفعيل إرشادات التنقّل الآمن (مزيد من المعلومات حول هذا الموضوع في القسم التالي)، سيغيّر Chrome الطريقة التي يُعدّ بها بعض مقاييس الأداء:

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

ما هي تداعيات تفعيل عمليات التنقّل السلس في Chrome؟

في ما يلي بعض التغييرات التي يجب أن يأخذها مالكو المواقع الإلكترونية في الاعتبار بعد تفعيل هذه الميزة:

  • قد تتم إعادة إصدار أحداث FP وFCP وLCP إضافية للتنقّلات الناعمة. سيتجاهل تقرير تجربة مستخدمي Chrome (CrUX) هذه القيم الإضافية، ولكن قد يؤثر ذلك في أيّ عمليات مراقبة لقياس المستخدِمين الفعليين على موقعك الإلكتروني. تحقّق من مزوّد RUM إذا كانت لديك أي مخاوف بشأن ما إذا كان هذا الإجراء سيؤثّر في هذه القياسات. راجِع القسم الخاص بقياس "مؤشرات أداء الويب الأساسية" للتنقّلات السلسة.
  • قد تحتاج إلى تضمين سمة navigationID الجديدة (والاختيارية) في إدخالات الأداء في رمز تطبيقك باستخدام هذه الإدخالات.
  • لن يتوفّر هذا الوضع الجديد إلا في المتصفّحات المستندة إلى Chromium. على الرغم من أنّ العديد من المقاييس الجديدة لا تتوفّر إلا في المتصفّحات المستندة إلى Chromium، يتوفّر بعضها (FCP وLCP) في المتصفّحات الأخرى، وقد لا يكون الجميع قد أجرى ترقية إلى أحدث إصدار من المتصفّحات المستندة إلى Chromium. لذا، يُرجى العِلم أنّ بعض المستخدمين قد لا يُبلغون عن مقاييس التنقّل السلس.
  • وبما أنّ هذه الميزة جديدة وتجريبية وغير مفعّلة تلقائيًا، على المواقع الإلكترونية اختبارها للتأكّد من عدم حدوث أيّ تأثيرات جانبية غير مقصودة أخرى.

لمزيد من المعلومات عن كيفية قياس مقاييس عمليات التنقّل السلس، اطّلِع على قسم "قياس مؤشرات أداء الويب الأساسية لكل عملية تنقّل سلسة".

كيف يمكنني تفعيل عمليات التنقّل السلس في Chrome؟

لا تكون ميزة التنقُّل البسيط مُفعّلة تلقائيًا في متصفِّح Chrome، ولكنها متاحة للتجربة من خلال تفعيل هذه الميزة صراحةً.

يمكن للمطوّرين تفعيل هذه الميزة من خلال تفعيل علامة ميزات منصة الويب التجريبية في chrome://flags/#enable-experimental-web-platform-features أو باستخدام الوسيطة --enable-experimental-web-platform-features لسطر الأوامر عند تشغيل Chrome.

كيف يمكنني قياس عمليات التنقّل البسيطة؟

بعد تفعيل تجربة التنقّل السلس، سيتم إعداد تقارير المقاييس باستخدام واجهة برمجة التطبيقات PerformanceObserver كالمعتاد. ومع ذلك، هناك بعض العوامل الإضافية التي يجب أخذها في الاعتبار عند استخدام هذه المقاييس.

الإبلاغ عن عمليات التنقّل البسيطة

يمكنك استخدام PerformanceObserver لمراقبة عمليات التنقّل السلس. في ما يلي مثال على مقتطف رمز يسجّل إدخالات التنقّل البسيط في وحدة التحكّم، بما في ذلك عمليات التنقّل البسيطة السابقة في هذه الصفحة باستخدام الخيار buffered:

const observer = new PerformanceObserver(console.log);
observer.observe({ type: "soft-navigation", buffered: true });

ويمكن استخدام هذه البيانات لإنهاء مقاييس الصفحة الكاملة خلال عملية التنقّل السابقة.

الإبلاغ عن المقاييس وفقًا لعنوان URL المناسب

بما أنّه لا يمكن الاطّلاع على عمليات التنقّل البسيطة إلا بعد حدوثها، يجب إنهاء بعض المقاييس عند هذا الحدث، ثم إعداد تقارير عنها لعنوان URL السابق، لأنّ عنوان URL الحالي سيعكس الآن عنوان URL المعدَّل للصفحة الجديدة.

يمكن استخدام سمة navigationId في PerformanceEntry المناسب لربط الحدث بعنوان URL الصحيح. يمكن البحث عن ذلك باستخدام واجهة برمجة التطبيقات PerformanceEntry API:

const softNavEntry =
  performance.getEntriesByType('soft-navigation').filter(
    (entry) => entry.navigationId === navigationId
  )[0];
const hardNavEntry = performance.getEntriesByType('navigation')[0];
const navEntry = softNavEntry || hardNavEntry;
const pageUrl = navEntry?.name;

يجب استخدام pageUrl هذا للإبلاغ عن المقاييس مقابل عنوان URL الصحيح، بدلاً من عنوان URL الحالي الذي قد يكون تم استخدامه في الماضي.

الحصول على startTime من عمليات التنقّل البسيطة

يمكن الحصول على وقت بدء التنقّل بطريقة مشابهة:

const softNavEntry =
  performance.getEntriesByType('soft-navigation').filter(
    (entry) => entry.navigationId === navigationId
  )[0];
const hardNavEntry = performance.getEntriesByType('navigation')[0];
const navEntry = softNavEntry || hardNavEntry;
const startTime = navEntry?.startTime;

يشير المقياس startTime إلى وقت التفاعل الأوّلي (مثل النقر على زر) الذي بدأ التنقّل السلس.

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

قياس "مؤشرات أداء الويب الأساسية" لكل عملية تنقّل سلسة

لتضمين إدخالات مقياس التنقّل السلس، عليك تضمين includeSoftNavigationObservations: true في طلب observe من مراقب الأداء.

new PerformanceObserver((entryList) => {
  for (const entry of entryList.getEntries()) {
    console.log('Layout Shift time:', entry);
  }
}).observe({type: 'layout-shift', buffered: true, includeSoftNavigationObservations: true});

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

سيتم عرض الأوقات حسب وقت بدء التنقّل "الصعب" الأصلي. وبالتالي، لاحتساب LCP لمسار تنقّل سلس، على سبيل المثال، عليك أخذ توقيت LCP وطرح وقت بدء المسار السلس المناسب كما هو موضّح سابقًا للحصول على توقيت نسبي إلى المسار السلس. على سبيل المثال، بالنسبة إلى LCP:

new PerformanceObserver((entryList) => {
  for (const entry of entryList.getEntries()) {
    const softNavEntry =
      performance.getEntriesByType('soft-navigation').filter(
        (navEntry) => navEntry.navigationId === entry.navigationId
      )[0];
    const hardNavEntry = performance.getEntriesByType('navigation')[0];
    const navEntry = softNavEntry || hardNavEntry;
    const startTime = navEntry?.startTime;
    console.log('LCP time:', entry.startTime - startTime);
  }
}).observe({type: 'largest-contentful-paint', buffered: true, includeSoftNavigationObservations: true});

كان يتم قياس بعض المقاييس بشكلٍ تقليدي طوال مدة عرض الصفحة: على سبيل المثال، يمكن أن يتغيّر مقياس LCP إلى أن يحدث تفاعل. يمكن تعديل مقياسَي CLS وINP إلى أن يتم الانتقال بعيدًا عن الصفحة. لذلك، قد تحتاج كل عملية "تنقّل" (بما في ذلك عملية التنقّل الأصلية) إلى إنهاء مقاييس الصفحة السابقة عند حدوث كل عملية تنقّل جديدة. وهذا يعني أنّه قد يتم الانتهاء من المقاييس الأولية للتنقّل "الصعب" في وقت أبكر من المعتاد.

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

كيف يجب التعامل مع المحتوى الذي يظلّ كما هو بين عمليات التنقّل؟

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

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

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

كيف يمكن قياس TTFB؟

يمثّل وقت وصول أول بايت (TTFB) لتحميل الصفحة العادي الوقت الذي يتم فيه عرض أول بايت من الطلب الأصلي.

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

وهناك طريقة أبسط وهي الإبلاغ عن وقت استجابة خادم 0 للتنقّلات البسيطة، وذلك بالطريقة نفسها التي ننصح بها لاستعادة التخزين المؤقت للصفحات. هذه هي الطريقة التي تستخدمها web-vitals المكتبة للتنقّل السلس.

في المستقبل، قد نوفّر طرقًا أكثر دقة لمعرفة الطلب الذي يمثّل "طلب التنقّل" في ميزة التنقّل الإلكتروني، وسنتمكن من إجراء قياسات أكثر دقة لـ TTFB. ولكن هذا ليس جزءًا من التجربة الحالية.

كيف يمكن قياس كلا الإصدارَين القديم والجديد؟

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

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

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

استخدام مكتبة web-vitals لقياس "مؤشرات أداء الويب الأساسية" لعمليات التنقّل السلسة

إنّ أسهل طريقة لمراعاة جميع الاختلافات الدقيقة هي استخدام مكتبة JavaScript web-vitals التي تتضمّن دعمًا تجريبيًا للتنقّل السلس في soft-navs branch منفصل (وهو متاح أيضًا على npm وunpkg). يمكن قياس ذلك بالطريقة التالية (مع استبدال doTraditionalProcessing وdoSoftNavProcessing حسب الاقتضاء):

import {
  onTTFB,
  onFCP,
  onLCP,
  onCLS,
  onINP,
} from 'https://unpkg.com/web-vitals@soft-navs/dist/web-vitals.js?module';

onTTFB(doTraditionalProcessing);
onFCP(doTraditionalProcessing);
onLCP(doTraditionalProcessing);
onCLS(doTraditionalProcessing);
onINP(doTraditionalProcessing);

onTTFB(doSoftNavProcessing, {reportSoftNavs: true});
onFCP(doSoftNavProcessing, {reportSoftNavs: true});
onLCP(doSoftNavProcessing, {reportSoftNavs: true});
onCLS(doSoftNavProcessing, {reportSoftNavs: true});
onINP(doSoftNavProcessing, {reportSoftNavs: true});

تأكَّد من تسجيل المقاييس وفقًا لعنوان URL الصحيح كما هو موضّح سابقًا.

تعرض مكتبة web-vitals المقاييس التالية للتنقّل البسيط:

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

هل ستصبح هذه التغييرات جزءًا من قياسات "مؤشرات أداء الويب الأساسية"؟

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

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

كيف سيتم تسجيل عمليات التنقّل البسيطة في CrUX؟

في حال نجاح هذه التجربة، لا يزال من غير المعروف بالضبط كيف سيتم تسجيل عمليات التنقّل السلس في CrUX. وليس من المؤكد أن يتم التعامل معها بالطريقة نفسها التي يتم بها التعامل مع عمليات التنقّل "الثابتة" الحالية.

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

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

يركز الفريق على التنفيذ الاستكشافي والفني، ما سيتيح لنا الحكم على نجاح هذه التجربة، لذلك لم يتم اتخاذ أي قرار بشأن هذه الجوانب.

ملاحظات

نحن نبحث بنشاط عن ملاحظات حول هذه التجربة في الأماكن التالية:

سجلّ التغييرات

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

الخاتمة

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

الشكر والتقدير

صورة مصغّرة من إنشاء جوردان مدريد على Unsplash