CSS_Houdini, the magician of JSinCSS

CSS_Houdini, the magician of JSinCSS

Houdini is a set of low-level APIs that expose various parts of the CSS engine, allowing developers to extend CSS by joining the styling and layout process of the browser rendering engine. Houdini is a set of APIs that give developers direct access to the CSS Object Model (CSSOM), allowing developers to write code that the browser can parse into CSS, creating new CSS features without waiting for them to be localized in the browser. accomplish.

background

Front-end developers need more CSS control "authority"

html/css parse -> style calculate -> layout > paint > compositeThe browser usually goes through such a process during the rendering process of web pages . The two phases of javascript and style will parse HTML and create a Render Tree for the loaded JS and CSS, which are the so-called DOM and CSSOM.

Currently, what front-end developers can do is to manipulate DOM and CSSDOM through js to affect page changes, but they have almost no control over the subsequent Layout, Paint and Composite. An obvious problem caused by this is that when js changes the style (that is, operates the DOM), the entire web page rendering process will be repeated, resulting in poor performance.

The front-end developers raised the question: Is there any way for the browser to directly operate these previous links? Is there any way to change the style without using js to manipulate the DOM or switch classes to change the style?

Pain points of new CSS features

New features of js can often be polyfilled through babel or other means, so that new features can be quickly applied to the production environment. Unlike js, CSS is not a dynamic language like js. We cannot simply provide polyfills. At most, we can use postCSS, Less/Sass/Stylus and other tools to translate CSS that the browser can accept. Whether it is compatible or not is up to the browser. , however, the reality is that the version and update progress of each browser is very slow.

From this, front-end developers have raised the question: Can developers be given the ability to create their own custom CSS features and have these features run efficiently in the browser's native rendering pipeline. In this way, CSS polyfills can be implemented to allow these new features to run efficiently in the browser's native rendering pipeline.

These pain points are the reason why CSS Houdini was born.

introduce

Houdini was America's great magician, a master of escape techniques.

CSS Houdini is a working group composed of engineers from W3C, Mozilla, Apple, Opera, Microsoft, HP, Intel, IBM, Adobe and Google. It aims to establish a series of APIs that allow developers to intervene in browser CSS. The engine operation brings developers more solutions to solve the long-standing problems of CSS:

  • Cross-Browser isse
  • CSS Polyfill

Features and Benefits

Houdini has faster parsing times compared to using JavaScript.style to make DOM style changes. The browser parses the CSSOM (including layout, painting, and composition processes) before applying any style updates found in the script. In addition, the layout, drawing, and compositing processes repeatedly update JavaScript styles. Houdini code does not wait for the first render cycle to complete. Instead, it's contained within the first loop - creating renderable, understandable styles. Houdini provides an object-based API for manipulating CSS values ​​in JavaScript.

good

Houdini's CSS-Typed OM is a CSS object model that contains types and methods that expose values ​​as JavaScript objects, making CSS operations more intuitive than previous string-based HTMLElement.style operations. Each element and style sheet rule has a style map accessible through its style attribute map.

One feature of CSS Houdini is working sets. Using working sets you can create modular CSS that requires a single line of JavaScript to import configurable components: no pre-processors, post-processors or JavaScript frameworks required.

This added module contains registerPaint() functions that register fully configurable working sets.

CSS paint() function parameters include the name of the working set and optional parameters. Working sets also have access to custom properties of elements: they do not need to be passed as function arguments.

like:

<script> 
  CSS.paintWorklet.addModule('csscomponent.js'); 
</script>
<style>
li {
     
     
	background-image: paint(myComponent, stroke, 10px);
    --hilights: blue;
    --lowlights: green;
}
</style>

API

Accessible Documentation >>

CSS Parser API

An API that more directly exposes a CSS parser for parsing arbitrary CSS-like languages ​​into a lightly typed representation.

(No guide or reference has been written for this API yet, see https://drafts.css-houdini.org/css-parser-api/ .)

CSS Properties and Values API

Defines an API for registering new CSS properties. Properties registered using this API provide a parsing syntax for defining types, inherited behavior, and initial values. For details about CSS3 variables, please see another note "[Notes] Using CSS Variables"

Use like:

$font-size: 10px;
$brightBlue: blue;

.m-mark {
    
    
    font-size: 1.5 * $font-size; 
	color: $brightBlue 
}

Compared with variables of preprocessing tools, CSS variables have advantages. For example, dynamic adjustment:

.u-txt {
    
     
	--box-shadow-color: yellow; 
	box-shadow: 0 0 30px var(--box-shadow-color); 
} 
.u-txt:hover {
    
     
	/* 实现了box-shadow: 0 0 30px red; */ 
	--box-shadow-color: red; 
}

Or js control:

const textBox = document.querySelector('.u-txt'); // GET 
const Bxshc = getComputedStyle(textBox).getPropertyValue('--box-shadow-color'); // SET 
textBox.style.setProperty('--box-shadow-color', 'new color');
Compatibility

Variable mobile compatibility is okay, and PC compatibility is very good except for IE.

[The external link image transfer failed. The source site may have an anti-leeching mechanism. It is recommended to save the image and upload it directly.(img-VhusXLhn-1579750809019)(/images/csshoudini/p-value.png)]

*Registered properties

Currently there is no fully stable compatible use

Use like:

CSS.registerProperty({
    
    
    name: '--myprop', //属性名字
    syntax: '<length>', // 什么类型的单位,这里是长度
    initialValue: '1px', // 默认值
    inherits: true // 会不会继承,true为继承父元素
  });

If inherits: true, it will be the effect of the scope we see. If it is not true, it will not be affected by the parent scope and take the default value.

The genius of this custom attribute is that it can use a permanently looping animation to drive a one-time transformation. In other words, if we use css variable + transform, we can use js to change this variable to achieve fancy effects. However, now there is no need for js, as long as the css is internally digested, the transform becomes a perpetual motion machine.

// 我们先注册几种属性
['x1','y1','z1','x2','y2','z2'].forEach(p => {
    
    
    CSS.registerProperty({
    
    
        name: `--${
      
      p}`,
        syntax: '<angle>',
        inherits: false,
        initialValue: '0deg'
    });
});

style:

<style>
#myprop, #myprop1 {
     
     
  width: 200px;
  border: 2px dashed #000;
  border-bottom: 10px solid #000;
  animation:myprop 3000ms alternate infinite ease-in-out;
  transform: 
    rotateX(var(--x2))
    rotateY(var(--y2))
    rotateZ(var(--z2))
}
@keyframes myprop {
     
     
  25% {
     
     
    --x1: 20deg;
    --y1: 30deg;
    --z1: 40deg;
  }
  50% {
     
     
    --x1: -20deg;
    --z1: -40deg;
    --y1: -30deg;
  }
  75% {
     
     
    --x2: -200deg;
    --y2: 130deg;
    --z2: -350deg;
  }
  100% {
     
     
    --x1: -200deg;
    --y1: 130deg;
    --z1: -350deg;
  }
}

@keyframes myprop1 {
     
     
  25% {
     
     
    --x1: 20deg;
    --y1: 30deg;
    --z1: 40deg;
  }
  50% {
     
     
    --x2: -200deg;
    --y2: 130deg;
    --z2: -350deg;
  }
  75% {
     
     
    --x1: -20deg;
    --z1: -40deg;
    --y1: -30deg;
  }
  100% {
     
     
    --x1: -200deg;
    --y1: 130deg;
    --z1: -350deg;
  }
}
</style>

<div id="myprop"></div>
<div id="myprop1"></div>
CSS Typed OM

Type om is a way to store CSS attributes and values ​​in an attributeStyleMap object. As long as we directly operate this object, we can perform the previous js operation of changing css, which will cause significant performance overhead. CSS typed OM exposes CSS values ​​as typed JavaScript objects to allow performance manipulation of them. Another very important point is that attributeStyleMap stores CSS values ​​instead of strings, and supports various arithmetic and unit conversions. Compared with operating strings, the performance is significantly better.

Like all CSS Values ​​have a base class interface:

The main function is to convert the string values ​​used by CSSOM into JavaScript representations with type meaning. For example, all CSS Values ​​have a base class interface:

interface CSSStyleValue { 
	stringifier; 
	static CSSStyleValue? parse(DOMString property, DOMString cssText); 
	static sequence<CSSStyleValue>? parseAll(DOMString property, DOMString cssText); 
};

You can manipulate CSS styles as follows:

// CSS -> JS 
const map = document.querySelector('.example').styleMap; 
console.log( map.get('font-size') ); // CSSSimpleLength {value: 12, type: "px", cssText: "12px"} 

// JS -> JS 
console.log( new CSSUnitValue(5, "px") ); // CSSUnitValue{value:5,unit:"px",type:"length",cssText:"5px"} 

// JS -> CSS 
// set style "transform: translate3d(0px, -72.0588%, 0px);" 

elem.outputStyleMap.set('transform', new CSSTransformValue([ new CSSTranslation( 0, new CSSSimpleLength(100 - currentPercent, '%'), 0 )]));

unit of measurement:

if ('CSS' in window) {
    
    
    CSS.px(1); // 1px  返回的结果是:CSSUnitValue {value: 1, unit: "px"}
	CSS.number(0); // 0  比如top:0,也经常用到
	CSS.rem(2); //2rem
	new CSSUnitValue(2, 'percent'); // 还可以用构造函数,这里的结果就是2%
}

computation:

new CSSMathSum(CSS.rem(10), CSS.px(-1)) // calc(10rem - 1px),要用new不然报错
new CSSMathMax(CSS.px(1), CSS.px(2)) // 就是较大值,单位不同也可以进行比较
CSS Layout API

This API aims to improve the scalability of CSS by enabling developers to write their own layout algorithms, such as masonry or line snapping. It's not yet available locally. As the name suggests, it allows developers to write their own Layout module. The Layout module is used to assign the value of the display attribute, such as display: grid or display: flex. You only need to define the logic of the Layout by passing in the Layout name and JS class through the registerLayout function. For example, let's practice a block-like Layout:

registerLayout('block-like', class extends Layout {
    
     
	static blockifyChildren = true; 
	static inputProperties = super.inputProperties; 
	
	*layout(space, children, styleMap) {
    
     
		const inlineSize = resolveInlineSize(space, styleMap); 
		const bordersAndPadding = resolveBordersAndPadding(constraintSpace, styleMap); 
		const scrollbarSize = resolveScrollbarSize(constraintSpace, styleMap); 
		const availableInlineSize = inlineSize - bordersAndPadding.inlineStart - bordersAndPadding.inlineEnd - scrollbarSize.inline; 
		const availableBlockSize = resolveBlockSize(constraintSpace, styleMap) - bordersAndPadding.blockStart - bordersAndPadding.blockEnd - scrollbarSize.block; 
		const childFragments = []; 
		const childConstraintSpace = new ConstraintSpace({
    
     inlineSize: availableInlineSize, blockSize: availableBlockSize, }); 
		let maxChildInlineSize = 0; 
		let blockOffset = bordersAndPadding.blockStart; 
		for (let child of children) {
    
     
			const fragment = yield child.layoutNextFragment(childConstraintSpace); // 这段控制 Layout 下的 children 要 inline 排列 // fragment 就是前述的 Box Tree API 內提到的 fragment 
			fragment.blockOffset = blockOffset; 
			fragment.inlineOffset = Math.max( bordersAndPadding.inlineStart, (availableInlineSize - fragment.inlineSize) / 2); 
			maxChildInlineSize = Math.max(maxChildInlineSize, childFragments.inlineSize); 
			blockOffset += fragment.blockSize; 
		} 
		const inlineOverflowSize = maxChildInlineSize + bordersAndPadding.inlineEnd; 
		const blockOverflowSize = blockOffset + bordersAndPadding.blockEnd; 
		const blockSize = resolveBlockSize( constraintSpace, styleMap, blockOverflowSize); 
		return {
    
     inlineSize: inlineSize, blockSize: blockSize, inlineOverflowSize: inlineOverflowSize, blockOverflowSize: blockOverflowSize, childFragments: childFragments, }; 
	} 
});

Use like:

.wrapper {
    
     
	display: layout('block-like'); 
}

(No guides or references have been written for this API yet.)

CSS Painting API

Developed to increase the extensibility of CSS - allowing developers to write JavaScript functions that can be painted directly into an element's background, border, or content via the paint() CSS function.

For example, define Paint Method:

registerPaint('simpleRect', class {
    
     
	static get inputProperties() {
    
     
		return ['--rect-color']; 
	} 
	paint(ctx, size, properties) {
    
     // 根据 properties 改变颜色
		const color = properties.get('--rect-color'); 
		ctx.fillStyle = color.cssText; 
		ctx.fillRect(0, 0, size.width, size.height); 
	} 
});

Use like:

.demo-1 {
    
     
	--rect-color: red; 
	width: 50px; 
	height: 50px; 
	background-image: paint(simpleRect); 
} 
.demo-2 {
    
     
	--rect-color: yellow; 
	width: 100px; 
	height: 100px; 
	background-size: 50% 50%; 
	background-image: paint(simpleRect); 
}

In this way, .demo-1 and .demo-2 can have square background-images with their own defined width and height colors.

Compatibility

Except for new versions of Chrome and newer Android, nothing else is compatible.

[The external link image transfer failed. The source site may have an anti-leeching mechanism. It is recommended to save the image and upload it directly.(img-9ChLKk0q-1579750809020)(/images/csshoudini/p-paint.png)]

Worklets

API for running scripts at different stages of the rendering pipeline, independent of the main JavaScript execution environment. Working sets are conceptually similar to web workers and are called and extended by the rendering engine. In the above-mentioned Layout API and Paint API, we write a JS file to define new attributes, and then use them in the CSS file. You may think that the JS file is directly like the way of embedding JS on the general Web. That's it, but it's not actually the case. We need to use Worklets to help us load it. Take the Painting API above as an example:

// add a Worklet 
paintWorklet.addModule('simpleRect.js'); // WORKLET "simpleRect.js" 
registerPaint('simpleRect', class {
    
     
	static get inputProperties() {
    
     
		return ['--rect-color']; 
	} 
	paint(ctx, size, properties) {
    
     // 根据 properties 改变顏色 
		const color = properties.get('--rect-color'); 
		ctx.fillStyle = color.cssText; 
		ctx.fillRect(0, 0, size.width, size.height); 
	} 
});

Worklets are characterized by their lightweight and short life cycle.

Overall compatible

[The external link image transfer failed. The source site may have an anti-leeching mechanism. It is recommended to save the image and upload it directly.(img-UjAct3l7-1579750809022)(/images/csshoudini/p-total.png)]

use

Package library

Third-party packaging library extra.css

use

There are two steps to using it:

  • HTML includes working set and custom properties files<script src='<packageName></script>
  • CSS uses background access to the paint working set: paint(<workletName>);

The format of the URL is as follows: https://unpkg.com/extra.css/.js Click on the link above to get any correct URL to get the CDN link. If you go to this link in your URL bar, you will automatically be linked to the latest version number of the JS package, which includes all registered custom properties and working sets. Here is a link to the latest version (i.e. https://unpkg.com/[email protected]/.js), but you can skip the version number if you always want the evergreen latest version. The Demo tab will open an explanatory codepen project with everything connected correctly.

Here's an example illustrating HTML and CSS across paths:

<h1>Hello<br/> World</h1>
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Velit <span>asperiores</span> cupiditate dolor quo iste animi, eveniet in inventore <span>obcaecati</span> quisquam architecto pariatur et perspiciatis, deleniti voluptatum consequuntur <span>voluptas</span> voluptatibus repellat!</p>

<!-- This is where we include the worklet -->
<script src='https://unpkg.com/extra.css/crossOut.js'</script>

<style>
// This is where we use the custom properties

@supports (background: paint(something)) {
     
     
  h1 {
     
     
    /* 
      Optionally set property values. 
      This can be done at root or local scope 
    */
    --extra-crossColor: #fc0; /* default: black */
    --extra-crossWidth: 3; /* default: 1 */

    background: paint(extra-crossOut);
    line-height: 1.5;
  }

  h2 {
     
     
    margin: 10rem auto;
    display: block;
    width: 80vw;
    max-width: 900px;
    height: 400px;
    display: flex;
    justify-content: center;
    align-items: center;
  }

  span {
     
     
    --extra-crossColor: #d4f;/* default: black */

    background: paint(extra-crossOut);
  }
}

// Additional Styling

body {
     
     
  font-family: monospace;
  text-align: center;
  padding: 1rem;
  margin: 0;
}

p {
     
     
  max-width: 660px;
  margin: 0 auto;
  font-weight: 300;
  font-size: 1.2rem;
  line-height: 1.5;
  text-align: left;
}

h1 {
     
     
  font-size: 4rem;
  display: inline-block;
  font-weight: 100;
  margin: 1rem auto;
  width: 100%;
  max-width: 760px;
  position: relative;
}

h2 {
     
     
  font-size: 2.5rem;
  font-weight: 100;
  display: inline-block;
  position: relative;
}
</style>

Demo

There is a good library: https://github.com/GoogleChromeLabs/houdini-samples


Related Links

Guess you like

Origin blog.csdn.net/qq_24357165/article/details/104074806