Tag: Introduction

An Introduction to the Picture-in-Picture Web API

Picture-in-Picture made its first appearance on the web in the Safari browser with the release of macOS Sierra in 2016. It made it possible for a user to pop a video out into a small floating window that stays above all others, so that they can keep watching while doing other things. It’s an idea that came from TV, where, for example, you might want to keep watching your Popular Sporting Event even as you browse the guide or even other channels.

Not long after that, Android 8.0 was released which included picture-in-picture support via native APIs. Chrome for Android was able to play videos in picture-in-picture mode through this API even though its desktop counterpart was unable to do so.

This lead to the drafting of a standard Picture-in-Picture Web API which makes it possible for websites to initiate and control this behavior.

At the time of writing, only Chrome (version 70+) and Edge (version 76+) support this feature. Firefox, Safari, and Opera all use proprietary APIs for their implementations.

This browser support data is from Caniuse, which has more detail. A number indicates that browser supports the feature at that version and up.

Desktop

Chrome Opera Firefox IE Edge Safari
70 64 72 No 76 TP

Mobile / Tablet

iOS Safari Opera Mobile Opera Mini Android Android Chrome Android Firefox
13.3 No No No 78 68

How to use the Picture-In-Picture API

Let’s start by adding a video to a webpage.

<video controls src="video.mp4"></video>

In Chrome, there will already be a toggle for entering and exiting Picture-in-Picture mode.

Showing a video with the picture-in-picture option in the bottom right of the screen.

To test Firefox’s implementation, you’ll need to enable the media.videocontrols.picture-in-picture.enabled flag in about:config first, then right-click on the video to find the picture-in-picture option.

Showing the Firefox settings that enable picture-in-picture.

While this works, in many cases, you want your video controls to be consistent across browsers and you might want control over which videos can be entered into picture-in-picture mode and which ones cannot.

We can replace the default method of entering picture-in-picture mode in the browser with our own method using the Picture-in-Picture Web API. For example, let’s add a button that, when clicked, enables it:

<button id="pipButton" class="hidden" disabled>Enter Picture-in-Picture mode</button>

Then select both the video and the button in JavaScript:

const video = document.getElementById('video'); const pipButton = document.getElementById('pipButton');

The button is hidden and disabled by default because we need to know if the Picture-in-Picture API is supported and enabled in the user’s browser before displaying the it. This is a form of progressive enhancement which helps avoid a broken experience in browsers that do not support the feature.

We can check that the API is supported and enable the button as shown below:

if ('pictureInPictureEnabled' in document) {   pipButton.classList.remove('hidden')   pipButton.disabled = false; }

Entering picture-in-picture mode

Let’s say our JavaScript has determined that the browser has picture-in-picture support enabled. Let’s call requestPictureInPicture() on the video element when the button with #pipButton is clicked. This method returns a promise that places the video in a mini window on the bottom-right side of the screen by default when resolved, although it can be moved around by the user.

if ('pictureInPictureEnabled' in document) {   pipButton.classList.remove('hidden')   pipButton.disabled = false;    pipButton.addEventListener('click', () => {     video.requestPictureInPicture();   }); }

We cannot leave the code above as is because requestPictureInPicture() returns a promise and it’s possible for the promise to reject if, for example, the video metadata is not yet loaded or if the disablePictureInPicture attribute is present on the video.

Let’s add a catch block to capture this potential error and let the user know what is going on:

pipButton.addEventListener('click', () => {   video     .requestPictureInPicture()     .catch(error => {       // Error handling     }); });

Exiting picture-in-picture mode

The browser helpfully provides a close button on the picture-in-picture window, which enables the window to be close when clicked. However, we can also provide another way to exit picture-in-picture mode as well. For example, we can make clicking our #pipButton close any active picture-in-picture window.

pipButton.addEventListener('click', () => {   if (document.pictureInPictureElement) {     document       .exitPictureInPicture()       .catch(error => {       // Error handling     })   } else {     // Request Picture-in-Picture   } });

One other situation where you might want to close the picture-in-picture window is when the video enters full-screen mode. Chrome already does this automatically without having to write any code.

Picture-in-Picture events

The browser allows us to detect when a video enters or leaves picture-in-picture mode. Since there are many ways picture-in-picture mode can be entered or exited, it is better to rely on event detection to update media controls.

The events are enterpictureinpicture and leavepictureinpicture which, as their names imply, fire when a video enters or exits picture-in-picture mode, respectively.

In our example, we need to update the #pipButton label depending on whether the video is or is not currently in picture-in-picture mode.

Here’s the code that helps us achieve that:

video.addEventListener('enterpictureinpicture', () => {   pipButton.textContent = 'Exit Picture-in-Picture mode'; });  video.addEventListener('leavepictureinpicture', () => {   pipButton.textContent = 'Enter Picture-in-Picture mode'; });

Here’s a demo of that:

See the Pen “>@ayoisaiah)
on MediaStream object (produced by a virtual video source such as a camera, video recording device, screen sharing service, or other hardware sources).

It’s also possible to add controls that go to the previous or next track straight from the picture-in-picture window:

navigator.mediaSession.setActionHandler('previoustrack', () => {   // Go to previous track });  navigator.mediaSession.setActionHandler('nexttrack', () => {   // Go to next track });

Displaying a webcam feed in a picture-in-picture window

Video meeting web applications could benefit from placing a webcam feed in picture-in-picture mode when a user switches back and forth between the app and other browser tabs or windows.

Here’s an example:

See the Pen “>@ayoisaiah)
on <video disablePictureInPicture controls src="video.mp4>"></video>

Wrapping up

That’s pretty much all you need to about the Picture-in-Picture Web API at this time! Currently, the API only supports the <video> element but it is meant to be extensible to other elements as well.

Although browser support is spotty right now, you can still use it as a way to progressively enhance the video experience on your website.

Further reading

The post An Introduction to the Picture-in-Picture Web API appeared first on CSS-Tricks.

CSS-Tricks

,

An Introduction to Web Components

Front-end development moves at a break-neck pace. This is made evident by the myriad articles, tutorials, and Twitter threads bemoaning the state of what once was a fairly simple tech stack. In this article, I’ll discuss why Web Components are a great tool to deliver high-quality user experiences without complicated frameworks or build steps and that don’t run the risk of becoming obsolete. In subsequent articles of this five-part series, we will dive deeper into each of the specifications.

This series assumes a basic understanding of HTML, CSS, and JavaScript. If you feel weak in one of those areas, don’t worry, building a custom element actually simplifies many complexities in front-end development.

Article Series:

  1. An Introduction to Web Components (This post)
  2. Crafting Reusable HTML Templates (Coming soon!)
  3. Creating a Custom Element from Scratch (Coming soon!)
  4. Encapsulating Style and Structure with Shadow DOM (Coming soon!)
  5. Advanced Tooling for Web Components (Coming soon!)

What are Web Components, anyway?

Web Components consist of three separate technologies that are used together:

  1. Custom Elements. Quite simply, these are fully-valid HTML elements with custom templates, behaviors and tag names (e.g. <one-dialog>) made with a set of JavaScript APIs. Custom Elements are defined in the HTML Living Standard specification.
  2. Shadow DOM. Capable of isolating CSS and JavaScript, almost like an <iframe>. This is defined in the Living Standard DOM specification.
  3. HTML templates. User-defined templates in HTML that aren’t rendered until called upon. The <template> tag is defined in the HTML Living Standard specification.

These are what make up the Web Components specification.

HTML Imports is likely to be the fourth technology in the stack, but it has yet to be implemented in any of the big four browsers. The Chrome team has announced it an intent to implement them in a future release.

Web Components are generally available in all of the major browsers with the exception of Microsoft Edge and Internet Explorer 11, but polyfills exist to fill in those gaps.

Referring to any of these as Web Components is technically accurate because the term itself is a bit overloaded. As a result, each of the technologies can be used independently or combined with any of the others. In other words, they are not mutually exclusive.

Let’s take a quick look at each of those first three. We’ll dive deeper into them in other articles in this series.

Custom elements

As the name implies, custom elements are HTML elements, like <div>, <section> or <article>, but something we can name ourselves that are defined via a browser API. Custom elements are just like those standard HTML elements — names in angle brackets — except they always have a dash in them, like <news-slider> or <bacon-cheeseburger>. Going forward, browser vendors have committed not to create new built-in elements containing a dash in their names to prevent conflicts.

Custom elements contain their own semantics, behaviors, markup and can be shared across frameworks and browsers.

class MyComponent extends HTMLElement {   connectedCallback() {     this.innerHTML = `<h1>Hello world</h1>`;   } }      customElements.define('my-component', MyComponent);

See the Pen
Custom elements demo
by Caleb Williams (@calebdwilliams)
on CodePen.

In this example, we define <my-component>, our very own HTML element. Admittedly, it doesn’t do much, however this is the basic building block of a custom element. All custom elements must in some way extend an HTMLElement in order to be registered with the browser.

Custom elements exist without third-party frameworks and the browser vendors are dedicated to the continued backward compatibility of the spec, all but guaranteeing that components written according to the specifications will not suffer from breaking API changes. What’s more, these components can generally be used out-of-the-box with today’s most popular frameworks, including Angular, React, Vue, and others with minimal effort.

Shadow DOM

The shadow DOM is an encapsulated version of the DOM. This allows authors to effectively isolate DOM fragments from one another, including anything that could be used as a CSS selector and the styles associated with them. Generally, any content inside of the document’s scope is referred to as the light DOM, and anything inside a shadow root is referred to as the shadow DOM.

When using the light DOM, an element can be selected by using document.querySelector('selector') or by targeting any element’s children by using element.querySelector('selector'); in the same way, a shadow root’s children can be targeted by calling shadowRoot.querySelector where shadowRoot is a reference to the document fragment — the difference being that the shadow root’s children will not be select-able from the light DOM. For example, If we have a shadow root with a <button> inside of it, calling shadowRoot.querySelector('button') would return our button, but no invocation of the document’s query selector will return that element because it belongs to a different DocumentOrShadowRoot instance. Style selectors work in the same way.

In this respect, the shadow DOM works sort of like an <iframe> where the content is cut off from the rest of the document; however, when we create a shadow root, we still have total control over that part of our page, but scoped to a context. This is what we call encapsulation.

If you’ve ever written a component that reuses the same id or relies on either CSS-in-JS tools or CSS naming strategies (like BEM), shadow DOM has the potential to improve your developer experience.

Imagine the following scenario:

<div>   <div id="example">     <!-- Pseudo-code used to designate a shadow root -->     <#shadow-root>       <style>       button {         background: tomato;         color: white;       }       </style>       <button id="button">This will use the CSS background tomato</button>     </#shadow-root>   </div>   <button id="button">Not tomato</button> </div>

Aside from the pseudo-code of <#shadow-root> (which is used here to demarcate the shadow boundary which has no HTML element), the HTML is fully valid. To attach a shadow root to the node above, we would run something like:

const shadowRoot = document.getElementById('example').attachShadow({ mode: 'open' }); shadowRoot.innerHTML = `<style> button {   color: tomato; } </style> <button id="button">This will use the CSS color tomato <slot></slot></button>`;

A shadow root can also include content from its containing document by using the <slot> element. Using a slot will drop user content from the outer document at a designated spot in your shadow root.

See the Pen
Shadow DOM style encapsulation demo
by Caleb Williams (@calebdwilliams)
on CodePen.

HTML templates

The aptly-named HTML <template> element allows us to stamp out re-usable templates of code inside a normal HTML flow that won’t be immediately rendered, but can be used at a later time.

<template id="book-template">   <li><span class="title"></span> &mdash; <span class="author"></span></li> </template>  <ul id="books"></ul>

The example above wouldn’t render any content until a script has consumed the template, instantiated the code and told the browser what to do with it.

const fragment = document.getElementById('book-template'); const books = [   { title: 'The Great Gatsby', author: 'F. Scott Fitzgerald' },   { title: 'A Farewell to Arms', author: 'Ernest Hemingway' },   { title: 'Catch 22', author: 'Joseph Heller' } ];  books.forEach(book => {   // Create an instance of the template content   const instance = document.importNode(fragment.content, true);   // Add relevant content to the template   instance.querySelector('.title').innerHTML = book.title;   instance.querySelector('.author').innerHTML = book.author;   // Append the instance ot the DOM   document.getElementById('books').appendChild(instance); });

Notice that this example creates a template (<template id="book-template">) without any other Web Components technology, illustrating again that the three technologies in the stack can be used independently or collectively.

Ostensibly, the consumer of a service that utilizes the template API could write a template of any shape or structure that could be created at a later time. Another page on a site might use the same service, but structure the template this way:

<template id="book-template">   <li><span class="author"></span>'s classic novel <span class="title"></span></li> </template>  <ul id="books"></ul>

See the Pen
Template example
by Caleb Williams (@calebdwilliams)
on CodePen.

That wraps up our introduction to Web Components

As web development continues to become more and more complicated, it will begin to make sense for developers like us to begin deferring more and more development to the web platform itself which has continued to mature. The Web Components specifications are a set of low-level APIs that will continue to grow and evolve as our needs as developers evolve.

In the next article, we will take a deeper look at the HTML templates part of this. Then, we’ll follow that up with a discussion of custom elements and shadow DOM. Finally, we’ll wrap it all up by looking at higher-level tooling and incorporation with today’s popular libraries and frameworks.

Article Series:

  1. An Introduction to Web Components (This post)
  2. Crafting Reusable HTML Templates (Coming soon!)
  3. Creating a Custom Element from Scratch (Coming soon!)
  4. Encapsulating Style and Structure with Shadow DOM (Coming soon!)
  5. Advanced Tooling for Web Components (Coming soon!)

The post An Introduction to Web Components appeared first on CSS-Tricks.

CSS-Tricks

,
[Top]

Get Started with Node: An Introduction to APIs, HTTP and ES6+ JavaScript

Jamie Corkhill has written this wonderful post about Node and I think it’s perhaps one of the best technical articles I’ve ever read. Not only is it jam-packed with information for folks like me who aren’t writing JavaScript everyday, it is also incredibly deliberate as Jamie slowly walks through the very basics of JavaScript (such as synchronous and asynchronous functions) all the way up to working with our very own API.

Jamie writes:

What is Node in the first place? What exactly does it mean for Node to be “asynchronous”, and how does that differ from “synchronous”? What is the meaning “event-driven” and “non-blocking” anyway, and how does Node fit into the bigger picture of applications, Internet networks, and servers?

We’ll attempt to answer all of these questions and more throughout this series as we take an in-depth look at the inner workings of Node, learn about the HyperText Transfer Protocol, APIs, and JSON, and build our very own Bookshelf API utilizing MongoDB, Express, Lodash, Mocha, and Handlebars.

I would highly recommend this post if JavaScript isn’t your day job but you’ve always wanted to learn about Node in a bit more detail.

Direct Link to ArticlePermalink

The post Get Started with Node: An Introduction to APIs, HTTP and ES6+ JavaScript appeared first on CSS-Tricks.

CSS-Tricks

, , , , , ,
[Top]

An Introduction and Guide to the CSS Object Model (CSSOM)

If you’ve been writing JavaScript for some time now, it’s almost certain you’ve written some scripts dealing with the Document Object Model (DOM). DOM scripting takes advantage of the fact that a web page opens up a set of APIs (or interfaces) so you can manipulate and otherwise deal with elements on a page.

But there’s another object model you might want to become more familiar with: The CSS Object Model (CSSOM). Likely you’ve already used it but didn’t necessarily realize it.

In this guide, I’m going to go through many of the most important features of the CSSOM, starting with stuff that’s more commonly known, then moving on to some more obscure, but practical, features.

What is the CSSOM?

According to MDN:

The CSS Object Model is a set of APIs allowing the manipulation of CSS from JavaScript. It is much like the DOM, but for the CSS rather than the HTML. It allows users to read and modify CSS style dynamically.

MDN’s info is based on the official W3C CSSOM specification. That W3C document is a somewhat decent way to get familiar with what’s possible with the CSSOM, but it’s a complete disaster for anyone looking for some practical coding examples that put the CSSOM APIs into action.

MDN is much better, but still largely lacking in certain areas. So for this post, I’ve tried to do my best to create useful code examples and demos of these interfaces in use, so you can see the possibilities and mess around with the live code.

As mentioned, the post starts with stuff that’s already familiar to most front-end developers. These common features are usually lumped in with DOM scripting, but they are technically part of the larger group of interfaces available via the CSSOM (though they do cross over into the DOM as well).

Inline Styles via element.style

The most basic way you can manipulate or access CSS properties and values using JavaScript is via the style object, or property, which is available on all HTML elements. Here’s an example:

document.body.style.background = 'lightblue';

Most of you have probably seen or used that syntax before. I can add to or change the CSS for any object on the page using that same format: element.style.propertyName.

In that example, I’m changing the value of the background property to lightblue. Of course, background is shorthand. What if I want to change the background-color property? For any hyphenated property, just convert the property name to camel case:

document.body.style.backgroundColor = 'lightblue';

In most cases, a single-word property would be accessed in this way by the single equivalent word in lowercase, while hyphenated properties are represented in camel case. The one exception to this is when using the float property. Because float is a reserved word in JavaScript, you need to use cssFloat (or styleFloat if you’re supporting IE8 and earlier). This is similar to the HTML for attribute being referenced as htmlFor when using something like getAttribute().

Here’s a demo that uses the style property to allow the user to change the background color of the current page:

See the Pen Using the style Object to Change the Background Color by Louis Lazaris (@impressivewebs) on CodePen.

So that’s an easy way to define a CSS property and value using JavaScript. But there’s one huge caveat to using the style property in this way: This will only apply to inline styles on the element.

This becomes clear when you use the style property to read CSS:

document.body.style.backgroundColor = 'lightblue'; console.log(document.body.style.backgroundColor); // "lightblue"

In the example above, I’m defining an inline style on the <body> element, then I’m logging that same style to the console. That’s fine. But if I try to read another property on that element, it will return nothing — unless I’ve previously defined an inline style for that element in my CSS or elsewhere in my JavaScript. For example:

console.log(document.body.style.color); // Returns nothing if inline style doesn't exist

This would return nothing even if there was an external stylesheet that defined the color property on the <body> element, as in the following CodePen:

See the Pen element.style Reads Only Inline Styles by Louis Lazaris (@impressivewebs) on CodePen.

Using element.style is the simplest and most common way to add styles to elements via JavaScript. But as you can see, this clearly has some significant limitations, so let’s look at some more useful techniques for reading and manipulating styles with JavaScript.

Getting Computed Styles

You can read the computed CSS value for any CSS property on an element by using the window.getComputedStyle() method:

window.getComputedStyle(document.body).background; // "rgba(0, 0, 0, 0) none repeat scroll 0% 0% / auto padding-box border-box"

Well, that’s an interesting result. In a way, window.getComputedStyle() is the style property’s overly-benevolent twin. While the style property gives you far too little information about the actual styles on an element, window.getComputedStyle() can sometimes give you too much.

See the Pen The getComputedStyle() Method Can Read any CSS Property by Louis Lazaris (@impressivewebs) on CodePen.

In the example above, the background property of the <body> element was defined using a single value. But the getComputedStyle() method returns all values contained in background shorthand. The ones not explicitly defined in the CSS will return the initial (or default) values for those properties.

This means, for any shorthand property, window.getComputedStyle() will return all the initial values, even if none of them is defined in the CSS:

See the Pen window.getComputedStyle() Returns All Longhand Values for a Shorthand Property by Louis Lazaris (@impressivewebs) on CodePen.

Similarly, for properties like width and height, it will reveal the computed dimensions of the element, regardless of whether those values were specifically defined anywhere in the CSS, as the following interactive demo shows:

See the Pen window.getComputedStyle() Returns Width and Height Values Even if Not Defined in the CSS by Louis Lazaris (@impressivewebs) on CodePen.

Try resizing the parent element in the above demo to see the results. This is somewhat comparable to reading the value of window.innerWidth, except this is the computed CSS for the specified property on the specified element and not just a general window or viewport measurement.

There are a few different ways to access properties using window.getComputedStyle(). I’ve already demonstrated one way, which uses dot-notation to add the camel-cased property name to the end of the method. You can see three different ways to do it in the following code:

// dot notation, same as above window.getComputedStyle(el).backgroundColor;  // square bracket notation window.getComputedStyle(el)['background-color'];  // using getPropertyValue() window.getComputedStyle(el).getPropertyValue('background-color');

The first line uses the same format as in the previous demo. The second line is using square bracket notation, a common JavaScript alternative to dot notation. This format is not recommended and code linters will warn about it. The third example uses the getPropertyValue() method.

The first example requires the use of camel casing (although in this case both float and cssFloat would work) while the next two access the property via the same syntax as that used in CSS (with hyphens, often called “kebab case”).

Here’s the same demo as the previous, but this time using getPropertyValue() to access the widths of the two elements:

See the Pen Using window.getComputedStyle() along with getPropertyValue() by Louis Lazaris (@impressivewebs) on CodePen.

Getting Computed Styles of Pseudo-Elements

One little-known tidbit about window.getComputedStyle() is the fact that it allows you to retrieve style information on pseudo-elements. You’ll often see a window.getComputedStyle() declaration like this:

window.getComputedStyle(document.body, null).width;

Notice the second argument, null, passed into the method. Firefox prior to version 4 required a second argument, which is why you might see it used in legacy code or by those accustomed to including it. But it’s not required in any browser currently in use.

That second optional parameter is what allows me to specify that I’m accessing the computed CSS of a pseudo-element. Consider the following CSS:

.box::before {   content: 'Example';   display: block;   width: 50px; }

Here I’m adding a ::before pseudo-element inside the .box element. With the following JavaScript, I can access the computed styles for that pseudo-element:

let box = document.querySelector('.box'); window.getComputedStyle(box, '::before').width; // "50px"

See the Pen Using getComputedStyle() to get styles from a pseudo-element by Louis Lazaris (@impressivewebs) on CodePen.

You can also do this for other pseudo-elements like ::first-line, as in the following code and demo:

let p = document.querySelector('.box p'); window.getComputedStyle(p, '::first-line').color;

See the Pen Using getComputedStyle() to get styles from a pseudo-element by Louis Lazaris (@impressivewebs) on CodePen.

And here’s another example using the ::placeholder pseudo-element, which apples to <input> elements:

let input = document.querySelector('input'); window.getComputedStyle(input, '::placeholder').color

See the Pen Using getComputedStyle() to get styles from a ::placeholder pseudo-element by Louis Lazaris (@impressivewebs) on CodePen.

The above works in the latest Firefox, but not in Chrome or Edge (I’ve filed a bug report for Chrome).

It should also be noted that browsers have different results when trying to access styles for a non-existent (but valid) pseudo-element compared to a pseudo-element that the browser doesn’t support at all (like a made up ::banana pseudo-element). You can try this out in various browsers using the following demo:

See the Pen Testing getComputedStyle() on non-existent pseudo-elements by Louis Lazaris (@impressivewebs) on CodePen.

As a side point to this section, there is a Firefox-only method called getDefaultComputedStyle() that is not part of the spec and likely never will be.

The CSSStyleDeclaration API

Earlier when I showed you how to access properties via the style object or using getComputedStyle(), in both cases those techniques were exposing the CSSStyleDeclaration interface.

In other words, both of the following lines will return a CSSStyleDeclaration object on the document’s body element:

document.body.style; window.getComputedStyle(document.body);

In the following screenshot you can see what the console produces for each of these lines:

The CSSStyleDeclaration API in the DevTools console

In the case of getComputedStyle(), the values are read-only. In the case of element.style, getting and setting the values is possible but, as mentioned earlier, these will only affect the document’s inline styles.

setProperty(), getPropertyValue(), and item()

Once you’ve exposed a CSSStyleDeclaration object in one of the above ways, you have access to a number of useful methods to read or manipulate the values. Again, the values are read-only in the case of getComputedStyle(), but when used via the style property, some methods are available for both getting and setting.

Consider the following code and demo:

let box = document.querySelector('.box');  box.style.setProperty('color', 'orange'); box.style.setProperty('font-family', 'Georgia, serif'); op.innerHTML = box.style.getPropertyValue('color'); op2.innerHTML = `$ {box.style.item(0)}, $ {box.style.item(1)}`;

See the Pen Using Three Different Methods of the CSSStyleDeclaration API by Louis Lazaris (@impressivewebs) on CodePen.

In this example, I’m using three different methods of the style object:

  • The setProperty() method. This takes two arguments, each a string: The property (in regular CSS notation) and the value you wish to assign to the property.
  • The getPropertyValue() method. This takes a single argument: The property whose value you want to obtain. This method was used in a previous example using getComputedStyle(), which, as mentioned, likewise exposes a CSSStyleDeclaration object.
  • The item() method. This takes a single argument, which is a positive integer representing the index of the property you want to access. The return value is the property name at that index.

Keep in mind that in my simple example above, there are only two styles added to the element’s inline CSS. This means that if I were to access item(2), the return value would be an empty string. I’d get the same result if I used getPropertyValue() to access a property that isn’t set in that element’s inline styles.

Using removeProperty()

In addition to the three methods mentioned above, there are two others exposed on a CSSStyleDeclaration object. In the following code and demo, I’m using the removeProperty() method:

box.style.setProperty('font-size', '1.5em'); box.style.item(0) // "font-size"  document.body.style.removeProperty('font-size'); document.body.style.item(0); // ""

See the Pen Using the removeProperty() method of the CSSSTyleDeclaration API by Louis Lazaris (@impressivewebs) on CodePen.

In this case, after I set font-size using setProperty(), I log the property name to ensure it’s there. The demo then includes a button that, when clicked, will remove the property using removeProperty().

In the case of setProperty() and removeProperty(), the property name that you pass in is hyphenated (the same format as in your stylesheet), rather than camel-cased. This might seem confusing at first, but the value passed in is a string in this example, so it makes sense.

Getting and Setting a Property’s Priority

Finally, here’s an interesting feature that I discovered while researching this article: The getPropertyPriority() method, demonstrated with the code and CodePen below:

box.style.setProperty('font-family', 'Georgia, serif', 'important'); box.style.setProperty('font-size', '1.5em');  box.style.getPropertyPriority('font-family'); // important op2.innerHTML = box.style.getPropertyPriority('font-size'); // ""

See the Pen Using getPropertyPriority() to get a property’s “importance” by Louis Lazaris (@impressivewebs) on CodePen.

In the first line of that code, you can see I’m using the setProperty() method, as I did before. However, notice I’ve included a third argument. The third argument is an optional string that defines whether you want the property to have the !important keyword attached to it.

After I set the property with !important, I use the getPropertyPriority() method to check that property’s priority. If you want the property to not have importance, you can omit the third argument, use the keyword undefined, or include the third argument as an empty string.

And I should emphasize here that these methods would work in conjunction with any inline styles already placed directly in the HTML on an element’s style attribute.

So if I had the following HTML:

<div class="box" style="border: solid 1px red !important;">

I could use any of the methods discussed in this section to read or otherwise manipulate that style. And it should be noted here that since I used a shorthand property for this inline style and set it to !important, all of the longhand properties that make up that shorthand will return a priority of important when using getPropertyPriority(). See the code and demo below:

// These all return "important" box.style.getPropertyPriority('border')); box.style.getPropertyPriority('border-top-width')); box.style.getPropertyPriority('border-bottom-width')); box.style.getPropertyPriority('border-color')); box.style.getPropertyPriority('border-style'));

See the Pen Using getPropertyPriority() to check the priority of longhand properties by Louis Lazaris (@impressivewebs) on CodePen.

In the demo, even though I explicitly set only the border property in the style attribute, all the associated longhand properties that make up border will also return a value of important.

The CSSStyleSheet Interface

So far, much of what I’ve considered deals with inline styles (which often aren’t that useful) and computed styles (which are useful, but are often too specific).

A much more useful API that allows you to retrieve a stylesheet that has readable and writable values, and not just for inline styles, is the CSSStyleSheet API. The simplest way to access information from a document’s stylesheets is using the styleSheets property of the current document. This exposes the CSSStyleSheet interface.

For example, the line below uses the length property to see how many stylesheets the current document has:

document.styleSheets.length; // 1

I can reference any of the document’s stylesheets using zero-based indexing:

document.styleSheets[0];

If I log that stylesheet to my console, I can view the methods and properties available:

The CSSStyleSheet Interface in the DevTools Console

The one that will prove useful is the cssRules property. This property provides a list of all CSS rules (including declaration blocks, at-rules, media rules, etc.) contained in that stylesheet. In the following sections, I’ll detail how to utilize this API to manipulate and read styles from an external stylesheet.

Working with a Stylesheet Object

For the purpose of simplicity, let’s work with a sample stylesheet that has only a handful of rules in it. This will allow me to demonstrate how to use the CSSOM to access the different parts of a stylesheet in a similar way to accessing elements via DOM scripting.

Here is the stylesheet I’ll be working with:

* {   box-sizing: border-box; }  body {   font-family: Helvetica, Arial, sans-serif;   font-size: 2em;   line-height: 1.4; }  main {   width: 1024px;   margin: 0 auto !important; }  .component {   float: right;   border-left: solid 1px #444;   margin-left: 20px; }  @media (max-width: 800px) {   body {     line-height: 1.2;   }    .component {     float: none;     margin: 0;   } }  a:hover {   color: lightgreen; }  @keyframes exampleAnimation {   from {     color: blue;   }      20% {     color: orange;   }      to {     color: green;   } }  code {   color: firebrick; }

There’s a number of different things I can attempt with this example stylesheet and I’ll demonstrate a few of those here. First, I’m going to loop through all the style rules in the stylesheet and log the selector text for each one:

let myRules = document.styleSheets[0].cssRules,     p = document.querySelector('p');  for (i of myRules) {   if (i.type === 1) {     p.innerHTML += `<c​ode>$ {i.selectorText}</c​ode><br>`;   } }

See the Pen Working with CSSStyleSheet – Logging the Selector Text by Louis Lazaris (@impressivewebs) on CodePen.

A couple of things to take note of in the above code and demo. First, I cache a reference to the cssRules object for my stylesheet. Then I loop over all the rules in that object, checking to see what type each one is.

In this case, I want rules that are type 1, which represents the STYLE_RULE constant. Other constants include IMPORT_RULE (3), MEDIA_RULE (4), KEYFRAMES_RULE (7), etc. You can view a full table of these constants in this MDN article.

When I confirm that a rule is a style rule, I print the selectorText property for each of those style rules. This will produce the following lines for the specified stylesheet:

* body main .component a:hover code

The selectorText property is a string representation of the selector used on that rule. This is a writable property, so if I want I can change the selector for a specific rule inside my original for loop with the following code:

if (i.selectorText === 'a:hover') {   i.selectorText = 'a:hover, a:active'; }

See the Pen Working with the CSSStyleSheet API – Changing the Selector Text by Louis Lazaris (@impressivewebs) on CodePen.

In this example, I’m looking for a selector that defines :hover styles on my links and expanding the selector to apply the same styles to elements in the :active state. Alternatively, I could use some kind of string method or even a regular expression to look for all instances of :hover, and then do something from there. But this should be enough to demonstrate how it works.

Accessing @media Rules with the CSSOM

You’ll notice my stylesheet also includes a media query rule and a keyframes at-rule block. Both of those were skipped when I searched for style rules (type 1). Let’s now find all @media rules:

let myRules = document.styleSheets[0].cssRules,     p = document.querySelector('.output');  for (i of myRules) {   if (i.type === 4) {     for (j of i.cssRules) {       p.innerHTML += `<c​ode>$ {j.selectorText}</c​ode><br>`;     }   } }

Based on the given stylesheet, the above will produce:

body .component

See the Pen Working with the CSSStyleSheet API – Accessing @media Rules by Louis Lazaris (@impressivewebs) on CodePen.

As you can see, after I loop through all the rules to see if any @media rules exist (type 4), I then loop through the cssRules object for each media rule (in this case, there’s only one) and log the selector text for each rule inside that media rule.

So the interface that’s exposed on a @media rule is similar to the interface exposed on a stylesheet. The @media rule, however, also includes a conditionText property, as shown in the following snippet and demo:

let myRules = document.styleSheets[0].cssRules,     p = document.querySelector('.output');  for (i of myRules) {   if (i.type === 4) {     p.innerHTML += `<c​ode>$ {i.conditionText}</c​ode><br>`;     // (max-width: 800px)    } }

See the Pen Working with the CSSStyleSheet API – Accessing @media Rules by Louis Lazaris (@impressivewebs) on CodePen.

This code loops through all media query rules and logs the text that determines when that rule is applicable (i.e. the condition). There’s also a mediaText property that returns the same value. According to the spec, you can get or set either of these.

Accessing @keyframes Rules with the CSSOM

Now that I’ve demonstrated how to read information from a @media rule, let’s consider how to access a @keyframes rule. Here’s some code to get started:

let myRules = document.styleSheets[0].cssRules,     p = document.querySelector('.output');  for (i of myRules) {   if (i.type === 7) {     for (j of i.cssRules) {      p.innerHTML += `<c​ode>$ {j.keyText}</c​ode><br>`;     }   } }

See the Pen Working with the CSSStyleSheet API – Accessing @keyframes Rules by Louis Lazaris (@impressivewebs) on CodePen.

In this example, I’m looking for rules that have a type of 7 (i.e. @keyframes rules). When one is found, I loop through all of that rule’s cssRules and log the keyText property for each. The log in this case will be:

"0%" "20%" "100%"

You’ll notice my original CSS uses from and to as the first and last keyframes, but the keyText property computes these to 0% and 100%. The value of keyText can also be set. In my example stylesheet, I could hard code it like this:

// Read the current value (0%) document.styleSheets[0].cssRules[6].cssRules[0].keyText;  // Change the value to 10% document.styleSheets[0].cssRules[6].cssRules[0].keyText = '10%'  // Read the new value (10%) document.styleSheets[0].cssRules[6].cssRules[0].keyText;

See the Pen Working with the CSSStyleSheet API – Setting @keyframes Rules by Louis Lazaris (@impressivewebs) on CodePen.

Using this, we can dynamically alter an animation’s keyframes in the flow of a web app or possibly in response to a user action.

Another property available when accessing a @keyframes rule is name:

let myRules = document.styleSheets[0].cssRules,     p = document.querySelector('.output');  for (i of myRules) {   if (i.type === 7) {     p.innerHTML += `<c​ode>$ {i.name}</c​ode><br>`;   } }

See the Pen Working with the CSSStyleSheet API – Getting the name of a @keyframes rule by Louis Lazaris (@impressivewebs) on CodePen.

Recall that in the CSS, the @keyframes rule looks like this:

@keyframes exampleAnimation {   from {     color: blue;   }      20% {     color: orange;   }      to {     color: green;   } }

Thus, the name property allows me to read the custom name chosen for that @keyframes rule. This is the same name that would be used in the animation-name property when enabling the animation on a specific element.

One final thing I’ll mention here is the ability to grab specific styles that are inside a single keyframe. Here’s some example code with a demo:

let myRules = document.styleSheets[0].cssRules,     p = document.querySelector('.output');  for (i of myRules) {   if (i.type === 7) {     for (j of i.cssRules) {       p.innerHTML += `<c​ode>$ {j.style.color}</c​ode><br>`;     }   } }

See the Pen Working with the CSSStyleSheet API – Accessing Property Values inside @keyframes Rules by Louis Lazaris (@impressivewebs) on CodePen.

In this example, after I find the @keyframes rule, I loop through each of the rules in the keyframe (e.g. the “from” rule, the “20%” rule, etc). Then, within each of those rules, I access an individual style property. In this case, since I know color is the only property defined for each, I’m merely logging out the color values.

The main takeaway in this instance is the use of the style property, or object. Earlier I showed how this property can be used to access inline styles. But in this case, I’m using it to access the individual properties inside of a single keyframe.

You can probably see how this opens up some possibilities. This allows you to modify an individual keyframe’s properties on the fly, which could happen as a result of some user action or something else taking place in an app or possibly a web-based game.

Adding and Removing CSS Declarations

The CSSStyleSheet interface has access to two methods that allow you to add or remove an entire rule from a stylesheet. The methods are: insertRule() and deleteRule(). Let’s see both of them in action manipulating our example stylesheet:

let myStylesheet = document.styleSheets[0]; console.log(myStylesheet.cssRules.length); // 8  document.styleSheets[0].insertRule('article { line-height: 1.5; font-size: 1.5em; }', myStylesheet.cssRules.length); console.log(document.styleSheets[0].cssRules.length); // 9

See the Pen Working with the CSSStyleSheet API – Inserting Rules by Louis Lazaris (@impressivewebs) on CodePen.

In this case, I’m logging the length of the cssRules property (showing that the stylesheet originally has 8 rules in it), then I add the following CSS as an individual rule using the insertRule() method:

article {   line-height: 1.5;   font-size: 1.5em; }

I log the length of the cssRules property again to confirm that the rule was added.

The insertRule() method takes a string as the first parameter (which is mandatory), comprising the full style rule that you want to insert (including selector, curly braces, etc). If you’re inserting an at-rule, then the full at-rule, including the individual rules nested inside the at-rule can be included in this string.

The second argument is optional. This is an integer that represents the position, or index, where you want the rule inserted. If this isn’t included, it defaults to 0 (meaning the rule will be inserted at the beginning of the rules collection). If the index happens to be larger than the length of the rules object, it will throw an error.

The deleteRule() method is much simpler to use:

let myStylesheet = document.styleSheets[0]; console.log(myStylesheet.cssRules.length); // 8  myStylesheet.deleteRule(3); console.log(myStylesheet.cssRules.length); // 7

See the Pen Working with the CSSStyleSheet API – Deleting Rules by Louis Lazaris (@impressivewebs) on CodePen.

In this case, the method accepts a single argument that represents the index of the rule I want to remove.

With either method, because of zero-based indexing, the selected index passed in as an argument has to be less than the length of the cssRules object, otherwise it will throw an error.

Revisiting the CSSStyleDeclaration API

Earlier I explained how to access individual properties and values declared as inline styles. This was done via element.style, exposing the CSSStyleDeclaration interface.

The CSSStyleDeclaration API, however, can also be exposed on an individual style rule as a subset of the CSSStyleSheet API. I already alluded to this when I showed you how to access properties inside a @keyframes rule. To understand how this works, compare the following two code snippets:

<div style="color: lightblue; width: 100px; font-size: 1.3em !important;"></div>
.box {   color: lightblue;   width: 100px;   font-size: 1.3em !important; }

The first example is a set of inline styles that can be accessed as follows:

document.querySelector('div').style

This exposes the CSSStyleDeclaration API, which is what allows me to do stuff like element.style.color, element.style.width, etc.

But I can expose the exact same API on an individual style rule in an external stylesheet. This means I’m combining my use of the style property with the CSSStyleSheet interface.

So the CSS in the second example above, which uses the exact same styles as the inline version, can be accessed like this:

document.styleSheets[0].cssRules[0].style

This opens up a single CSSStyleDeclaration object on the one style rule in the stylesheet. If there were multiple style rules, each could be accessed using cssRules[1], cssRules[2], cssRules[3], and so on.

So within an external stylesheet, inside of a single style rule that is of type 1, I have access to all the methods and properties mentioned earlier. This includes setProperty(), getPropertyValue(), item(), removeProperty(), and getPropertyPriority(). In addition to this, those same features are available on an individual style rule inside of a @keyframes or @media rule.

Here’s a code snippet and demo that demonstrates how these methods would be used on an individual style rule in our sample stylesheet:

// Grab the style rules for the body and main elements let myBodyRule = document.styleSheets[0].cssRules[1].style,     myMainRule = document.styleSheets[0].cssRules[2].style;  // Set the bg color on the body myBodyRule.setProperty('background-color', 'peachpuff');  // Get the font size of the body myBodyRule.getPropertyValue('font-size');  // Get the 5th item in the body's style rule myBodyRule.item(5);  // Log the current length of the body style rule (8) myBodyRule.length;  // Remove the line height myBodyRule.removeProperty('line-height');  // log the length again (7) myBodyRule.length;  // Check priority of font-family (empty string) myBodyRule.getPropertyPriority('font-family');  // Check priority of margin in the "main" style rule (!important) myMainRule.getPropertyPriority('margin');

See the Pen Working with the style object of an individual style rule in an external Stylesheet by Louis Lazaris (@impressivewebs) on CodePen.

The CSS Typed Object Model… The Future?

After everything I’ve considered in this article, it would seem odd that I’d have to break the news that it’s possible that one day the CSSOM as we know it will be mostly obsolete.

That’s because of something called the CSS Typed OM which is part of the Houdini Project. Although some people have noted that the new Typed OM is more verbose compared to the current CSSOM, the benefits, as outlined in this article by Eric Bidelman, include:

  • Fewer bugs
  • Arithmetic operations and unit conversion
  • Better performance
  • Error handling
  • CSS property names are always strings

For full details on those features and a glimpse into the syntax, be sure to check out the full article.

As of this writing, CSS Typed OM is supported only in Chrome. You can see the progress of browser support in this document.

Final Words

Manipulating stylesheets via JavaScript certainly isn’t something you’re going to do in every project. And some of the complex interactions made possible with the methods and properties I’ve introduced here have some very specific use cases.

If you’ve built some kind of tool that uses any of these APIs I’d love to hear about it. My research has only scratched the surface of what’s possible, but I’d love to see how any of this can be used in real-world examples.

I’ve put all the demos from this article into a CodePen collection, so you can feel free to mess around with those as you like.

The post An Introduction and Guide to the CSS Object Model (CSSOM) appeared first on CSS-Tricks.

CSS-Tricks

, , , ,
[Top]