├── .github
├── labels.json
└── workflows
│ └── set-default-labels.yml
├── CODE_OF_CONDUCT.md
├── LICENSE
├── README.md
├── cssPaint
└── intro
│ ├── 01partOne
│ ├── header-highlight.js
│ ├── index.html
│ └── style.css
│ ├── 02partTwo
│ ├── header-highlight.js
│ ├── index.html
│ └── style.css
│ ├── 03partThree
│ ├── header-highlight.js
│ ├── index.html
│ └── style.css
│ ├── 04partFour
│ ├── header-highlight.js
│ ├── index.html
│ └── style.css
│ └── worklets
│ ├── boxbg.js
│ ├── hilite.js
│ └── hollow.js
├── sharedAssets
├── ZillaSlab.woff2
├── normalize.css
├── prism.css
├── prism.js
├── shared-styles.css
└── styleguide.html
└── typedOM
├── intro
├── 01partOne
│ ├── app.js
│ ├── index.html
│ └── style.css
├── 02partTwo
│ ├── app.js
│ ├── index.html
│ └── style.css
└── 03partThree
│ ├── app.js
│ ├── index.html
│ └── style.css
├── numericValue
├── 01unitValue
│ ├── app.js
│ ├── index.html
│ └── style.css
├── 02mathValue
│ ├── app.js
│ ├── index.html
│ └── style.css
├── 03methods
│ ├── app.js
│ ├── index.html
│ └── style.css
└── 04example
│ ├── app.js
│ ├── index.html
│ ├── marker.svg
│ ├── style.css
│ └── world-map.jpg
├── reference
└── README.md
└── transformValue
├── 01values
├── app.js
├── index.html
└── style.css
└── 02example
├── app.js
├── circle.svg
├── cross.svg
├── index.html
├── square.svg
├── star.svg
└── style.css
/.github/labels.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "good first issue",
4 | "color": "028c46",
5 | "description": "A good issue for newcomers to get started with."
6 | },
7 | {
8 | "name": "help wanted",
9 | "color": "028c46",
10 | "description": "If you know something about this, we would love your help!"
11 | },
12 | {
13 | "name": "needs info",
14 | "color": "028c46",
15 | "description": "This needs more information to review or act on."
16 | },
17 | {
18 | "name": "needs triage",
19 | "color": "028c46",
20 | "description": "Triage needed by staff and/or partners. Automatically applied when an issue is opened."
21 | },
22 | {
23 | "name": "expert help needed",
24 | "color": "028c46",
25 | "description": "This needs more information from a subject matter expert (SME)."
26 | },
27 | {
28 | "name": "idle",
29 | "color": "028c46",
30 | "description": "Issues and pull requests with no activity for three months."
31 | },
32 | {
33 | "name": "on hold",
34 | "color": "028c46",
35 | "description": "Waiting on something else before this can be moved forward."
36 | },
37 | {
38 | "name": "for later",
39 | "color": "028c46",
40 | "description": "Not planned at this time."
41 | },
42 | {
43 | "name": "needs content update",
44 | "color": "028c46",
45 | "description": "Needs update to the content to support this change."
46 | },
47 | {
48 | "name": "chore",
49 | "color": "028c46",
50 | "description": "A routine task."
51 | },
52 | {
53 | "name": "enhancement",
54 | "color": "028c46",
55 | "description": "Improves an existing feature."
56 | },
57 | {
58 | "name": "bug",
59 | "color": "c05964",
60 | "description": "Indicates an unexpected problem or unintended behavior."
61 | },
62 | {
63 | "name": "wontfix",
64 | "color": "c05964",
65 | "description": "Deemed to be outside the scope of the project or would require significant time and resources to fix."
66 | },
67 | {
68 | "name": "effort: small",
69 | "color": "866dc1",
70 | "description": "Task is a small effort."
71 | },
72 | {
73 | "name": "effort: medium",
74 | "color": "866dc1",
75 | "description": "Task is a medium effort."
76 | },
77 | {
78 | "name": "effort: large",
79 | "color": "866dc1",
80 | "description": "Task is large effort."
81 | },
82 | {
83 | "name": "p0",
84 | "color": "6e8bc1",
85 | "description": "Urgent. We will address this as soon as possible."
86 | },
87 | {
88 | "name": "p1",
89 | "color": "6e8bc1",
90 | "description": "We will address this soon and will provide capacity from our team for it in the next few releases."
91 | },
92 | {
93 | "name": "p2",
94 | "color": "6e8bc1",
95 | "description": "We want to address this but may have other higher priority items."
96 | },
97 | {
98 | "name": "p3",
99 | "color": "6e8bc1",
100 | "description": "We don't have visibility when this will be addressed."
101 | }
102 | ]
103 |
--------------------------------------------------------------------------------
/.github/workflows/set-default-labels.yml:
--------------------------------------------------------------------------------
1 | name: set-default-labels
2 | on: [workflow_dispatch]
3 |
4 | jobs:
5 | set-default-labels:
6 | uses: mdn/workflows/.github/workflows/set-default-labels.yml@main
7 | with:
8 | target-repo: "mdn/houdini-examples"
9 | should-delete-labels: true
10 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Community Participation Guidelines
2 |
3 | This repository is governed by Mozilla's code of conduct and etiquette guidelines.
4 | For more details, please read the
5 | [Mozilla Community Participation Guidelines](https://www.mozilla.org/about/governance/policies/participation/).
6 |
7 | ## How to Report
8 | For more information on how to report violations of the Community Participation Guidelines, please read our [How to Report](https://www.mozilla.org/about/governance/policies/participation/reporting/) page.
9 |
10 |
16 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | CC0 1.0 Universal
2 |
3 | Statement of Purpose
4 |
5 | The laws of most jurisdictions throughout the world automatically confer
6 | exclusive Copyright and Related Rights (defined below) upon the creator and
7 | subsequent owner(s) (each and all, an "owner") of an original work of
8 | authorship and/or a database (each, a "Work").
9 |
10 | Certain owners wish to permanently relinquish those rights to a Work for the
11 | purpose of contributing to a commons of creative, cultural and scientific
12 | works ("Commons") that the public can reliably and without fear of later
13 | claims of infringement build upon, modify, incorporate in other works, reuse
14 | and redistribute as freely as possible in any form whatsoever and for any
15 | purposes, including without limitation commercial purposes. These owners may
16 | contribute to the Commons to promote the ideal of a free culture and the
17 | further production of creative, cultural and scientific works, or to gain
18 | reputation or greater distribution for their Work in part through the use and
19 | efforts of others.
20 |
21 | For these and/or other purposes and motivations, and without any expectation
22 | of additional consideration or compensation, the person associating CC0 with a
23 | Work (the "Affirmer"), to the extent that he or she is an owner of Copyright
24 | and Related Rights in the Work, voluntarily elects to apply CC0 to the Work
25 | and publicly distribute the Work under its terms, with knowledge of his or her
26 | Copyright and Related Rights in the Work and the meaning and intended legal
27 | effect of CC0 on those rights.
28 |
29 | 1. Copyright and Related Rights. A Work made available under CC0 may be
30 | protected by copyright and related or neighboring rights ("Copyright and
31 | Related Rights"). Copyright and Related Rights include, but are not limited
32 | to, the following:
33 |
34 | i. the right to reproduce, adapt, distribute, perform, display, communicate,
35 | and translate a Work;
36 |
37 | ii. moral rights retained by the original author(s) and/or performer(s);
38 |
39 | iii. publicity and privacy rights pertaining to a person's image or likeness
40 | depicted in a Work;
41 |
42 | iv. rights protecting against unfair competition in regards to a Work,
43 | subject to the limitations in paragraph 4(a), below;
44 |
45 | v. rights protecting the extraction, dissemination, use and reuse of data in
46 | a Work;
47 |
48 | vi. database rights (such as those arising under Directive 96/9/EC of the
49 | European Parliament and of the Council of 11 March 1996 on the legal
50 | protection of databases, and under any national implementation thereof,
51 | including any amended or successor version of such directive); and
52 |
53 | vii. other similar, equivalent or corresponding rights throughout the world
54 | based on applicable law or treaty, and any national implementations thereof.
55 |
56 | 2. Waiver. To the greatest extent permitted by, but not in contravention of,
57 | applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and
58 | unconditionally waives, abandons, and surrenders all of Affirmer's Copyright
59 | and Related Rights and associated claims and causes of action, whether now
60 | known or unknown (including existing as well as future claims and causes of
61 | action), in the Work (i) in all territories worldwide, (ii) for the maximum
62 | duration provided by applicable law or treaty (including future time
63 | extensions), (iii) in any current or future medium and for any number of
64 | copies, and (iv) for any purpose whatsoever, including without limitation
65 | commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes
66 | the Waiver for the benefit of each member of the public at large and to the
67 | detriment of Affirmer's heirs and successors, fully intending that such Waiver
68 | shall not be subject to revocation, rescission, cancellation, termination, or
69 | any other legal or equitable action to disrupt the quiet enjoyment of the Work
70 | by the public as contemplated by Affirmer's express Statement of Purpose.
71 |
72 | 3. Public License Fallback. Should any part of the Waiver for any reason be
73 | judged legally invalid or ineffective under applicable law, then the Waiver
74 | shall be preserved to the maximum extent permitted taking into account
75 | Affirmer's express Statement of Purpose. In addition, to the extent the Waiver
76 | is so judged Affirmer hereby grants to each affected person a royalty-free,
77 | non transferable, non sublicensable, non exclusive, irrevocable and
78 | unconditional license to exercise Affirmer's Copyright and Related Rights in
79 | the Work (i) in all territories worldwide, (ii) for the maximum duration
80 | provided by applicable law or treaty (including future time extensions), (iii)
81 | in any current or future medium and for any number of copies, and (iv) for any
82 | purpose whatsoever, including without limitation commercial, advertising or
83 | promotional purposes (the "License"). The License shall be deemed effective as
84 | of the date CC0 was applied by Affirmer to the Work. Should any part of the
85 | License for any reason be judged legally invalid or ineffective under
86 | applicable law, such partial invalidity or ineffectiveness shall not
87 | invalidate the remainder of the License, and in such case Affirmer hereby
88 | affirms that he or she will not (i) exercise any of his or her remaining
89 | Copyright and Related Rights in the Work or (ii) assert any associated claims
90 | and causes of action with respect to the Work, in either case contrary to
91 | Affirmer's express Statement of Purpose.
92 |
93 | 4. Limitations and Disclaimers.
94 |
95 | a. No trademark or patent rights held by Affirmer are waived, abandoned,
96 | surrendered, licensed or otherwise affected by this document.
97 |
98 | b. Affirmer offers the Work as-is and makes no representations or warranties
99 | of any kind concerning the Work, express, implied, statutory or otherwise,
100 | including without limitation warranties of title, merchantability, fitness
101 | for a particular purpose, non infringement, or the absence of latent or
102 | other defects, accuracy, or the present or absence of errors, whether or not
103 | discoverable, all to the greatest extent permissible under applicable law.
104 |
105 | c. Affirmer disclaims responsibility for clearing rights of other persons
106 | that may apply to the Work or any use thereof, including without limitation
107 | any person's Copyright and Related Rights in the Work. Further, Affirmer
108 | disclaims responsibility for obtaining any necessary consents, permissions
109 | or other rights required for any use of the Work.
110 |
111 | d. Affirmer understands and acknowledges that Creative Commons is not a
112 | party to this document and has no duty or obligation with respect to this
113 | CC0 or use of the Work.
114 |
115 | For more information, please see
116 |
117 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # CSS Houdini Examples
2 |
3 | CSS Houdini examples relating to MDN content.
4 |
5 | ## Typed OM
6 |
7 | - [Part One: Intro](https://mdn.github.io/houdini-examples/typedOM/intro/01partOne)
8 | - [Part Two: Style Value Types & Parsing](https://mdn.github.io/houdini-examples/typedOM/intro/02partTwo)
9 | - [Part Three: Style Maps & Methods](https://mdn.github.io/houdini-examples/typedOM/intro/03partThree)
10 |
11 | #### Numeric Values
12 |
13 | - [Part One: Unit Values](https://mdn.github.io/houdini-examples/typedOM/numericValue/01unitValue)
14 | - [Part Two: Math Values](https://mdn.github.io/houdini-examples/typedOM/numericValue/02mathValue)
15 | - [Part Three: Methods](https://mdn.github.io/houdini-examples/typedOM/numericValue/03methods)
16 | - [Part Four: Working Example](https://mdn.github.io/houdini-examples/typedOM/numericValue/04example)
17 |
18 | ## CSS Paint API
19 |
20 | - [Part One: Intro](https://mdn.github.io/houdini-examples/cssPaint/intro/01partOne)
21 | - [Part Two: Size](https://mdn.github.io/houdini-examples/cssPaint/intro/02partTwo)
22 | - [Part Three: Custom Properties](https://mdn.github.io/houdini-examples/cssPaint/intro/03partThree)
23 | - [Part Four: Custom Arguments](https://mdn.github.io/houdini-examples/cssPaint/intro/04partFour)
24 |
25 |
--------------------------------------------------------------------------------
/cssPaint/intro/01partOne/header-highlight.js:
--------------------------------------------------------------------------------
1 | registerPaint('headerHighlight', class {
2 |
3 | // Whether Alpha is allowed - This is set to true by default, if it is set to false all colours used on the canvas will have full opacity, or alpha of 1.0
4 | static get contextOptions() { return {alpha: true}; }
5 |
6 | paint(ctx) {
7 | // ctx -> drawing context
8 |
9 | ctx.fillStyle = 'hsla(55, 90%, 60%, 1.0)';
10 | ctx.fillRect(0, 15, 200, 20);
11 |
12 | }
13 | });
--------------------------------------------------------------------------------
/cssPaint/intro/01partOne/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
The CSS Paint API is designed to be used wherever you would invoke an image in CSS — backgrounds, borders, masks etc.
27 |
28 |
Here's a really simple example - let's say you want to create a cool half-highlight background, like on this header:
29 |
30 |
My Cool Header
31 |
32 |
The main setup & design will happen in an external script, a worklet, which we'll discuss in a minute. We first need to register that worklet from our main script:
33 |
34 |
// in our main script
35 | await CSS.paintWorklet.addModule('header-highlight.js');
36 |
37 |
Now we'll create that header-highlight.js worklet; inside it we use the registerPaint() function to name our CSS Paint worklet, and also pass in a class that does all the magic:
38 |
39 |
registerPaint('headerHighlight', class {
40 |
41 | // Whether Alpha is allowed -> This is set to true by default, if it is set to false all colours used on the canvas will have full opacity, or alpha of 1.0
42 | static get contextOptions() { return {alpha: true}; }
43 |
44 | paint(ctx) {
45 | // ctx - drawing context
46 |
47 | ctx.fillStyle = 'hsla(55, 90%, 60%, 1.0)';
48 | ctx.fillRect(0, 15, 200, 20);
49 |
50 | }
51 | });
52 |
53 |
Now we have created a new paint worklet, and given it a name headerHighlight. We can now use that in our CSS, along with the paint() function, where we would normally call an image.
Our paint function takes one argument (we'll look at more in due course): ctx. This is the 2D Rendering Context, which is a subset of the HTML5 Canvas API. It contains pretty much all the features apart from "...CanvasImageData, CanvasUserInterface, CanvasText, or CanvasTextDrawingStyles APIs..." (taken from spec https://drafts.css-houdini.org/css-paint-api-1/#2d-rendering-context). So in short we can use it to draw whatever we like as our background image.
60 |
61 |
The above example just simple draws a filled rectangle, you can do a lot more than that (check out the Canvas API docs).
62 |
63 |
Let's take a look at some of the other data available to the paint() function. Go to Part Two
It would be better if we had an idea of the size of our element, so we could create the background relative to the element; this way we could use the same background on other elements as well, and the background would always fit.
23 |
24 |
The second parameter we can pass into the paint() function gives us access to the width and the height of the element, via .width and .height properties. Pretty handy.
25 |
26 |
Let's take a look at our header-highlight.js script now we can use this functionality.
27 |
28 |
29 | registerPaint('headerHighlight', class {
30 | // Whether Alpha is allowed - This is set to true by default, if it is set to false all colours used on the canvas will have full opacity, or alpha of 1.0
31 | static get contextOptions() { return {alpha: true}; }
32 |
33 | paint(ctx, size) {
34 | // ctx - drawing context
35 | // size - size of the box being painted
36 |
37 | ctx.fillStyle = 'hsla(55, 90%, 60%, 1.0)';
38 | ctx.fillRect(0, size.height/4, size.width*0.66, size.height*0.7);
39 |
40 | }
41 | });
42 |
43 |
Our header now has a highlight which changes according to it's size.
44 |
45 |
My Cool Header
46 |
47 |
And there's more, we can start to access custom properties inside our worklet function as well. Go to Part Three
48 |
49 |
50 |
53 |
54 |
55 |
56 |
59 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/cssPaint/intro/02partTwo/style.css:
--------------------------------------------------------------------------------
1 | @import '/sharedAssets/shared-styles.css';
2 |
3 | .example {
4 | background-image: paint(headerHighlight);
5 | }
--------------------------------------------------------------------------------
/cssPaint/intro/03partThree/header-highlight.js:
--------------------------------------------------------------------------------
1 | console.clear();
2 |
3 | registerPaint('headerHighlight', class {
4 | // use this function to retrieve any custom props defined for the element, return them in the specified array
5 | static get inputProperties() { return ['--highColour']; }
6 | // Input arguments that can be passed to the `paint` function
7 | // static get inputArguments() { return ['']; }
8 | // Whether Alpha is allowed? - This is set to true by default, if it is set to false all colours used on the canvas will have full opacity, or alpha of 1.0
9 | static get contextOptions() { return {alpha: true}; }
10 |
11 | paint(ctx, size, props) {
12 | // ctx -> drawing context
13 | // size -> size of the box being painted
14 | // props -> list of custom properties available to the element
15 |
16 | // set where to start the highlight & dimensions
17 | const x = 0;
18 | const y = size.height*0.3;
19 | const blockWidth = size.width*0.33;
20 | const highlightHeight = size.height*0.85;
21 |
22 | // Paint uses Typed OM to retireve the custom property value, so we have to use the get method on it
23 | ctx.fillStyle = props.get('--highColour');
24 | // block
25 | ctx.beginPath();
26 | ctx.moveTo(x, y);
27 | ctx.lineTo(blockWidth, y);
28 | ctx.lineTo(blockWidth+highlightHeight, highlightHeight);
29 | ctx.lineTo(x, highlightHeight);
30 | ctx.lineTo(x, y);
31 | ctx.closePath();
32 | ctx.fill();
33 | // dashes
34 | for (let i=0; i<4; i++) {
35 | let start = i*2;
36 | ctx.beginPath();
37 | ctx.moveTo((blockWidth)+(start*10)+10, y);
38 | ctx.lineTo((blockWidth)+(start*10)+20, y);
39 | ctx.lineTo((blockWidth)+(start*10)+20+(highlightHeight), highlightHeight);
40 | ctx.lineTo((blockWidth)+(start*10)+10+(highlightHeight), highlightHeight);
41 | ctx.lineTo((blockWidth)+(start*10)+10, y);
42 | ctx.closePath();
43 | ctx.fill();
44 | }
45 | } // paint
46 | });
--------------------------------------------------------------------------------
/cssPaint/intro/03partThree/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Part 3 Custom Properties | CSS Paint API
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
CSS Houdini Collection
17 |
CSS Paint API: Part 3 Custom Properties
18 |
19 |
20 |
21 |
22 |
So now we have our cool header with a custom paint background, let's expand on it.
23 |
24 |
We'll update the paint() function to draw a more funky background and wouldn't it be great if we could choose the colour of the background whenever we wanted to use it? Another feature of the Paint API is the ability to access custom properties available to the element.
Now we can use the inputProperties() method in the registerPaint() class, grab that property and use it within our paint() function:
40 |
41 |
static get inputProperties() { return ['--highColour']; }
42 |
43 |
44 |
Note: The inputProperties() method returns all properties affecting the element, not just custom properties.
45 |
46 |
paint(ctx, size, props) {
47 | // Paint uses Typed OM to retrieve the custom property value, so, as with the Typed OM API, we have to use the get() method to access the value
48 | ctx.fillStyle = props.get('--highColour');
49 | ...
50 | }
51 |
52 |
53 |
Back in our CSS, on elements that have this paint() function generating their backgound, we can reset the --highColour custom property to change its color:
You can check out the entire registerPaint() function in header-highlight.js here. Not only can we pass in custom properties, but also custom arguments when we call the paint() function in our css. Let's take a look in part 4.
Not only do we have access to our own custom properties, but we can also pass in our own custom arguments to the paint() function as well.
23 |
24 |
We add these extra arguments when we call the function in the CSS. Let's say we want to sometimes stroke our background instead of fill it, let's pass in an extra argument for this occasion.
Now we can use the inputArguments() method in the registerPaint() class, to get a list of these custom arguments we have added to our paint() function:
34 |
35 |
static get inputArguments() { return ['*']; }
36 |
In this case we have just added the string 'stroke', but there's more — if the custom argument is a CSS value, for instance a unit, we can invoke Typed OM CSSStyleValue class (and sub classes) by using the value type keyword when we retrieve it in the registerPaint() function.
53 |
54 |
Let's say we add a second argument with how many pixels wide we want the stroke to be:
Note: It's worth noting the difference between using custom properties to control different parts of this worklet and the arguments set out here. The situation will depend on this: Custom properties (and in fact any properties on the style map) are global — they can be used elsewhere within our CSS (and JS). You may for example have a --mainColor, which will be useful for setting the color within a paint() function, but also sets colors elsewhere in your CSS. If you wanted to change it specifically for paint, it could prove difficult, so this is where thinking about custom arguments may come in. Another way to think about it is that arguments are set to control what you are actually drawing, whereas properties are set to control styling.
83 |
84 |
Now we can really start to see the benefits of this API, if we can control a myriad of drawing parameters from our CSS through both custom properties and extra paint() function arguments, then we can really start to build reusable and infinitely controlled styling elements.
85 |
86 |
In the next part we take a look at building such a styling element and how we can configure it to be used throughout our code. Carry on with CSS Paint in the next part here.
Let's use this cool button to go over some of the basic features of Typed OM.
28 |
For any element you can use the computedStyleMap() method, to retrieve all the styles affecting the element.
29 |
30 |
31 |
Some super cool Term
32 |
Some other super cool Definition
33 |
34 |
35 |
registerPaint('headerHighlight', class {
36 | // Whether Alpha is allowed - This is set to true by default, if it is set to false all colours used on the canvas will have full opacity, or alpha of 1.0
37 | static get contextOptions() { return {alpha: true}; }
38 |
39 | paint(ctx, size) {
40 | // ctx - drawing context
41 | // size - size of the box being painted
42 | ctx.fillStyle = 'hsla(55, 90%, 60%, 1.0)';
43 | ctx.fillRect(0, size.height/4, size.width*0.66, size.height*0.7);
44 | }
45 | });
46 |
47 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/typedOM/intro/01partOne/app.js:
--------------------------------------------------------------------------------
1 | console.clear();
2 |
3 | // get the button element
4 | const buttonEl = document.querySelector('button');
5 | // get our element to append info to
6 | const stylesList = document.querySelector('#all-styles');
7 |
8 | // let's get some styles -> we can retrieve all computed styles with `computedStyleMap`
9 | const allComputedStyles = buttonEl.computedStyleMap();
10 |
11 | // which returns a map of all computed styles -> ie those already set in non-inline css
12 | for (const [prop, val] of allComputedStyles) {
13 | // properties
14 | const term = document.createElement('dt');
15 | term.appendChild(document.createTextNode(prop));
16 | stylesList.appendChild(term);
17 |
18 | // values
19 | const valDef = document.createElement('dd');
20 | valDef.appendChild(document.createTextNode(val));
21 | stylesList.appendChild(valDef);
22 | }
23 |
24 |
--------------------------------------------------------------------------------
/typedOM/intro/01partOne/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Part 1 Intro | Typed OM
6 |
7 |
8 |
9 |
10 |
11 |
12 |
15 |
16 |
17 |
18 |
19 |
20 |
CSS Houdini Collection
21 |
Typed OM: Part 1 Intro
22 |
23 |
24 |
25 |
26 |
27 |
28 |
Let's use this cool button to go over some of the basic features of Typed OM.
29 |
For any element you can use the computedStyleMap() method to retrieve all the styles affecting the element.
30 |
Let's use this method to get all the styles for the below button.
31 |
32 |
// get the button element
33 | const buttonEl = document.querySelector('button');
34 | // get our element to append info to
35 | const stylesList = document.querySelector('#all-styles');
36 |
37 | // let's get some styles -> we can retrieve all computed styles with `computedStyleMap`
38 | const allComputedStyles = buttonEl.computedStyleMap();
39 |
40 | // which returns a map of all computed styles -> ie those already set in non-inline css
41 | for (const [prop, val] of allComputedStyles) {
42 | // properties
43 | const term = document.createElement('dt');
44 | term.appendChild(document.createTextNode(prop));
45 | stylesList.appendChild(term);
46 |
47 | // values
48 | const valDef = document.createElement('dd');
49 | valDef.appendChild(document.createTextNode(val));
50 | stylesList.appendChild(valDef);
51 | }
A huge array of properties and values is returned. Let's take a closer look at those values. They are all of type CSSStyleValue — let's retrieve some values using the get() method, which comes with computedStyleMap().
29 |
30 |
CSSStyleValue has sub classes, depending on the type of value you use for the defined property. Let's take a look at them.
31 |
32 |
33 |
CSSStyleValue
34 |
Parent class for all values set on an element.
35 |
This class holds the parse and parseAll methods, which we will look at a bit later. Before we do that, let's take a look at all the sub classes.
36 |
37 |
38 |
39 |
CSSKeywordValue
40 |
Class that defines things like keywords (see the list here) — e.g. inherit, initial, unset, and anything you don't quote such as auto, grid etc...
At the time of writing this is yet to be implemented - the spec only holds the CSSImageValue type as it's needed for the CSS Paint API. This will be expanded on in future versions: cssImageValue.value
60 |
61 |
console.log(cssImageValue.value);
62 | // Logs:
63 |
64 |
65 |
66 |
CSSNumericValue
67 |
Returned when the value is numeric. This is a superclass from which CSSUnitValue and CSSMathSum are derived, so one of these will be returned instead, depending on whether the value is determined via the calc(), min(), max() function or not.
With this type we can access the calculation that has occurred, as well as units involved in that calculation. These are returned as an array of CSSUnitValues
91 |
let units = ''
92 |
93 | for (const unit of cssMathSum.values) {
94 | units += ` value: ${unit.value}, unit: ${unit.unit}`;
95 | }
96 |
97 | console.log(`Operator: ${cssMathSum.operator}, | Values: ${units}`)
98 | // Logs:
99 |
100 |
101 |
102 |
CSSTransformValue
103 |
If a transform value is used, this type is returned.
Returns a list of all the different transforms applied to the element and their different values. The transform determines what is returned. In our example here we just have a scale applied.
This returns any values of a custom property that could be set or affect said element. Here we're specifying which property we want.
128 |
129 |
console.log(cssUnparseValue[0]);
130 | // Logs:
131 |
132 |
133 |
134 |
CSSVariableReferenceValue
135 |
136 |
If we want to create a custom property, rather than just retrieve its value, we would use the CSSVariableReferenceValue type. This is not returned, as you can use any value with a custom property when setting it, however it can be defined when created. The value we enter here needs to be of type CSSUnparsedValue.
There's more about creating properties and values using these types in the next section.
145 |
146 |
147 |
148 |
Parsing Properties & Values
149 |
150 |
We can parse CSSUnparsedValue object instances with a couple of methods we get with the CSSStyleValue type: parse() and parseAll(). As it's a numeric value we'll use the CSSNumericValue.parse() method on the value. Let's take a look:
These are all of the types we will get to work with, and there are more to come as well. Not only do we have better access to CSS value information, we also have better ways of setting and retrieving styles on an element. Let's take a look in part three.
Now that we know all about the CSSStyleValue types, let's take a look at creating them and setting them on an element.
29 |
30 |
So far we've been working with computedStyleMap, which returns all the properties and values affecting an element, but there is also the attributeStyleMap for getting and setting inline styles. This is much like our already commonly used style() method, and you might wonder why we need to change something that works. With this new Typed OM method we also work with all the CSSStyleValue types, rather than just strings. This makes our code far more robust and flexible.
31 |
32 |
Note: Both the computed and attribute style maps are of type StylePropertyMap, which is a map-like object, and so supports all the common methods like get(), set(), keys(), values(), and entries() — making them intuitive to work with1. There is also styleMap, which is described further down.
33 |
34 |
Let's take a look at the methods available to us when using the attributeStyleMap. To simply add a style and then retrieve it, we can use the set() and get() methods.
A couple of things to note here: When we are setting the pixel value we are creating a CSSUnitValue using the CSS.px() factory method. We could have set it thus:
44 |
45 |
new CSSUnitValue(10, px);
46 |
47 |
We can use this to create any CSSStyleValue we've already seen
48 |
49 |
Also, when we get the property a CSSUnitValue returned. That's handy because we don't have to do any parsing to retrieve the number or the unit, they are just available to us.
50 |
51 |
And there's more, as well as the set() and get() methods, there are has(), delete(), and clear().
Might be worth noting this should be used with caution, as it's a pretty powerful way to update your styles.
81 |
82 |
Well done! You've covered all the basics of using Typed OM. There's quite a bit there and with all the values quite a bit still to think about (all the math sums for instance) There's a reference with resources here and loads more information in the MDN docs.
Here we'll take a more in depth look at the CSSNumericValue type. As we've seen it's the super class for both the CSSUnitValue and CSSMathValue.
27 |
28 |
Let's start by looking at the CSSUnitValue, the different types of units there are and different ways to create them.
29 |
30 |
Before we do that, let's refresh what's returned if a value is of type CSSunitValue
31 |
32 |
NB Due to the nature of code examples on this page, the javascript file is logging results to the console, (rather than affecting an element as in the intro), so you can view the code running in the developer tools pane and you can find the javascript file here
Here we are setting a line-height value on an h2 element and retrieving it again with the set() and get() methods of the attributeStyleMap property.
44 |
45 |
A CSSUnitValue is returned, which contains a value and a unit. Here the unit is of type 'number'. Let's see how we create a CSSUnitValue and while we're at it a couple of different unit types.
46 |
47 |
Creating a unit value
48 |
49 |
There are two ways to do this. The first is to create the object directly:
In the examples above we've seen number, percent and length type units. There are also angle, time, frequency, resolution and flex; eight altogether. Here's a list of all the units, sorted by type, with examples of how to create them with both methods.
72 |
73 |
NB Remember you can use a string instead of a number in any of these examples.
NB These only create numeric values. If, for instance, you wanted to create a flex: auto; CSS syntax you would need to use the CSSKeywordVaue type instead.
305 |
306 |
There's a lot of different unit types, but it makes sense they are all implemented. Now let's see the other CSSNumericValue sub class: CSSMathValuein the next section.
This creates a new CSSMathValue with the operator type "sum". CSSMathValues always take CSSUnitValues. As you can see with what is created in the above example, CSSMathSum actually takes a CSSNumericArray which is an array of CSSNumericValues.
40 |
41 |
If we use the toString() method on a CSSMathValue, it returns the ready made calc() function as a string. Handy.
NB As with the previous part on numeric types, there is an associated javascript file which you can find here. It is logging code to the console which you can see in the developer tools pane.
47 |
48 |
Other CSSMathValue types that associate with the calc() function are 'sum', 'product', 'negate' and 'invert'. Each of the mathematical operations returns it's own interface. Here's an example of each them, how to create each, set() the value on an element and in turn get() that value to see the interface returned:
49 |
50 |
CSSMathSum
51 |
Addition & Subtraction. Values are of type CSSNumericValue and it takes a CSSNumericArray, so you can put in as many values as you want.
Negation. Only takes one CSSNumericValue. Note here how we have to use the aforementioned toString() method, as the type itself does not negate the value, it just specifies it is of negated type.
The calc() function is not the only mathematical function we have in CSS however, there is also the min(), max() and yet to be implemented clamp() functions. These all have their own CSSMathValue types. Let's take a look at creating each in turn, and what each interface contains.
In CSS you can nest any of the above functions: calc(), min() or max(). Consider the following CSS: min( calc(1.2em * 1.4), 10vw), to recreate this using CSSMathValues we would also need to nest them:
On the surface this could seem overly complex, however it makes sense and we have to remember it's the underlying information about these CSSValueTypes that is being exposed and we have access to. It might be a bit to take in at this point, but as support starts to advance and we gain familiarity with this API, we'll probably wonder how we ever managed without it.
The maths doesn't stop there. CSSNumericValues come with their own set of operations that can be performed on all numeric values. What operation you use and with what values, depends on what is returned. Let's take a look at all the methods and a few examples of each. As these methods are available on the CSSNumericValue super class they can take a unit or a sum.
27 |
28 |
add
29 |
30 |
let sum = CSS.px(54).add(CSS.px(30));
31 | // Returns: CSSUnitValue {value: 84, unit: "px"}
32 |
33 |
In the above example we are using the same unit, and so a direct addition takes place and a CSSUnitValue is returned. If we use varying units, a CSSMathValue is returned.
let sub = CSS.mm(300).sub(CSS.mm(210));
42 | // Returns: CSSUnitValue {value: 90, unit: "mm"}
43 |
44 |
As with add above, if we use different units a CSSMathValue is returned
45 |
46 |
let sub = CSS.cm(5).add(CSS.mm(90));
47 | sub.toString() // calc(5cm + -90mm)
48 |
49 |
mul
50 |
51 |
Notice here you can pass in just a number to the multiply function. A CSSNumericValue would work as well. Differing values will return a CSSMathValue.
52 |
53 |
let mul = CSS.em(1.2).mul(1.4);
54 | // Returns: CSSUnitValue {value: 1.68, unit: "em"}
55 |
56 |
div
57 |
58 |
As with mul you can enter a number or a CSSNumericValue. Different units return a CSSMathValue
59 |
60 |
let div = CSS.turn(1).div(2);
61 | // Returns: CSSUnitValue {value: 0.5, unit: "turn"}
62 |
63 |
min
64 |
65 |
min returns the lowest value, and you can input as many values as you please. If the units differ a CSSMathMin type is returned with the values and units that have been used.
66 |
67 |
let min = CSS.px(500).min(CSS.px(400), CSS.px(200));
68 | // Returns: CSSUnitValue {value: 200, unit: "px"}
69 |
70 | min = CSS.em(1.2).min(CSS.rem(1.4));
71 | min.toString() // min(1.2em, 1.4rem)
72 |
73 |
max
74 |
75 |
Same as min, however it returns the maximum value in the list, or if the units differ, a CSSMathMax type
76 |
77 |
let max = CSS.pt(50).max(CSS.pt(90), CSS.pt(10));
78 | // Returns: CSSUnitValue {value: 90, unit: "pt"}
79 |
80 | max = CSS.px(500).max(CSS.percent(60));
81 | max.toString() // max(500px, 60%)
82 |
83 |
Conversion & Comparison
84 |
85 |
Maths methods are pretty useful, so is a way to convert units on the fly. We can do that with the to method.
86 |
87 |
to
88 |
89 |
It only allows us to convert absolute units. (Unfortunately not relative ones at this time).
90 |
91 |
// inches to centimetres
92 | const cm = CSS.in(2).to('cm');
93 | // Returns: CSSUnitValue {value: 5.08, unit: "cm"}
94 |
95 |
equals
96 |
Another very interesting bit of functionality. We can test to see if one value is equal to another with just one method.
When using these latter two pieces of functionality together we can do some clever stuff, like convert degrees to radians and check it within our code, without having to know the precise radian value, which is always a bit trickier to remember than degrees.
Let's start by building a example of placing items where a mouse or pointer is clicked: A map where you can place markers.
27 |
28 |
We can take the mouse co-ordinates and know where to position a created item. This will be a lot easier with Typed OM as we can use numbers and units rather than strings.
29 |
30 |
We'll have an image of a map and where we click on the map a marker will appear, to either represent somewhere we wish to go or have been, for example.
31 |
32 |
The focus here is on how we use Typed OM - so let's keep the HTML and CSS simple to start. We'll just have the map as an image and when we click an SVG will be added. We'll absolutely position each marker, with the top and left values being added based on mouse position on click.
33 |
34 |
Let's start with the HTML and CSS.
35 |
36 |
<div class="map">
37 | <figure class="map__image">
38 | <img src="world-map.jpg" alt="A picture of the map of the world" />
39 | <figcaption>Image from: https://mapswire.com/world/physical-maps/</figcaption>
40 | </figure>
41 | </div>
The SVG is an inverted arrow shape, so we'll place it at the tip where the mouse clicks. For this we'll need the width and height of the SVG, note in the function above we're creating CSSUnitValue's for both of these properties.
88 |
89 |
When the user clicks to place a marker, we need to calculate the top and left position values. For this we'll need the mouse coordinates, given to us by the click event, the maps position and the SVG's dimensions. Below is how we'll calculate the top value.
90 |
91 |
marker.top = new CSSUnitValue(e.clientY - mapEl.y - marker.width.value, 'px')
92 |
93 |
Using Typed OM, we can both create a CSSUnitValue and set it on the SVG image.
But what's going to happen if the window changes size. We've set absolute values. We are, however, working with numbers so we can set a percentage, rather than a pixel.
119 |
120 |
We'll create a ratio based on the dimensions of the map element and use that to create a percentage value instead of a pixel value for positioning.
The benefits of this are not just better typing and using less strings, but we can just set one position and not have to worry about calling any code on window resize.
CSS Transform property has it's own CSSStyleValue type: CSSTransformValue, and so it's subsequent values have their own type too. This is really nice because so far in javaScript, when we've wanted to modify one transform value, we've had to save and recreate a whole string if there have been multiple transform values affecting an element.
This can get a little frustrating, especially if we have quite a few transform values and are only changing one.
40 |
41 |
It would be much better if each part could be built individually and automatically, as well as the overall transform, and with types rather than a string.
42 |
43 |
Well, with Typed OM, you can!
44 |
45 |
CSSTransformValue
46 |
47 |
This creates a new transform value, which takes an array of the transform value types. Let's see what our above example would look like:
48 |
49 |
const transform = new CSSTransformValue([
50 | new CSSRotate(CSS.deg(30)),
51 | new CSSScale(CSS.number(1.2))
52 | ])
53 |
54 |
Each different transform has it's own type, which takes relevant CSSNumericValues. Let's go through them.
Note also the values that can transform in three dimensions. There's a boolean property on CSSTransformValue and all the values we've seen above, to test if they return 2D or 3D.
Now let's create a Sparkle class, which creates the image and all it's randomised transforms. We can create new sparkles when the mouse moves around the area.
We'll look at the createImage() method later, first let's look at all the methods to define the position, rotation and scaling.
56 |
57 |
Each sparkle will be positioned absolutely based on the mouse coordinates, but offset slightly. We'll create a new CSSTranslateValue with the randomised offset.
We want the sparkle to fade over time, we can do this by creating an animation where the start opacity is 1 and the end opacity is 0, and making sure all sparkles have this animation. We can do most of this in the CSS
And finally we need the createImage() method, which creates an image element, set's it's source and other attributes and applies the styles we've created with the above methods.