במאמר הזה נסביר למה ואיך הטמענו סימולציה של עיוורון צבעים ב-DevTools וב-Blink Renderer.
רקע: ניגודיות צבעים נמוכה
טקסט עם ניגודיות נמוכה הוא בעיית הנגישות הנפוצה ביותר באינטרנט שאפשר לזהות באופן אוטומטי.
לפי ניתוח הנגישות של WebAIM במיליוני האתרים המובילים, יותר מ-86% מדפי הבית הם עם ניגודיות נמוכה. בממוצע, בכל דף בית יש 36 מופעים נפרדים של טקסט בניגודיות נמוכה.
איך משתמשים בכלי הפיתוח כדי למצוא בעיות של ניגודיות, להבין אותן ולפתור אותן
כלי הפיתוח של Chrome יכולים לעזור למפתחים ולמעצבים לשפר את הניגודיות ולבחור ערכות צבעים נגישות יותר לאפליקציות אינטרנט:
- בתיאור המפורט של מצב הבדיקה שמופיע בחלק העליון של דף האינטרנט מוצג יחס הניגודיות של רכיבי הטקסט.
- הכלי לבחירת צבעים ב-DevTools מציין יחסים נמוכים של ניגודיות ברכיבי טקסט, מציג את קו הניגודיות המומלץ כדי לעזור לכם לבחור צבעים טובים יותר באופן ידני, ואפילו יכול להציע צבעים נגישים.
- גם חלונית הסקירה הכללית של CSS וגם דוח הביקורת של Lighthouse בנושא נגישות כוללים פירוט של אלמנטים של טקסט עם ניגודיות נמוכה שנמצאו בדף.
לאחרונה הוספנו לרשימה הזו כלי חדש, שהוא קצת שונה מהכלים האחרים. הכלים שלמעלה מתמקדים בעיקר בהצגת מידע על יחס הניגוד ובמתן אפשרויות לתיקון שלו. הבנו שעדיין חסר ב-DevTools כלי שיעזור למפתחים להבין לעומק את התחום הזה. כדי לטפל בבעיה הזו, הטמענו סימולציה של לקות ראייה בכרטיסייה 'עיבוד' ב-DevTools.
ב-Puppeteer, ה-API החדש של page.emulateVisionDeficiency(type)
מאפשר להפעיל את הסימולציות האלה באופן פרוגרמטי.
לקויות בראיית צבעים
כ-1 מתוך 20 אנשים סובלים מלקות בראיית צבעים (שנקראת גם 'עיוורון צבעים', מונח פחות מדויק). קשיים כאלה מקשים על ההבחנה בין צבעים שונים, ויכולים להעצים בעיות של ניגודיות.
מפתחים עם ראייה תקינה עשויים לראות ב-DevTools יחס ניגודיות נמוך לזוגות צבעים שנראים להם תקינים מבחינה חזותית. הסיבה לכך היא שנוסחאות יחס הניגודיות מביאות בחשבון את הליקויים האלה בראיית הצבעים. יכול להיות שאתם עדיין תוכלו לקרוא טקסט עם ניגודיות נמוכה במקרים מסוימים, אבל לאנשים עם ליקויי ראייה אין את האפשרות הזו.
אנחנו רוצים לספק את החלק החסר: עכשיו מעצבים ומפתחים יכולים לדמות את ההשפעה של ליקויים בראייה על האפליקציות שלהם לאינטרנט, ולכן הם יכולים לא רק למצוא ולתקן בעיות של ניגודיות, אלא גם להבין אותן.
הדמיה של לקויות בראיית צבעים באמצעות HTML, CSS, SVG ו-C++
לפני שנצלול להטמעת התכונה שלנו ב-Blink Renderer, כדאי להבין איך מטמיעים פונקציונליות דומה באמצעות טכנולוגיית אינטרנט.
אפשר לחשוב על כל אחת מהסימולציות האלה של לקות בראיית צבעים כשכבת-על שמכסה את כל הדף. בפלטפורמת האינטרנט יש דרך לעשות זאת: מסנני 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 שורות: השורה הראשונה של המטריצה משמשת לחישוב הערך האדום החדש, השורה השנייה משמשת לחישוב הערך הירוק, השורה השלישית משמשת לחישוב הערך הכחול והשורה האחרונה משמשת לחישוב הערך האלפא.
יכול להיות שאתם תוהים מאיפה מגיעים המספרים המדויקים בדוגמה שלנו. מה הופך את מטריצת הצבעים הזו לאומדן טוב של עיוורון ירוק? התשובה היא: מדע! הערכים מבוססים על מודל סימולציה מדויק מבחינה פיזיולוגית של לקות בראיית הצבעים, שנוצר על ידי Machado, Oliveira ו-Fernandes.
בכל מקרה, יש לנו את מסנן ה-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 ב-HTML תומכים בקיצור הדרך </feColorMatrix>
, כדאי להשתמש בו./>
בכל מקרה, בעזרת השינויים האלה אפשר סוף סוף לשמור את הקובץ כקובץ 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 של הנתונים לא יסתיימו ב-literal של מחרוזת ה-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 של נתונים שאפשר להשתמש בהן בערך של מאפיין ה-CSS filter
. האם יש בעיה כלשהי בשיטה הזו? מסתבר שאנחנו לא יכולים להסתמך על כך שכתובת ה-URL של הנתונים תיטען בכל המקרים, כי יכול להיות שבדף היעד יש Content-Security-Policy
שחוסם כתובות URL של נתונים. במהלך ההטמעה הסופית ברמת Blink, אנחנו מקפידים במיוחד לעקוף את CSP עבור כתובות ה-URL ה'פנימיות' האלה של הנתונים במהלך הטעינה.
מלבד מקרי קצה, התקדמנו יפה. מכיוון שאנחנו כבר לא תלויים בכך ש-<svg>
בתוך שורת קוד יופיע באותו מסמך, הפכנו את הפתרון שלנו למעשה להגדרה אחת של מאפיין CSS filter
עצמאי. נהדר! עכשיו ניפטר גם מזה.
הימנעות מהתלות ב-CSS בתוך המסמך
לסיכום, זה המצב עד עכשיו:
<style>
:root {
filter: url('data:…');
}
</style>
אנחנו עדיין תלויים במאפיין ה-CSS filter
הזה, שעלול לשנות את הערך של filter
במסמך האמיתי ולגרום לשיבושים. הוא יופיע גם כשבודקים את הסגנונות המחושבים ב-DevTools, וזה עלול לבלבל. איך אפשר להימנע מהבעיות האלה? אנחנו צריכים למצוא דרך להוסיף מסנן למסמך בלי שהמפתחים יוכלו לראות אותו באופן פרוגרמטי.
אחת מהרעיונות שהוצעו הייתה ליצור מאפיין CSS חדש בתוך Chrome שפועל כמו filter
, אבל עם שם אחר, כמו --internal-devtools-filter
. לאחר מכן נוכל להוסיף לוגיקה מיוחדת כדי לוודא שהמאפיין הזה אף פעם לא יופיע ב-DevTools או בסגנונות המחושבים ב-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.
- קודם כול, שיפרנו את העצמאות של אב הטיפוס על ידי הטמעת כתובות URL של נתונים בקוד.
- לאחר מכן, שינינו את כתובות ה-URL הפנימיות של הנתונים כך שיתאימו ל-CSP, על ידי שינוי מיוחד של הטעינה שלהן.
- כדי שההטמעה שלנו לא תהיה תלויה ב-DOM ולא תהיה אפשרות לצפות בה באופן פרוגרמטי, העברנו את הסגנונות ל-
viewport
הפנימי של Blink.
מה שמייחד את ההטמעה הזו הוא שהאב טיפוס שלנו ב-HTML/CSS/SVG השפיע בסופו של דבר על התכנון הטכני הסופי. מצאנו דרך להשתמש בפלטפורמת האינטרנט, גם בתוך המנגן של Blink!
מידע נוסף זמין בהצעת העיצוב שלנו או בבאג המעקב של Chromium, שבו מפורטות כל התיקונים הקשורים.
הורדת הערוצים לתצוגה מקדימה
מומלץ להשתמש ב-Chrome Canary, ב-Dev או ב-Beta כדפדפן הפיתוח שמוגדר כברירת מחדל. ערוצי התצוגה המקדימה האלה מעניקים לכם גישה לתכונות העדכניות ביותר של DevTools, מאפשרים לכם לבדוק ממשקי API מתקדמים לפלטפורמות אינטרנט ולמצוא בעיות באתר לפני שהמשתמשים שלכם יעשו זאת.
יצירת קשר עם צוות כלי הפיתוח ל-Chrome
אתם יכולים להשתמש באפשרויות הבאות כדי לדון בתכונות החדשות, בעדכונים או בכל דבר אחר שקשור ל-DevTools.
- אתם יכולים לשלוח לנו משוב ובקשות להוספת תכונות בכתובת crbug.com.
- מדווחים על בעיה בכלי הפיתוח באמצעות הסמל אפשרויות נוספות > עזרה > דיווח על בעיה בכלי הפיתוח ב-DevTools.
- שולחים ציוץ אל @ChromeDevTools.
- אפשר להשאיר תגובות בסרטונים של מה חדש בכלי הפיתוח ב-YouTube או בסרטונים של טיפים לכלי הפיתוח ב-YouTube.