<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom">

  <channel>
    <title>Vincent De Oliveira - CSS, SVG, UI &#38; Maps</title>
    <link>http://iamvdo.me/en</link>
    <lastBuildDate>Wed, 16 Dec 2020 15:00:00 +0100</lastBuildDate>
    <description></description>
    <atom:link href="https://iamvdo.me/en/rss" rel="self" type="application/rss+xml" />
  
            <item>
      <title>SVG-in-CSS</title>
      <link>https://css-houdini.iamvdo.me/svg-in-css</link>
      <guid>https://css-houdini.iamvdo.me/svg-in-css</guid>
      <pubDate>Wed, 16 Dec 2020 15:00:00 +0100</pubDate>
        
                  <description><![CDATA[<p>Generic Houdini worklet to draw SVG shapes - <a href="https://css-houdini.iamvdo.me/svg-in-css">https://css-houdini.iamvdo.me/svg-in-css</a></p>]]></description>
            
    </item>
            <item>
      <title>CSS Houdini</title>
      <link>https://iamvdo.me/en/blog/css-houdini</link>
      <guid>https://iamvdo.me/en/blog/css-houdini</guid>
      <pubDate>Mon, 08 Jun 2020 16:30:00 +0200</pubDate>
        
                  <description><![CDATA[<p>Before we dive in, let me provide some background.</p>
<p>In 2013, a bunch of people signed the <a href="https://extensiblewebmanifesto.org/">extensible web manifesto</a>, in favor of an Extensible Web Platform. The goal is pretty obvious: elaborate new kind of standards, that provides authors freedom and flexibility to build their own features. <strong>The aim is to define low-level APIs, an access to the core of the browsers</strong>, and so, involve authors into the innovative process, without restrict them to the historic standards.</p>
<p>In the HTML ecosytem, web components arise from that philosophy. Many standards have been set up, and we’re now able to build our own HTML components, thus extending the HTML language. Of course, the solution is based on web languages: HTML, CSS and JavaScript.</p>
<p>On the CSS land, that’s the ambition of CSS Houdini: new standards to design our own graphic effects, our own layouts, and maybe our own extensions (new selectors, new properties, new functions, etc.), ans so on. <strong>In one word, extend CSS as we want</strong>.</p>
<p>Technically, <strong>it is possible by enabling access to every phase browsers perform to render text files as pixels on screen</strong>. We can break down every phase that way:</p>
<ul>
<li>the first phase is the parsing, the browser reads and deciphers HTML and CSS files</li>
<li>the browser builds the <abbr title="Document Object Model">DOM</abbr> and the <abbr title="CSS Object Model">CSSOM</abbr>, object’s representation of these files</li>
<li>from that, derive the Render Tree, or Layer Tree, a kind of a list of styles to be applied to each elements</li>
<li>then, the browser draws each element going through 3 steps:
<ul>
<li><em>Layout</em>, the browser applies layout rules (display, sizes, margins, etc.) and thus, builds the architecture. We also use the word <em>reflow</em>.</li>
<li><em>Paint</em>, the browser applies graphical rules (backgrounds, borders, images). We also use the word <em>repaint</em>.</li>
<li><em>Composite</em>, a compositing phase, which stacks together layers created from specific CSS properties (transforms, opacity, etc.). Often performed by the GPU and in a separate thread.</li>
</ul></li>
</ul>
<p><strong>Right now, if we want to build a fancy graphic effect, we have to alter the DOM</strong>. That’s the only available phase to the core mechanism of browsers.</p>
<figure><img src="http://iamvdo.me/content/01-blog/33-css-houdini/pipeline-1.jpg"><figcaption class="caption">Rendering pipeline of browsers, with only DOM available</figcaption></figure>
<p>The ambition of CSS Houdini is to enable all internals steps, as shown in image below.</p>
<figure><img src="http://iamvdo.me/content/01-blog/33-css-houdini/pipeline-2.jpg"><figcaption class="caption">Rendering pipeline of browsers, with all steps enabled (future)</figcaption></figure>
<p>To make this happen, <strong>many new APIs (mostly JavaScript) are actively standardized</strong>.</p>
<p>You can notice that CSSOM (pretty complex and badly implemented by browsers) is more or less replaced by Typed OM. This new, more robust, standard is an object-based API to manipulate CSS (files, at-rules, selectors, declarations, properties, values, etc.).</p>
<p>Typed OM is therefore useful every time you have to handle CSS with JS. Like for example to limit risky concatenations:</p>
<pre><code class="language-javascript">// CSSOM
el.style.setProperty('transform', 'translate(' + x + 'px, ' + y + 'px)')
// Typed OM
el.attributeStyleMap.set('transform', new CSSTranslate(CSS.px(x), CSS.px(y)))</code></pre>
<p>Or simply, to retrieve values as objects instead of strings:</p>
<pre><code class="language-javascript">// CSSOM
getComputedStyle(el).getPropertyValue('width')      // '50px'
// Typed OM
el.computedStyleMap().get('width')                  // CSSUnitValue {value: 50, unit: 'px'}</code></pre>
<p>CSS Houdini makes extensively use of JavaScript.</p>
<p class="note"><b>Note:</b> you can find CSS Houdini support on <a href="https://ishoudinireadyyet.com">https://ishoudinireadyyet.com</a>. You’ll notice that Chrome (and Chromium-based) is leading, but it’s a bit embellished (well, the website is maintained by Google’s developers). I’ll add details along the article. For Typed OM, only a subset of all CSS properties is supported, but <a href="https://chromium.googlesource.com/chromium/src/+/master/third_party/blink/renderer/core/css/cssom/README.md">there is a list</a>.</p>
<h2 id="build-our-own-properties">Build our own properties<a href="#build-our-own-properties" class="self-link"></a></h2>
<p>Now, talk about use cases.</p>
<p>For many years, <strong>it was already possible to create our own CSS properties</strong>, thanks to custom properties. We also know them as CSS variables.</p>
<p>Let’s take the <code>box-shadow</code> property. If we want to change one of its value, we need to rewrite the whole rule, like in this example to change the blur size on hover</p>
<pre><code class="language-css">.el {
  box-shadow: 0 3px 3px black;
}
.el:hover {
  box-shadow: 0 3px 10px black;
}</code></pre>
<p>Thanks to CSS custom properties, we can define a property, say <code>--box-shadow-blur</code>, and only alter it afterwards. We can use it from the initial state, thanks to the <code>var()</code> function</p>
<pre><code class="language-css">.el {
  --box-shadow-blur: 3px;
  box-shadow: 0 3px var(--box-shadow-blur) black;
}
.el:hover {
  --box-shadow-blur: 10px;
}</code></pre>
<p>It’s really convenient. But, in that specific case, we can’t animate the property. Browsers don’t know what is the expected type, and how to handle it.</p>
<p>This is where the <a href="https://drafts.css-houdini.org/css-properties-values-api/">Properties &amp; Values API</a> from Houdini comes in handy. That specification defines the new at-rule <code>@property</code> (in CSS) and <code>CSS.registerProperty()</code> (in JS), that allow to <strong>register a new custom property, by specifying the expected type</strong>. One benefit is that browsers will now know how to animate it (if possible). Let’s go back to our previous case, and register our new property</p>
<pre><code class="language-css">.el {
  --box-shadow-blur: 3px;
  box-shadow: 0 3px var(--box-shadow-blur) black;
  transition: --box-shadow-blur .45s;
}
.el:hover {
  --box-shadow-blur: 10px;
}

@property --box-shadow-blur {
  syntax: "&lt;length&gt;";
  inherits: false;
  initial-value: 0;
}</code></pre>
<p>There you go, a nice hovering animation, only updating the desired value.</p>
<div class="codepen-placeholder" style="height:418px"><p data-height="418" data-theme-id="0" data-slug-hash="zYvZKqZ" data-user="iamvdo" data-default-tab="css,result" data-preview="false" class="codepen">See the Pen <a href="http://codepen.io/iamvdo/pen/zYvZKqZ">zYvZKqZ</a> by iamvdo (<a href="http://codepen.io/iamvdo">@iamvdo</a>) on <a href="http://codepen.io">CodePen</a></p></div>
<p><strong>This is a first step to extend CSS: ask browsers to learn a new property, previously unknown</strong>. And animations aren’t the only purpose of registering custom properties. It can also improve peformance overall, by specifying that a custom property doesn’t inherit (it will prevent browsers to update styles of many nested elements).</p>
<blockquote>
<p>By the way, avoid adding too many custom properties on <code>:root</code> or <code>body</code> elements. <a href="https://lisilinhart.info/posts/css-variables-performance">You will face some performance pitfalls</a>.</p>
</blockquote>
<p class="note">Support for registering properties is only Blink-based browsers (Chrome, Opera, Edge) for now. However, in both cases (CSS & JS), not every types are implemented (also bound to Typed OM), without exhautive list.</p>
<h2 id="build-our-own-graphic-effects">Build our own graphic effects<a href="#build-our-own-graphic-effects" class="self-link"></a></h2>
<p>Nowadays, the only graphic effects availables are the ones defined by the language. Backgrounds colors, borders, gradients, rounded corners, shadows, etc. Well, you know that.</p>
<p>The future <a href="https://drafts.css-houdini.org/css-paint-api/">CSS Paint API</a> standard, as the name suggests, enables the browsers’ <em>Paint</em> phase. This standard describes an isolated execution environment (a worklet), in which we can programmatically draw an image, like in a HTML <code>&lt;canvas&gt;</code> element. That image can then be used with image-related CSS properties, mainly <code>background-image</code>, <code>border-image</code> and <code>mask-image</code>.</p>
<p>This new standard defines:</p>
<ul>
<li><code>CSS.paintWorklet.addModule('paint.js')</code> to load a worklet</li>
<li><code>registerPaint()</code> to create the image inside the worklet (in a separate file)</li>
<li>the CSS <code>paint()</code> function to use the worklet</li>
</ul>
<p>The worklet’s code is thus isolated from the rest of the page, and called during the <em>Paint</em> phase, <strong>making the drawing very performant, because browsers no longer need to execute every steps each time</strong>. Moreover, browsers can easily improve performance of that specific code (execute it in a separated thread for example).</p>
<p>Let’s take a rather simple effect, no so easy to create: an element with a slanted side, as shown in the image below:</p>
<figure><img src="http://iamvdo.me/content/01-blog/33-css-houdini/css-paint-1.jpg"><figcaption class="caption">Slanted right side effect that we want to achieve</figcaption></figure>
<p>We should be able to design it using a linear gradient, or maybe transformations, but responsive will be hard (and dealing with font sizes also). In any case, many elements or pseudo-elements would be involved.</p>
<p>With Houdini, it becomes so easy. First step, register a new worklet, with our drawing instructions, called <em>slanted</em>:</p>
<pre><code class="language-javascript">registerPaint('slanted', class {
  paint (ctx, geom) {
    ctx.fillStyle = 'hsl(296, 100%, 50%)';
    ctx.beginPath();
    ctx.moveTo(0, 0);
    ctx.lineTo(geom.width, 0);
    ctx.lineTo(geom.width - 20, geom.height);
    ctx.lineTo(0, geom.height);
    ctx.fill();
  }
})</code></pre>
<p>Its <code>paint()</code> method contains drawing commands that build the slanted shape, and can use its 2 arguments:</p>
<ul>
<li><code>ctx</code> is the drawing context</li>
<li><code>geom</code> is an object containing the size of the element where the painting will occur</li>
</ul>
<p>The drawing is based on simple commands, as for the HTML <code>&lt;canvas&gt;</code> element: <code>moveTo()</code> to move the pointer, <code>lineTo()</code> to draw a straight line, etc.</p>
<p>Then, we need to load the worklet and call it from our CSS:</p>
<pre><code class="language-css">.el {
  background-image: paint(slanted);
}</code></pre>
<p>And voilà! The rendering is responsive by default, and automatically redrawn every time the element’s size change (try edit the text).</p>
<div class="codepen-placeholder" style="height:363px"><p data-height="363" data-theme-id="0" data-slug-hash="RwWpGvm" data-user="iamvdo" data-default-tab="result" data-preview="false" class="codepen">See the Pen <a href="http://codepen.io/iamvdo/pen/RwWpGvm">RwWpGvm</a> by iamvdo (<a href="http://codepen.io/iamvdo">@iamvdo</a>) on <a href="http://codepen.io">CodePen</a></p></div>
<p><strong>It’ll become really interesting when we’ll retrieve custom properties values from inside the worklet, and combine them with animations</strong>. To start, let’s build a new worklet in which we’re drawing a circle that adapts to the smallest size of our element:</p>
<pre><code class="language-javascript">// New worklet
registerPaint('circle', class {
  paint(ctx, geom, props) {
    // Get the center point and radius
    const x = geom.width / 2;
    const y = geom.height / 2;
    const radius = Math.min(x, y);

    // Draw the circle
    ctx.fillStyle = 'deeppink';
    ctx.beginPath();
    ctx.arc(x, y, radius, 0, 2 * Math.PI);
    ctx.fill();
  }
}</code></pre>
<div class="codepen-placeholder" style="height:400px"><p data-height="400" data-theme-id="0" data-slug-hash="bGVJWpB" data-user="iamvdo" data-default-tab="result" data-preview="false" class="codepen">See the Pen <a href="http://codepen.io/iamvdo/pen/bGVJWpB">bGVJWpB</a> by iamvdo (<a href="http://codepen.io/iamvdo">@iamvdo</a>) on <a href="http://codepen.io">CodePen</a></p></div>
<p>Next, we’re registering a new custom property <code>--circle-color</code> from CSS, and using it inside the worklet, thanks to the third <code>props</code> argument of the <code>paint()</code> method:</p>
<pre><code class="language-css">.el {
  --circle-color: deepskyblue;
  background-image: paint(circle);
}

@property --circle-color {
  syntax: "&lt;color&gt;";
  inherits: false;
  initial-value: currentcolor;
}</code></pre>
<pre><code class="language-javascript">registerPaint('circle', class {
  static get inputProperties() { return ['--circle-color'] }
  paint(ctx, geom, props) {
    ...
    ctx.fillStyle = props.get('--circle-color').value;
    ...
  }
}</code></pre>
<p>The background of the circle is now authorable right from CSS.</p>
<div class="codepen-placeholder" style="height:400px"><p data-height="400" data-theme-id="0" data-slug-hash="MWaRmbQ" data-user="iamvdo" data-default-tab="css,result" data-preview="false" class="codepen">See the Pen <a href="http://codepen.io/iamvdo/pen/MWaRmbQ">MWaRmbQ</a> by iamvdo (<a href="http://codepen.io/iamvdo">@iamvdo</a>) on <a href="http://codepen.io">CodePen</a></p></div>
<p>Last step, creating three new custom properties, <code>--circle-x</code> and <code>--circle-y</code> to set the circle’s center, and <code>--circle-radius</code> to its size. These three properties are then bring back inside the worklet</p>
<pre><code class="language-javascript">registerPaint('circle', class {
  static get inputProperties() { 
    return [ 
      '--circle-color', '--circle-radius', '--circle-x', '--circle-y'
    ]
  }
  paint(ctx, geom, props) {
    const x = props.get('--circle-x').value;
    const y = props.get('--circle-y').value;
    const radius = props.get('--circle-radius').value;
  }
}</code></pre>
<p>At initial state, the circle’s size is 0, and that property will be animatable in CSS.</p>
<pre><code class="language-css">.el {
  --circle-radius: 0;
  --circle-color: deepskyblue;
  background-image: paint(circle-ripple);
}
.el.animating {
  transition: --circle-radius 1s,
              --circle-color 1s;
  --circle-radius: 300;
  --circle-color: transparent;
}</code></pre>
<p>To finish, we set the center (x, y) in JS each time the user clicks on the element. We’re adding the <code>class</code> to animate the size.</p>
<pre><code class="language-javascript">el.addEventListener('click', e =&gt; {
  el.classList.add('animating');
  el.attributeStyleMap.set('--circle-x', e.offsetX);
  el.attributeStyleMap.set('--circle-y', e.offsetY);
});</code></pre>
<div class="codepen-placeholder" style="height:400px"><p data-height="400" data-theme-id="0" data-slug-hash="RwWVzar" data-user="iamvdo" data-default-tab="result" data-preview="false" class="codepen">See the Pen <a href="http://codepen.io/iamvdo/pen/RwWVzar">RwWVzar</a> by iamvdo (<a href="http://codepen.io/iamvdo">@iamvdo</a>) on <a href="http://codepen.io">CodePen</a></p></div>
<p><strong>Boom! The famous ripple effect from Google Material Design in a few lines of code</strong>. And, a very performant solution.</p>
<p>Thanks to these kind of worklets, we can consider many new effects, or at least make some easier to build. In all my experiments, you can check how to create <a href="https://css-houdini.iamvdo.me/tooltip">a tooltip arrow</a>, a <a href="https://css-houdini.iamvdo.me/smooth-corners">superellipse</a> (aka, iOS rounded corners), <a href="https://css-houdini.iamvdo.me/rough-boxes">rough borders</a> or <a href="https://css-houdini.iamvdo.me/highlighter-marker-annotations">highlighter marker annotations</a>, <a href="https://css-houdini.iamvdo.me/corners-gradient">corners gradient</a>, or a <a href="https://css-houdini.iamvdo.me/irregular-grid">randomly irregular grid</a> if we combine it with CSS masks.</p>
<figure><img src="http://iamvdo.me/content/01-blog/33-css-houdini/css-houdini-rocks.jpg"><figcaption class="caption">Many available effects on https://css-houdini.iamvdo.me</figcaption></figure>
<p class="note">Support of CSS Paint API is only Blink-based browsers. And not 100%: attributes of the CSS <code>paint()</code> function are not supported yet. Using attributes, instead of custom properties, we can produce different results on the same element, as it is shown on the <a href="https://css-houdini.iamvdo.me/inner-borders">inset borders demo</a></p>
<p class="note">Also, all Houdini APIs are closely tied together. To retrieve a custom property from inside a worklet, and use it as object, browsers should implement the Properties & Values API (to register a custom property’s type) and also Typed OM. Even Chrome has an unpredictable implementation. Many tests are required to discover what is supported.</p>
<h2 id="build-our-own-layouts">Build our own layouts<a href="#build-our-own-layouts" class="self-link"></a></h2>
<p>With the same approach, a specific kind of worklet exists to create its own layout mode. This is defined by the <a href="https://drafts.css-houdini.org/css-layout-api/">CSS Layout API</a> standard.</p>
<p>In the same way Flexbox or Grid work, <strong>you can write your own layout engine to lay out elements inside a container</strong>. How? Well, as for the CSS Paint API:</p>
<ul>
<li><code>CSS.layoutWorklet.addModule('layout.js')</code> to load a worklet</li>
<li><code>registerLayout()</code> to build your layout rules inside the worklet</li>
<li>the CSS <code>layout()</code> function to apply the worklet, using the <code>display</code> property</li>
</ul>
<p>Although Flexbox and Grid are giving us many possibilities, some layouts are unachievable in CSS. The most popular one is the <a href="https://masonry.desandro.com/">Masonry layout</a>. Thanks to that new API, it becomes possible, in around 40 lines of JS:</p>
<pre><code class="language-javascript">// Code from https://github.com/GoogleChromeLabs/houdini-samples/blob/master/layout-worklet/masonry/masonry.js 
registerLayout('masonry', class {
  async layout(children, edges, constraints, styleMap) {
    const inlineSize = constraints.fixedInlineSize;

    let columns = Math.ceil(inlineSize / 350);
    let padding = 10;

    // Layout all children with simply their column size.
    const childInlineSize = (inlineSize - ((columns + 1) * padding)) / columns;
    const childFragments = await Promise.all(children.map((child) =&gt; {
      return child.layoutNextFragment({fixedInlineSize: childInlineSize});
    }));

    let autoBlockSize = 0;
    const columnOffsets = Array(columns).fill(0);
    for (let childFragment of childFragments) {
      // Select the column with the least amount of stuff in it.
      const min = columnOffsets.reduce((acc, val, idx) =&gt; {
        if (!acc || val &lt; acc.val) {
          return {idx, val};
        }

        return acc;
      }, {val: +Infinity, idx: -1});

      childFragment.inlineOffset = padding + (childInlineSize + padding) * min.idx;
      childFragment.blockOffset = padding + min.val;

      columnOffsets[min.idx] = childFragment.blockOffset + childFragment.blockSize;
      autoBlockSize = Math.max(autoBlockSize, columnOffsets[min.idx] + padding);
    }

    return {autoBlockSize, childFragments};
  }
});</code></pre>
<p>Then, CSS side:</p>
<pre><code class="language-css">.el {
  display: layout(masonry);
}</code></pre>
<p class="note">To see result, load the following CodePen in a Blink-based browser, with the <i>Web Platform</i> flag enabled</p>
<div class="codepen-placeholder" style="height:543px"><p data-height="543" data-theme-id="0" data-slug-hash="pojPXKx" data-user="iamvdo" data-default-tab="result" data-preview="true" class="codepen">See the Pen <a href="http://codepen.io/iamvdo/pen/pojPXKx">pojPXKx</a> by iamvdo (<a href="http://codepen.io/iamvdo">@iamvdo</a>) on <a href="http://codepen.io">CodePen</a></p></div>
<p>Well, JS code may seems complex at first, but not that much in reality. And above all, the code is isolated from the rest of the page, and called only during the <em>Layout</em> phase, which makes it very performant, as explained before.</p>
<p>Of course, we can build many other layout systems, like the ones used when designing iOS/Android applications. As an example, Google engineers implemented the <a href="https://github.com/GoogleChromeLabs/houdini-samples/tree/master/layout-worklet/relative">Android’s RelativeLayout</a>. We can also be more creative, and build a <a href="https://css-houdini.iamvdo.me/svg-path-layout">layout where elements are set along a SVG path</a>, defined on a custom property:</p>
<pre><code class="language-css">.el {
  display: layout(svg-path);
  --path: path("M100,300c100,-100,150,-120,300,0c150,50,300,0,400,-200");
}</code></pre>
<figure><img src="http://iamvdo.me/content/01-blog/33-css-houdini/svg-path-layout.jpg"><figcaption class="caption">HTML elements are positionned along a SVG path</figcaption></figure>
<p>In that specific case, it prevents us from using absolutely-positionned elements, with arbitrarily values. Maybe we could achieve a similar effect using the <a href="https://www.w3.org/TR/motion-1/">CSS Motion</a> standard (not Houdini) and the <code>offset</code> property, but SVG path isn’t responsive by default (so JS needed) and CSS should set how many items will be laid out on path beforehand.</p>
<p class="note">Support of CSS Layout API is very limited right now. Only Blink-based browsers, with <i>Web Platform</i> flag enabled. This is just the beginning.</p>
<h2 id="even-more">Even more?<a href="#even-more" class="self-link"></a></h2>
<p><strong>There is a last kind of worklet inside Houdini, dedicated to animations performance</strong>, the <a href="https://drafts.css-houdini.org/css-animationworklet/">Animation Worklet API</a>, based on WAAPI (<a href="https://drafts.csswg.org/web-animations/">Web Animations API</a>). As for the other worklets, animation code is isolated, but above all, it extends the concept of time-based baseline. It could be pretty useful to get <strong>performant animations based on user’s interaction, as for the scroll</strong> for example (manual, but also animated):</p>
<p>Let’s take an example, a new worklet that register a simple linear animation (1 to 1)</p>
<pre><code class="language-javascript">registerAnimator('simple', class {
  animate(currentTime, effect) {
    effect.localTime = currentTime;
  }
});</code></pre>
<p>The worklet is loaded, and we create a new JS animation:</p>
<ul>
<li>that updates a custom property <code>--angle</code> for a duration of 1 (with value from 0 to 1 turn)</li>
<li>based on scroll (<code>new ScrollTimeline</code> with <code>scrollSource: scrollElement</code>) and “time” is equivalent to 1</li>
</ul>
<pre><code class="language-javascript">CSS.animationWorklet.addModule('...').then(r =&gt; {
  new WorkletAnimation('simple',
    new KeyframeEffect(el, [
        { '--angle': 0 },
        { '--angle': '1turn' }
      ],
      { duration: 1 }
    ),
    new ScrollTimeline({
      scrollSource: scrollElement,
      timeRange: 1
    }),
  ).play();
});</code></pre>
<p>Finally, the <code>--angle</code> custom property is used in CSS to rotate an entire cube in 3D</p>
<pre><code class="language-css">.cube {
  --angle: 0;
  transform: rotateX(var(--angle)) rotateZ(45deg) rotateY(-45deg);
}</code></pre>
<p class="note">To see result, load the following CodePen in a Blink-based browser, with the <i>Web Platform</i> flag enabled</p>
<div class="codepen-placeholder" style="height:510px"><p data-height="510" data-theme-id="0" data-slug-hash="ExVmqVP" data-user="iamvdo" data-default-tab="result" data-preview="true" class="codepen">See the Pen <a href="http://codepen.io/iamvdo/pen/ExVmqVP">ExVmqVP</a> by iamvdo (<a href="http://codepen.io/iamvdo">@iamvdo</a>) on <a href="http://codepen.io">CodePen</a></p></div>
<p class="note">Support of Animation Worklet is only Blink-based browsers for now, and with <i>Web Platform</i> flag enabled</p>
<p>The ambition of CSS Houdini is to go even further. Nothing really exists for now, but we can mention:</p>
<ul>
<li>the <a href="https://drafts.css-houdini.org/css-parser-api/">CSS Parser API</a> to enable the first phase of browser rendering: read and parse files. I suppose that it will enable us to create our own functions, our own selectors, etc., as we should be able to handle them by ourselves. It is still unclear whether and how everything will work.</li>
<li>the <a href="https://drafts.css-houdini.org/font-metrics-api/">Font Metrics API</a> to get the <a href="http://iamvdo.me/en/blog/css-font-metrics-line-height-and-vertical-align">font metrics from CSS</a>. And this could be very cool.</li>
</ul>
<h2 id="so-real-magic-or-smoke-and-mirrors">So, real magic or smoke and mirrors?<a href="#so-real-magic-or-smoke-and-mirrors" class="self-link"></a></h2>
<p>We could be very exited about all of this (and I am). But, we should take some points into consideration.</p>
<h3 id="new-features">New features<a href="#new-features" class="self-link"></a></h3>
<p>These new APIs boost creativity, by allowing the creation of new effects, or simplifying actual ones.</p>
<p>As mentioned earlier, we can create our own properties, but unfortunately <strong>we can’t really extend existing features</strong>. Speaking of box-shadow’s blur, it is for example impossible to create a directional blur, splitting it in two sub-properties <code>--box-shadow-blur-x</code> and <code>--box-shadow-blur-y</code>. As there is no way to “hack” shadow drawing from browsers.</p>
<p>And even if the CSS Paint API seems ultramodern, it is nothing more than a performant version of <a href="https://webkit.org/blog/176/css-canvas-drawing/"><code>-webkit-canvas()</code></a> that exists since 2008, but now removed from Chrome.</p>
<p>The drawing is executed in a canvas, via its rendering context <code>CanvasRendering2D</code> (and a limited one). <strong>That rendering context was not initially designed for CSS, thus many limitations are emerging</strong>:</p>
<ul>
<li>no simple way to handle borders (<code>border-clip</code>, multiples, etc.), nor shadows, nor background images (repetition, position, size, etc.)</li>
<li>not really convenient to draw outside the background area of an element (doable combining <code>border-image</code> + <code>border-outset</code>)</li>
<li>no way to deal with texts</li>
<li>nothing new to style form elements</li>
<li>etc.</li>
</ul>
<p><strong>In many cases, SVG is a far better and simpler choice</strong>. There are many effects that are undoable today, and Houdini wouldn’t help much. </p>
<p>Regarding the CSS Layout API, only complete layout mode are achievable (like Flexbox or Grid). It’s a big step forward, but <strong>we’re not able to modify how CSS works</strong>.</p>
<p>It is therefore impossible to set sizes, nor margins, on one element, nor change its containing block (for absolutely-positioned elements for example) or stacking context (in particular when there is conflict between properties), nor even to add new pseudo-elements or new entities (maybe, it’s rather a web components use case?). Nothing new for container queries neither.</p>
<h3 id="polyfill">Polyfill<a href="#polyfill" class="self-link"></a></h3>
<p>One of the main purpose of CSS Houdini is to be able to create polyfills (write own code for browsers that lack support). It’s true, Houdini can help, but keep in mind that browsers that implement Houdini but do not support other feature are very rare. Here are some counter-examples I can think of:</p>
<ul>
<li><a href="https://drafts.csswg.org/css-backgrounds-4/#corner-shaping">the <code>corner-shape</code> property</a> isn’t implemented in any browsers, but <a href="https://css-houdini.iamvdo.me/corner-shape">doable with the Paint API</a></li>
<li>the <code>subgrid</code> mode from Grid Layout supported in Firefox, <a href="https://rawgit.com/FremyCompany/css-grid-polyfill/houdini-experiment/bin/demo%5Bsubgrid%5D.html">doable with the Layout API</a></li>
<li><a href="http://iamvdo.me/blog/filtres-css-avances#filter">the <code>filter()</code> function</a> supported in Safari and <a href="https://css-houdini.iamvdo.me/background-properties">doable with the Paint API</a></li>
<li>and presumably more</li>
</ul>
<p>However, no magic here, <strong>the vast majority of CSS is non-polyfillable<sup class="footnote"><a href="#fn-1" id="fnref-1" title="If you doubt, read The Dark Side Of Polyfilling CSS">1</a></sup></strong></p>
<h3 id="performance">Performance<a href="#performance" class="self-link"></a></h3>
<p>This is the key point of CSS Houdini: improve performance of browser rendering. Right now, in 2020, <strong>build performant UIs is very restrictive, and even more when animated</strong>. Layout properties (<code>width</code>, <code>height</code>, <code>margin</code>, <code>left</code> etc.) and even graphic ones (<code>background-color</code>, <code>background-size</code>, etc.) are very expensive to render. That’s why <code>transform</code> and <code>opacity</code> are overused, because these specific properties are rendered during the compositing phase, and often in a separate thread.</p>
<p>As an example, see <a href="https://tobiasahlin.com/blog/how-to-animate-box-shadow/">how a box-shadow can be animated efficiently</a> (spoiler: animate the opacity of a pseudo-element instead)</p>
<p><strong>The use of worklets, isolated from the page and the main thread <sup class="footnote"><a href="#fn-2" id="fnref-2" title="Since Chrome 81 with some constraints">2</a></sup>, gives performant results</strong>, without the exclusive use of <code>transform</code>/<code>opacity</code>. And this is great!</p>
<p>Ironically, my first demo above (registering a custom property to set box-shadow’s blur) is not performant, cause there is no worklet involved.</p>
<p>Regarding the Animation Worklet API, I’m personally not a fan of that solution. WAAPI is, in my opinion, good enough to build performant animations, and to deal with transitions/animations in CSS. To create a scroll-based timeline, I do prefer the <a href="https://drafts.csswg.org/scroll-animations-1/">Scroll-linked Animations</a> specification, with the <code>animation-timeline</code> property and <code>@scroll-timeline</code>, but that is not part of Houdini.</p>
<h3 id="browser-engine-innovation">Browser engine innovation<a href="#browser-engine-innovation" class="self-link"></a></h3>
<p>We can’t talk about rendering performance, with no mention to browser engines. As of today, there are 3 modern browser rendering engines: <strong>Blink</strong> (Chrome, Opera, Edge, etc.), <strong>WebKit</strong> (Safari), and <strong>Gecko</strong> (Firefox).</p>
<p><strong>Houdini APIs are based on a rendering consensus</strong>, which is pretty much the same in each major browsers, but we should mention <a href="https://hacks.mozilla.org/2017/10/the-whole-web-at-maximum-fps-how-webrender-gets-rid-of-jank/">the new Firefox rendering engine: WebRender</a>. The goal of this new core component is to revolutionize the rendering process, by combining the <em>Paint</em> and <em>Compositing</em> phases, and send the whole elements to the GPU, as for video games. It is still in early stage, but once in place, techniques using <code>transform</code>/<code>opacity</code> will be obsoletes. And according to <a href="https://twitter.com/gsnedders">@gsnedders</a>, Houdini APIs that are designed to fix performance issues <strong>in the actual context</strong>, <a href="https://thereshouldbenored.com/posts/edgehtml-demise/">could be harder to implement in a different one</a>.</p>
<p>And this is problematic, either for innovation, or for Houdini.</p>
<h3 id="everything-javascript">Everything is JavaScript<a href="#everything-javascript" class="self-link"></a></h3>
<p>We can regret the biggest part of all APIs is JavaScript. <strong>CSS Houdini is basically JS-in-CSS</strong>. <a href="http://kryogenix.org/code/browser/everyonehasjs.html">No JS, no styles</a>.</p>
<p>Personnally, I’d have liked to be able to use SVG from a worklet. Declarative languages are sometimes better than imperatives ones. But to be performant, Blink/Webkit should hardware accelerate SVG rendering first. <a href="https://blogs.igalia.com/nzimmermann/posts/2019-12-12-3d-transformations/">It will be soon in WebKit</a></p>
<p>In any case, it therefore appears that the produced code is complex to write and to set up. <strong>Above all, it is often more complex than classic JS, using the DOM</strong>.</p>
<p>Without going deep dive, worklets are autonomous environments, and cannot handle state. To be reliable, browsers should instanciate 2 different worklets, and render only one, indifferently. It makes really difficult to achieve some simple effects, <a href="https://css-houdini.iamvdo.me/rough-boxes/">like this one on rough borders</a>, where each repaint draws different borders. I’ve had a lot of trouble with that. Alternatives exist, but once again, it makes code harder and thus leads to more side effects.</p>
<p>More simply, you should not under estimate the loading time of JS, and also the non-presence. And also Houdini support. As of today, styles set using <code>paint()</code> and <code>layout()</code> produce FOUC (Flash of Unstyled Content).</p>
<p><strong>Progressive enhancement is more relevant than ever</strong>. But will be harder to ensure.</p>
<h3 id="security">Security<a href="#security" class="self-link"></a></h3>
<p>Giving developers more control on browsers core engine, leads to security concerns. <strong>Main limitation is that worklets can only be used on HTTPS site</strong>. No secured website, no CSS. That is harsh <sup class="footnote"><a href="#fn-3" id="fnref-3" title="It seems to be a good design principle for client-side features">3</a></sup>.</p>
<p>Despite this point, researchers have been able to exploit a vulnerability that easily retrieves a user browsing history. The Chrome team workaround was to prohibit the <code>paint()</code> function on HTML links. Again, <strong>it’s a big constraint that will limit wide adoption</strong>, if no other solution can be found.</p>
<p>More importantly, how  will it take to find new security breach? Will the future of CSS Houdini is bound to the CSS Shaders one (custom filters to apply WebGL shaders, right from CSS), removed overnight from browsers that already started development?</p>
<h2 id="conclusion">Conclusion<a href="#conclusion" class="self-link"></a></h2>
<p>That new way to design standard is interesting. <strong>It gives more power to authors, and includes them in the innovative process</strong>. With CSS Houdini, new effects are achievable, in a performant way in actual browsers. But, here comes with constraints: more JS, harder to use, security, etc.</p>
<p>In any cases, CSS Houdini is designed with performance in mind, not creativity.</p>
<p>These APIs could also be seen as opportunities for standardization. If a new graphical effect, or layout, becomes mainstream, it could be standardized to be included right in CSS. But what about performance if rendering techniques are maintained?</p>
<p>So, what do you think about all that stuff?</p><div class="footnotes" id="footnotes"><div class="footnotedivider"></div><ol><li id="fn-1" value="1">If you doubt, read <a href="https://philipwalton.com/articles/the-dark-side-of-polyfilling-css/">The Dark Side Of Polyfilling CSS</a> <span class="footnotereverse"><a href="#fnref-1">&#8617;</a></span></li><li id="fn-2" value="2"><a href="https://twitter.com/flackrw/status/1225098733752328192">Since Chrome 81</a> with <a href="https://twitter.com/flackrw/status/1225101685577736198">some constraints</a> <span class="footnotereverse"><a href="#fnref-2">&#8617;</a></span></li><li id="fn-3" value="3">It seems to be a <a href="https://w3ctag.github.io/design-principles/#secure-context">good design principle for client-side features</a> <span class="footnotereverse"><a href="#fnref-3">&#8617;</a></span></li></ol></div>]]></description>
            
    </item>
            <item>
      <title>Inner borders</title>
      <link>https://css-houdini.iamvdo.me/inner-borders</link>
      <guid>https://css-houdini.iamvdo.me/inner-borders</guid>
      <pubDate>Wed, 25 Mar 2020 15:00:00 +0100</pubDate>
        
                  <description><![CDATA[<p>Draw inner borders with CSS Houdini - <a href="https://css-houdini.iamvdo.me/inner-borders">https://css-houdini.iamvdo.me/inner-borders</a></p>]]></description>
            
    </item>
            <item>
      <title>CSS debugging is hard</title>
      <link>http://iamvdo.me/en/blog/css-debugging-is-hard</link>
      <guid>http://iamvdo.me/en/blog/css-debugging-is-hard</guid>
      <pubDate>Wed, 13 Mar 2019 17:15:00 +0100</pubDate>
        
                  <description><![CDATA[<p><strong>CSS is sometimes hard to debug</strong>. Let’s take an example based on the <a href="https://apple.com">apple.com</a> website.</p>
<p>Today, <a href="https://groups.google.com/a/chromium.org/forum/m/#!topic/blink-dev/GRl1_Qy97jM">Blink announces intent to ship <code>backdrop-filter</code></a>, a really nice CSS property that allow to <a href="advanced-css-filters">apply filters to the backdrop</a> (what is underneath) of an element. This property is already shipped into Safari and Edge, and can be tested in Chrome with the Web Platfom flag since late 2015.</p>
<p>The Apple website is using it since then, and I’m aware of a specific bug where the backdrop is blurred outside of the element. I’ve always thought of an implementation bug. </p>
<figure><img src="http://iamvdo.me/content/01-blog/32-le-debug-css-est-difficile/1-apple-website.png"><figcaption class="caption">backdrop-filter seems to be applied outside of the element</figcaption></figure>
<p>Checking the code again, I’ve found out this is not a bug <sup class="footnote"><a href="#fn-1" id="fnref-1" title="To be fair, I'm not sure if backdrop-filter should be applied to line-boxes or background area. So maybe it’s a bug">1</a></sup>, and not related to <code>backdrop-filter</code> either. The problem is just CSS basics.</p>
<p>When I’m inspecting for an unknown empty space, the first thing I do is to look at each elements to see how it goes (and trying to think at all CSS properties that affects layout, checking and unchecking things). No luck here.</p>
<figure><img src="http://iamvdo.me/content/01-blog/32-le-debug-css-est-difficile/2-apple-inspecting.gif"><figcaption class="caption">Inspecting with no luck</figcaption></figure>
<p>The problem might be elsewhere. I shall not waste your time, <strong>this is a problem with inline alignments</strong>, as explained in my <a href="css-font-metrics-line-height-and-vertical-align">deep-dive CSS line-height and vertical-align blog post</a>. Specifically, with default alignment which is <code>baseline</code> and <code>display: inline-block</code> elements.</p>
<p>Let’s look at Apple website again. Each link inside <code>&lt;li&gt;</code> is set to be <code>inline-block</code>, with a height of 44px (the same height that the navigation bar). In inline formatting context, elements are aligned regarding their baselines, unless specified. But for <code>display: inline-block</code>, things get complicated.</p>
<ul>
<li>if element has text and <code>overflow</code> is <code>visible</code>, the baseline is its last line-box (the last line)</li>
<li>if element hasn’t text or if <code>overflow</code> is other than <code>visible</code>, the baseline is the bottom margin edge. This is what happens here.</li>
</ul>
<p>To better understand, let’s add a zero-width joiner character (<code>&amp;zwj;</code>) at the beginning of an <code>&lt;li&gt;</code></p>
<figure><img src="http://iamvdo.me/content/01-blog/32-le-debug-css-est-difficile/3-apple-zwj.gif"><figcaption class="caption">Adding a zero-width joiner character reveal the problem</figcaption></figure>
<p>It becomes obvious. <strong>The link is aligned with the baseline of the parent, and so the line-box is taller than expected</strong>. As I mentioned in <a href="css-font-metrics-line-height-and-vertical-align">my previous article</a>, the line-box can’t be seen, even with a background.</p>
<p>Thus, the fix is easy: using a simple <code>vertical-align</code>.</p>
<figure><img src="http://iamvdo.me/content/01-blog/32-le-debug-css-est-difficile/4-apple-fixed.gif"><figcaption class="caption">Using vertical-align to fix line-box height</figcaption></figure>
<p>Other solutions could have been used:</p>
<ul>
<li>Using <code>display: inline-flex</code> for the <code>&lt;li&gt;</code> (children are blocksified and the links’ computed values become <code>block</code>)</li>
<li>Using <code>float: left</code> for the links (but hey, who is using <code>float</code> these days ;) )</li>
</ul><div class="footnotes" id="footnotes"><div class="footnotedivider"></div><ol><li id="fn-1" value="1">To be fair, I'm not sure if <code>backdrop-filter</code> should be applied to line-boxes or background area. So maybe it’s a bug <span class="footnotereverse"><a href="#fnref-1">&#8617;</a></span></li></ol></div>]]></description>
            
    </item>
            <item>
      <title>Random irregular grid with CSS Houdini</title>
      <link>https://css-houdini.iamvdo.me/irregular-grid</link>
      <guid>https://css-houdini.iamvdo.me/irregular-grid</guid>
      <pubDate>Fri, 26 Oct 2018 17:20:00 +0200</pubDate>
        
                  <description><![CDATA[<p><a href="https://css-houdini.iamvdo.me/irregular-grid">https://css-houdini.iamvdo.me/irregular-grid</a></p>]]></description>
            
    </item>
            <item>
      <title>SVG Path Layout with CSS Houdini</title>
      <link>https://css-houdini.iamvdo.me/svg-path-layout</link>
      <guid>https://css-houdini.iamvdo.me/svg-path-layout</guid>
      <pubDate>Mon, 23 Apr 2018 17:20:00 +0200</pubDate>
        
                  <description><![CDATA[<p><a href="https://css-houdini.iamvdo.me/svg-path-layout">https://css-houdini.iamvdo.me/svg-path-layout</a></p>]]></description>
            
    </item>
            <item>
      <title>Highlighter marker annotations with CSS Houdini</title>
      <link>https://css-houdini.iamvdo.me/highlighter-marker-annotations</link>
      <guid>https://css-houdini.iamvdo.me/highlighter-marker-annotations</guid>
      <pubDate>Tue, 10 Apr 2018 17:20:00 +0200</pubDate>
        
                  <description><![CDATA[<p><a href="https://css-houdini.iamvdo.me/highlighter-marker-annotations">https://css-houdini.iamvdo.me/highlighter-marker-annotations</a></p>]]></description>
            
    </item>
            <item>
      <title>Random bubbles mask with CSS Houdini</title>
      <link>https://css-houdini.iamvdo.me/random-bubbles-mask</link>
      <guid>https://css-houdini.iamvdo.me/random-bubbles-mask</guid>
      <pubDate>Thu, 22 Mar 2018 17:22:00 +0100</pubDate>
        
                  <description><![CDATA[<p><a href="https://css-houdini.iamvdo.me/random-bubbles-mask">https://css-houdini.iamvdo.me/random-bubbles-mask</a></p>]]></description>
            
    </item>
            <item>
      <title>Dynamic hover masks with CSS Houdini</title>
      <link>https://css-houdini.iamvdo.me/dynamic-hover-masks</link>
      <guid>https://css-houdini.iamvdo.me/dynamic-hover-masks</guid>
      <pubDate>Thu, 22 Mar 2018 17:21:00 +0100</pubDate>
        
                  <description><![CDATA[<p><a href="https://css-houdini.iamvdo.me/dynamic-hover-masks">https://css-houdini.iamvdo.me/dynamic-hover-masks</a></p>]]></description>
            
    </item>
            <item>
      <title>Slanted backgrounds with CSS Houdini</title>
      <link>https://css-houdini.iamvdo.me/slanted-backgrounds</link>
      <guid>https://css-houdini.iamvdo.me/slanted-backgrounds</guid>
      <pubDate>Thu, 22 Mar 2018 17:20:00 +0100</pubDate>
        
                  <description><![CDATA[<p><a href="https://css-houdini.iamvdo.me/slanted-backgrounds">https://css-houdini.iamvdo.me/slanted-backgrounds</a></p>]]></description>
            
    </item>
            
  </channel>
</rss>