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

توضّح هذه المقالة سبب تنفيذنا لمحاكاة نقص رؤية الألوان في "أدوات مطوري البرامج" وBlink Renderer وكيفية تنفيذ هذه المحاكاة.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

يمكنك اعتبار كلّ محاكاة من محاكاهات عجز الرؤية اللونية على أنّها تراكب يغطي الصفحة بأكملها. يوفر نظام الويب الأساسي طريقة لإجراء ذلك: فلاتر 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 في أعلى الصفحة. والإضافة الثانية هي ما يُعرف باسم Solidus، أي أنّ الشرطة المائلة تشير إلى أنّ العلامة <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 في عنوان URL باستخدام Base64، لأنّ ذلك سيضر بسهولة القراءة ويزيد من حجم الملف. أضفنا الشرطة المائلة للخلف في نهاية كل سطر لضمان عدم إنهاء أحرف سطر جديد في عنوان 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"

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