ממשק API של צבע CSS

אפשרויות חדשות ב-Chrome 65

ממשק CSS Paint API (שנקרא גם "CSS Custom Paint" או "Houdini's Paint Worklet) מופעל כברירת מחדל החל מ-Chrome 65. מה זה? מה אפשר לעשות איתה? ואיך זה עובד? טוב, המשיכו לקרוא, נכון?...

באמצעות CSS Paint API, אפשר ליצור באופן פרוגרמטי תמונה בכל פעם שנכס CSS מצפה לתמונה. מאפיינים כמו background-image או border-image מופיעים בדרך כלל עם url() כדי לטעון קובץ תמונה או עם פונקציות מובנות של CSS כמו linear-gradient(). במקום להשתמש באלה, עכשיו אפשר להשתמש ב-paint(myPainter) כדי לבצע הפניה לסביבת עבודה (worklet).

כתיבה של עבודת צבע

כדי להגדיר worklet של המרת צבעים בשם myPainter, עלינו לטעון קובץ עבודת צבע של CSS באמצעות CSS.paintWorklet.addModule('my-paint-worklet.js'). בקובץ הזה, נוכל להשתמש בפונקציה registerPaint כדי לרשום מחלקה של צבע משטח (worklet):

class MyPainter {
  paint(ctx, geometry, properties) {
    // ...
  }
}

registerPaint('myPainter', MyPainter);

בתוך הקריאה החוזרת (callback) של paint(), אנחנו יכולים להשתמש ב-ctx באותו אופן שבו אנחנו משתמשים ב-CanvasRenderingContext2D כפי שאנחנו מכירים אותו מ-<canvas>. אם אתם יודעים איך לצייר ב-<canvas>, אתם יכולים לצייר בעבודת צבע! geometry מציין את הרוחב והגובה של הקנבס שעומד לרשותנו. properties אסביר את זה מאוחר יותר במאמר הזה.

לדוגמה מבוא, נכתוב עבודת צבע בלוח דמקה ונשתמש בה כתמונת רקע של <textarea>. (אני משתמש/ת באזור טקסט כי ניתן לשנות אותו כברירת מחדל):

<!-- index.html -->
<!doctype html>
<style>
  textarea {
    background-image: paint(checkerboard);
  }
</style>
<textarea></textarea>
<script>
  CSS.paintWorklet.addModule('checkerboard.js');
</script>
// checkerboard.js
class CheckerboardPainter {
  paint(ctx, geom, properties) {
    // Use `ctx` as if it was a normal canvas
    const colors = ['red', 'green', 'blue'];
    const size = 32;
    for(let y = 0; y < geom.height/size; y++) {
      for(let x = 0; x < geom.width/size; x++) {
        const color = colors[(x + y) % colors.length];
        ctx.beginPath();
        ctx.fillStyle = color;
        ctx.rect(x * size, y * size, size, size);
        ctx.fill();
      }
    }
  }
}

// Register our class under a specific name
registerPaint('checkerboard', CheckerboardPainter);

אם השתמשתם ב-<canvas> בעבר, הקוד הזה אמור להיראות לכם מוכר. לצפייה בהדגמה בשידור חי

אזור טקסט עם תבנית של לוח דמקה כתמונת רקע
אזור טקסט עם תבנית של לוח דמקה כתמונת רקע.

ההבדל בין שימוש בתמונת רקע נפוצה כאן הוא שהדפוס ישורטט מחדש לפי דרישה, בכל פעם שהמשתמש ישנה את גודל אזור הטקסט. המשמעות היא שתמונת הרקע תמיד גדולה בדיוק כמו שהיא צריכה להיות, כולל הפיצוי על מסכים בצפיפות גבוהה.

זה די מגניב, אבל הוא גם די סטטי. האם נרצה לכתוב Worklet חדש בכל פעם שנרצה להשתמש באותה תבנית, אבל עם ריבועים בגודל שונה? התשובה היא לא.

יצירת פרמטרים ל-worklet

למרבה המזל, העבודה של צבע הקוד יכולה לגשת למאפייני CSS אחרים, וכאן הפרמטר הנוסף properties נכנס לתמונה. כשנותנים למחלקה מאפיין inputProperties סטטי, אפשר להירשם לשינויים בכל נכס CSS, כולל מאפיינים מותאמים אישית. הערכים יוצגו לכם דרך הפרמטר properties.

<!-- index.html -->
<!doctype html>
<style>
  textarea {
    /* The paint worklet subscribes to changes of these custom properties. */
    --checkerboard-spacing: 10;
    --checkerboard-size: 32;
    background-image: paint(checkerboard);
  }
</style>
<textarea></textarea>
<script>
  CSS.paintWorklet.addModule('checkerboard.js');
</script>
// checkerboard.js
class CheckerboardPainter {
  // inputProperties returns a list of CSS properties that this paint function gets access to
  static get inputProperties() { return ['--checkerboard-spacing', '--checkerboard-size']; }

  paint(ctx, geom, properties) {
    // Paint worklet uses CSS Typed OM to model the input values.
    // As of now, they are mostly wrappers around strings,
    // but will be augmented to hold more accessible data over time.
    const size = parseInt(properties.get('--checkerboard-size').toString());
    const spacing = parseInt(properties.get('--checkerboard-spacing').toString());
    const colors = ['red', 'green', 'blue'];
    for(let y = 0; y < geom.height/size; y++) {
      for(let x = 0; x < geom.width/size; x++) {
        ctx.fillStyle = colors[(x + y) % colors.length];
        ctx.beginPath();
        ctx.rect(x*(size + spacing), y*(size + spacing), size, size);
        ctx.fill();
      }
    }
  }
}

registerPaint('checkerboard', CheckerboardPainter);

עכשיו אפשר להשתמש באותו קוד לכל לוח דמקה מסוגים שונים. אבל יותר מזה, עכשיו אנחנו יכולים להיכנס לכלי הפיתוח ולשחק עם הערכים עד שמוצאים את המראה המתאים.

דפדפנים שלא תומכים ב-worklet של ציורים

נכון למועד כתיבת הנתונים, הוטמעו רק עבודת צבע ב-Chrome. אומנם יש אותות חיוביים מכל ספקי הדפדפנים האחרים, אבל אין התקדמות רבה. כדי להתעדכן, בדקו באופן קבוע האם Houdini מוכן? בינתיים, הקפידו להשתמש בשיפור הדרגתי כדי שהקוד ימשיך לפעול גם אם אין תמיכה במעבודת צבע. כדי לוודא שהכול יפעל כמו שצריך, צריך להתאים את הקוד בשני מקומות: ב-CSS וב-JS.

כדי לזהות תמיכה ב-painting ב-JS, צריך לבדוק את האובייקט CSS: js if ('paintWorklet' in CSS) { CSS.paintWorklet.addModule('mystuff.js'); } בצד ה-CSS יש שתי אפשרויות. אפשר להשתמש ב-@supports:

@supports (background: paint(id)) {
  /* ... */
}

טריק קומפקטי יותר הוא להשתמש בעובדה ששירות CSS מבטל תוקף ולאחר מכן מתעלם מהצהרת נכס מלאה אם יש בה פונקציה לא ידועה. אם מציינים מאפיין פעמיים – קודם בלי עבודת צבע ואחר כך באמצעות עבודת הצבע – מקבלים שיפור הדרגתי:

textarea {
  background-image: linear-gradient(0, red, blue);
  background-image: paint(myGradient, red, blue);
}

בדפדפנים עם תמיכה ב-worklet של ציורים, ההצהרה השנייה של background-image תחליף את ההצהרה הראשונה. בדפדפנים ללא תמיכה ב-worklet של ציורים, ההצהרה השנייה לא תקינה ותימחק, כך שההצהרה הראשונה תישאר בתוקף.

צבע Polyfill של CSS

לשימושים רבים אפשר גם להשתמש ב-CSS Paint Polyfill, שמוסיף תמיכה ב-CSS Custom Paint ו- Paint Worklets לדפדפנים מודרניים.

תרחישים לדוגמה

יש תרחישי שימוש רבים לעבודות צבע, חלקם בולטים יותר מאחרים. אחת הדרכים הבולטות יותר היא שימוש במודל ציור כדי להקטין את גודל ה-DOM. לעיתים קרובות, אלמנטים נוספים אך ורק כדי ליצור קישוטים באמצעות CSS. לדוגמה, ב-Material Design Lite, הלחצן עם אפקט הגלים מכיל עוד 2 רכיבי <span> שמאפשרים להטמיע את גלגול היד. אם יש לכם הרבה לחצנים, זה יכול להוסיף עד מספר רב של רכיבי DOM ועלולים לפגוע בביצועים בניידים. אם מטמיעים את אפקט הגלים באמצעות עבודת צבע במקום זאת, מקבלים 0 רכיבים נוספים ורק עבודת צבע אחת. בנוסף, יש משהו שהרבה יותר קל להתאים אישית ולהגדיר לו פרמטרים.

חיסרון נוסף של שימוש ב-צייר לסביבת עבודה הוא שברוב המקרים, פתרון השימוש ב-painting הוא קטן במונחים של בייטים. כמובן שיש חילוק: קוד הצבע שלכם יפעל בכל פעם שגודל הקנבס או הפרמטרים שלכם ישתנו. כך שאם הקוד שלכם מורכב ולוקח זמן רב, הוא עלול ליצור בעיות בממשק (jank). אנחנו ב-Chrome עובדים על העברה של לעבודות צבע מה-thread הראשי, כדי שאפילו לעבודות צבע ממושכות לא ישפיעו על הרספונסיביות של ה-thread הראשי.

מבחינתי, הלקוח הפוטנציאלי המרתק ביותר הוא שעבודת הצביעה מאפשרת מילוי יעיל של תכונות CSS שעדיין לא קיים בדפדפן. דוגמה אחת היא למלא שכבות-על של פוליגונים עד שהם מגיעים ל-Chrome במקור. דוגמה נוספת: בפגישת CSS, הוחלט שעכשיו יש לכם אפשרות להשתמש בכמה צבעי גבול. בזמן שהפגישה עדיין נמשכה, הקולגה שלי, איאן קילפטריק, כתב polyfill לפעולת ה-CSS החדשה הזו באמצעות עבודת צבע.

חשיבה מחוץ לקופסה

רוב האנשים מתחילים לחשוב על תמונות רקע ותמונות גבולות כשהם לומדים על עבודת צבע. תרחיש שימוש פחות אינטואיטיבי אחד לסביבת העבודה הוא mask-image ליצור צורות שרירותיות לרכיבי DOM. לדוגמה, יהלום:

רכיב DOM בצורת יהלום.
רכיב DOM בצורת יהלום.

mask-image לוקחת תמונה בגודל הרכיב. אזורים שבהם תמונת המסכה שקופה, הרכיב שקוף. אזורים שבהם תמונת המסכה אטומה, הרכיב אטום.

עכשיו ב-Chrome

עבודת הצביעה נמצאת ב-Chrome Canary כבר זמן מה. ב-Chrome 65 הוא מופעל כברירת מחדל. נסו את האפשרויות החדשות שנוצרת מעבודת הצבע והראו לנו מה יצרתם! לקבלת השראה נוספת, עיינו באוסף של וינסנט דה אוליביירה.