Houdini – הסרת מסתורי CSS

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

הזן את הודיני!

כוח המשימה של Houdini מורכב מהנדסים מ-Mozilla,‏ Apple,‏ Opera,‏ Microsoft,‏ HP,‏ Intel ו-Google, שפועלים יחד כדי לחשוף חלקים מסוימים ממנוע ה-CSS למפתחי אינטרנט. צוות המשימה עובד על אוסף של טיוטות במטרה לגרום ל-W3C לאשר אותן להפוך לסטנדרטים בפועל באינטרנט. הם הגדירו לעצמם כמה יעדים ברמה גבוהה, הפכו אותם לטיוטות של מפרטים, שהובילו ליצירה של קבוצה של טיוטות מפרטים תומכות ברמה נמוכה יותר.

האוסף של הטיוטים האלה הוא בדרך כלל מה שאנשים מתכוונים אליו כשהם מדברים על 'Houdini'. נכון למועד כתיבת המאמר, רשימת הטיוטים חלקית וחלק מהטיוטות הן פשוט placeholders.

המפרטים

worklet (מפרט)

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

המטרה של Houdini היא לחשוף ממשקי API חדשים כדי לאפשר למפתחי אינטרנט לחבר את הקוד שלהם למנוע ה-CSS ולמערכות שמסביב. סביר להניח שחלק מקטעי הקוד האלה יצטרכו לפעול בכל פריים. חלק מהם חייבים להיות כאלה מעצם הגדרתם. ציטוט ממפרט Web Worker:

כלומר, מופעי worker של אינטרנט לא מתאימים למשימות ש-Houdini מתכנן לבצע. לכן, הומצאו רכיבי worklet. ב-Worklets נעשה שימוש ב-ES2015 classes כדי להגדיר אוסף של שיטות, שהחתימות שלהן מוגדרות מראש לפי סוג ה-Worklet. הם קלילים ולטווח קצר.

CSS Paint API (מפרט)

Paint API מופעל כברירת מחדל ב-Chrome 65. מבוא מפורט

worklet של Compositor

ה-API שמתואר כאן יצא משימוש. ה-worklet של Compositor עוצב מחדש ונקרא עכשיו 'Animation Worklet'. מידע נוסף על הגרסה הנוכחית של ה-API

למרות שמפרט ה-worklet של המאגר עבר ל-WICG ויעבור שיפורים, זהו המפרט שמעניין אותי הכי הרבה. מנוע ה-CSS מעביר חלק מהפעולות אל כרטיס המסך של המחשב, אבל זה תלוי גם בכרטיס המסך וגם במכשיר באופן כללי.

בדרך כלל הדפדפן לוקח את עץ ה-DOM, ומחליט, על סמך קריטריונים ספציפיים, לתת לשלוחי משנה ולענפים מסוימים שכבה משלהם. תתי-העצים האלה צובעים את עצמם (יכול להיות שבעתיד ייעשה שימוש בעבודות צבע). בשלב האחרון, כל השכבות האלה, שעכשיו צבועים, הן נערמות וממוקמות זו על גבי זו, תוך שמירה על צירי z, המרות בתלת-ממד וכאלה, כדי ליצור את התמונה הסופית שמוצגת במסך. התהליך הזה נקרא הרכבה והוא מופעל על ידי המחבר.

היתרון של תהליך הרכבת התמונה הוא שאין צורך לגרום לכל הרכיבים לצייר את עצמם מחדש כשגוללים בדף קצת. במקום זאת, תוכלו להשתמש שוב בשכבות מהפריים הקודם ולהריץ מחדש את המאגר עם מיקום הגלילה המעודכן. כך הדברים מתבצעים מהר. כך אנחנו יכולים להגיע ל-60fps.

מודול worklet של רכיב עיבוד תמונה.

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

כדי לקבל מידע ספציפי יותר, אפשר להודיע לדפדפן שאתם רוצים להתחבר לתהליך הרכבת התמונה של צומת DOM מסוים, ולבקש גישה למאפיינים מסוימים כמו מיקום הגלילה, transform או opacity. כך הרכיב יועבר לשכבה משלו, ובכל פריים הקוד שלכם ייכלל. אפשר להזיז את השכבה על ידי ביצוע מניפולציות על השכבות שמתחלפות ולשנות את המאפיינים שלה (למשל opacity), וכך לעשות דברים מיוחדים במהירות מסחררת של 60FPS.

הנה הטמעה מלאה של גלילה פרלקסית באמצעות worklet של הקומפוזיטור.

// main.js
window.compositorWorklet.import('worklet.js')
    .then(function() {
    var animator = new CompositorAnimator('parallax');
    animator.postMessage([
        new CompositorProxy($('.scroller'), ['scrollTop']),
        new CompositorProxy($('.parallax'), ['transform']),
    ]);
    });

// worklet.js
registerCompositorAnimator('parallax', class {
    tick(timestamp) {
    var t = self.parallax.transform;
    t.m42 = -0.1 * self.scroller.scrollTop;
    self.parallax.transform = t;
    }

    onmessage(e) {
    self.scroller = e.data[0];
    self.parallax = e.data[1];
    };
});

רוברטו פלאק (Robert Flack) כתב polyfill ל-worklet של המאגר, כדי שתוכלו לנסות אותו – כמובן עם השפעה משמעותית יותר על הביצועים.

worklet של פריסה (מפרט)

הוצעה טיוטה ראשונה של המפרט. תהליך ההטמעה נמשך זמן קצר.

שוב, המפרט לכך כמעט ריק, אבל הקונספט מעניין: כותבים את הפריסה שלכם! ה-worklet של הפריסה אמור לאפשר לכם לבצע את הפעולה display: layout('myLayout') ולהריץ את ה-JavaScript כדי לסדר את הצאצאים של הצומת בתיבה של הצומת.

כמובן, הפעלה של הטמעה מלאה של JavaScript של פריסה flex-box ב-CSS איטית יותר מהפעלה של הטמעה מקורית מקבילה, אבל קל לדמיין תרחיש שבו קיצורי דרך יכולים להניב שיפור בביצועים. נניח שהאתר מכיל כל דבר חוץ מאריחים, כמו Windows 10 או פריסה בסגנון בנייה. לא נעשה שימוש במיקום מוחלט וקבוע, לא נעשה שימוש ב-z-index, וגם לא באלמנטים אף פעם אין חפיפה או שיש להם גבול או אפשרויות נוספות. היכולת לדלג על כל הבדיקות האלה בפריסה מחדש יכולה להניב ביצועים טובים יותר.

registerLayout('random-layout', class {
    static get inputProperties() {
        return [];
    }
    static get childrenInputProperties() {
        return [];
    }
    layout(children, constraintSpace, styleMap) {
        const width = constraintSpace.width;
        const height = constraintSpace.height;
        for (let child of children) {
            const x = Math.random()*width;
            const y = Math.random()*height;
            const constraintSubSpace = new ConstraintSpace();
            constraintSubSpace.width = width-x;
            constraintSubSpace.height = height-y;
            const childFragment = child.doLayout(constraintSubSpace);
            childFragment.x = x;
            childFragment.y = y;
        }

        return {
            minContent: 0,
            maxContent: 0,
            width: width,
            height: height,
            fragments: [],
            unPositionedChildren: [],
            breakToken: null
        };
    }
});

CSSOM ממוין (מפרט)

Typed CSSOM (מודל אובייקטים של CSS או מודל אובייקטים של גיליונות סגנונות מדורגים) פותר בעיה שכולנו כנראה נתקלת בה ולימדנו פשוט לסבול אותה. אני רוצה להדגים באמצעות שורת JavaScript:

    $('#someDiv').style.height = getRandomInt() + 'px';

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

הטיוטה הזו היא אחת מהטיוטות המתקדמות יותר, וpolyfill כבר נמצא בפיתוח. (כתב ויתור: השימוש ב-polyfill יוסיף כמובן עוד יותר עלות מחשוב. המטרה היא להראות עד כמה ה-API נוח).

במקום מחרוזות, תעבדו על StylePropertyMap של רכיב, שבו לכל מאפיין CSS יש מפתח משלו וסוג ערך תואם. למאפיינים כמו width, סוג הערך הוא LengthValue. LengthValue הוא מילון של כל היחידות ב-CSS, כמו em, ‏ rem, ‏ px, ‏ percent וכו'. הגדרת height: calc(5px + 5%) תגרום ליצירת LengthValue{px: 5, percent: 5}. נכסים מסוימים, כמו box-sizing, מקבלים רק מילות מפתח מסוימות ולכן יש להם ערך מסוג KeywordValue. כך ניתן לבדוק את החוקיות של המאפיינים האלה בזמן הריצה.

<div style="width: 200px;" id="div1"></div>
<div style="width: 300px;" id="div2"></div>
<div id="div3"></div>
<div style="margin-left: calc(5em + 50%);" id="div4"></div>
var w1 = $('#div1').styleMap.get('width');
var w2 = $('#div2').styleMap.get('width');
$('#div3').styleMap.set('background-size',
    [new SimpleLength(200, 'px'), w1.add(w2)])
$('#div4')).styleMap.get('margin-left')
    // => {em: 5, percent: 50}

מאפיינים וערכים

(spec)

האם אתם מכירים את המאפיינים המותאמים אישית של CSS (או את הכינוי הלא רשמי שלהם, 'משתני CSS')? זהו, אבל עם סוגים! עד עכשיו, המשתנים יכלו לכלול רק ערכים של מחרוזות, והם השתמשו בגישה פשוטה של חיפוש והחלפה. הטיוטה הזו תאפשר לכם לא רק לציין סוג למשתנים, אלא גם להגדיר ערך ברירת מחדל ולהשפיע על התנהגות הירושה באמצעות JavaScript API. מבחינה טכנית, האפשרות הזו תאפשר גם להציג אנימציה של מאפיינים מותאמים אישית באמצעות מעברים ואנימציות רגילים של CSS, ואז אנחנו מביאים זאת בחשבון.

["--scale-x", "--scale-y"].forEach(function(name) {
document.registerProperty({
    name: name,
    syntax: "<number>",
    inherits: false,
    initialValue: "1"
    });
});

מדדי גופן

מדדי הגופן פועלים בדיוק כמו שהם נשמעים. מהי התיבה התוחמת (או התיבות התוחמות) כשמעבדים את מחרוזת X עם גופן Y בגודל Z? מה קורה אם משתמשים בהערות ב-Ruby? התבקשנו הרבה בקשות, ולכן הודיני צריך סוף סוף להגשים את המשאלות האלה.

רגע, יש עוד!

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

הדגמות

הקוד של ההדגמה זמין כקוד פתוח (הדגמה פעילה באמצעות polyfill).