ממשק API של צבע CSS

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

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

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

כתיבת worklet של צבע

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

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

registerPaint('myPainter', MyPainter);

בתוך הקריאה החוזרת של 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> בעבר, הקוד הזה אמור להיראות מוכר. ההדגמה בשידור חי

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

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

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

הגדרת פרמטרים של 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 של צבע

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

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

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

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

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

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

מילוי פומבי של צבע CSS

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

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

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

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

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

חשיבה מחוץ לתיבה

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

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

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

עכשיו ב-Chrome

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