ฮูดินี่ - ไขข้อข้องใจเกี่ยวกับ CSS

เคยคิดไหมว่า CSS ทำงานหนักเพียงใด คุณเปลี่ยนแอตทริบิวต์เดียวและทันใดนั้นทั้งเว็บไซต์ก็ปรากฏในเลย์เอาต์อื่น การดำเนินการนี้คล้ายกับเวทมนตร์ จนถึงตอนนี้ เราซึ่งเป็นชุมชนนักพัฒนาเว็บได้เพียงเห็นและสังเกตความมหัศจรรย์นี้ จะเกิดอะไรขึ้นหากเราต้องการคิดคำพูดเอง ในกรณีที่เราต้องเป็นผู้วิเศษ

พบกับ Houdini

ทีมปฏิบัติการ Houdini ประกอบด้วยวิศวกรจาก Mozilla, Apple, Opera, Microsoft, HP, Intel และ Google ที่ทำงานร่วมกันเพื่อแสดงข้อมูลบางส่วนของเครื่องมือ CSS ต่อนักพัฒนาเว็บ คณะทำงานกำลังดำเนินการกับชุดร่างโดยมีเป้าหมายเพื่อให้ W3C ยอมรับร่างเหล่านี้เพื่อใช้เป็นมาตรฐานเว็บจริง พวกเขาตั้งเป้าหมายระดับสูงไว้ 2-3 รายการ แล้วเปลี่ยนเป็นร่างข้อกำหนด ซึ่งทำให้เกิดชุดร่างข้อกำหนดระดับล่างที่สนับสนุน

โดยทั่วไปแล้ว รายงานฉบับร่างเหล่านี้คือสิ่งที่คนพูดถึงเมื่อพูดถึง "Houdini" ขณะเขียนบทความนี้ รายการฉบับร่างยังไม่สมบูรณ์และฉบับร่างบางรายการเป็นเพียงตัวยึดตำแหน่ง

ข้อกำหนด

เวิร์กเลต (spec)

ชิ้นงานเพียงอย่างเดียวนั้นไม่ค่อยมีประโยชน์ แนวคิดเหล่านี้เป็นแนวคิดที่นำมาใช้ในภายหลังเพื่อทำให้ร่างต่างๆ เป็นไปได้ หากคุณนึกถึง Web Worker เมื่ออ่าน "Worklet" ก็ไม่ผิด แนวคิดของคำเหล่านี้ทับซ้อนกันมาก แล้วทำไมเราถึงต้องสร้างสิ่งใหม่เมื่อมีแรงงานอยู่แล้ว

เป้าหมายของ Houdini คือการแสดง API ใหม่เพื่อช่วยให้นักพัฒนาเว็บสามารถเชื่อมต่อโค้ดของตนเองเข้ากับเครื่องมือ CSS และระบบรอบข้างได้ เราอาจคาดเดาได้ว่าจะต้องเรียกใช้เศษโค้ดเหล่านี้ทุกเฟรม บางรายการต้องระบุตามคำจำกัดความ ข้อความอ้างอิงจากข้อกําหนดของ Web Worker

ซึ่งหมายความว่า Web Worker ไม่เหมาะสําหรับสิ่งที่ Houdini วางแผนจะทำ เราจึงคิดค้น Worklet ขึ้นมา เวิร์กเลตใช้คลาส ES2015 เพื่อกำหนดชุดเมธอด ซึ่งลายเซ็นจะกำหนดไว้ล่วงหน้าโดยประเภทของเวิร์กเลต ข้อมูลเหล่านี้มีน้ำหนักเบาและมีอายุสั้น

CSS Paint API (ข้อกำหนด)

Paint API จะเปิดใช้โดยค่าเริ่มต้นใน Chrome 65 อ่านข้อมูลเบื้องต้นโดยละเอียด

Worklet คอมโพสเซอร์

API ที่อธิบายไว้ที่นี่ล้าสมัยแล้ว เราได้ออกแบบเวิร์กเลตคอมโพสิตใหม่และเสนอชื่อเป็น "เวิร์กเลตสำหรับแอนิเมชัน" อ่านข้อมูลเพิ่มเติมเกี่ยวกับรุ่นปัจจุบันของ API

แม้ว่าข้อกำหนดของเวิร์กเลตคอมโพสิตจะย้ายไปยัง WICG และจะมีการปรับปรุง แต่ข้อกำหนดนี้ก็เป็นข้อกำหนดที่เราตื่นเต้นมากที่สุด เครื่องมือ CSS จะส่งการดำเนินการบางอย่างไปยังการ์ดกราฟิกของคอมพิวเตอร์ แต่ทั้งนี้ขึ้นอยู่กับการ์ดกราฟิกและอุปกรณ์ของคุณโดยทั่วไป

โดยปกติแล้วเบราว์เซอร์จะใช้ต้นไม้ DOM และตัดสินใจที่จะให้บางสาขาและซับต้นไม้มีเลเยอร์ของตัวเองตามเกณฑ์ที่เฉพาะเจาะจง ต้นไม้ย่อยเหล่านี้จะวาดภาพตัวเองลงบนพื้นผิว (อาจใช้เวิร์กเลตการวาดภาพในอนาคต) ขั้นตอนสุดท้ายคือระบบจะวางซ้อนเลเยอร์ทั้งหมดที่วาดแล้วเหล่านี้และวางซ้อนกันโดยคำนึงถึง Z-Index, การเปลี่ยนรูปแบบ 3 มิติ และอื่นๆ เพื่อให้ได้รูปภาพสุดท้ายที่ปรากฏบนหน้าจอ กระบวนการนี้เรียกว่าการจัดวางและดำเนินการโดยคอมโพสิตอร์

ข้อดีของกระบวนการคอมโพสคือคุณไม่จําเป็นต้องทําให้องค์ประกอบทั้งหมดวาดภาพใหม่เมื่อหน้าเว็บเลื่อนไปเพียงเล็กน้อย แต่คุณใช้เลเยอร์จากเฟรมก่อนหน้าซ้ำได้ แล้วเรียกใช้คอมโพสิตอีกครั้งโดยระบุตําแหน่งการเลื่อนที่อัปเดตแล้ว ซึ่งจะช่วยให้ดำเนินการต่างๆ ได้อย่างรวดเร็ว ซึ่งจะช่วยให้เราเล่นที่ 60 fps ได้

Worklet คอมโพสเซอร์

อย่างที่ชื่อบอกไว้ เวิร์กเลตคอมโพสิตอร์ให้คุณเชื่อมต่อกับคอมโพสิตอร์และส่งผลต่อวิธีวางตำแหน่งและวางเลเยอร์ขององค์ประกอบที่วาดไว้แล้วบนเลเยอร์อื่นๆ

หากต้องการเจาะจงมากขึ้น คุณสามารถบอกเบราว์เซอร์ว่าคุณต้องการเชื่อมต่อกับกระบวนการคอมโพสิตสำหรับโหนด DOM บางโหนด และสามารถขอสิทธิ์เข้าถึงแอตทริบิวต์บางอย่าง เช่น ตำแหน่งการเลื่อน, transform หรือ opacity ซึ่งจะบังคับให้องค์ประกอบนี้อยู่ในเลเยอร์ของตัวเอง และในเฟรมแต่ละเฟรม ระบบจะเรียกใช้โค้ดของคุณ คุณสามารถย้ายเลเยอร์ได้ด้วยการปรับเปลี่ยนการเปลี่ยนรูปแบบเลเยอร์และเปลี่ยนแอตทริบิวต์ (เช่น opacity) ซึ่งจะช่วยให้คุณทำสิ่งต่างๆ เจ๋งๆ ได้ในอัตราเฟรม 60 fps

ต่อไปนี้เป็นการใช้งานแบบเต็มสำหรับการเลื่อนแบบพารัลแลกซ์โดยใช้คอมโพสิเตอร์ เวิร์กเลต

// 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 เลย์เอาต์ (spec)

มีการเสนอฉบับร่างข้อมูลจำเพาะจริงฉบับแรกแล้ว การนำไปใช้ยังอยู่อีกสักพัก

อีกครั้ง ข้อมูลจำเพาะสำหรับข้อกำหนดนี้แทบจะว่างเปล่า แต่แนวคิดน่าสนใจมาก นั่นคือเขียนเลย์เอาต์ของคุณเอง เวิร์กเลตเลย์เอาต์ควรช่วยให้คุณทำ 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 ที่มีประเภท (ข้อกำหนด)

CSSOM แบบมีประเภท (CSS Object Model หรือ Cascading Style Sheets Object Model) แก้ปัญหาที่เราทุกคนอาจพบเจอและเรียนรู้ที่จะทนรับ เราขออธิบายด้วยบรรทัด 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 จะช่วยให้ความปรารถนาเหล่านี้เป็นจริงได้ในที่สุด

แต่ยังไม่หมดเท่านี้!

ยังมีข้อมูลจำเพาะอื่นๆ อีกมากมายในรายการฉบับร่างของ Houdini แต่อนาคตของข้อมูลจำเพาะเหล่านั้นค่อนข้างไม่แน่นอนและเป็นเพียงตัวยึดตําแหน่งสำหรับแนวคิดเท่านั้น ตัวอย่าง ได้แก่ ลักษณะการรองรับการแสดงผลที่มากเกินไปที่กำหนดเอง, API ส่วนขยายไวยากรณ์ CSS, การขยายลักษณะการเลื่อนแบบเนทีฟ และสิ่งที่ท้าทายในทำนองเดียวกัน ซึ่งทั้งหมดนี้ช่วยให้แพลตฟอร์มเว็บทำสิ่งต่างๆ ที่ไม่เคยทำได้มาก่อน

เดโม

เราได้เปิดแหล่งที่มาของโค้ดสําหรับเดโม (เดโมเวอร์ชันที่ใช้จริงโดยใช้ polyfill)