ממשק 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 של ציור.

כתיבת וורקלט של Paint

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

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

registerPaint('myPainter', MyPainter);

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

כדוגמה מבוא, נכתוב שעבודת צבע של לוח דמקה ונשתמש בה כתמונת רקע של <textarea>. (אני משתמש ב-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);

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

דפדפנים שלא תומכים ב-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 Paint Polyfill

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

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

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

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

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

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

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

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

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

עכשיו ב-Chrome

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