محاكاة قصور في رؤية الألوان في "عارض الألوان"

توضّح هذه المقالة سبب تنفيذنا لمحاكاة ضعف الرؤية اللونية وكيفية تنفيذها في DevTools وBlink Renderer.

الخلفية: تباين الألوان غير جيد

النص المنخفض التباين هو أكثر مشاكل إمكانية الوصول التي يمكن رصدها تلقائيًا على الويب.

قائمة بالمشاكل الشائعة المتعلّقة بإمكانية الوصول على الويب إنّ النص المنخفض التباين هو المشكلة الأكثر شيوعًا على الإطلاق.

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

استخدام أدوات مطوّري البرامج للعثور على مشاكل التباين وفهمها وحلّها

يمكن أن تساعد "أدوات مطوري البرامج في Chrome" المطوّرين والمصمّمين على تحسين التباين واختيار أنظمة ألوان يسهل الوصول إليها في تطبيقات الويب:

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

في Puppeteer، تتيح لك واجهة برمجة التطبيقات الجديدة page.emulateVisionDeficiency(type) API تفعيل هذه المحاكاة آليًا.

قصور في رؤية الألوان

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

صورة ملونة لأقلام تلوين مذابّة، بدون محاكاة أي قصور في رؤية الألوان
صورة ملونة لأقلام تلوين مذابّة، بدون محاكاة أي قصور في رؤية الألوان
ALT_TEXT_HERE
تأثير محاكاة عمى الألوان على صورة ملونة لأقلام تلوين مذابّة.
تأثير محاكاة عمى الأخضر والأصفر على صورة ملوّنة لأقلام تلوين مذابّة
تأثير محاكاة عمى الديترانوبيا على صورة ملونة لأقلام تلوين مذابّة.
تأثير محاكاة عمى الألوان الأحمر والأخضر على صورة ملوّنة من أقلام الرصاص الملوّنة المذابة
تأثير محاكاة عمى البروتانوبيا على صورة ملونة لأقلام تلوين مذابّة.
تأثير محاكاة عمى تمييز اللون الأزرق والأصفر على صورة ملوّنة لأقلام تلوين مذابّة
تأثير محاكاة عمى التريتانوبيا على صورة ملونة لأقلام تلوين مذابّة.

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

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

محاكاة قصور الرؤية في الألوان باستخدام HTML وCSS وSVG وC++

قبل الغوص في تنفيذ ميزة "مُعِدّ عرض Blink"، من المفيد فهم كيفية تنفيذ وظيفة مماثلة باستخدام تكنولوجيا الويب.

يمكنك اعتبار كلّ محاكاة من محاكاهات عجز الرؤية اللونية على أنّها تراكب يغطي الصفحة بأكملها. تتوفّر في Web Platform طريقة لإجراء ذلك: فلاتر CSS. باستخدام خاصية CSS‏ filter، يمكنك استخدام بعض دوال الفلترة المحدّدة مسبقًا، مثل blur وcontrast وgrayscale وhue-rotate وغيرها الكثير. لمزيد من التحكّم، يقبل السمة filter أيضًا عنوان URL يمكن أن يشير إلى تعريف فلتر SVG مخصّص:

<style>
  :root {
    filter: url(#deuteranopia);
  }
</style>
<svg>
  <filter id="deuteranopia">
    <feColorMatrix values="0.367  0.861 -0.228  0.000  0.000
                           0.280  0.673  0.047  0.000  0.000
                          -0.012  0.043  0.969  0.000  0.000
                           0.000  0.000  0.000  1.000  0.000">
    </feColorMatrix>
  </filter>
</svg>

يستخدم المثال أعلاه تعريف فلتر مخصّصًا استنادًا إلى مصفوفة ألوان. من الناحية النظرية، يتم ضرب قيمة اللون [Red, Green, Blue, Alpha] لكل بكسل في مصفوفة لإنشاء لون جديد [R′, G′, B′, A′].

يحتوي كل صف في المصفوفة على 5 قيم: مُضاعِف للألوان (من اليمين إلى اليسار) R وG وB وA، بالإضافة إلى قيمة خامسة لقيمة التحويل الثابت. هناك 4 صفوف: يتم استخدام الصف الأول من المصفوفة لاحتساب القيمة الجديدة للّون الأحمر، والصف الثاني للّون الأخضر، والصف الثالث للّون الأزرق، والصف الأخير للّون ألفا.

قد تتساءل عن مصدر الأرقام الدقيقة في مثالنا. ما الذي يجعل مصفوفة الألوان هذه تقريبية جيدة لعمى الديترانوبيا؟ الإجابة هي: العلوم. تستند القيم إلى نموذج محاكاة دقيق من الناحية الفسيولوجية للقصور في رؤية الألوان من تأليف "ماتشادو" و"أولييرا" و"فيرنانديز".

على أي حال، لدينا فلتر SVG هذا، ويمكننا الآن تطبيقه على عناصر عشوائية في الصفحة باستخدام CSS. يمكننا تكرار النمط نفسه مع أي عيوب أخرى في الرؤية. في ما يلي عرض توضيحي للشكل الذي سيظهر به ذلك:

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

  • قد تحتوي الصفحة على فلتر في عنصرها الجذر، ما قد يؤدي إلى إلغاء تأثير رمزنا البرمجي.
  • قد تحتوي الصفحة على عنصر يتضمّن id="deuteranopia"، ما يتعارض مع تعريف الفلتر.
  • قد تعتمد الصفحة على بنية معيّنة لنموذج DOM، ومن خلال إدراج <svg> في نموذج DOM، قد نخالف هذه الافتراضات.

بصرف النظر عن الحالات الشاذة، تكمن المشكلة الرئيسية في هذا النهج في أنّنا سنُجري تغييرات يمكن رصدها آليًا على الصفحة. إذا فحص أحد مستخدمي DevTools نموذج DOM، قد يظهر له فجأة عنصر <svg> لم يُضيفه مطلقًا أو filter CSS لم يكتبه مطلقًا. سيكون ذلك مربكًا. لتنفيذ هذه الوظيفة في أدوات المطوّرين، نحتاج إلى حلّ لا يتضمّن هذه العيوب.

لنطّلِع على كيفية تقليل هذا التأثير. هناك جزءان من هذا الحلّ يجب إخفاؤهما: 1) نمط CSS باستخدام السمة filter، و2) تعريف فلتر SVG، الذي يشكّل حاليًا جزءًا من DOM.

<!-- Part 1: the CSS style with the filter property -->
<style>
  :root {
    filter: url(#deuteranopia);
  }
</style>
<!-- Part 2: the SVG filter definition -->
<svg>
  <filter id="deuteranopia">
    <feColorMatrix values="0.367  0.861 -0.228  0.000  0.000
                           0.280  0.673  0.047  0.000  0.000
                          -0.012  0.043  0.969  0.000  0.000
                           0.000  0.000  0.000  1.000  0.000">
    </feColorMatrix>
  </filter>
</svg>

تجنُّب الاعتماد على ملفات SVG داخل المستند

لنبدأ بالجزء 2: كيف يمكننا تجنُّب إضافة رسوم SVG إلى DOM؟ يمكنك نقله إلى ملف SVG منفصل. يمكننا نسخ <svg>…</svg> من ملف HTML أعلاه وحفظه باسم filter.svg، ولكن علينا إجراء بعض التغييرات أولاً. تلتزم ملفات SVG المضمّنة في HTML بقواعد تحليل HTML. وهذا يعني أنّه يمكنك تجنُّب الأخطاء، مثل حذف علامات الاقتباس حول قيم السمات في بعض الحالات. ومع ذلك، من المفترض أن تكون ملفات SVG المنفصلة بتنسيق XML صالحًا، ويكون تحليل ملفات XML أكثر صرامة من تحليل ملفات HTML. في ما يلي مقتطف SVG في HTML مرة أخرى:

<svg>
  <filter id="deuteranopia">
    <feColorMatrix values="0.367  0.861 -0.228  0.000  0.000
                           0.280  0.673  0.047  0.000  0.000
                          -0.012  0.043  0.969  0.000  0.000
                           0.000  0.000  0.000  1.000  0.000">
    </feColorMatrix>
  </filter>
</svg>

لجعل ملف SVG هذا مستقلاً وصالحًا (وبالتالي XML)، يجب إجراء بعض التغييرات. هل يمكنك تخمين أيّ منها؟

<svg xmlns="http://www.w3.org/2000/svg">
 
<filter id="deuteranopia">
   
<feColorMatrix values="0.367  0.861 -0.228  0.000  0.000
                           0.280  0.673  0.047  0.000  0.000
                          -0.012  0.043  0.969  0.000  0.000
                           0.000  0.000  0.000  1.000  0.000"
/>
 
</filter>
</svg>

التغيير الأول هو بيان مساحة اسم XML في أعلى الصفحة. الإضافة الثانية هي ما يُعرف باسم "الفاصلة المائلة"، وهي الشرطة المائلة التي تشير إلى أنّ علامة <feColorMatrix> تفتح العنصر وتُغلقه. هذا التغيير الأخير ليس ضروريًا في الواقع (يمكننا الاكتفاء بالعلامة الختامية الصريحة </feColorMatrix> بدلاً من ذلك)، ولكن بما أنّ كلّ من XML وSVG-in-HTML يتيحان استخدام الاختصار />، يمكننا الاستفادة منه أيضًا.

على أي حال، بعد إجراء هذه التغييرات، يمكننا أخيرًا حفظ هذا الملف كملف SVG صالح، والإشارة إليه من قيمة سمة CSS filter في مستند HTML:

<style>
  :root {
    filter: url(filters.svg#deuteranopia);
  }
</style>

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

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

data:image/svg+xml,
  <svg xmlns="http://www.w3.org/2000/svg">
    <filter id="deuteranopia">
      <feColorMatrix values="0.367  0.861 -0.228  0.000  0.000
                             0.280  0.673  0.047  0.000  0.000
                            -0.012  0.043  0.969  0.000  0.000
                             0.000  0.000  0.000  1.000  0.000" />
    </filter>
  </svg>

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

<style>
  :root {
    filter: url('data:image/svg+xml,\
      <svg xmlns="http://www.w3.org/2000/svg">\
        <filter id="deuteranopia">\
          <feColorMatrix values="0.367  0.861 -0.228  0.000  0.000\
                                 0.280  0.673  0.047  0.000  0.000\
                                -0.012  0.043  0.969  0.000  0.000\
                                 0.000  0.000  0.000  1.000  0.000" />\
        </filter>\
      </svg>#deuteranopia');
  }
</style>

في نهاية عنوان URL، ما زلنا نحدّد رقم تعريف الفلتر الذي نريد استخدامه، تمامًا كما في السابق. يُرجى العِلم أنّه ما مِن حاجة إلى ترميز مستند SVG بترميز Base64 في عنوان URL، لأنّ ذلك سيؤدي فقط إلى خفض سهولة القراءة وزيادة حجم الملف. أضفنا الشرطة المائلة للخلف في نهاية كل سطر لضمان عدم إنهاء أحرف سطر جديد في عنوان URL للبيانات سلسلة CSS الحرفية.

حتى الآن، تحدثنا فقط عن كيفية محاكاة عيوب الرؤية باستخدام تكنولوجيا الويب. من المثير للاهتمام أنّ عملية التنفيذ النهائية في Blink Renderer متشابهة جدًا. في ما يلي أداة مساعدة C++ أضفناها لإنشاء عنوان URL للبيانات باستخدام تعريف فلتر معيّن، استنادًا إلى الأسلوب نفسه:

AtomicString CreateFilterDataUrl(const char* piece) {
  AtomicString url =
      "data:image/svg+xml,"
        "<svg xmlns=\"http://www.w3.org/2000/svg\">"
          "<filter id=\"f\">" +
            StringView(piece) +
          "</filter>"
        "</svg>"
      "#f";
  return url;
}

في ما يلي كيفية استخدامنا لها لإنشاء جميع الفلاتر التي نحتاجها:

AtomicString CreateVisionDeficiencyFilterUrl(VisionDeficiency vision_deficiency) {
  switch (vision_deficiency) {
    case VisionDeficiency::kAchromatopsia:
      return CreateFilterDataUrl("…");
    case VisionDeficiency::kBlurredVision:
      return CreateFilterDataUrl("<feGaussianBlur stdDeviation=\"2\"/>");
    case VisionDeficiency::kDeuteranopia:
      return CreateFilterDataUrl(
          "<feColorMatrix values=\""
          " 0.367  0.861 -0.228  0.000  0.000 "
          " 0.280  0.673  0.047  0.000  0.000 "
          "-0.012  0.043  0.969  0.000  0.000 "
          " 0.000  0.000  0.000  1.000  0.000 "
          "\"/>");
    case VisionDeficiency::kProtanopia:
      return CreateFilterDataUrl("…");
    case VisionDeficiency::kTritanopia:
      return CreateFilterDataUrl("…");
    case VisionDeficiency::kNoVisionDeficiency:
      NOTREACHED();
      return "";
  }
}

يُرجى العِلم أنّ هذه التقنية تتيح لنا الاستفادة من إمكانات فلاتر SVG الكاملة بدون الحاجة إلى إعادة تنفيذ أي شيء أو إعادة ابتكار أي أدوات. نعمل على تنفيذ ميزة Blink Renderer، ولكننا نفعل ذلك من خلال الاستفادة من Web Platform.

حسنًا، لقد اكتشفنا كيفية إنشاء فلاتر SVG وتحويلها إلى عناوين URL للبيانات يمكننا استخدامها ضمن قيمة سمة filter في CSS. هل يمكنك التفكير في مشكلة في هذه الطريقة؟ تبيّن لنا أنّه لا يمكننا الاعتماد على تحميل عنوان URL للبيانات في جميع الحالات، لأنّ الصفحة المستهدفة قد تحتوي على Content-Security-Policy يحظر عناوين URL للبيانات. يحرص التنفيذ النهائي على مستوى Blink على تجاوز CSP لعناوين URL "الداخلية" للبيانات هذه أثناء التحميل.

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

تجنُّب الاعتماد على CSS في المستند

في ما يلي ملخّص لما تم إنجازه حتى الآن:

<style>
  :root {
    filter: url('data:…');
  }
</style>

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

من الأفكار التي تم طرحها هي إنشاء خاصية CSS جديدة داخل Chrome تعمل مثل filter، ولكن لها اسم مختلف، مثل --internal-devtools-filter. يمكننا بعد ذلك إضافة منطق خاص لضمان عدم ظهور هذه السمة مطلقًا في أدوات مطوّري البرامج أو في الأنماط المحسوبة في نموذج DOM. يمكننا أيضًا التأكّد من أنّه يعمل على العنصر الوحيد الذي نحتاجه: العنصر الجذر. ومع ذلك، لن يكون هذا الحلّ مثاليًا: سنكرّر الوظيفة المتوفّرة حاليًا في filter، وحتى لو حاولنا جاهدين إخفاء هذه السمة غير العادية، سيظل بإمكان مطوّري الويب اكتشافها والبدء في استخدامها، ما سيكون أمرًا سيئًا لمنصّة الويب. نحتاج إلى طريقة أخرى لتطبيق أسلوب CSS بدون أن يكون مرئيًا في DOM. هل بإمكانك مساعدتي في ذلك؟

تتضمّن مواصفات CSS قسمًا يقدّم نموذج التنسيق المرئي الذي تستخدمه، ومن المفاهيم الرئيسية فيه مساحة العرض. هذه هي طريقة العرض المرئية التي يراجع من خلالها المستخدمون صفحة الويب. ومن المفاهيم ذات الصلة الحقل الأولي الذي يحتوي على المحتوى، وهو يشبه إلى حدٍ ما إطار العرض القابل للتنسيق <div> الذي لا يظهر إلا على مستوى المواصفات. تشير المواصفات إلى مفهوم "مساحة العرض" في كل مكان. على سبيل المثال، هل تعرف كيف يعرض المتصفّح أشرطة التمرير عندما لا يتلاءم المحتوى مع الشاشة؟ ويتم تحديد كل ذلك في مواصفات CSS، استنادًا إلى "مساحة العرض" هذه.

يتوفّر هذا الرمز viewport أيضًا في أداة عرض Blink، كتفاصيل تنفيذ. في ما يلي الرمز الذي يطبّق أنماط إطار العرض التلقائية وفقًا للمواصفات:

scoped_refptr<ComputedStyle> StyleResolver::StyleForViewport() {
  scoped_refptr<ComputedStyle> viewport_style =
      InitialStyleForElement(GetDocument());
  viewport_style->SetZIndex(0);
  viewport_style->SetIsStackingContextWithoutContainment(true);
  viewport_style->SetDisplay(EDisplay::kBlock);
  viewport_style->SetPosition(EPosition::kAbsolute);
  viewport_style->SetOverflowX(EOverflow::kAuto);
  viewport_style->SetOverflowY(EOverflow::kAuto);
  // …
  return viewport_style;
}

لست بحاجة إلى فهم لغة C++ أو تعقيدات محرّك الأنماط في Blink لمعرفة أنّ هذا الرمز البرمجي يعالج z-index وdisplay وposition وoverflow لإطار العرض (أو بتعبير أدق: للوحدة الأولية التي تحتوي على المحتوى). هذه هي جميع المفاهيم التي قد تكون على دراية بها من CSS. هناك بعض الحيل الأخرى المرتبطة بسياقات التراص، والتي لا تُترجم بشكل مباشر إلى سمة CSS، ولكن بشكل عام، يمكنك اعتبار عنصر viewport هذا كشيء يمكن تصميمه باستخدام CSS من داخل Blink، تمامًا مثل عنصر DOM، باستثناء أنّه ليس جزءًا من DOM.

هذا ما نريد الحصول عليه بالضبط. يمكننا تطبيق أنماط filter على عنصر viewport، ما يؤثّر في العرض بشكل مرئي، بدون التدخل في أنماط الصفحة المرئية أو DOM بأي شكل من الأشكال.

الخاتمة

لتلخيص رحلتنا الصغيرة هنا، بدأنا بإنشاء نموذج أولي باستخدام تكنولوجيا الويب بدلاً من C++، ثم بدأنا العمل على نقل أجزاء منه إلى أداة Blink Renderer.

  • أولاً، جعلنا النموذج الأولي أكثر اكتمالاً من خلال تضمين عناوين URL للبيانات.
  • بعد ذلك، جعلنا عناوين URL للبيانات الداخلية هذه متوافقة مع تقنية CSP من خلال وضع علامة خاصة على عملية تحميلها.
  • لقد جعلنا عملية التنفيذ غير معتمدة على نموذج DOM وغير قابلة للرصد آليًا من خلال نقل الأنماط إلى viewport الداخلي في Blink.

ما يميز عملية التنفيذ هذه هو أنّ النموذج الأولي لتنسيقات HTML/CSS/SVG انتهى به المطاف بالتأثير في التصميم الفني النهائي. لقد عثرنا على طريقة لاستخدام Web Platform، حتى داخل Blink Renderer.

لمزيد من المعلومات الأساسية، يمكنك الاطّلاع على اقتراح التصميم أو خطأ تتبُّع Chromium الذي يشير إلى جميع الرموز البرمجية ذات الصلة.

تنزيل قنوات المعاينة

ننصحك باستخدام إصدار Canary أو Dev أو الإصدار التجريبي من Chrome كمتصفّح التطوير التلقائي. تتيح لك قنوات المعاينة هذه الوصول إلى أحدث ميزات DevTools، وتتيح لك اختبار واجهات برمجة تطبيقات منصات الويب المتطوّرة، وتساعدك في العثور على المشاكل في موقعك الإلكتروني قبل أن يعثر عليها المستخدمون.

التواصل مع فريق "أدوات مطوّري البرامج في Chrome"

استخدِم الخيارات التالية لمناقشة الميزات الجديدة أو التحديثات أو أي شيء آخر مرتبط بـ "أدوات مطوّري البرامج".