├── .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 | 4 | 5 | Part 1 Intro | CSS Paint API 6 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 18 | 19 |
20 |

CSS Houdini Collection

21 |

CSS Paint API: Part 1 Intro

22 |
23 | 24 |
25 | 26 |

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.

54 | 55 |
.example {
56 | 	background-image: paint(headerHighlight);
57 | }
58 | 59 |

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

64 |
65 | 66 | 69 | 70 | 71 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /cssPaint/intro/01partOne/style.css: -------------------------------------------------------------------------------- 1 | @import '/sharedAssets/shared-styles.css'; 2 | 3 | .example { 4 | background-image: paint(headerHighlight); 5 | } -------------------------------------------------------------------------------- /cssPaint/intro/02partTwo/header-highlight.js: -------------------------------------------------------------------------------- 1 | console.clear(); 2 | 3 | registerPaint('headerHighlight', class { 4 | 5 | static get contextOptions() { return {alpha: true}; } 6 | 7 | paint(ctx, size) { 8 | // ctx -> drawing context 9 | // size -> size of the box being painted 10 | 11 | ctx.fillStyle = 'hsla(55, 90%, 60%, 1.0)'; 12 | ctx.fillRect(0, size.height/3, size.width*0.4, size.height*0.6); 13 | 14 | } 15 | }); -------------------------------------------------------------------------------- /cssPaint/intro/02partTwo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Part 2 Size | CSS Paint API 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |

CSS Houdini Collection

17 |

CSS Paint API: Part 2 Size

18 |
19 | 20 |
21 | 22 |

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.

25 | 26 |

Let's take a look:

27 | 28 |

My Cool Header

29 |

My Cool Header

30 | 31 |

We add a custom property in our CSS:

32 | 33 |
.example {
34 | 	--highColour: hsla(55, 90%, 60%, 1.0);
35 | 	background-image: paint(headerHighlight);
36 | }
37 | 
38 | 39 |

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:

54 | 55 |
.example {
56 | 	--highColour: hsla(55, 90%, 60%, 1.0);
57 | 	background-image: paint(headerHighlight);
58 | }
59 | .example:nth-of-type(2) {
60 | 	--highColour: hsla(155, 90%, 60%, 1.0);
61 | }
62 | 
63 | 64 |

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.

65 | 66 |
67 | 68 | 71 | 72 | 73 | 74 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /cssPaint/intro/03partThree/style.css: -------------------------------------------------------------------------------- 1 | @import '/sharedAssets/shared-styles.css'; 2 | 3 | .example { 4 | --highColour: hsla(55, 90%, 60%, 1.0); 5 | background-image: paint(headerHighlight); 6 | } 7 | 8 | .example:nth-of-type(2) {--highColour: hsla(125, 90%, 60%, 1.0);} 9 | -------------------------------------------------------------------------------- /cssPaint/intro/04partFour/header-highlight.js: -------------------------------------------------------------------------------- 1 | 2 | registerPaint('headerHighlight', class { 3 | static get inputProperties() { return ['--highColour']; } 4 | // Input arguments that can be passed to the `paint` function 5 | static get inputArguments() { return ['*','']; } 6 | 7 | static get contextOptions() { return {alpha: true}; } 8 | 9 | paint(ctx, size, props, args) { 10 | // ctx -> drawing context 11 | // size -> size of the box being painted 12 | // props -> list of custom properties available to the element 13 | // args -> list of arguments we can set when calling the paint() function in our css 14 | 15 | // set where to start the highlight & dimensions 16 | const x = 0; 17 | const y = size.height*0.3; 18 | const blockWidth = size.width*0.33; 19 | const highlightHeight = size.height*0.85; 20 | const colour = props.get('--highColour'); 21 | 22 | // use our custom arguments 23 | ctx.fillStyle = colour; 24 | ctx.strokeStyle = 'transparent'; 25 | 26 | const strokeWidth = args[1]; 27 | if (strokeWidth.unit === 'px') { 28 | ctx.lineWidth = strokeWidth.value; 29 | } else { 30 | ctx.lineWidth = 1.0; 31 | } 32 | // set a stroke 33 | const hasStroke = args[0].toString(); 34 | if (hasStroke === 'stroke') { 35 | ctx.fillStyle = 'transparent'; 36 | ctx.strokeStyle = colour; 37 | } 38 | 39 | // block 40 | ctx.beginPath(); 41 | ctx.moveTo(x, y); 42 | ctx.lineTo(blockWidth, y); 43 | ctx.lineTo(blockWidth+highlightHeight, highlightHeight); 44 | ctx.lineTo(x, highlightHeight); 45 | ctx.lineTo(x, y); 46 | ctx.closePath(); 47 | ctx.fill(); 48 | ctx.stroke(); 49 | // dashes 50 | for (let i=0; i<4; i++) { 51 | let start = i*2; 52 | ctx.beginPath(); 53 | ctx.moveTo((blockWidth)+(start*10)+10, y); 54 | ctx.lineTo((blockWidth)+(start*10)+20, y); 55 | ctx.lineTo((blockWidth)+(start*10)+20+(highlightHeight), highlightHeight); 56 | ctx.lineTo((blockWidth)+(start*10)+10+(highlightHeight), highlightHeight); 57 | ctx.lineTo((blockWidth)+(start*10)+10, y); 58 | ctx.closePath(); 59 | ctx.fill(); 60 | ctx.stroke(); 61 | } 62 | 63 | } // paint 64 | }); 65 | 66 | -------------------------------------------------------------------------------- /cssPaint/intro/04partFour/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Part 4 Custom Arguments | CSS Paint API 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |

CSS Houdini Collection

17 |

CSS Paint API: Part 4 Custom Arguments

18 |
19 | 20 |
21 | 22 |

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.

25 | 26 |
h1 {
 27 | 	background-image: paint(headerHighlight, stroke);
 28 | }
 29 | 
30 | 31 |

My Cool Header

32 | 33 |

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 | 
37 | 38 |
paint(ctx, size, props, args) {
 39 | 
 40 | 	// use our custom arguments
 41 | 	const hasStroke = args[0].toString();
 42 | 
 43 | 	// if stroke arg is stroke, don't fill
 44 | 	if (hasStroke === 'stroke') {
 45 | 		ctx.fillStyle = 'transparent';
 46 | 		ctx.strokeStyle = colour;
 47 | 	}
 48 | 	...
 49 | }
 50 | 
51 | 52 |

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:

55 | 56 |
h1 {
 57 | 	background-image: paint(headerHighlight, stroke, 3px);
 58 | }
 59 | 
60 | 61 |

When we get our list of argument values, we ask specifically for a <length> unit.

62 | 63 |
static get inputArguments() { return ['*', '<length>']; }
 64 | 
65 | 66 |

Now we can access the type and value properties, meaning we can get the number of pixels and a number type right out of the box!

67 | 68 |
paint(ctx, size, props, args) {
 69 | 
 70 | 		const strokeWidth = args[1];
 71 | 
 72 | 		if (strokeWidth.unit === 'px') {
 73 | 			ctx.lineWidth = strokeWidth.value;
 74 | 		} else {
 75 | 			ctx.lineWidth = 1.0;
 76 | 		}
 77 | 
 78 | 	...
 79 | }
 80 | 
81 | 82 |

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.

87 | 88 |
89 | 90 | 93 | 94 | 95 | 96 | 99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /cssPaint/intro/04partFour/style.css: -------------------------------------------------------------------------------- 1 | @import '/sharedAssets/shared-styles.css'; 2 | 3 | .example { 4 | --highColour: hsla(3, 90%, 60%, 1.0); 5 | background-image: paint(headerHighlight, stroke, 3px); 6 | /*background-image: paint(headerHighlight, stroke, 10px);*/ 7 | } 8 | 9 | .example:nth-of-type(2) {--highColour: hsla(125, 90%, 60%, 1.0);} 10 | -------------------------------------------------------------------------------- /cssPaint/intro/worklets/boxbg.js: -------------------------------------------------------------------------------- 1 | registerPaint('boxbg', 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 | // use this function to retrieve any custom props defined for the element, return them in the specified array 7 | static get inputProperties() { return ['--boxColor', '--widthSubtractor']; } 8 | 9 | paint(ctx, size, props) { 10 | // ctx -> drawing context 11 | // size -> paintSize: width and height 12 | // props -> properties: get() method 13 | 14 | ctx.fillStyle = props.get('--boxColor'); 15 | ctx.fillRect(0, size.height/3, size.width*0.4 - props.get('--widthSubtractor'), size.height*0.6); 16 | 17 | } 18 | }); -------------------------------------------------------------------------------- /cssPaint/intro/worklets/hilite.js: -------------------------------------------------------------------------------- 1 | 2 | registerPaint('hollowHighlights', class { 3 | 4 | static get inputProperties() { return ['--boxColor']; } 5 | // Input arguments that can be passed to the `paint` function 6 | static get inputArguments() { return ['*','']; } 7 | 8 | static get contextOptions() { return {alpha: true}; } 9 | 10 | paint(ctx, size, props, args) { 11 | // ctx -> drawing context 12 | // size -> size of the box being painted 13 | // props -> list of custom properties available to the element 14 | // args -> list of arguments set when calling the paint() function in the css 15 | 16 | // 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 blockHeight = size.height * 0.85; 21 | 22 | // the values passed in the paint() function in the CSS 23 | const theColor = props.get( '--boxColor' ); 24 | const strokeType = args[0].toString(); 25 | const strokeWidth = parseInt(args[1]); 26 | 27 | console.log(theColor); 28 | 29 | // set the stroke width 30 | if ( strokeWidth ) { 31 | ctx.lineWidth = strokeWidth; 32 | } else { 33 | ctx.lineWidth = 1.0; 34 | } 35 | // set the fill type 36 | if ( strokeType === 'stroke' ) { 37 | ctx.fillStyle = 'transparent'; 38 | ctx.strokeStyle = theColor; 39 | } else if ( strokeType === 'filled' ) { 40 | ctx.fillStyle = theColor; 41 | ctx.strokeStyle = theColor; 42 | } else { 43 | ctx.fillStyle = 'none'; 44 | ctx.strokeStyle = 'none'; 45 | } 46 | 47 | // block 48 | ctx.beginPath(); 49 | ctx.moveTo( x, y ); 50 | ctx.lineTo( blockWidth, y ); 51 | ctx.lineTo( blockWidth + blockHeight, blockHeight ); 52 | ctx.lineTo( x, blockHeight ); 53 | ctx.lineTo( x, y ); 54 | ctx.closePath(); 55 | ctx.fill(); 56 | ctx.stroke(); 57 | // dashes 58 | for (let i = 0; i < 4; i++) { 59 | let start = i * 2; 60 | ctx.beginPath(); 61 | ctx.moveTo( blockWidth + (start * 10) + 10, y); 62 | ctx.lineTo( blockWidth + (start * 10) + 20, y); 63 | ctx.lineTo( blockWidth + (start * 10) + 20 + blockHeight, blockHeight); 64 | ctx.lineTo( blockWidth + (start * 10) + 10 + blockHeight, blockHeight); 65 | ctx.lineTo( blockWidth + (start * 10) + 10, y); 66 | ctx.closePath(); 67 | ctx.fill(); 68 | ctx.stroke(); 69 | } 70 | 71 | } // paint 72 | }); -------------------------------------------------------------------------------- /cssPaint/intro/worklets/hollow.js: -------------------------------------------------------------------------------- 1 | 2 | registerPaint('hollowHighlights', class { 3 | 4 | static get inputProperties() { return ['--boxColor']; } 5 | // Input arguments that can be passed to the `paint` function 6 | static get inputArguments() { return ['*','']; } 7 | 8 | static get contextOptions() { return {alpha: true}; } 9 | 10 | paint(ctx, size, props, args) { 11 | // ctx -> drawing context 12 | // size -> size of the box being painted 13 | // props -> list of custom properties available to the element 14 | // args -> list of arguments set when calling the paint() function in the css 15 | 16 | // 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 blockHeight = size.height * 0.85; 21 | 22 | // the values passed in the paint() function in the CSS 23 | const colour = props.get( '--boxColor' ); 24 | const strokeType = args[0].toString(); 25 | const strokeWidth = parseInt(args[1]); 26 | 27 | 28 | // set the stroke width 29 | if ( strokeWidth ) { 30 | ctx.lineWidth = strokeWidth; 31 | } else { 32 | ctx.lineWidth = 1.0; 33 | } 34 | // set the fill type 35 | if ( strokeType === 'stroke' ) { 36 | ctx.fillStyle = 'transparent'; 37 | ctx.strokeStyle = colour; 38 | } else if ( strokeType === 'filled' ) { 39 | ctx.fillStyle = colour; 40 | ctx.strokeStyle = colour; 41 | } else { 42 | ctx.fillStyle = 'none'; 43 | ctx.strokeStyle = 'none'; 44 | } 45 | 46 | // block 47 | ctx.beginPath(); 48 | ctx.moveTo( x, y ); 49 | ctx.lineTo( blockWidth, y ); 50 | ctx.lineTo( blockWidth + blockHeight, blockHeight ); 51 | ctx.lineTo( x, blockHeight ); 52 | ctx.lineTo( x, y ); 53 | ctx.closePath(); 54 | ctx.fill(); 55 | ctx.stroke(); 56 | // dashes 57 | for (let i = 0; i < 4; i++) { 58 | let start = i * 2; 59 | ctx.beginPath(); 60 | ctx.moveTo( blockWidth + (start * 10) + 10, y); 61 | ctx.lineTo( blockWidth + (start * 10) + 20, y); 62 | ctx.lineTo( blockWidth + (start * 10) + 20 + blockHeight, blockHeight); 63 | ctx.lineTo( blockWidth + (start * 10) + 10 + blockHeight, blockHeight); 64 | ctx.lineTo( blockWidth + (start * 10) + 10, y); 65 | ctx.closePath(); 66 | ctx.fill(); 67 | ctx.stroke(); 68 | } 69 | 70 | } // paint 71 | }); 72 | -------------------------------------------------------------------------------- /sharedAssets/ZillaSlab.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/houdini-examples/f6d085ad3877a3bd80845d637c8799a49d2ac4b0/sharedAssets/ZillaSlab.woff2 -------------------------------------------------------------------------------- /sharedAssets/normalize.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */ 2 | 3 | /* Document 4 | ========================================================================== */ 5 | 6 | /** 7 | * 1. Correct the line height in all browsers. 8 | * 2. Prevent adjustments of font size after orientation changes in iOS. 9 | */ 10 | 11 | html { 12 | line-height: 1.15; /* 1 */ 13 | -webkit-text-size-adjust: 100%; /* 2 */ 14 | } 15 | 16 | /* Sections 17 | ========================================================================== */ 18 | 19 | /** 20 | * Remove the margin in all browsers. 21 | */ 22 | 23 | body { 24 | margin: 0; 25 | } 26 | 27 | /** 28 | * Render the `main` element consistently in IE. 29 | */ 30 | 31 | main { 32 | display: block; 33 | } 34 | 35 | /** 36 | * Correct the font size and margin on `h1` elements within `section` and 37 | * `article` contexts in Chrome, Firefox, and Safari. 38 | */ 39 | 40 | h1 { 41 | font-size: 2em; 42 | margin: 0.67em 0; 43 | } 44 | 45 | /* Grouping content 46 | ========================================================================== */ 47 | 48 | /** 49 | * 1. Add the correct box sizing in Firefox. 50 | * 2. Show the overflow in Edge and IE. 51 | */ 52 | 53 | hr { 54 | box-sizing: content-box; /* 1 */ 55 | height: 0; /* 1 */ 56 | overflow: visible; /* 2 */ 57 | } 58 | 59 | /** 60 | * 1. Correct the inheritance and scaling of font size in all browsers. 61 | * 2. Correct the odd `em` font sizing in all browsers. 62 | */ 63 | 64 | pre { 65 | font-family: monospace, monospace; /* 1 */ 66 | font-size: 1em; /* 2 */ 67 | } 68 | 69 | /* Text-level semantics 70 | ========================================================================== */ 71 | 72 | /** 73 | * Remove the gray background on active links in IE 10. 74 | */ 75 | 76 | a { 77 | background-color: transparent; 78 | } 79 | 80 | /** 81 | * 1. Remove the bottom border in Chrome 57- 82 | * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. 83 | */ 84 | 85 | abbr[title] { 86 | border-bottom: none; /* 1 */ 87 | text-decoration: underline; /* 2 */ 88 | text-decoration: underline dotted; /* 2 */ 89 | } 90 | 91 | /** 92 | * Add the correct font weight in Chrome, Edge, and Safari. 93 | */ 94 | 95 | b, 96 | strong { 97 | font-weight: bolder; 98 | } 99 | 100 | /** 101 | * 1. Correct the inheritance and scaling of font size in all browsers. 102 | * 2. Correct the odd `em` font sizing in all browsers. 103 | */ 104 | 105 | code, 106 | kbd, 107 | samp { 108 | font-family: monospace, monospace; /* 1 */ 109 | font-size: 1em; /* 2 */ 110 | } 111 | 112 | /** 113 | * Add the correct font size in all browsers. 114 | */ 115 | 116 | small { 117 | font-size: 80%; 118 | } 119 | 120 | /** 121 | * Prevent `sub` and `sup` elements from affecting the line height in 122 | * all browsers. 123 | */ 124 | 125 | sub, 126 | sup { 127 | font-size: 75%; 128 | line-height: 0; 129 | position: relative; 130 | vertical-align: baseline; 131 | } 132 | 133 | sub { 134 | bottom: -0.25em; 135 | } 136 | 137 | sup { 138 | top: -0.5em; 139 | } 140 | 141 | /* Embedded content 142 | ========================================================================== */ 143 | 144 | /** 145 | * Remove the border on images inside links in IE 10. 146 | */ 147 | 148 | img { 149 | border-style: none; 150 | } 151 | 152 | /* Forms 153 | ========================================================================== */ 154 | 155 | /** 156 | * 1. Change the font styles in all browsers. 157 | * 2. Remove the margin in Firefox and Safari. 158 | */ 159 | 160 | button, 161 | input, 162 | optgroup, 163 | select, 164 | textarea { 165 | font-family: inherit; /* 1 */ 166 | font-size: 100%; /* 1 */ 167 | line-height: 1.15; /* 1 */ 168 | margin: 0; /* 2 */ 169 | } 170 | 171 | /** 172 | * Show the overflow in IE. 173 | * 1. Show the overflow in Edge. 174 | */ 175 | 176 | button, 177 | input { /* 1 */ 178 | overflow: visible; 179 | } 180 | 181 | /** 182 | * Remove the inheritance of text transform in Edge, Firefox, and IE. 183 | * 1. Remove the inheritance of text transform in Firefox. 184 | */ 185 | 186 | button, 187 | select { /* 1 */ 188 | text-transform: none; 189 | } 190 | 191 | /** 192 | * Correct the inability to style clickable types in iOS and Safari. 193 | */ 194 | 195 | button, 196 | [type="button"], 197 | [type="reset"], 198 | [type="submit"] { 199 | -webkit-appearance: button; 200 | } 201 | 202 | /** 203 | * Remove the inner border and padding in Firefox. 204 | */ 205 | 206 | button::-moz-focus-inner, 207 | [type="button"]::-moz-focus-inner, 208 | [type="reset"]::-moz-focus-inner, 209 | [type="submit"]::-moz-focus-inner { 210 | border-style: none; 211 | padding: 0; 212 | } 213 | 214 | /** 215 | * Restore the focus styles unset by the previous rule. 216 | */ 217 | 218 | button:-moz-focusring, 219 | [type="button"]:-moz-focusring, 220 | [type="reset"]:-moz-focusring, 221 | [type="submit"]:-moz-focusring { 222 | outline: 1px dotted ButtonText; 223 | } 224 | 225 | /** 226 | * Correct the padding in Firefox. 227 | */ 228 | 229 | fieldset { 230 | padding: 0.35em 0.75em 0.625em; 231 | } 232 | 233 | /** 234 | * 1. Correct the text wrapping in Edge and IE. 235 | * 2. Correct the color inheritance from `fieldset` elements in IE. 236 | * 3. Remove the padding so developers are not caught out when they zero out 237 | * `fieldset` elements in all browsers. 238 | */ 239 | 240 | legend { 241 | box-sizing: border-box; /* 1 */ 242 | color: inherit; /* 2 */ 243 | display: table; /* 1 */ 244 | max-width: 100%; /* 1 */ 245 | padding: 0; /* 3 */ 246 | white-space: normal; /* 1 */ 247 | } 248 | 249 | /** 250 | * Add the correct vertical alignment in Chrome, Firefox, and Opera. 251 | */ 252 | 253 | progress { 254 | vertical-align: baseline; 255 | } 256 | 257 | /** 258 | * Remove the default vertical scrollbar in IE 10+. 259 | */ 260 | 261 | textarea { 262 | overflow: auto; 263 | } 264 | 265 | /** 266 | * 1. Add the correct box sizing in IE 10. 267 | * 2. Remove the padding in IE 10. 268 | */ 269 | 270 | [type="checkbox"], 271 | [type="radio"] { 272 | box-sizing: border-box; /* 1 */ 273 | padding: 0; /* 2 */ 274 | } 275 | 276 | /** 277 | * Correct the cursor style of increment and decrement buttons in Chrome. 278 | */ 279 | 280 | [type="number"]::-webkit-inner-spin-button, 281 | [type="number"]::-webkit-outer-spin-button { 282 | height: auto; 283 | } 284 | 285 | /** 286 | * 1. Correct the odd appearance in Chrome and Safari. 287 | * 2. Correct the outline style in Safari. 288 | */ 289 | 290 | [type="search"] { 291 | -webkit-appearance: textfield; /* 1 */ 292 | outline-offset: -2px; /* 2 */ 293 | } 294 | 295 | /** 296 | * Remove the inner padding in Chrome and Safari on macOS. 297 | */ 298 | 299 | [type="search"]::-webkit-search-decoration { 300 | -webkit-appearance: none; 301 | } 302 | 303 | /** 304 | * 1. Correct the inability to style clickable types in iOS and Safari. 305 | * 2. Change font properties to `inherit` in Safari. 306 | */ 307 | 308 | ::-webkit-file-upload-button { 309 | -webkit-appearance: button; /* 1 */ 310 | font: inherit; /* 2 */ 311 | } 312 | 313 | /* Interactive 314 | ========================================================================== */ 315 | 316 | /* 317 | * Add the correct display in Edge, IE 10+, and Firefox. 318 | */ 319 | 320 | details { 321 | display: block; 322 | } 323 | 324 | /* 325 | * Add the correct display in all browsers. 326 | */ 327 | 328 | summary { 329 | display: list-item; 330 | } 331 | 332 | /* Misc 333 | ========================================================================== */ 334 | 335 | /** 336 | * Add the correct display in IE 10+. 337 | */ 338 | 339 | template { 340 | display: none; 341 | } 342 | 343 | /** 344 | * Add the correct display in IE 10. 345 | */ 346 | 347 | [hidden] { 348 | display: none; 349 | } -------------------------------------------------------------------------------- /sharedAssets/prism.css: -------------------------------------------------------------------------------- 1 | /* PrismJS 1.16.0 2 | https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript&plugins=toolbar+show-language */ 3 | /** 4 | * prism.js default theme for JavaScript, CSS and HTML 5 | * Based on dabblet (http://dabblet.com) 6 | * @author Lea Verou 7 | */ 8 | 9 | code[class*="language-"], 10 | pre[class*="language-"] { 11 | color: black; 12 | background: none; 13 | text-shadow: 0 1px white; 14 | font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; 15 | font-size: 1em; 16 | text-align: left; 17 | white-space: pre; 18 | word-spacing: normal; 19 | word-break: normal; 20 | word-wrap: normal; 21 | line-height: 1.5; 22 | 23 | -moz-tab-size: 4; 24 | -o-tab-size: 4; 25 | tab-size: 4; 26 | 27 | -webkit-hyphens: none; 28 | -moz-hyphens: none; 29 | -ms-hyphens: none; 30 | hyphens: none; 31 | } 32 | 33 | pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection, 34 | code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection { 35 | text-shadow: none; 36 | background: #b3d4fc; 37 | } 38 | 39 | pre[class*="language-"]::selection, pre[class*="language-"] ::selection, 40 | code[class*="language-"]::selection, code[class*="language-"] ::selection { 41 | text-shadow: none; 42 | background: #b3d4fc; 43 | } 44 | 45 | @media print { 46 | code[class*="language-"], 47 | pre[class*="language-"] { 48 | text-shadow: none; 49 | } 50 | } 51 | 52 | /* Code blocks */ 53 | pre[class*="language-"] { 54 | padding: 1em; 55 | margin: .5em 0; 56 | overflow: auto; 57 | } 58 | 59 | :not(pre) > code[class*="language-"], 60 | pre[class*="language-"] { 61 | background: #f5f2f0; 62 | } 63 | 64 | /* Inline code */ 65 | :not(pre) > code[class*="language-"] { 66 | padding: .1em; 67 | border-radius: .3em; 68 | white-space: normal; 69 | } 70 | 71 | .token.comment, 72 | .token.prolog, 73 | .token.doctype, 74 | .token.cdata { 75 | color: slategray; 76 | } 77 | 78 | .token.punctuation { 79 | color: #999; 80 | } 81 | 82 | .namespace { 83 | opacity: .7; 84 | } 85 | 86 | .token.property, 87 | .token.tag, 88 | .token.boolean, 89 | .token.number, 90 | .token.constant, 91 | .token.symbol, 92 | .token.deleted { 93 | color: #905; 94 | } 95 | 96 | .token.selector, 97 | .token.attr-name, 98 | .token.string, 99 | .token.char, 100 | .token.builtin, 101 | .token.inserted { 102 | color: #690; 103 | } 104 | 105 | .token.operator, 106 | .token.entity, 107 | .token.url, 108 | .language-css .token.string, 109 | .style .token.string { 110 | color: #9a6e3a; 111 | background: hsla(0, 0%, 100%, .5); 112 | } 113 | 114 | .token.atrule, 115 | .token.attr-value, 116 | .token.keyword { 117 | color: #07a; 118 | } 119 | 120 | .token.function, 121 | .token.class-name { 122 | color: #DD4A68; 123 | } 124 | 125 | .token.regex, 126 | .token.important, 127 | .token.variable { 128 | color: #e90; 129 | } 130 | 131 | .token.important, 132 | .token.bold { 133 | font-weight: bold; 134 | } 135 | .token.italic { 136 | font-style: italic; 137 | } 138 | 139 | .token.entity { 140 | cursor: help; 141 | } 142 | 143 | div.code-toolbar { 144 | position: relative; 145 | } 146 | 147 | div.code-toolbar > .toolbar { 148 | position: absolute; 149 | top: .3em; 150 | right: .2em; 151 | transition: opacity 0.3s ease-in-out; 152 | opacity: 0; 153 | } 154 | 155 | div.code-toolbar:hover > .toolbar { 156 | opacity: 1; 157 | } 158 | 159 | div.code-toolbar > .toolbar .toolbar-item { 160 | display: inline-block; 161 | } 162 | 163 | div.code-toolbar > .toolbar a { 164 | cursor: pointer; 165 | } 166 | 167 | div.code-toolbar > .toolbar button { 168 | background: none; 169 | border: 0; 170 | color: inherit; 171 | font: inherit; 172 | line-height: normal; 173 | overflow: visible; 174 | padding: 0; 175 | -webkit-user-select: none; /* for button */ 176 | -moz-user-select: none; 177 | -ms-user-select: none; 178 | } 179 | 180 | div.code-toolbar > .toolbar a, 181 | div.code-toolbar > .toolbar button, 182 | div.code-toolbar > .toolbar span { 183 | color: #bbb; 184 | font-size: .8em; 185 | padding: 0 .5em; 186 | background: #f5f2f0; 187 | background: rgba(224, 224, 224, 0.2); 188 | box-shadow: 0 2px 0 0 rgba(0,0,0,0.2); 189 | border-radius: .5em; 190 | } 191 | 192 | div.code-toolbar > .toolbar a:hover, 193 | div.code-toolbar > .toolbar a:focus, 194 | div.code-toolbar > .toolbar button:hover, 195 | div.code-toolbar > .toolbar button:focus, 196 | div.code-toolbar > .toolbar span:hover, 197 | div.code-toolbar > .toolbar span:focus { 198 | color: inherit; 199 | text-decoration: none; 200 | } 201 | 202 | -------------------------------------------------------------------------------- /sharedAssets/prism.js: -------------------------------------------------------------------------------- 1 | /* PrismJS 1.16.0 2 | https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript&plugins=toolbar+show-language */ 3 | var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(g){var c=/\blang(?:uage)?-([\w-]+)\b/i,a=0,C={manual:g.Prism&&g.Prism.manual,disableWorkerMessageHandler:g.Prism&&g.Prism.disableWorkerMessageHandler,util:{encode:function(e){return e instanceof M?new M(e.type,C.util.encode(e.content),e.alias):Array.isArray(e)?e.map(C.util.encode):e.replace(/&/g,"&").replace(/e.length)return;if(!(k instanceof M)){if(f&&y!=a.length-1){if(c.lastIndex=v,!(x=c.exec(e)))break;for(var b=x.index+(h?x[1].length:0),w=x.index+x[0].length,A=y,P=v,O=a.length;A"+t.content+""},!g.document)return g.addEventListener&&(C.disableWorkerMessageHandler||g.addEventListener("message",function(e){var a=JSON.parse(e.data),t=a.language,n=a.code,r=a.immediateClose;g.postMessage(C.highlight(n,C.languages[t],t)),r&&g.close()},!1)),C;var e=document.currentScript||[].slice.call(document.getElementsByTagName("script")).pop();return e&&(C.filename=e.src,C.manual||e.hasAttribute("data-manual")||("loading"!==document.readyState?window.requestAnimationFrame?window.requestAnimationFrame(C.highlightAll):window.setTimeout(C.highlightAll,16):document.addEventListener("DOMContentLoaded",C.highlightAll))),C}(_self);"undefined"!=typeof module&&module.exports&&(module.exports=Prism),"undefined"!=typeof global&&(global.Prism=Prism); 4 | Prism.languages.markup={comment://,prolog:/<\?[\s\S]+?\?>/,doctype://i,cdata://i,tag:{pattern:/<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/i,greedy:!0,inside:{tag:{pattern:/^<\/?[^\s>\/]+/i,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"attr-value":{pattern:/=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/i,inside:{punctuation:[/^=/,{pattern:/^(\s*)["']|["']$/,lookbehind:!0}]}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:/&#?[\da-z]{1,8};/i},Prism.languages.markup.tag.inside["attr-value"].inside.entity=Prism.languages.markup.entity,Prism.hooks.add("wrap",function(a){"entity"===a.type&&(a.attributes.title=a.content.replace(/&/,"&"))}),Object.defineProperty(Prism.languages.markup.tag,"addInlined",{value:function(a,e){var s={};s["language-"+e]={pattern:/(^$)/i,lookbehind:!0,inside:Prism.languages[e]},s.cdata=/^$/i;var n={"included-cdata":{pattern://i,inside:s}};n["language-"+e]={pattern:/[\s\S]+/,inside:Prism.languages[e]};var i={};i[a]={pattern:RegExp("(<__[\\s\\S]*?>)(?:\\s*|[\\s\\S])*?(?=<\\/__>)".replace(/__/g,a),"i"),lookbehind:!0,greedy:!0,inside:n},Prism.languages.insertBefore("markup","cdata",i)}}),Prism.languages.xml=Prism.languages.extend("markup",{}),Prism.languages.html=Prism.languages.markup,Prism.languages.mathml=Prism.languages.markup,Prism.languages.svg=Prism.languages.markup; 5 | !function(s){var e=/("|')(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/;s.languages.css={comment:/\/\*[\s\S]*?\*\//,atrule:{pattern:/@[\w-]+?[\s\S]*?(?:;|(?=\s*\{))/i,inside:{rule:/@[\w-]+/}},url:RegExp("url\\((?:"+e.source+"|.*?)\\)","i"),selector:RegExp("[^{}\\s](?:[^{};\"']|"+e.source+")*?(?=\\s*\\{)"),string:{pattern:e,greedy:!0},property:/[-_a-z\xA0-\uFFFF][-\w\xA0-\uFFFF]*(?=\s*:)/i,important:/!important\b/i,function:/[-a-z0-9]+(?=\()/i,punctuation:/[(){};:,]/},s.languages.css.atrule.inside.rest=s.languages.css;var a=s.languages.markup;a&&(a.tag.addInlined("style","css"),s.languages.insertBefore("inside","attr-value",{"style-attr":{pattern:/\s*style=("|')(?:\\[\s\S]|(?!\1)[^\\])*\1/i,inside:{"attr-name":{pattern:/^\s*style/i,inside:a.tag.inside},punctuation:/^\s*=\s*['"]|['"]\s*$/,"attr-value":{pattern:/.+/i,inside:s.languages.css}},alias:"language-css"}},a.tag))}(Prism); 6 | Prism.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/,lookbehind:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0,greedy:!0}],string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/((?:\b(?:class|interface|extends|implements|trait|instanceof|new)\s+)|(?:catch\s+\())[\w.\\]+/i,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/,boolean:/\b(?:true|false)\b/,function:/\w+(?=\()/,number:/\b0x[\da-f]+\b|(?:\b\d+\.?\d*|\B\.\d+)(?:e[+-]?\d+)?/i,operator:/--?|\+\+?|!=?=?|<=?|>=?|==?=?|&&?|\|\|?|\?|\*|\/|~|\^|%/,punctuation:/[{}[\];(),.:]/}; 7 | Prism.languages.javascript=Prism.languages.extend("clike",{"class-name":[Prism.languages.clike["class-name"],{pattern:/(^|[^$\w\xA0-\uFFFF])[_$A-Z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\.(?:prototype|constructor))/,lookbehind:!0}],keyword:[{pattern:/((?:^|})\s*)(?:catch|finally)\b/,lookbehind:!0},{pattern:/(^|[^.])\b(?:as|async(?=\s*(?:function\b|\(|[$\w\xA0-\uFFFF]|$))|await|break|case|class|const|continue|debugger|default|delete|do|else|enum|export|extends|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)\b/,lookbehind:!0}],number:/\b(?:(?:0[xX][\dA-Fa-f]+|0[bB][01]+|0[oO][0-7]+)n?|\d+n|NaN|Infinity)\b|(?:\b\d+\.?\d*|\B\.\d+)(?:[Ee][+-]?\d+)?/,function:/[_$a-zA-Z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\s*(?:\.\s*(?:apply|bind|call)\s*)?\()/,operator:/-[-=]?|\+[+=]?|!=?=?|<>?>?=?|=(?:==?|>)?|&[&=]?|\|[|=]?|\*\*?=?|\/=?|~|\^=?|%=?|\?|\.{3}/}),Prism.languages.javascript["class-name"][0].pattern=/(\b(?:class|interface|extends|implements|instanceof|new)\s+)[\w.\\]+/,Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:/((?:^|[^$\w\xA0-\uFFFF."'\])\s])\s*)\/(\[(?:[^\]\\\r\n]|\\.)*]|\\.|[^/\\\[\r\n])+\/[gimyu]{0,5}(?=\s*($|[\r\n,.;})\]]))/,lookbehind:!0,greedy:!0},"function-variable":{pattern:/[_$a-zA-Z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\s*[=:]\s*(?:async\s*)?(?:\bfunction\b|(?:\((?:[^()]|\([^()]*\))*\)|[_$a-zA-Z\xA0-\uFFFF][$\w\xA0-\uFFFF]*)\s*=>))/,alias:"function"},parameter:[{pattern:/(function(?:\s+[_$A-Za-z\xA0-\uFFFF][$\w\xA0-\uFFFF]*)?\s*\(\s*)(?!\s)(?:[^()]|\([^()]*\))+?(?=\s*\))/,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/[_$a-z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\s*=>)/i,inside:Prism.languages.javascript},{pattern:/(\(\s*)(?!\s)(?:[^()]|\([^()]*\))+?(?=\s*\)\s*=>)/,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/((?:\b|\s|^)(?!(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)(?![$\w\xA0-\uFFFF]))(?:[_$A-Za-z\xA0-\uFFFF][$\w\xA0-\uFFFF]*\s*)\(\s*)(?!\s)(?:[^()]|\([^()]*\))+?(?=\s*\)\s*\{)/,lookbehind:!0,inside:Prism.languages.javascript}],constant:/\b[A-Z](?:[A-Z_]|\dx?)*\b/}),Prism.languages.insertBefore("javascript","string",{"template-string":{pattern:/`(?:\\[\s\S]|\${[^}]+}|[^\\`])*`/,greedy:!0,inside:{interpolation:{pattern:/\${[^}]+}/,inside:{"interpolation-punctuation":{pattern:/^\${|}$/,alias:"punctuation"},rest:Prism.languages.javascript}},string:/[\s\S]+/}}}),Prism.languages.markup&&Prism.languages.markup.tag.addInlined("script","javascript"),Prism.languages.js=Prism.languages.javascript; 8 | !function(){if("undefined"!=typeof self&&self.Prism&&self.document){var r=[],i={},n=function(){};Prism.plugins.toolbar={};var t=Prism.plugins.toolbar.registerButton=function(t,n){var e;e="function"==typeof n?n:function(t){var e;return"function"==typeof n.onClick?((e=document.createElement("button")).type="button",e.addEventListener("click",function(){n.onClick.call(this,t)})):"string"==typeof n.url?(e=document.createElement("a")).href=n.url:e=document.createElement("span"),e.textContent=n.text,e},t in i?console.warn('There is a button with the key "'+t+'" registered already.'):r.push(i[t]=e)},e=Prism.plugins.toolbar.hook=function(a){var t=a.element.parentNode;if(t&&/pre/i.test(t.nodeName)&&!t.parentNode.classList.contains("code-toolbar")){var e=document.createElement("div");e.classList.add("code-toolbar"),t.parentNode.insertBefore(e,t),e.appendChild(t);var o=document.createElement("div");o.classList.add("toolbar"),document.body.hasAttribute("data-toolbar-order")&&(r=document.body.getAttribute("data-toolbar-order").split(",").map(function(t){return i[t]||n})),r.forEach(function(t){var e=t(a);if(e){var n=document.createElement("div");n.classList.add("toolbar-item"),n.appendChild(e),o.appendChild(n)}}),e.appendChild(o)}};t("label",function(t){var e=t.element.parentNode;if(e&&/pre/i.test(e.nodeName)&&e.hasAttribute("data-label")){var n,a,o=e.getAttribute("data-label");try{a=document.querySelector("template#"+o)}catch(t){}return a?n=a.content:(e.hasAttribute("data-url")?(n=document.createElement("a")).href=e.getAttribute("data-url"):n=document.createElement("span"),n.textContent=o),n}}),Prism.hooks.add("complete",e)}}(); 9 | !function(){if("undefined"!=typeof self&&self.Prism&&self.document)if(Prism.plugins.toolbar){var r={html:"HTML",xml:"XML",svg:"SVG",mathml:"MathML",css:"CSS",clike:"C-like",js:"JavaScript",abap:"ABAP",abnf:"Augmented Backus–Naur form",apacheconf:"Apache Configuration",apl:"APL",arff:"ARFF",asciidoc:"AsciiDoc",adoc:"AsciiDoc",asm6502:"6502 Assembly",aspnet:"ASP.NET (C#)",autohotkey:"AutoHotkey",autoit:"AutoIt",shell:"Bash",basic:"BASIC",bnf:"Backus–Naur form",rbnf:"Routing Backus–Naur form",csharp:"C#",dotnet:"C#",cpp:"C++",cil:"CIL",coffee:"CoffeeScript",cmake:"CMake",csp:"Content-Security-Policy","css-extras":"CSS Extras",django:"Django/Jinja2",jinja2:"Django/Jinja2",dockerfile:"Docker",ebnf:"Extended Backus–Naur form",ejs:"EJS",erb:"ERB",fsharp:"F#",gcode:"G-code",gedcom:"GEDCOM",glsl:"GLSL",gml:"GameMaker Language",gamemakerlanguage:"GameMaker Language",graphql:"GraphQL",hs:"Haskell",hcl:"HCL",http:"HTTP",hpkp:"HTTP Public-Key-Pins",hsts:"HTTP Strict-Transport-Security",ichigojam:"IchigoJam",inform7:"Inform 7",javadoc:"JavaDoc",javadoclike:"JavaDoc-like",javastacktrace:"Java stack trace",jsdoc:"JSDoc","js-extras":"JS Extras",json:"JSON",jsonp:"JSONP",json5:"JSON5",latex:"LaTeX",emacs:"Lisp",elisp:"Lisp","emacs-lisp":"Lisp",lolcode:"LOLCODE",md:"Markdown","markup-templating":"Markup templating",matlab:"MATLAB",mel:"MEL",n1ql:"N1QL",n4js:"N4JS",n4jsd:"N4JS","nand2tetris-hdl":"Nand To Tetris HDL",nasm:"NASM",nginx:"nginx",nsis:"NSIS",objectivec:"Objective-C",ocaml:"OCaml",opencl:"OpenCL",parigp:"PARI/GP",objectpascal:"Object Pascal",php:"PHP",phpdoc:"PHPDoc","php-extras":"PHP Extras",plsql:"PL/SQL",powershell:"PowerShell",properties:".properties",protobuf:"Protocol Buffers",py:"Python",q:"Q (kdb+ database)",jsx:"React JSX",tsx:"React TSX",renpy:"Ren'py",rest:"reST (reStructuredText)",rb:"Ruby",sas:"SAS",sass:"Sass (Sass)",scss:"Sass (Scss)",sql:"SQL",soy:"Soy (Closure Template)",tap:"TAP",toml:"TOML",tt2:"Template Toolkit 2",ts:"TypeScript","t4-cs":"T4 Text Templates (C#)",t4:"T4 Text Templates (C#)","t4-vb":"T4 Text Templates (VB)","t4-templating":"T4 templating",vbnet:"VB.Net",vhdl:"VHDL",vim:"vim","visual-basic":"Visual Basic",vb:"Visual Basic",wasm:"WebAssembly",wiki:"Wiki markup",xeoracube:"XeoraCube",xojo:"Xojo (REALbasic)",xquery:"XQuery",yaml:"YAML",yml:"YAML"};Prism.plugins.toolbar.registerButton("show-language",function(e){var a=e.element.parentNode;if(a&&/pre/i.test(a.nodeName)){var s,t=a.getAttribute("data-language")||r[e.language]||((s=e.language)?(s.substring(0,1).toUpperCase()+s.substring(1)).replace(/s(?=cript)/,"S"):s);if(t){var o=document.createElement("span");return o.textContent=t,o}}})}else console.warn("Show Languages plugin loaded before Toolbar plugin.")}(); 10 | -------------------------------------------------------------------------------- /sharedAssets/shared-styles.css: -------------------------------------------------------------------------------- 1 | @import 'normalize.css'; 2 | @import 'prism.css'; 3 | /* Real Simple MDN Styles */ 4 | 5 | @font-face { 6 | font-family: 'zillaslab'; 7 | src: url('ZillaSlab.woff2') format('woff2'); 8 | } 9 | 10 | :root { 11 | --black: hsl(0, 0%, 16%); 12 | --white: hsl(0,0%,97%); 13 | --blue: hsl(198, 100%, 66%); 14 | --teal: hsl(198, 43%, 42%); 15 | --lightYellow: hsl(43, 100%, 92%); 16 | --grey: hsl(0, 0%, 80%); 17 | --unit: 1.2rem; 18 | } 19 | 20 | body { 21 | padding: var(--unit); 22 | background-color: var(--white); 23 | font-family: 'Arial', sans-serif; font-size: 100%; 24 | color: var(--black); line-height: 1.3; 25 | max-width: 900px; 26 | } 27 | 28 | /* page partials */ 29 | footer { 30 | padding: var(--unit); 31 | margin-top: calc(var(--unit)*2); 32 | border-top: 1px solid var(--grey); 33 | } 34 | footer p { 35 | margin: 0px; text-align: center; 36 | } 37 | 38 | /* base styles */ 39 | h1, h2 { 40 | font-family: "zillaslab", serif; 41 | } 42 | 43 | h2 { 44 | padding: calc(var(--unit)/2); 45 | background-color: var(--black); 46 | color: var(--white); font-weight: normal; 47 | } 48 | 49 | p {} 50 | 51 | a { 52 | border: 1px solid var(--teal); 53 | border-width: 0px 0px 1px 0px; 54 | color: var(--teal); 55 | text-decoration: none; 56 | } 57 | a:hover { 58 | border-width: 1px 0px 0px 0px; 59 | } 60 | 61 | nav ul { 62 | display: flex; justify-content: space-between; 63 | margin: 0px; padding: 0px; 64 | list-style: none; 65 | } 66 | nav li {margin: 0px; padding: 0px;} 67 | 68 | dl {display: flex; flex-wrap: wrap;} 69 | dt, dd {padding: 2%; box-sizing: border-box;} 70 | dt {width: 30%; font-weight: bold; text-align: right;} 71 | dd {width: 66%; margin: 0px;} 72 | 73 | code { 74 | background-color: var(--lightYellow); 75 | font-family:monospace; font-size:110%; 76 | letter-spacing:0.5px; 77 | } 78 | 79 | pre { 80 | padding: var(--unit); 81 | background-color: var(--grey); 82 | border-left: 4px solid var(--teal); 83 | white-space: pre-wrap; 84 | overflow-wrap: break-word; 85 | tab-size: 4; font-size: 86%; 86 | } 87 | 88 | pre code { 89 | background: none; 90 | } 91 | 92 | figure { 93 | margin: 0px; padding: 0px; 94 | } -------------------------------------------------------------------------------- /sharedAssets/styleguide.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Houdini Demos Styleguide 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |

This is a h2

15 |

This is a h1

16 |
17 | 18 | 25 | 26 |

This is a h3

27 |

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 |
48 |

This is the footer

49 |
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 | }
52 | 53 |

Check out all the retrieved styles

54 |
55 | Expand all the styles 56 |
57 |
58 | 59 |

Let's take a look at different value types in part two.

60 | 61 |
62 | 63 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /typedOM/intro/01partOne/style.css: -------------------------------------------------------------------------------- 1 | @import '/sharedAssets/shared-styles.css'; 2 | 3 | .example { 4 | --unit: 1.2rem; 5 | --mainColour: hsl(198, 100%, 66%); 6 | display: inline-block; 7 | padding: var(--unit) calc(var(--unit)*2); 8 | width: calc(30% + 20px); 9 | background: var(--mainColour) linear-gradient(180deg, var(--mainColour), hsl(198, 100%, 56%)) no-repeat left top; 10 | background-position: 0% 0%; 11 | border: 4px solid var(--mainColour); 12 | border-radius: 2px; 13 | 14 | font-size: calc(var(--unit)*2); color: var(--white); 15 | font-family: "zillaslab", serif; 16 | 17 | cursor: pointer; 18 | transform: scale(0.95); 19 | } 20 | 21 | .example:hover { 22 | background: var(--white) none no-repeat top left; 23 | color: var(--black); 24 | border-color: var(--black); 25 | transform: scale(1); 26 | } -------------------------------------------------------------------------------- /typedOM/intro/02partTwo/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 | // Part 1 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 9 | // let's get some styles -> we can retrieve all computed styles with `computedStyleMap` 10 | const allComputedStyles = buttonEl.computedStyleMap(); 11 | 12 | // for every sub class, 13 | // get a relevant computed style and return it's type 14 | // essentially a basic code test 15 | function logType(section, property) { 16 | const commentEl = section.querySelector('.get-type .comment'); 17 | const subclass = allComputedStyles.get(property); 18 | commentEl.appendChild( 19 | document.createTextNode(subclass.constructor.name) 20 | ); 21 | return subclass; 22 | } 23 | // CSSKeywordValue 24 | const keywordSection = document.querySelector('.keyword-val'); 25 | const cssKeywordValue = logType(keywordSection, 'display'); 26 | 27 | const keywordPropsEl = keywordSection.querySelector('.props .comment'); 28 | keywordPropsEl.appendChild( 29 | document.createTextNode(cssKeywordValue.value) 30 | ); 31 | 32 | // CSSImageValue 33 | const imageSection = document.querySelector('.image-val'); 34 | const cssImageVal = logType(imageSection, 'background-image'); 35 | 36 | const imagePropsEl = imageSection.querySelector('.props .comment'); 37 | imagePropsEl.appendChild( 38 | document.createTextNode(cssImageVal.value) 39 | ); 40 | 41 | // CSSUnitValue 42 | const unitSection = document.querySelector('.unit-val'); 43 | const cssUnitVal = logType(unitSection, 'border-top-width'); 44 | 45 | const unitPropsEl = unitSection.querySelector('.props .comment'); 46 | unitPropsEl.appendChild( 47 | document.createTextNode(`Value: ${cssUnitVal.value}, Unit: ${cssUnitVal.unit}`) 48 | ); 49 | 50 | // CSSMathValue 51 | const mathSection = document.querySelector('.math-val'); 52 | const cssMathVal = logType(mathSection, 'width'); 53 | 54 | let units = '' 55 | for (const unit of cssMathVal.values) { 56 | units += ` value: ${unit.value}, unit: ${unit.unit}`; 57 | } 58 | 59 | const mathPropsEl = mathSection.querySelector('.props .comment'); 60 | mathPropsEl.appendChild( 61 | document.createTextNode(`Operator: ${cssMathVal.operator}, | Values: ${units}`) 62 | ); 63 | 64 | // CSSTransformValue 65 | const transformSection = document.querySelector('.transform-val'); 66 | const cssTransformVal = logType(transformSection, 'transform'); 67 | 68 | // TODO make object to write out later 69 | // TODO change above code 70 | let scale = {} 71 | for (const transform of cssTransformVal) { 72 | scale.is2d = transform.is2D; 73 | scale.vec = ` x: ${transform.x}, y: ${transform.y}, z: ${transform.z}`; 74 | } 75 | 76 | const transformPropsEl = transformSection.querySelector('.props .comment'); 77 | transformPropsEl.appendChild( 78 | document.createTextNode(`is2D: ${scale.is2d} | Coords: ${scale.vec}`) 79 | ); 80 | 81 | // CSSUnparsedValue 82 | const parseSection = document.querySelector('.unparse-val'); 83 | const cssUnparseVal = logType(parseSection, '--unit'); 84 | 85 | const parsePropsEl = parseSection.querySelector('.props .comment'); 86 | parsePropsEl.appendChild( 87 | document.createTextNode(cssUnparseVal[0]) 88 | ); 89 | 90 | // parsing 91 | const parsed = CSSNumericValue.parse(cssUnparseVal); 92 | 93 | const anotherParsed = CSSStyleValue.parse('background', 'red'); 94 | 95 | // create a custom prop 96 | const customProp = new CSSVariableReferenceValue('--secondColour', new CSSUnparsedValue(['black'])); 97 | 98 | console.log(customProp); -------------------------------------------------------------------------------- /typedOM/intro/02partTwo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Part 2 Style Value Types | Typed OM 6 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 18 | 19 |
20 |

CSS Houdini Collection

21 |

Typed OM: Part 2 Style Value Types & Parsing

22 |
23 | 24 |
25 | 26 | 27 | 28 |

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...

41 | 42 |
const cssKeywordValue = allComputedStyles.get('display');
 43 | // Returns: 
44 | 45 |

This subclass gives you a value property which returns the display type set: cssKeywordValue.value

46 | 47 |
console.log(cssKeywordValue.value);
 48 | // Logs: 
49 | 50 |
51 | 52 |
53 |

CSSImageValue

54 |

Returned when the value of a CSS property is an image.

55 | 56 |
const cssImageValue = allComputedStyles.get('background-image');
 57 | // Returns: 
58 | 59 |

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.

68 |
69 | 70 |
71 |

CSSUnitValue

72 |

Returned if a value is of unit type.

73 | 74 |
const cssUnitValue = allComputedStyles.get('border-top-width');
 75 | // Returns: 
76 | 77 |

With this type we can access the numeric value: cssUnitVal.value and its unit: cssUnitVal.unit

78 | 79 |
console.log(`Value: ${cssUnitVal.value}, Unit: ${cssUnitVal.unit}`);
 80 | // Logs: 
81 |
82 | 83 |
84 |

CSSMathSum

85 |

Returned if the CSS value is determined via the calc(), min(), max() functions, and the units specified are of different unit types.

86 | 87 |
const cssMathSum = allComputedStyles.get('width');
 88 | // Returns: 
89 | 90 |

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.

104 | 105 |
const cssTransformValue = allComputedStyles.get('transform');
106 | // Returns: 
107 | 108 |

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.

109 | 110 |
let scale = {}
111 | for (const transform of cssTransformVal) {
112 | 	scale.is2d = transform.is2D;
113 | 	scale.vec = ` x: ${transform.x}, y: ${transform.y}, z: ${transform.z}`;
114 | }
115 | 
116 | console.log(`is2D: ${scale.is2d}, | Coords: ${scale.vec}`);
117 | // Logs: 
118 |
119 | 120 |
121 |

CSSUnparsedValue

122 |

Represents custom properties

123 | 124 |
const cssUnparsedValue = allComputedStyles.get('--unit');
125 | // Returns: 
126 | 127 |

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.

137 | 138 |
const customProp = new CSSVariableReferenceValue('--secondColour', new CSSUnparsedValue(['black']));
139 | 
140 | console.log(customProp);
141 | // Logs: CSSVariableReferenceValue {variable: "--secondColour", fallback: CSSUnparsedValue}
142 | 143 | 144 |

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:

151 | 152 |
const parsed = CSSNumericValue.parse(cssUnparseVal);
153 | console.log(parsed);
154 | // Logs: CSSUnitValue {value: 1.2, unit: "rem"}
155 | 156 |

We can do this with any property and value if we use the parse() method on the CSSStyleVal super class:

157 | 158 |
const anotherParsed = CSSStyleValue.parse('background', 'red');
159 | console.log(anotherParsed);
160 | // Logs: CSSStyleValue
161 | 162 |

This can be handy for a number of reasons. These new types are nice to work with in JavaScipt, plus we get out of the box error handling:1

163 | 164 |
try {
165 |   const css = CSSStyleValue.parse('transform', 'translate4d(bogus value)');
166 |   // use css
167 | } catch (err) {
168 |   console.err(err);
169 | }
170 | 171 |
172 | 173 |

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.

174 | 175 |

1 https://developers.google.com/web/updates/2018/03/cssom

176 | 177 |
178 | 179 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | -------------------------------------------------------------------------------- /typedOM/intro/02partTwo/style.css: -------------------------------------------------------------------------------- 1 | @import '/sharedAssets/shared-styles.css'; 2 | 3 | .example { 4 | --unit: 1.2rem; 5 | --mainColour: hsl(198, 100%, 66%); 6 | display: inline-block; 7 | padding: var(--unit) calc(var(--unit)*2); 8 | width: calc(30% + 20px); 9 | background: var(--mainColour) linear-gradient(180deg, var(--mainColour), hsl(198, 100%, 56%)) no-repeat left top; 10 | background-position: 0% 0%; 11 | border: 4px solid var(--mainColour); 12 | border-radius: 2px; 13 | 14 | font-size: calc(var(--unit)*2); color: var(--white); 15 | font-family: "zillaslab", serif; 16 | 17 | cursor: pointer; 18 | transform: scale(0.95) rotate(0deg); 19 | } 20 | 21 | .example:hover { 22 | background: var(--white) none no-repeat top left; 23 | color: var(--black); 24 | border-color: var(--black); 25 | transform: scale(1); 26 | } -------------------------------------------------------------------------------- /typedOM/intro/03partThree/app.js: -------------------------------------------------------------------------------- 1 | console.clear(); 2 | 3 | // get the button element 4 | const buttonEl = document.querySelector('.example'); 5 | 6 | // set top padding on button 7 | buttonEl.attributeStyleMap.set('padding-top', CSS.px(10)); 8 | const padTop = buttonEl.attributeStyleMap.get('padding-top'); 9 | console.log(padTop); 10 | 11 | // access stylesheet rules 12 | const stylesheet = document.styleSheets[0]; 13 | 14 | let buttonCSS = ''; 15 | Object.values(stylesheet.cssRules).forEach(block => { 16 | 17 | if (block.selectorText === '.example') { 18 | block.styleMap.set('--mainColour', 'black'); 19 | } 20 | }) 21 | 22 | -------------------------------------------------------------------------------- /typedOM/intro/03partThree/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Part 3 Methods | Typed OM 6 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 18 | 19 |
20 |

CSS Houdini Collection

21 |

Typed OM: Part 3 Style Maps & Methods

22 |
23 | 24 |
25 | 26 | 27 | 28 |

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.

35 | 36 |
buttonEl.attributeStyleMap.set('padding-top', CSS.px(10));
37 | 
38 | const padTop = buttonEl.attributeStyleMap.get('padding-top');
39 | 
40 | console.log(padTop);
41 | // logs: CSSUnitValue {value: 10, unit: 'px'}
42 | 43 |

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().

52 | 53 |
buttonEl.attributeStyleMap.has('padding-top') // returns true
54 | 
55 | buttonEl.attributeStyleMap.delete('padding-top') // removes padding-top from attribute styles
56 | 
57 | buttonEl.attributeStyleMap.clear() // removes all attribute styles
58 | 	
59 | 60 |

As well as attribute styles we can also access stylesheet rules:

61 | 62 |
const stylesheet = document.styleSheets[0];
63 | 
64 | Object.values(stylesheet.cssRules).forEach(block => {
65 | 
66 | 	if (block.selectorText === 'button') {
67 | 		console.log(block.cssText);
68 | 	}
69 | })
70 | // Logs css block: button { --unit: 1.2rem; --mainColour: hsl(198, 100%, 66%); display: inline-block; padding: var(--unit) calc(var(--unit)*2); ...}
71 | 
72 | 73 |

The methods already discussed are available to us and we can modify or update any of these properties like so:

74 | 75 |
if (block.selectorText === '.example') {
76 | 	block.styleMap.set('--mainColour', 'black');
77 | }
78 | 
79 | 80 |

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.

83 | 84 |

There's more examples here of using Typed OM in practice.

85 | 86 |

1https://developers.google.com/web/updates/2018/03/cssom

87 |
88 | 89 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /typedOM/intro/03partThree/style.css: -------------------------------------------------------------------------------- 1 | @import '/sharedAssets/shared-styles.css'; 2 | 3 | .example { 4 | --unit: 1.2rem; 5 | --mainColour: hsl(198, 100%, 66%); 6 | display: inline-block; 7 | padding: var(--unit) calc(var(--unit)*2); 8 | width: calc(30% + 20px); 9 | background: var(--mainColour) linear-gradient(180deg, var(--mainColour), hsl(198, 100%, 56%)) no-repeat left top; 10 | background-position: 0% 0%; 11 | border: 4px solid var(--mainColour); 12 | border-radius: 2px; 13 | 14 | font-size: calc(var(--unit)*2); color: var(--white); 15 | font-family: "zillaslab", serif; 16 | 17 | cursor: pointer; 18 | transform: scale(0.95); 19 | } 20 | 21 | .example:hover { 22 | background: var(--white) none no-repeat top left; 23 | color: var(--black); 24 | border-color: var(--black); 25 | transform: scale(1); 26 | } -------------------------------------------------------------------------------- /typedOM/numericValue/01unitValue/app.js: -------------------------------------------------------------------------------- 1 | console.clear(); 2 | 3 | // CSSUnitValue refresher 4 | const h2El = document.querySelector('header h2'); 5 | // set line-height 6 | h2El.attributeStyleMap.set('line-height', '1.3'); 7 | // get line-height 8 | const lineHeight = h2El.attributeStyleMap.get('line-height'); 9 | console.log('CSSUnitValue refresher:', lineHeight); 10 | 11 | // creating CSSUnitValues 12 | const percentVal = new CSSUnitValue(50, 'percent'); 13 | console.log('Creating value', percentVal); 14 | // factory method 15 | const pixelVal = CSS.px(42); 16 | console.log('Factory method', pixelVal); 17 | // using a string instead of a number 18 | const usingString = CSS.rem('2.4', 'rem'); 19 | console.log('Using a string', usingString); 20 | 21 | // Different Unit Types 22 | console.log('~~~~~~~~~~~~~~~~ Unit Types') 23 | // const numVal = new CSSUnitValue(1.2, 'number'); 24 | const numVal = CSS.number(1.2); 25 | console.log('Number:', numVal); 26 | 27 | const perVal = new CSSUnitValue(30, 'percent'); 28 | // const perVal = CSS.percent(30); 29 | console.log('Percent:', perVal); 30 | 31 | // const emVal = new CSSUnitValue(1.2, 'em'); 32 | const emVal = CSS.em(1.2); 33 | console.log('em:', emVal); 34 | 35 | // const remVal = new CSSUnitValue(2.4, 'rem'); 36 | const remVal = CSS.rem(2.4); 37 | console.log('rem:', remVal); 38 | 39 | // const chVal = new CSSUnitValue(2, 'ch'); 40 | const chVal = CSS.ch(2); 41 | console.log('ch:', chVal); 42 | 43 | // const exVal = new CSSUnitValue(1.6, 'ex'); 44 | const exVal = CSS.ex(1.6); 45 | console.log('ex:', exVal); 46 | 47 | // const vwVal = new CSSUnitValue(40, 'vw'); 48 | const vwVal = CSS.vw(40); 49 | console.log('vw:', vwVal); 50 | 51 | // const vhVal = new CSSUnitValue(60, 'vh'); 52 | const vhVal = CSS.vh(60); 53 | console.log('vh:', vhVal); 54 | 55 | // const vminVal = new CSSUnitValue(50, 'vmin'); 56 | const vminVal = CSS.vmin(50); 57 | console.log('vmin:', vminVal); 58 | 59 | // const vmaxVal = new CSSUnitValue(40, 'vmax'); 60 | const vmaxVal = CSS.vmax(40); 61 | console.log('vmax:', vmaxVal); 62 | 63 | // const pxVal = new CSSUnitValue(10, 'px'); 64 | const pxVal = CSS.px(10); 65 | console.log('px:', pxVal); 66 | 67 | // const cmVal = new CSSUnitValue(5, 'cm'); 68 | const cmVal = CSS.cm(5); 69 | console.log('cm:', cmVal); 70 | 71 | // const mmVal = new CSSUnitValue(15, 'mm'); 72 | const mmVal = CSS.mm(15); 73 | console.log('mm:', mmVal); 74 | 75 | // const QVal = new CSSUnitValue(100, 'Q'); 76 | const QVal = CSS.Q(100); 77 | console.log('Q:', QVal); 78 | 79 | // const inVal = new CSSUnitValue(60, 'in'); 80 | const inVal = CSS.in(60); 81 | console.log('in:', inVal); 82 | 83 | // const pcVal = new CSSUnitValue(12, 'pc'); 84 | const pcVal = CSS.pc(12); 85 | console.log('pc:', pcVal); 86 | 87 | // const ptVal = new CSSUnitValue(72, 'pt'); 88 | const ptVal = CSS.pt(72); 89 | console.log('pt:', ptVal); 90 | 91 | // The following have not been implemented at time of writing 92 | // const icVal = new CSSUnitValue(3, 'ic'); 93 | // const icVal = CSS.ic(3); 94 | 95 | // const lhVal = new CSSUnitValue(1.4, 'lh'); 96 | // const lhVal = CSS.lh(1.4); 97 | 98 | // const rlhVal = new CSSUnitValue(1.6, 'rlh'); 99 | // const rlhVal = CSS.rlh(1.6); 100 | 101 | // const viVal = new CSSUnitValue(70, 'vi'); 102 | // const viVal = CSS.vi(70); 103 | 104 | // const vbVal = new CSSUnitValue(60, 'vb'); 105 | // const vbVal = CSS.vb(60); 106 | 107 | // const degVal = new CSSUnitValue(180, 'deg'); 108 | const degVal = CSS.deg(180); 109 | console.log('deg:', degVal); 110 | 111 | // const gradVal = new CSSUnitValue(200, 'grad'); 112 | const gradVal = CSS.grad(200); 113 | console.log('grad:', gradVal); 114 | 115 | // const radVal = new CSSUnitValue(3.1, 'rad'); 116 | const radVal = CSS.rad(3.1); 117 | console.log('rad:', radVal); 118 | 119 | // const turnVal = new CSSUnitValue(0.5, 'turn'); 120 | const turnVal = CSS.turn(0.5); 121 | console.log('turn:', turnVal); 122 | 123 | // const sVal = new CSSUnitValue(1, 's'); 124 | const sVal = CSS.s(1); 125 | console.log('s:', sVal); 126 | 127 | // const msVal = new CSSUnitValue(200, 'ms'); 128 | const msVal = CSS.ms(200); 129 | console.log('ms:', msVal); 130 | 131 | // const HzVal = new CSSUnitValue(440, 'Hz'); 132 | const HzVal = CSS.Hz(440); 133 | console.log('Hz:', HzVal); 134 | 135 | // const kHzVal = new CSSUnitValue(3, 'kHz'); 136 | const kHzVal = CSS.kHz(3); 137 | console.log('kHz:', kHzVal); 138 | 139 | // const dpiVal = new CSSUnitValue(3, 'dpi'); 140 | const dpiVal = CSS.dpi(3); 141 | console.log('dpi:', dpiVal); 142 | 143 | // const dpcmVal = new CSSUnitValue(2.54, 'dpcm'); 144 | const dpcmVal = CSS.dpcm(2.54); 145 | console.log('dpcm:', dpcmVal); 146 | 147 | // const dppxVal = new CSSUnitValue(1, 'dppx'); 148 | const dppxVal = CSS.dppx(1); 149 | console.log('dppx:', dppxVal); 150 | 151 | // const frVal = new CSSUnitValue(1, 'fr'); 152 | const frVal = CSS.fr(1); 153 | console.log('fr:', frVal); 154 | 155 | -------------------------------------------------------------------------------- /typedOM/numericValue/01unitValue/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Numeric Value 1: Unit Values | Typed OM 6 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 18 | 19 |
20 |

CSS Houdini Collection

21 |

Typed OM Numeric Value: Unit Values

22 |
23 | 24 |
25 | 26 |

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

33 | 34 |
const el = document.querySelector('h2');
 35 | 
 36 | // set line-height
 37 | el.attributeStyleMap.set('line-height', '1.3');
 38 | 
 39 | // get line-height
 40 | const lineHeight = el.attributeStyleMap.get('line-height');
 41 | // Returns: CSSUnitValue {value: 1.3, unit: "number"}
42 | 43 |

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:

50 | 51 |
const percentVal = new CSSUnitValue(50, 'percent');
 52 | // Returns: CSSUnitValue {value: 50, unit: "percent"}
53 | 54 |

Here we're creating a percent unit with 50 as it's value, the equivalent of 50% in CSS.

55 | 56 |

The other way is using the factory method, in practise it creates the same and for the most part would be preferable as the code is simpler.

57 | 58 |
const pixelVal = CSS.px(42);
 59 | // Returns: CSSUnitValue {value: 42, unit: "pixel"}
60 | 61 |

Both of these methods can accept a string instead of a number as it's value and will still parse.

62 | 63 |
const emVal = new CSSUnitValue('1.2', 'em');
 64 | // Returns: CSSUnitValue {value: 1.2, unit: "em"}
 65 | 
 66 | const remVal = CSS.rem('2.4', 'rem');
 67 | // Returns: CSSUnitValue {value: 2.4, unit: "rem"}
68 | 69 |

Different unit types

70 | 71 |

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.

74 | 75 |

Number

76 | 77 |
const numVal = new CSSUnitValue(1.2, 'number');
 78 | // or
 79 | const numVal = CSS.number(1.2);
 80 | // Returns: CSSUnitValue {value: 1.2, unit: "number"}
81 | 82 |

Percent

83 | 84 |
const percentVal = new CSSUnitValue(30, 'percent');
 85 | // or
 86 | const percentVal = CSS.percent(30);
 87 | // Returns: CSSUnitValue {value: 30, unit: "percent"}
88 | 89 |

Length

90 | 91 |

There are lots of different length values you can use in CSS. Here is a link to the relevant documentation for each in this list in case you're unfamiliar with the unit.

92 | 93 |

em

94 | 95 |
const emVal = new CSSUnitValue(1.2, 'em');
 96 | // or
 97 | const emVal = CSS.em(1.2);
 98 | // Returns: CSSUnitValue {value: 1.2, unit: "em"}
99 | 100 |

rem

101 | 102 |
const remVal = new CSSUnitValue(2.4, 'rem');
103 | // or
104 | const remVal = CSS.rem(2.4);
105 | // Returns: CSSUnitValue {value: 2.4, unit: "rem"}
106 | 107 |

ch

108 | 109 |
const chVal = new CSSUnitValue(2, 'ch');
110 | // or
111 | const chVal = CSS.ch(2);
112 | // Returns: CSSUnitValue {value: 2, unit: "ch"}
113 | 114 |

ex

115 | 116 |
const exVal = new CSSUnitValue(1.6, 'ex');
117 | // or
118 | const exVal = CSS.ex(1.6);
119 | // Returns: CSSUnitValue {value: 1.6, unit: "ex"}
120 | 121 |

vw

122 | 123 |
const vwVal = new CSSUnitValue(40, 'vw');
124 | // or
125 | const vwVal = CSS.vw(40);
126 | // Returns: CSSUnitValue {value: 40, unit: "vw"}
127 | 128 |

vh

129 | 130 |
const vhVal = new CSSUnitValue(60, 'vh');
131 | // or
132 | const vhVal = CSS.vh(60);
133 | // Returns: CSSUnitValue {value: 60, unit: "vh"}
134 | 135 |

vmin

136 | 137 |
const vminVal = new CSSUnitValue(50, 'vmin');
138 | // or
139 | const vminVal = CSS.vmin(50);
140 | // Returns: CSSUnitValue {value: 50, unit: "vmin"}
141 | 142 |

vmax

143 | 144 |
const vmaxVal = new CSSUnitValue(40, 'vmax');
145 | // or
146 | const vmaxVal = CSS.vmax(40);
147 | // Returns: CSSUnitValue {value: 40, unit: "vmax"}
148 | 149 |

px

150 | 151 |
const pxVal = new CSSUnitValue(10, 'px');
152 | // or
153 | const pxVal = CSS.px(10);
154 | // Returns: CSSUnitValue {value: 10, unit: "px"}
155 | 156 |

cm

157 | 158 |
const cmVal = new CSSUnitValue(5, 'cm');
159 | // or
160 | const cmVal = CSS.cm(5);
161 | // Returns: CSSUnitValue {value: 5, unit: "cm"}
162 | 163 |

mm

164 | 165 |
const mmVal = new CSSUnitValue(15, 'mm');
166 | // or
167 | const mmVal = CSS.mm(15);
168 | // Returns: CSSUnitValue {value: 15, unit: "mm"}
169 | 170 |

Q

171 | 172 |
const QVal = new CSSUnitValue(100, 'Q');
173 | // or
174 | const QVal = CSS.Q(100);
175 | // Returns: CSSUnitValue {value: 100, unit: "q"}
176 | 177 |

in

178 | 179 |
const inVal = new CSSUnitValue(60, 'in');
180 | // or
181 | const inVal = CSS.in(60);
182 | // Returns: CSSUnitValue {value: 60, unit: "in"}
183 | 184 |

pc

185 | 186 |
const pcVal = new CSSUnitValue(12, 'pc');
187 | // or
188 | const pcVal = CSS.pc(12);
189 | // Returns: CSSUnitValue {value: 12, unit: "pc"}
190 | 191 |

pt

192 | 193 |
const ptVal = new CSSUnitValue(72, 'pt');
194 | // or
195 | const ptVal = CSS.pt(72);
196 | // Returns: CSSUnitValue {value: 72, unit: "pt"}
197 | 198 |

NB: The following are all length units but have not been implemented at the time of writing: ic lh rlh vi vb

199 | 200 |

Angle

201 | 202 |

There is a reference for all angle type units here.

203 | 204 |

deg

205 | 206 |
const degVal = new CSSUnitValue(180, 'deg');
207 | // or
208 | const degVal = CSS.deg(180);
209 | // Returns: CSSUnitValue {value: 180, unit: "deg"}
210 | 211 |

grad

212 | 213 |
const gradVal = new CSSUnitValue(200, 'grad');
214 | // or
215 | const gradVal = CSS.grad(200);
216 | // Returns: CSSUnitValue {value: 200, unit: "grad"}
217 | 218 |

rad

219 | 220 |
const radVal = new CSSUnitValue(3.1, 'rad');
221 | // or
222 | const radVal = CSS.rad(3.1);
223 | // Returns: CSSUnitValue {value: 3.1, unit: "rad"}
224 | 225 |

turn

226 | 227 |
const turnVal = new CSSUnitValue(0.5, 'turn');
228 | // or
229 | const turnVal = CSS.turn(0.5);
230 | // Returns: CSSUnitValue {value: 0.5, unit: "turn"}
231 | 232 |

Time

233 | 234 |

There is a reference for time units here.

235 | 236 |

s

237 | 238 |
const sVal = new CSSUnitValue(1, 's');
239 | // or
240 | const sVal = CSS.s(1);
241 | // Returns: CSSUnitValue {value: 1, unit: "s"}
242 | 243 |

ms

244 | 245 |
const msVal = new CSSUnitValue(200, 'ms');
246 | // or
247 | const msVal = CSS.ms(200);
248 | // Returns: CSSUnitValue {value: 200, unit: "ms"}
249 | 250 |

Frequency

251 | 252 |

The reference for frequency units can be found here.

253 | 254 |

Hz

255 | 256 |
const HzVal = new CSSUnitValue(440, 'Hz');
257 | // or
258 | const HzVal = CSS.Hz(440);
259 | // Returns: CSSUnitValue {value: 440, unit: "hz"}
260 | 261 |

kHz

262 | 263 |
const kHzVal = new CSSUnitValue(3, 'kHz');
264 | // or
265 | const kHzVal = CSS.kHz(3);
266 | // Returns: CSSUnitValue {value: 3, unit: "khz"}
267 | 268 |

Resolution

269 | 270 |

There is a reference for all resolution units here.

271 | 272 |

dpi

273 | 274 |
const dpiVal = new CSSUnitValue(3, 'dpi');
275 | // or
276 | const dpiVal = CSS.dpi(3);
277 | // Returns: CSSUnitValue {value: 3, unit: "dpi"}
278 | 279 |

dpcm

280 | 281 |
const dpcmVal = new CSSUnitValue(2.54, 'dpcm');
282 | // or
283 | const dpcmVal = CSS.dpcm(2.54);
284 | // Returns: CSSUnitValue {value: 2.54, unit: "dpcm"}
285 | 286 |

dppx

287 | 288 |
const dppxVal = new CSSUnitValue(1, 'dppx');
289 | // or
290 | const dppxVal = CSS.dppx(1);
291 | // Returns: CSSUnitValue {value: 1, unit: "dppx"}
292 | 293 |

Flex

294 | 295 |

The flex CSS property sets how a flex item will grow or shrink to fit the space available in its flex container. You can read more about it here.

296 | 297 |

fr

298 | 299 |
const frVal = new CSSUnitValue(1, 'fr');
300 | // or
301 | const frVal = CSS.fr(1);
302 | // Returns: CSSUnitValue {value: 1, unit: "fr"}
303 | 304 |

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: CSSMathValue in the next section.

307 | 308 |
309 | 310 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | -------------------------------------------------------------------------------- /typedOM/numericValue/01unitValue/style.css: -------------------------------------------------------------------------------- 1 | @import '/sharedAssets/shared-styles.css'; 2 | -------------------------------------------------------------------------------- /typedOM/numericValue/02mathValue/app.js: -------------------------------------------------------------------------------- 1 | console.clear(); 2 | 3 | const mathVal = new CSSMathSum(CSS.px(10), CSS.vw(20)); 4 | console.log('CSSMathValue example:', mathVal); 5 | console.log('Using toString(): ', mathVal.toString()); 6 | 7 | // Different math value types 8 | const el = document.querySelector('.example'); 9 | 10 | // addition 11 | const sumVal = new CSSMathSum(CSS.px(10), CSS.vw(20), CSS.em(1.2)); 12 | el.attributeStyleMap.set('width', sumVal); 13 | const gotSumVal = el.attributeStyleMap.get('width'); 14 | console.log('Sum addition:', gotSumVal); 15 | 16 | // subtraction 17 | const subVal = new CSSMathSum(CSS.s(2), CSS.ms(-200)); 18 | el.attributeStyleMap.set('animation-duration', subVal); 19 | const gotSubVal = el.attributeStyleMap.get('animation-duration'); 20 | console.log('Sum subtraction:', gotSubVal); 21 | 22 | // multiplication 23 | const productVal = new CSSMathProduct(CSS.em(1.6), CSS.number(1.2)); 24 | el.attributeStyleMap.set('font-size', productVal); 25 | const gotProductVal = el.attributeStyleMap.get('font-size'); 26 | console.log('Product:', gotProductVal); 27 | 28 | // negation 29 | const negVal = new CSSMathNegate(CSS.px(20)); 30 | el.attributeStyleMap.set('margin-top', negVal.toString()); 31 | const gotNegVal = el.attributeStyleMap.get('margin-top'); 32 | console.log('Negate:', negVal); 33 | 34 | // invert 35 | // const invVal = new CSSMathInvert(CSS.s(10)); 36 | // console.log(invVal.toString()); 37 | // el.attributeStyleMap.set('padding-bottom', invVal.toString()); 38 | // const gotInvVal = el.attributeStyleMap.get('padding-bottom'); 39 | 40 | const minVal = new CSSMathMin(CSS.vh(10), CSS.px(300)); 41 | console.log('min():', minVal); 42 | // el.attributeStyleMap.set('height', minVal.toString()); 43 | // const gotMinVal = el.attributeStyleMap.get('height'); 44 | 45 | const maxVal = new CSSMathMax(CSS.px(50), CSS.percent(20)); 46 | console.log('max():', maxVal); 47 | // el.attributeStyleMap.set('width', maxVal); 48 | // const gotMaxVal = el.attributeStyleMap.get('width'); 49 | 50 | // const clampVal = new CSSMathClamp(CSS.px(10), CSS.em(4), CSS.px(80)); 51 | // console.log('clamp():', clampVal); 52 | // el.attributeStyleMap.set('width', clampVal); 53 | // const gotClampVal = el.attributeStyleMap.get('width'); 54 | 55 | const nestedVal = new CSSMathMin( 56 | new CSSMathProduct(CSS.em(1.2), CSS.number(1.4)), 57 | CSS.vw(10) 58 | ); 59 | console.log('Nested:', nestedVal); 60 | // el.attributeStyleMap.set('font-size', nestedVal; 61 | // const gotNestedVal = el.attributeStyleMap.get('font-size'); 62 | 63 | -------------------------------------------------------------------------------- /typedOM/numericValue/02mathValue/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Numeric Value 2: Math Values | Typed OM 6 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 18 | 19 |
20 |

CSS Houdini Collection

21 |

Typed OM Numeric Value: Math Values

22 |
23 | 24 |
25 | 26 |

Let's see how we create a CSSMathValue and whilst doing so see what is available within the type:

27 | 28 |
const mathVal = new CSSMathSum(CSS.px(10), CSS.vw(20));
 29 | /*
 30 | Returns: CSSMathSum {
 31 | 	operator: "sum",
 32 | 	values: CSSNumericArray
 33 | 		0: CSSUnitValue {value: 10, unit: "px"},
 34 | 		1: CSSUnitValue {value: 20, unit: "vw"},
 35 | 		length: 2
 36 | }
 37 | */
38 | 39 |

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.

42 | 43 |
console.log(mathVal.toString());
 44 | // Logs: calc(10px + 20vw)
45 | 46 |

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.

52 | 53 |
const sumVal = new CSSMathSum(CSS.px(10), CSS.vw(20), CSS.em(1.2));
 54 | 
 55 | el.attributeStyleMap.set('width', sumVal);
 56 | 
 57 | const gotSumVal = el.attributeStyleMap.get('width');
 58 | /*
 59 | Returns: CSSMathSum {
 60 | 	operator: "sum",
 61 | 	values: CSSNumericArray
 62 | 		0: CSSUnitValue {value: 10, unit: "px"},
 63 | 		1: CSSUnitValue {value: 20, unit: "vw"},
 64 | 		2: CSSUnitValue {value: 1.2, unit: "em"},
 65 | 		length: 3
 66 | }
 67 | */
68 | 69 |

To subtract use a negative number

70 | 71 |
const subVal = new CSSMathSum(CSS.s(2), CSS.ms(-200));
 72 | 
 73 | el.attributeStyleMap.set('animation-duration', subVal);
 74 | 
 75 | const gotSubVal = el.attributeStyleMap.get('animation-duration');
 76 | /*
 77 | Returns: CSSMathSum {
 78 | 	operator: "sum",
 79 | 	values: CSSNumericArray
 80 | 		0: CSSUnitValue {value: 2, unit: "s"},
 81 | 		1: CSSUnitValue {value: -200, unit: "ms"},
 82 | 		length: 2
 83 | }
 84 | */
85 | 86 |

CSSMathProduct

87 |

Multiplication. Again here you can pass in a CSSNumericArray.

88 | 89 |
const productVal = new CSSMathProduct(CSS.em(1.6), CSS.number(1.2));
 90 | 
 91 | el.attributeStyleMap.set('font-size', productVal);
 92 | 
 93 | const gotProductVal = el.attributeStyleMap.get('font-size');
 94 | /*
 95 | Returns: CSSMathProduct {
 96 | 	operator: "product",
 97 | 	values: CSSNumericArray
 98 | 		0: CSSUnitValue {value: 1.6, unit: "em"},
 99 | 		1: CSSUnitValue {value: 1.2, unit: "number"},
100 | 		length: 2
101 | }
102 | */
103 | 104 |

CSSMathNegate

105 |

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.

106 | 107 |
const negVal = new CSSMathNegate(CSS.px(20));
108 | 
109 | // negVal.toString() returns 'calc(-20px)'
110 | el.attributeStyleMap.set('margin-top', negVal.toString());
111 | 
112 | const gotNegVal = el.attributeStyleMap.get('margin-top');
113 | /*
114 | Returns: CSSMathNegate {
115 | 	operator: "negate",
116 | 	value: CSSUnitValue {value: 20, unit: "px"}
117 | }
118 | */
119 | 120 | 130 | 131 |

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.

132 | 133 |

CSSMathMin

134 |

Takes a CSSNumericArray. If you are unfamiliar with the min() function you can find out more here.

135 | 136 |
const minVal = new CSSMathMin(CSS.vh(10), CSS.px(300));
137 | /*
138 | Returns: CSSMathMin {
139 | 	operator: "min",
140 | 	values: CSSNumericArray
141 | 		0: CSSUnitValue {value: 10, unit: "vh"},
142 | 		1: CSSUnitValue {value: 300, unit: "px"},
143 | 		length: 2
144 | }
145 | */
146 | 147 |

CSSMathMax Again, takes a CSSNumericArray. If you are unfamiliar with the max() function you can find out more here.

148 | 149 |
const maxVal = new CSSMathMax(CSS.px(50), CSS.percent(20));
150 | /*
151 | Returns: CSSMathMax {
152 | 	operator: "max",
153 | 	values: CSSNumericArray
154 | 		0: CSSUnitValue {value: 50, unit: "px"},
155 | 		1: CSSUnitValue {value: 20, unit: "percent"},
156 | 		length: 2
157 | }
158 | */
159 | 160 | 165 | 166 |

Nested calculations

167 | 168 |

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:

169 | 170 |
const nestedVal = new CSSMathMin(
171 |   new CSSMathProduct(CSS.em(1.2), CSS.number(1.4)),
172 |   CSS.vw(10)
173 | );
174 | /*
175 | Returns: CSSMathMin {
176 | 	operator: "min",
177 | 	values: CSSNumericArray
178 | 		0: CSSMathProduct {
179 | 			operator: "product"
180 | 			values: CSSNumericArray
181 | 				0: CSSUnitValue {value: 1.2, unit: "em"},
182 | 				1: CSSUnitValue {value: 1.4, unit: "number"},
183 | 				length: 2
184 | 			}
185 | 		1: CSSUnitValue {value: 300, unit: "px"},
186 | 		length: 2
187 | }
188 | */
189 | 
190 | 191 |

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.

192 | 193 |

There is also more. There's methods that each CSSNumericValue is privy to, let's check them out in the next part.

194 | 195 |
196 | 197 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | -------------------------------------------------------------------------------- /typedOM/numericValue/02mathValue/style.css: -------------------------------------------------------------------------------- 1 | @import '/sharedAssets/shared-styles.css'; 2 | 3 | h2 {padding-top: calc(5px * 2);} 4 | 5 | .example {display: none;} -------------------------------------------------------------------------------- /typedOM/numericValue/03methods/app.js: -------------------------------------------------------------------------------- 1 | console.clear(); 2 | 3 | let add = CSS.px(54).add(CSS.px(30)); 4 | console.log('Add:', add); 5 | add = CSS.px(54).add(CSS.em(2.4)).toString(); 6 | console.log('Add (dif unit):', add); 7 | 8 | let sub = CSS.mm(300).sub(CSS.mm(210)); 9 | console.log('Subtract:', sub); 10 | sub = CSS.cm(5).sub(CSS.mm(90)); 11 | console.log('Subtract (dif unit):', sub.toString()); 12 | 13 | let mul = CSS.em(1.2).mul(1.4); 14 | console.log('Multiply:', mul); 15 | 16 | let div = CSS.turn(1).div(2); 17 | console.log('Divide:', div); 18 | 19 | let min = CSS.px(500).min(CSS.px(400), CSS.px(200)); 20 | console.log('Min:', min); 21 | min = CSS.em(1.2).min(CSS.rem(1.4)); 22 | console.log('Min (dif unit):', min.toString()); 23 | 24 | let max = CSS.pt(50).max(CSS.pt(90), CSS.pt(10)); 25 | console.log('Max:', max); 26 | max = CSS.px(500).max(CSS.percent(60)); 27 | console.log('Max (dif unit):', max.toString()); 28 | 29 | // centimetres to inches 30 | const cm = CSS.in(2).to('cm'); 31 | console.log('Conversion:', cm); 32 | 33 | // equals test 34 | const fontSize = CSS.em(1.2); 35 | const equals = CSS.em(1.2).equals(fontSize); 36 | console.log('Equals:', equals); 37 | 38 | // degrees to radians 39 | const radians = CSS.deg(90).to('rad'); 40 | const check = CSS.deg(90).equals(radians.to('deg')); 41 | console.log(check); 42 | 43 | -------------------------------------------------------------------------------- /typedOM/numericValue/03methods/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Numeric Value 3: Methods | Typed OM 6 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 18 | 19 |
20 |

CSS Houdini Collection

21 |

Typed OM Numeric Value: Methods

22 |
23 | 24 |
25 | 26 |

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.

34 | 35 |
let add = CSS.px(54).add(CSS.em(2.4));
 36 | add.toString() // calc(54px + 2.4em)
 37 | 	
38 | 39 |

sub

40 | 41 |
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.

97 | 98 |
const fontSize = CSS.em(1.2);
 99 | CSS.em(1.2).equals(fontSize); // true
100 | 101 |

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.

102 | 103 |
const radians = CSS.deg(90).to('rad');
104 | 
105 | const check = CSS.deg(90).equals(radians.to('deg')); // true
106 | 	
107 | 108 |

Let's have a look at an example using what we have learnt so far

109 | 110 |
111 | 112 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | -------------------------------------------------------------------------------- /typedOM/numericValue/03methods/style.css: -------------------------------------------------------------------------------- 1 | @import '/sharedAssets/shared-styles.css'; 2 | 3 | h2 {padding-top: calc(5px * 2);} 4 | 5 | .example {display: none;} -------------------------------------------------------------------------------- /typedOM/numericValue/04example/app.js: -------------------------------------------------------------------------------- 1 | console.clear(); 2 | 3 | const mapEl = document.querySelector('.map'); 4 | mapEl.widthRatio = 100/mapEl.getBoundingClientRect().width; 5 | mapEl.heightRatio = 100/mapEl.getBoundingClientRect().height; 6 | 7 | function createMarker() { 8 | const markerImg = {}; 9 | markerImg.img = document.createElement('img'); 10 | markerImg.img.src = 'marker.svg'; 11 | markerImg.img.alt = 'Map marker'; 12 | markerImg.img.classList.add('marker'); 13 | markerImg.height = CSS.px(31); 14 | markerImg.width = CSS.px(34); 15 | return markerImg; 16 | } 17 | 18 | mapEl.addEventListener('click', function(e) { 19 | 20 | mapEl.x = mapEl.getBoundingClientRect().x; 21 | mapEl.y = mapEl.getBoundingClientRect().y; 22 | 23 | const marker = createMarker(); 24 | 25 | const top = (e.clientY - mapEl.y - marker.height.value) * mapEl.heightRatio; 26 | const left = (e.clientX - mapEl.x - marker.width.value/2) * mapEl.widthRatio; 27 | 28 | marker.top = CSS.percent(top); 29 | marker.left = CSS.percent(left); 30 | 31 | marker.img.attributeStyleMap.set('top', marker.top); 32 | marker.img.attributeStyleMap.set('left', marker.left); 33 | 34 | mapEl.appendChild(marker.img); 35 | 36 | }, false) 37 | -------------------------------------------------------------------------------- /typedOM/numericValue/04example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Typed OM Example 1: Map 6 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 18 | 19 |
20 |

CSS Houdini Collection

21 |

Typed OM Example 1: Map

22 |
23 | 24 |
25 | 26 |

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>
42 | 43 |
.map {
 44 | 	margin: 20px auto;
 45 | 	width: 80vw; height: 40vw;
 46 | 	border: 2px solid var(--teal);
 47 | 	position: relative;
 48 | }
 49 | .map__image img {
 50 | 	height: 100%; width: 100%;
 51 | 	display: block;
 52 | 	object-fit: cover;
 53 | }
 54 | .map__image figcaption {
 55 | 	position: absolute;
 56 | 	bottom:0px; right: 20px;
 57 | }
 58 | .map .marker {
 59 | 	position: absolute;
 60 | 	width: 34px; height: 31px;
 61 | }
62 | 63 |
64 |
65 | A picture of the map of the world 66 |
Image from: https://mapswire.com/world/physical-maps/
67 |
68 |
69 | 70 |

We'll set up our javaScript by making sure we have the containing .map div and making a function that creates the svg marker image.

71 | 72 |
const mapEl = document.querySelector('.map');
 73 | mapEl.x = mapEl.getBoundingClientRect().x;
 74 | mapEl.y = mapEl.getBoundingClientRect().y;
 75 | 
 76 | function createMarker() {
 77 | 	const markerImg = {};
 78 | 	marker.img = document.createElement('img');
 79 | 	marker.img.src = 'marker.svg';
 80 | 	marker.img.alt = 'Map marker';
 81 | 	marker.img.classList.add('marker');
 82 | 	markerImg.height = CSS.px(31);
 83 | 	markerImg.width = CSS.px(34);
 84 | 	return markerImg;
 85 | }
86 | 87 |

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.

94 | 95 |
marker.img.attributeStyleMap.set('top', marker.top);
96 | 97 |

Anyway, this is great, because we can work in numbers and carry on working in numbers. No strings here.

98 | 99 |

Here's all the code within the click event.

100 | 101 |
mapEl.addEventListener('click', function(e) {
102 | 
103 | mapEl.x = mapEl.getBoundingClientRect().x;
104 | mapEl.y = mapEl.getBoundingClientRect().y;
105 | 
106 | const marker = createMarker();
107 | 
108 | marker.top = new CSSUnitValue(e.clientY - mapEl.y - marker.height.value, 'px')
109 | marker.left = CSS.px(e.clientX - mapEl.x - marker.width.value/2);
110 | 
111 | marker.img.attributeStyleMap.set('top', marker.top);
112 | marker.img.attributeStyleMap.set('left', marker.left);
113 | 
114 | mapEl.appendChild(marker.img);
115 | 
116 | }, false)
117 | 118 |

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.

121 | 122 |
mapEl.widthRatio = 100/mapEl.getBoundingClientRect().width;
123 | 
124 | const top = (e.clientY - mapEl.y - marker.height.value) * mapEl.heightRatio;
125 | 
126 | marker.top = CSS.percent(top)
127 | 128 |

Here's the final code:

129 | 130 |
const mapEl = document.querySelector('.map');
131 | mapEl.widthRatio = 100/mapEl.getBoundingClientRect().width;
132 | mapEl.heightRatio = 100/mapEl.getBoundingClientRect().height;
133 | 
134 | function createMarker() {
135 | 	const markerImg = {};
136 | 	markerImg.img = document.createElement('img');
137 | 	markerImg.img.src = 'marker.svg';
138 | 	markerImg.img.alt = 'Map marker';
139 | 	markerImg.img.classList.add('marker');
140 | 	markerImg.height = CSS.px(31);
141 | 	markerImg.width = CSS.px(34);
142 | 	return markerImg;
143 | }
144 | 
145 | mapEl.addEventListener('click', function(e) {
146 | 
147 | 	mapEl.x = mapEl.getBoundingClientRect().x;
148 | 	mapEl.y = mapEl.getBoundingClientRect().y;
149 | 
150 | 	const marker = createMarker();
151 | 
152 | 	const top = (e.clientY - mapEl.y - marker.height.value) * mapEl.heightRatio;
153 | 	const left = (e.clientX - mapEl.x - marker.width.value/2) * mapEl.widthRatio;
154 | 
155 | 	marker.top = CSS.percent(top);
156 | 	marker.left = CSS.percent(left);
157 | 
158 | 	marker.img.attributeStyleMap.set('top', marker.top);
159 | 	marker.img.attributeStyleMap.set('left', marker.left);
160 | 
161 | 	mapEl.appendChild(marker.img);
162 | 
163 | }, false)
164 | 165 | 166 |

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.

167 | 168 |
169 | 170 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | -------------------------------------------------------------------------------- /typedOM/numericValue/04example/marker.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /typedOM/numericValue/04example/style.css: -------------------------------------------------------------------------------- 1 | @import '/sharedAssets/shared-styles.css'; 2 | 3 | .map { 4 | margin: var(--unit) auto; 5 | width: 80vw; height: 40vw; 6 | border: 2px solid var(--teal); 7 | position: relative; 8 | } 9 | .map__image img { 10 | height: 100%; width: 100%; 11 | display: block; 12 | object-fit: cover; 13 | } 14 | .map__image figcaption { 15 | position: absolute; 16 | bottom:0px; right: 20px; 17 | } 18 | 19 | .map .marker { 20 | position: absolute; 21 | width: 34px; height: 31px; 22 | } -------------------------------------------------------------------------------- /typedOM/numericValue/04example/world-map.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/houdini-examples/f6d085ad3877a3bd80845d637c8799a49d2ac4b0/typedOM/numericValue/04example/world-map.jpg -------------------------------------------------------------------------------- /typedOM/reference/README.md: -------------------------------------------------------------------------------- 1 | # Typed OM Reference 2 | 3 | --- 4 | 5 | ## Style Maps 6 | 7 | #### Computed styles 8 | 9 | `element.getComputedStyles();` 10 | 11 | #### Attribute styles 12 | 13 | `element.attributeStyleMap` 14 | 15 | #### Stylesheets 16 | 17 | `document.styleSheets` 18 | 19 | #### Methods 20 | 21 | Returns CSSStyleValue type for specified property 22 | `styleMap.get(property)` 23 | 24 | Sets property and value 25 | `styleMap.set(property, value)` 26 | 27 | Removes property 28 | `styleMap.delete(property)` 29 | 30 | Returns true if has property and false if not 31 | `styleMap.has(property)` 32 | 33 | Clears all styles 34 | `styleMap.clear()` 35 | 36 | --- 37 | 38 | ## Value Types 39 | 40 | #### CSSStyleValue 41 | 42 | Super class 43 | 44 | #### CSSKeywordValue 45 | 46 | `CSSKeywordValue {value: "inline-block"}` 47 | 48 | #### CSSImageValue 49 | 50 | At the time of writing return CSSStyleValue 51 | 52 | #### CSSNumericValue 53 | 54 | Super class for Unit & Math 55 | 56 | #### CSSUnitValue 57 | 58 | `CSSUnitValue {value: 4, unit: "px"}` 59 | 60 | #### CSSMathSum 61 | 62 | ``` 63 | CSSMathSum { 64 | operator: "sum", 65 | values: CSSNumericArray 66 | 0: CSSUnitValue 67 | unit: "percent" 68 | value: 30 69 | 1: CSSUnitValue 70 | unit: "px" 71 | value: 20 72 | } 73 | ``` 74 | 75 | [//]: # (Need to add all MathSum here) 76 | 77 | #### CSSTransformValue 78 | 79 | ``` 80 | CSSTransformValue {0: CSSScale, 1: CSSRotate, length: 2, is2D: true} 81 | 82 | 0: CSSScale 83 | is2D: true 84 | x: CSSUnitValue {value: 0.95, unit: "number"} 85 | y: CSSUnitValue {value: 0.95, unit: "number"} 86 | z: CSSUnitValue {value: 1, unit: "number"} 87 | 88 | 1: CSSRotate 89 | angle: CSSUnitValue {value: 0, unit: "deg"} 90 | is2D: true 91 | x: CSSUnitValue {value: 0, unit: "number"} 92 | y: CSSUnitValue {value: 0, unit: "number"} 93 | z: CSSUnitValue {value: 1, unit: "number"} 94 | ``` 95 | 96 | [//]: # (Need to add all Transforms here) 97 | 98 | #### CSSUnparsedValue 99 | 100 | `CSSUnparsedValue {0: valueString, length: 1}` 101 | 102 | #### CSSVariableReferenceValue 103 | 104 | `new CSSVariableReferenceValue(property, new CSSUnparsedValue([value]));` 105 | 106 | #### Methods 107 | 108 | `CSSStyleValue.parse(propertyString, valueString)` 109 | 110 | `CSSNumericValue.parse(valueString)` 111 | 112 | --- 113 | 114 | ## Operations 115 | 116 | #### Convert units 117 | 118 | `CSSUnitValue.to(unit)` 119 | 120 | Only works with absolute and not relative units 121 | 122 | --- 123 | 124 | ## Some things to look out for 125 | 126 | The difference between the style maps: getComputedStyles, attributeStyleMap and styleSheets. 127 | 128 | You can't `get()` a property value from attributeStyleMap for instance without it being present on the element first (by using `set()` for example). 129 | 130 | The spec is being worked on, holes are being plugged and new features in discussion. 131 | 132 | --- 133 | 134 | ## Resources 135 | 136 | - [Working with the new CSS Typed Object Model](https://developers.google.com/web/updates/2018/03/cssom) 137 | - [An overview of Typed OM](https://houdini.glitch.me/typed-om) 138 | - [Current Draft Specification](https://drafts.css-houdini.org/css-typed-om-1/) -------------------------------------------------------------------------------- /typedOM/transformValue/01values/app.js: -------------------------------------------------------------------------------- 1 | console.clear(); 2 | 3 | const transform = new CSSTransformValue([ 4 | new CSSRotate(CSS.deg(30)), 5 | new CSSScale(CSS.number(1.2), CSS.number(1.2)) 6 | ]) 7 | 8 | const rotate = new CSSRotate(CSS.deg(50)); 9 | console.log('Rotate:', rotate); 10 | 11 | const scale = new CSSScale(CSS.number(2), CSS.number(2)); 12 | console.log('Scale:', scale); 13 | 14 | const skew = new CSSSkew(CSS.deg(10), CSS.deg(20)); 15 | console.log('Skew:', skew); 16 | 17 | const skewX = new CSSSkewX(CSS.grad(200)); 18 | console.log('SkewX:', skewX); 19 | 20 | const skewY = new CSSSkewY(CSS.rad(1.3)); 21 | console.log('SkewY:', skewY); 22 | 23 | const perspective = new CSSPerspective(CSS.px(50)); 24 | console.log('Perspective:', perspective); 25 | 26 | const translate = new CSSTranslate(CSS.px(20), CSS.px(50)); 27 | console.log('Translate:', translate); 28 | 29 | const translate2d = new CSSTranslate(CSS.px(20), CSS.px(50)); 30 | let twodee = translate2d.is2D; // true 31 | console.log('is2D:', twodee); 32 | 33 | const translate3d = new CSSTranslate(CSS.px(20), CSS.px(50), CSS.px(-5)); 34 | twodee = translate3d.is2D; // false 35 | console.log('is2D:', twodee); 36 | 37 | const transformMatrix = new CSSTransformValue([ 38 | new CSSRotate(CSS.deg(30)), 39 | new CSSScale(CSS.number(1.2), CSS.number(1.2)) 40 | ]); 41 | 42 | const matrix = transformMatrix.toMatrix(); 43 | console.log('Matrix:', matrix); 44 | 45 | -------------------------------------------------------------------------------- /typedOM/transformValue/01values/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Transform Values | Typed OM 6 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 18 | 19 |
20 |

CSS Houdini Collection

21 |

Typed OM Transform Values

22 |
23 | 24 |
25 | 26 |

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.

27 | 28 |

Take this CSS for example:

29 | 30 |
svg {
 31 |   transform: rotate(25deg) scale(1.2);
 32 | }
33 | 34 |

If we want to change the rotate value we have to remember the scale value, so if we reset the transform it stays scaled at 1.2, otherwise we loose it:

35 | 36 |
const rotate = 30;
 37 | svgEl.style.transform = `rotate(${rotate}deg) scale(1.2)`;
38 | 39 |

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.

55 | 56 |

CSSRotate

57 | 58 |
const rotate = new CSSRotate(CSS.deg(50));
 59 | /* Returns: CSSRotate
 60 | 	angle: CSSUnitValue
 61 | 		unit: "deg"
 62 | 		value: 50
 63 | 	is2D: true */
64 | 65 |

CSSScale

66 | 67 |

Scale can take two or three values. Two means it is scaled on a 2D plane, three in three dimensions.

68 | 69 |
const scale = new CSSScale(CSS.number(2), CSS.number(2));
 70 | /* Returns: CSSScale
 71 | 	is2D: true
 72 | 	x: CSSUnitValue {value: 2, unit: "number"}
 73 | 	y: CSSUnitValue {value: 2, unit: "number"}
 74 | 	z: CSSUnitValue {value: 1, unit: "number"} */
75 | 76 |

CSSSkew

77 | 78 |

There are 3 types of skew, as they all skew differently: CSSSkew, CSSSkewX and CSSSkewY.

79 | 80 |
const skew = new CSSSkew(CSS.deg(10), CSS.deg(20));
 81 | /* Returns: CSSSKew
 82 | 	is2D: true
 83 | 	ax: CSSUnitValue {value: 10, unit: "deg"}
 84 | 	ay: CSSUnitValue {value: 20, unit: "deg"} */
 85 | 
 86 | 	const skewX = new CSSSkewX(CSS.grad(200));
 87 | /* Returns: CSSSKewX
 88 | 	is2D: true
 89 | 	ax: CSSUnitValue {value: 200, unit: "grad"} */
 90 | 
 91 | 	const skewY = new CSSSkewY(CSS.rad(1.3));
 92 | /* Returns: CSSSKewY
 93 | 	is2D: true
 94 | 	ax: CSSUnitValue {value: 1.3, unit: "rad"} */
95 | 96 |

CSSPerspective

97 | 98 |
const perspective = new CSSPerspective(CSS.px(50));
 99 | /* Returns: CSSPerspective
100 | 	is2D: false
101 | 	length: CSSUnitValue {value: 50, unit: "px"} */
102 | 103 |

CSSTranslate

104 | 105 |

The translate value can take two or three values. Two means it's a 2D transform, three means it is 3D.

106 | 107 |
const translate = new CSSTranslate(CSS.px(20), CSS.px(50));
108 | /* Returns: CSSTranslate
109 | 	is2D: true
110 | 	x: CSSUnitValue {value: 20, unit: "px"}
111 | 	y: CSSUnitValue {value: 50, unit: "px"}
112 | 	z: CSSUnitValue {value: 0, unit: "px"} */
113 | 114 |

Notice how all the types require compatible CSSNumericValues. If you need a refresher on the transform property the documentation is here.

115 | 116 |

is2D

117 | 118 |

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.

119 | 120 |
const translate2d = new CSSTranslate(CSS.px(20), CSS.px(50));
121 | 	let twodee = translate2d.is2D; // true
122 | 
123 | 	const translate3d = new CSSTranslate(CSS.px(20), CSS.px(50), CSS.px(-5));
124 | 	twodee = translate3d.is2D; // false
125 | 126 |

toMatrix()

127 | 128 |

As well as the is2D property there is a toMatrix() method which returns the matrix value of the given transform value.

129 | 130 |
const transform = new CSSTransformValue([
131 |   new CSSRotate(CSS.deg(30)),
132 |   new CSSScale(CSS.number(1.2))
133 | ]);
134 | 
135 | const matrix = transform.toMatrix();
136 | /* Returns: DOMMatrix
137 | a: 1.0392304845413265
138 | b: 0.5999999999999999
139 | c: -0.5999999999999999
140 | d: 1.0392304845413265
141 | e: 0
142 | f: 0
143 | is2D: true
144 | isIdentity: false
145 | m11: 1.0392304845413265
146 | m12: 0.5999999999999999
147 | m13: 0
148 | m14: 0
149 | m21: -0.5999999999999999
150 | m22: 1.0392304845413265
151 | m23: 0
152 | m24: 0
153 | m31: 0
154 | m32: 0
155 | m33: 1
156 | m34: 0
157 | m41: 0
158 | m42: 0
159 | m43: 0
160 | m44: 1 */
161 | 162 |

This can appear a little overwhelming as it's returning both the 2D matrix (a-f) and the 3D matrix (m11 - m44).

163 | 164 |

Now we've seen all the types, let's put them into practise with an example in the next section

165 | 166 |
167 | 168 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | -------------------------------------------------------------------------------- /typedOM/transformValue/01values/style.css: -------------------------------------------------------------------------------- 1 | @import '/sharedAssets/shared-styles.css'; 2 | -------------------------------------------------------------------------------- /typedOM/transformValue/02example/app.js: -------------------------------------------------------------------------------- 1 | console.clear(); 2 | 3 | class Sparkle { 4 | 5 | constructor(originX, originY) { 6 | this.x = CSS.px(originX); 7 | this.y = CSS.px(originY); 8 | this.img = this.createImage(); 9 | } 10 | 11 | get _translate() { 12 | const shift = Math.floor(Math.random() * 40) - 20; 13 | return new CSSTranslate( 14 | CSS.px(shift), 15 | CSS.px(shift) 16 | ) 17 | } 18 | 19 | get _rotate() { 20 | return new CSSRotate(CSS.turn(Math.random())); 21 | } 22 | 23 | get _scale() { 24 | const scaleVal = Math.random() * 2; 25 | return new CSSScale(CSS.number(scaleVal), CSS.number(scaleVal)); 26 | } 27 | 28 | get transform() { 29 | return new CSSTransformValue([ 30 | this._translate, 31 | this._rotate, 32 | this._scale 33 | ]) 34 | } 35 | 36 | get _pickSource() { 37 | const svgs = ['star.svg', 'cross.svg', 'square.svg', 'circle.svg']; 38 | const pick = Math.floor(Math.random()*4); 39 | return svgs[pick]; 40 | } 41 | 42 | get _timeAlive() { 43 | return CSS.ms(Math.ceil(Math.random()*500)) 44 | } 45 | 46 | createImage() { 47 | const image = document.createElement('img'); 48 | image.src = this._pickSource; 49 | image.alt = 'sparkle'; 50 | image.classList.add('sparkle'); 51 | image.attributeStyleMap.set('left', this.x); 52 | image.attributeStyleMap.set('top', this.y); 53 | image.attributeStyleMap.set('transform', this.transform); 54 | image.attributeStyleMap.set('animation-duration', this._timeAlive); 55 | return image; 56 | } 57 | 58 | } 59 | 60 | const sparkleBoard = document.querySelector('.sparkleArea'); 61 | let boardDimensions = sparkleBoard.getBoundingClientRect(); 62 | let x = 0; 63 | let y = 0; 64 | 65 | sparkleBoard.addEventListener('mousemove', function(e) { 66 | 67 | boardDimensions = sparkleBoard.getBoundingClientRect(); 68 | x = e.clientX - boardDimensions.left; 69 | y = e.clientY - boardDimensions.top; 70 | 71 | const spark = new Sparkle(x, y); 72 | 73 | sparkleBoard.appendChild(spark.img); 74 | 75 | // quick and dirty way to remove sparkles from DOM to help performance 76 | setTimeout(() => sparkleBoard.firstChild.remove(), 500); 77 | 78 | }, false); 79 | 80 | 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /typedOM/transformValue/02example/circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /typedOM/transformValue/02example/cross.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /typedOM/transformValue/02example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Transform Values Example | Typed OM 6 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 18 | 19 |
20 |

CSS Houdini Collection

21 |

Typed OM Transform Values Example

22 |
23 | 24 |
25 | 26 |

Now we have all this transform power, let's use it to create an example. It is Houdini after all. How about some magical sparkles.

27 | 28 |

We'll create some randomised SVG images, that appear when the mouse is dragged around an area. We can do all of this within javaScript.

29 | 30 |
31 | 32 |

Let's set up our area and style it.

33 | 34 |
<section class="sparkleArea"></section>
35 | 36 |
.sparkleArea {
 37 | 	position: relative;
 38 | 	height: 50vw;
 39 | 	background-color: hsl(234,42%,42%);
 40 | 	overflow: hidden;
 41 | }
42 | 43 |

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.

44 | 45 |
class Sparkle {
 46 | 
 47 | 	constructor(originX, originY) {
 48 | 		this.x = CSS.px(originX);
 49 | 		this.y = CSS.px(originY);
 50 | 		this.img = this.createImage();
 51 | 	}
 52 | 
 53 | }
54 | 55 |

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.

58 | 59 |
get _translate() {
 60 | 	const shift = Math.floor(Math.random() * 40) - 20;
 61 | 	return new CSSTranslate(
 62 | 		CSS.px(shift),
 63 | 		CSS.px(shift)
 64 | 	)
 65 | }
66 | 67 |

We'll use similar methods for both rotating and scaling our sparkles, each can return a CSSRotate value and CSSScale respectively:

68 | 69 |
get _rotate() {
 70 | 	return new CSSRotate(CSS.turn(Math.random()));
 71 | }
 72 | 
 73 | get _scale() {
 74 | 	const scaleVal = Math.random() * 2;
 75 | 	return new CSSScale(CSS.number(scaleVal), CSS.number(scaleVal));
 76 | }
 77 | 
78 | 79 |

Now we can pop all of these into a CSSTransformValue.

80 | 81 |
get transform() {
 82 | 	return new CSSTransformValue([
 83 | 		this._translate,
 84 | 		this._rotate,
 85 | 		this._scale
 86 | 	])
 87 | }
88 | 89 |

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

90 | 91 |
.sparkle {
 92 | 	position: absolute;
 93 | 	animation-name: fadeOut;
 94 | 	animation-timing-function: ease-in;
 95 | 	opacity: 0;
 96 | }
 97 | 
 98 | @keyframes fadeOut {
 99 | 	from {opacity: 1;}
100 | 	to {opacity: 0;}
101 | }
102 | 103 |

When the image is shown we can set the animation-duration property to a random amount of milliseconds. We can create a CSSUnitValue for this.

104 | 105 |
get _timeAlive() {
106 | 	return CSS.ms(Math.ceil(Math.random()*500))
107 | }
108 | 109 |

We have four different SVGs to choose from for the source of our sparkle. Let's create a method to pick one at random:

110 | 111 |
get _pickSource() {
112 | 	const svgs = ['star.svg', 'cross.svg', 'square.svg', 'circle.svg'];
113 | 	const pick = Math.floor(Math.random()*4);
114 | 	return svgs[pick];
115 | }
116 | 117 |

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.

118 | 119 |
createImage() {
120 | 	const image = document.createElement('img');
121 | 	image.src = this._pickSource;
122 | 	image.alt = 'sparkle';
123 | 	image.classList.add('sparkle');
124 | 	image.attributeStyleMap.set('left', this.x);
125 | 	image.attributeStyleMap.set('top', this.y);
126 | 	image.attributeStyleMap.set('transform', this.transform);
127 | 	image.attributeStyleMap.set('animation-duration', this._timeAlive);
128 | 	return image;
129 | }
130 | 	
131 | 132 |

The entire class now looks like this:

133 | 134 |
class Sparkle {
135 | 
136 | 	constructor(originX, originY) {
137 | 		this.x = CSS.px(originX);
138 | 		this.y = CSS.px(originY);
139 | 		this.img = this.createImage();
140 | 	}
141 | 
142 | 	get _translate() {
143 | 		const shift = Math.floor(Math.random() * 40) - 20;
144 | 		return new CSSTranslate(
145 | 			CSS.px(shift),
146 | 			CSS.px(shift)
147 | 		)
148 | 	}
149 | 
150 | 	get _rotate() {
151 | 		return new CSSRotate(CSS.turn(Math.random()));
152 | 	}
153 | 
154 | 	get _scale() {
155 | 		const scaleVal = Math.random() * 2;
156 | 		return new CSSScale(CSS.number(scaleVal), CSS.number(scaleVal));
157 | 	}
158 | 
159 | 	get transform() {
160 | 		return new CSSTransformValue([
161 | 			this._translate,
162 | 			this._rotate,
163 | 			this._scale
164 | 		])
165 | 	}
166 | 
167 | 	get _pickSource() {
168 | 		const svgs = ['star.svg', 'cross.svg', 'square.svg', 'circle.svg'];
169 | 		const pick = Math.floor(Math.random()*4);
170 | 		return svgs[pick];
171 | 	}
172 | 
173 | 	get _timeAlive() {
174 | 		return CSS.ms(Math.ceil(Math.random()*500))
175 | 	}
176 | 
177 | 	createImage() {
178 | 		const image = document.createElement('img');
179 | 		image.src = this._pickSource;
180 | 		image.alt = 'sparkle';
181 | 		image.classList.add('sparkle');
182 | 		image.attributeStyleMap.set('left', this.x);
183 | 		image.attributeStyleMap.set('top', this.y);
184 | 		image.attributeStyleMap.set('transform', this.transform);
185 | 		image.attributeStyleMap.set('animation-duration', this._timeAlive);
186 | 		return image;
187 | 	}
188 | 
189 | }
190 | 191 |

All that's left to do is pick the .sparkleboard element and add a mousemove event, so we can create new sparkles when we move the mouse over it.

192 | 193 |

Note we also need the bounding rectangle of the element to adjust for the correct mouse coordinates.

194 | 195 |
const sparkleBoard = document.querySelector('.sparkleArea');
196 | let boardDimensions = sparkleBoard.getBoundingClientRect();
197 | let x = 0;
198 | let y = 0;
199 | 
200 | sparkleBoard.addEventListener('mousemove', function(e) {
201 | 
202 | 	boardDimensions = sparkleBoard.getBoundingClientRect();
203 | 	x = e.clientX - boardDimensions.left;
204 | 	y = e.clientY - boardDimensions.top;
205 | 
206 | 	const spark = new Sparkle(x, y);
207 | 
208 | 	sparkleBoard.appendChild(spark.img);
209 | 
210 | 	// quick and dirty way to remove sparkles from DOM to help performance
211 | 	setTimeout(() => sparkleBoard.firstChild.remove(), 500);
212 | 
213 | }, false);
214 | 215 |

You can see the final code here

216 | 217 |
218 | 219 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | -------------------------------------------------------------------------------- /typedOM/transformValue/02example/square.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /typedOM/transformValue/02example/star.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /typedOM/transformValue/02example/style.css: -------------------------------------------------------------------------------- 1 | @import '/sharedAssets/shared-styles.css'; 2 | 3 | :root { 4 | --navy: hsl(234,42%,42%); 5 | } 6 | 7 | .sparkleArea { 8 | position: relative; 9 | height: 50vw; 10 | background-color: var(--navy); 11 | overflow: hidden; 12 | } 13 | 14 | .sparkle { 15 | position: absolute; 16 | animation-name: fadeOut; 17 | animation-timing-function: ease-in; 18 | opacity: 0; 19 | } 20 | 21 | @keyframes fadeOut { 22 | from {opacity: 1;} 23 | to {opacity: 0;} 24 | } --------------------------------------------------------------------------------