شبیه سازی کمبودهای دید رنگی در Blink Renderer

این مقاله توضیح می دهد که چرا و چگونه شبیه سازی کمبود دید رنگ را در DevTools و Blink Renderer پیاده سازی کردیم.

زمینه: کنتراست رنگ بد

متن با کنتراست پایین رایج‌ترین مشکل دسترسی خودکار قابل تشخیص در وب است.

فهرستی از مشکلات دسترسی رایج در وب. متن با کنتراست پایین تا حد زیادی رایج ترین مشکل است.

با توجه به تجزیه و تحلیل دسترسی WebAIM از 1 میلیون وب سایت برتر ، بیش از 86٪ از صفحات اصلی کنتراست پایینی دارند. به طور متوسط، هر صفحه اصلی دارای 36 نمونه متمایز متن با کنتراست کم است.

استفاده از DevTools برای یافتن، درک و رفع مشکلات کنتراست

Chrome DevTools می‌تواند به توسعه‌دهندگان و طراحان برای بهبود کنتراست و انتخاب طرح‌های رنگی در دسترس‌تر برای برنامه‌های وب کمک کند:

ما اخیراً یک ابزار جدید به این لیست اضافه کرده ایم که کمی با سایر ابزارها متفاوت است. ابزارهای فوق عمدتاً بر روی نمایش اطلاعات نسبت کنتراست سطح تمرکز دارند و گزینه هایی برای رفع آن در اختیار شما قرار می دهند. ما متوجه شدیم که DevTools هنوز راهی را برای توسعه‌دهندگان برای درک عمیق‌تر این فضای مشکل از دست داده است. برای رفع این مشکل، شبیه‌سازی کمبود دید را در تب DevTools Rendering پیاده‌سازی کردیم.

در Puppeteer، API page.emulateVisionDeficiency(type) جدید به شما امکان می‌دهد این شبیه‌سازی‌ها را به صورت برنامه‌نویسی فعال کنید.

کمبود دید رنگ

تقریباً از هر 20 نفر 1 نفر از کمبود بینایی رنگ رنج می‌برد (همچنین به عنوان اصطلاح کمتر دقیق «کوررنگی» نیز شناخته می‌شود). چنین آسیب‌هایی تشخیص رنگ‌های مختلف را سخت‌تر می‌کند، که می‌تواند مشکلات کنتراست را تقویت کند .

تصویری رنگارنگ از مداد رنگی ذوب شده، بدون نقص دید رنگی شبیه سازی شده
تصویری رنگارنگ از مداد رنگی های ذوب شده ، بدون هیچ گونه نقص دید رنگی شبیه سازی شده است.
ALT_TEXT_HERE
تاثیر شبیه سازی آکروماتوپسی بر تصویر رنگارنگ مداد رنگی ذوب شده
تاثیر شبیه سازی دوترانوپیا بر روی تصویر رنگارنگ مداد رنگی ذوب شده
تاثیر شبیه سازی دوترانوپیا بر روی تصویر رنگارنگ مداد رنگی ذوب شده
تاثیر شبیه سازی پروتانوپیا بر روی تصویر رنگارنگ مداد رنگی ذوب شده.
تاثیر شبیه سازی پروتانوپیا بر روی تصویر رنگارنگ مداد رنگی ذوب شده.
تاثیر شبیه سازی تریتانوپیا بر روی یک تصویر رنگارنگ از مداد رنگی های ذوب شده.
تاثیر شبیه سازی تریتانوپیا بر روی یک تصویر رنگارنگ از مداد رنگی های ذوب شده.

به‌عنوان یک توسعه‌دهنده با دید منظم، ممکن است DevTools نسبت کنتراست بدی را برای جفت‌های رنگی که از نظر بصری برای شما خوب به نظر می‌رسند، نمایش می‌دهد. این به این دلیل اتفاق می افتد که فرمول های نسبت کنتراست این کمبودهای دید رنگ را در نظر می گیرند! ممکن است در برخی موارد همچنان بتوانید متنی با کنتراست پایین بخوانید، اما افرادی که دارای اختلالات بینایی هستند این امتیاز را ندارند.

با اجازه دادن به طراحان و توسعه‌دهندگان برای شبیه‌سازی تأثیر این کمبودهای بینایی بر روی برنامه‌های وب خود، هدف ما این است که قطعه گمشده را ارائه کنیم: DevTools نه تنها می‌تواند به شما در یافتن و رفع مشکلات کنتراست کمک کند، اکنون می‌توانید آنها را نیز درک کنید !

شبیه سازی کمبود دید رنگ با HTML، CSS، SVG و C++

قبل از اینکه به پیاده‌سازی Blink Renderer ویژگی خود بپردازیم، به درک چگونگی پیاده‌سازی عملکرد معادل با استفاده از فناوری وب کمک می‌کند.

می‌توانید هر یک از این شبیه‌سازی‌های کمبود دید رنگ را به عنوان پوششی در نظر بگیرید که کل صفحه را پوشش می‌دهد. پلتفرم وب راهی برای انجام این کار دارد: فیلترهای CSS! با ویژگی filter CSS، می توانید از برخی از توابع فیلتر از پیش تعریف شده مانند 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 که هرگز ننوشته است. این گیج کننده خواهد بود! برای پیاده سازی این قابلیت در DevTools، به راه حلی نیاز داریم که این اشکالات را نداشته باشد.

بیایید ببینیم چگونه می توانیم این موضوع را کمتر مداخله کنیم. دو بخش در این راه حل وجود دارد که باید آن ها را پنهان کنیم: 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-in-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 معتبر ذخیره کنیم و از مقدار ویژگی filter CSS در سند 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، ما هنوز شناسه فیلتری را که می خواهیم استفاده کنیم، مانند قبل مشخص می کنیم. توجه داشته باشید که نیازی به کد Base64 سند SVG در 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 هستیم، اما این کار را با استفاده از پلتفرم وب انجام می دهیم.

خوب، بنابراین ما متوجه شده ایم که چگونه فیلترهای 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 در سند واقعی لغو کند و چیزها را خراب کند. همچنین هنگام بازرسی سبک های محاسبه شده در DevTools نشان داده می شود که گیج کننده خواهد بود. چگونه می توانیم از این مسائل جلوگیری کنیم؟ ما باید راهی برای افزودن فیلتر به سند پیدا کنیم بدون اینکه از نظر برنامه‌نویسی برای توسعه‌دهندگان قابل مشاهده باشد.

یکی از ایده‌هایی که مطرح شد، ایجاد یک ویژگی CSS داخلی کروم بود که مانند filter عمل می‌کند، اما نام متفاوتی دارد، مانند --internal-devtools-filter . سپس می‌توانیم منطق خاصی اضافه کنیم تا مطمئن شویم این ویژگی هرگز در DevTools یا در سبک‌های محاسبه‌شده در DOM نشان داده نمی‌شود. حتی می‌توانیم مطمئن شویم که فقط روی یک عنصر کار می‌کند که به آن نیاز داریم: عنصر ریشه. با این حال، این راه‌حل ایده‌آل نخواهد بود: ما قابلیت‌هایی را که از قبل وجود دارد با filter کپی می‌کنیم، و حتی اگر تلاش زیادی برای پنهان کردن این ویژگی غیراستاندارد داشته باشیم، توسعه‌دهندگان وب همچنان می‌توانند درباره آن اطلاعات پیدا کنند و شروع به استفاده از آن کنند. برای پلتفرم وب بد خواهد بود. ما به روش دیگری برای اعمال یک سبک CSS بدون اینکه در DOM قابل مشاهده باشد نیاز داریم. هر ایده ای؟

مشخصات CSS دارای بخشی است که مدل قالب‌بندی بصری مورد استفاده را معرفی می‌کند و یکی از مفاهیم کلیدی در آن ، viewport است. این نمای بصری است که کاربران از طریق آن به صفحه وب مراجعه می کنند. یک مفهوم نزدیک مرتبط بلوک حاوی اولیه است که به نوعی شبیه یک viewport قابل استایل <div> است که فقط در سطح مشخصات وجود دارد. این مشخصات در همه جا به مفهوم "نمای دید" اشاره دارد. برای مثال، می‌دانید وقتی محتوا مناسب نیست مرورگر چگونه نوارهای پیمایش را نشان می‌دهد؟ همه اینها در مشخصات CSS، بر اساس این "viewport" تعریف شده است.

این viewport در Blink Renderer نیز به عنوان یک جزئیات پیاده سازی وجود دارد. در اینجا کدی است که سبک های نمای پیش فرض را با توجه به مشخصات اعمال می کند:

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;
}

برای اینکه متوجه شوید که این کد، z-index ، display ، position و overflow در ویوپورت (یا دقیق‌تر: بلوک حاوی اولیه) را کنترل می‌کند، نیازی به درک C++ یا پیچیدگی‌های موتور Blink's Style ندارید. اینها همه مفاهیمی هستند که ممکن است از CSS با آنها آشنا باشید! جادوی دیگری در ارتباط با انباشتن زمینه‌ها وجود دارد که مستقیماً به یک ویژگی CSS ترجمه نمی‌شود، اما در کل می‌توانید این شی viewport را به‌عنوان چیزی در نظر بگیرید که می‌توان با استفاده از CSS از داخل Blink، درست مانند یک عنصر DOM، استایل‌بندی کرد. بخشی از DOM

این دقیقا همان چیزی را که می خواهیم به ما می دهد! ما می‌توانیم استایل‌های filter خود را روی شی viewport اعمال کنیم، که به صورت بصری بر رندر تأثیر می‌گذارد، بدون اینکه به هیچ وجه با سبک‌های صفحه قابل مشاهده یا DOM تداخل داشته باشد.

نتیجه گیری

برای یادآوری سفر کوچک خود به اینجا، با ساختن یک نمونه اولیه با استفاده از فناوری وب به جای C++ شروع کردیم و سپس شروع به کار بر روی انتقال بخش هایی از آن به Blink Renderer کردیم.

  • ما ابتدا نمونه اولیه خود را با داخل کردن URL های داده، خودکفاتر کردیم.
  • سپس آن آدرس‌های اینترنتی داده‌های داخلی را با بارگیری آن‌ها با پوشش ویژه، سازگار با CSP کردیم.
  • با انتقال سبک‌ها به viewport داخلی Blink، پیاده‌سازی DOM را غیرقابل مشاهده و از نظر برنامه‌ریزی غیرقابل مشاهده کردیم.

چیزی که در مورد این پیاده سازی منحصر به فرد است این است که نمونه اولیه HTML/CSS/SVG ما در نهایت بر طراحی فنی نهایی تأثیر گذاشت. ما راهی برای استفاده از پلتفرم وب، حتی در Blink Renderer پیدا کردیم!

برای پیشینه بیشتر، پیشنهاد طراحی ما یا اشکال ردیابی Chromium را بررسی کنید که به همه وصله‌های مرتبط ارجاع می‌دهد.

کانال های پیش نمایش را دانلود کنید

استفاده از Chrome Canary ، Dev یا Beta را به عنوان مرورگر توسعه پیش‌فرض خود در نظر بگیرید. این کانال‌های پیش‌نمایش به شما امکان می‌دهند به جدیدترین ویژگی‌های DevTools دسترسی داشته باشید، APIهای پلت‌فرم وب پیشرفته را آزمایش کنید و قبل از اینکه کاربرانتان این کار را انجام دهند، مشکلات موجود در سایت خود را پیدا کنید!

تماس با تیم Chrome DevTools

از گزینه های زیر برای بحث در مورد ویژگی ها و تغییرات جدید در پست یا هر چیز دیگری مربوط به DevTools استفاده کنید.