Month: September 2022

Getting Started With WordPress Block Development

Let’s acknowledge that developing for WordPress is weird right now. Whether you’re new to WordPress or have worked with it for eons, the introduction of “Full-Site Editing” (FSE) features, including the Block Editor (WordPress 5.0) and the Site Editor (WordPress 5.9), have upended the traditional way we build WordPress themes and plugins.

Even though it’s been five years since we met the Block Editor for the first time, developing for it is difficult because documentation is either lacking or outdated. That’s more of a statement on how fast FSE features are moving, something Geoff lamented in a recent post.

Case in point: In 2018, an introductory series about getting into Gutenberg development was published right here on CSS-tricks. Times have changed since then, and, while that style of development does still work, it is not recommended anymore (besides, the create-guten-block project it’s based on is also no longer maintained).

In this article, I intend to help you get started with WordPress block development in a way that follows the current methodology. So, yes, things could very well change after this is published. But I’m going to try and focus on it in a way that hopefully captures the essence of block development, because even though the tools might evolve over time, the core ideas are likely to remain the same.

The WordPress Block Editor interface with highlighted areas showing three key features.
The Gutenberg Editor: (1) The block inserter, (2) the content area, and (3) the settings sidebar
Credit: WordPress Block Editor Handbook

What are WordPress blocks, exactly?

Let’s start by airing out some confusion with what we mean by terms like blocks. All of the development that went into these features leading up to WordPress 5.0 was codenamed “Gutenberg” — you know, the inventor of the printing press.

Since then, “Gutenberg” has been used to describe everything related to blocks, including the Block Editor and Site Editor, so it’s gotten convoluted to the extent that some folks depise the name. To top it all off, there’s a Gutenberg plugin where experimental features are tested for possible inclusion. And if you think calling all of this “Full-Site Editing” would solve the issue, there are concerns with that as well.

So, when we refer to “blocks” in this article what we mean are components for creating content in the WordPress Block Editor. Blocks are inserted into a page or post and provide the structure for a particular type of content. WordPress ships with a handful of “core” blocks for common content types, like Paragraph, List, Image, Video, and Audio, to name a few.

Apart from these core blocks, we can create custom blocks too. That is what WordPress block development is about (there’s also filtering core blocks to modify their functionality, but you likely won’t be needing that just yet).

What blocks do

Before we dive into creating blocks, we must first get some sense of how blocks work internally. That will definitely save us a ton of frustration later on.

The way I like to think about a block is rather abstract: to me, a block is an entity, with some properties (called attributes), that represents some content. I know this sounds pretty vague, but stay with me. A block basically manifests itself in two ways: as a graphical interface in the block editor or as a chunk of data in the database.

When you open up the WordPress Block Editor and insert a block, say a Pullquote block, you get a nice interface. You can click into that interface and edit the quoted text. The Settings panel to the right side of the Block Editor UI provides options for adjusting the text and setting the block’s appearance.

The Pullquote block that is included in WordPress Core

When you are done creating your fancy pullquote and hit Publish, the entire post gets stored in the database in the wp_posts table. This isn’t anything new because of Gutenberg. That’s how things have always worked — WordPress stores post content in a designated table in the database. But what’s new is that a representation of the Pullquote block is part of the content that gets stored in post_content field of the wp_posts table.

What does this representation look like? Have a look:

<!-- wp:pullquote {"textAlign":"right"} --> <figure class="wp-block-pullquote has-text-align-right">   <blockquote>     <p>It is not an exaggeration to say that peas can be described as nothing less than perfect spheres of joy.</p>     <cite>The Encyclopedia of world peas</cite>   </blockquote> </figure> <!-- /wp:pullquote -->

Looks like plain HTML, right?! This, in technical lingo, is the “serialized” block. Notice the JSON data in the HTML comment, "textAlign": "right". That’s an attribute — a property associated with the block.

Let’s say that you close the Block Editor, and then some time later, open it again. The content from the relevant post_content field is retrieved by the Block Editor. The editor then parses the retrieved content, and wherever it encounters this:

<!-- wp:pullquote {"textAlign":"right"} -->...<!-- /wp:pullquote -->

…it says out loud to itself:

OK, that seems like a Pullquote block to me. Hmm.. it’s got an attribute too… I do have a JavaScript file that tells me how to construct the graphical interface for a Pullquote block in the editor from its attributes. I should do that now to render this block in all its glory.

As a block developer, your job is to:

  1. Tell WordPress that you want to register a specific type of block, with so-and-so details.
  2. Provide the JavaScript file to the Block Editor that will help it render the block in the editor while also “serializing” it to save it in the database.
  3. Provide any additional resources the block needs for its proper functionality, e.g. styles and fonts.

One thing to note is that all of this conversion from serialized data to graphical interface — and vice versa — takes place only in the Block Editor. On the front end, the content is displayed exactly the way it is stored. Therefore, in a sense, blocks are a fancy way of putting data in the database.

Hopefully, this gives you some clarity as to how a block works.

Diagram outlining the post editor states and how data is saved to a database and parsed for rendering.

Blocks are just plugins

Blocks are just plugins. Well, technically, you can put blocks in themes and you can put multiple blocks in a plugin. But, more often than not, if you want to make a block, you’re going to be making a plugin. So, if you’ve ever created a WordPress plugin, then you’re already part-way there to having a handle on making a WordPress block.

But let’s assume for a moment that you’ve never set up a WordPress plugin, let alone a block. Where do you even start?

Setting up a block

We have covered what blocks are. Let’s start setting things up to make one.

Make sure you have Node installed

This will give you access to npm and npx commands, where npm installs your block’s dependencies and helps compile stuff, while npx runs commands on packages without installing them. If you’re on macOS, you probably already have Node and can can use nvm to update versions. If you’re on Windows, you’ll need to download and install Node.

Create a project folder

Now, you might run into other tutorials that jump straight into the command line and instruct you to install a package called @wordpress/create-block. This package is great because it spits out a fully formed project folder with all the dependencies and tools you need to start developing.

I personally go this route when setting up my own blocks, but humor me for a moment because I want to cut through the opinionated stuff it introduces and focus just on the required bits for the sake of understanding the baseline development environment.

These are the files I’d like to call out specifically:

  • readme.txt: This is sort of like the front face of the plugin directory, typically used to describe the plugin and provide additional details on usage and installation. If you submit your block to the WordPress Plugin Directory, this file helps populate the plugin page. If you plan on creating a GitHub repo for your block plugin, then you might also consider a README.md file with the same information so it displays nicely there.
  • package.json: This defines the Node packages that are required for development. We’ll crack it open when we get to installation. In classic WordPress plugin development, you might be accustomed to working with Composer and a composer.json file instead. This is the equivalent of that.
  • plugin.php: This is the main plugin file and, yes, it’s classic PHP! We’ll put our plugin header and metadata in here and use it to register the plugin.

In addition to these files, there’s also the src directory, which is supposed to contain the source code of our block.

Having these files and the src directory is all you need to get started. Out of that group, notice that we technically only need one file (plugin.php) to make the plugin. The rest either provide information or are used to manage the development environment.

The aforementioned @wordpress/create-block package scaffolds these files (and more) for us. You can think of it as an automation tool instead of a necessity. Regardless, it does make the job easier, so you can take the liberty of scaffolding a block with it by running:

npx @wordpress/create-block

Install block dependencies

Assuming you have the three files mentioned in the previous section ready, it’s time to install the dependencies. First, we need to specify the dependencies we will need. We do that by editing the package.json. While using the @wordpress/create-block utility, the following is generated for us (comments added; JSON does not support comments, so remove the comments if you’re copying the code):

{   // Defines the name of the project   "name": "block-example",   // Sets the project version number using semantic versioning   "version": "0.1.0",   // A brief description of the project   "description": "Example block scaffolded with Create Block tool.",   // You could replace this with yourself   "author": "The WordPress Contributors",   // Standard licensing information   "license": "GPL-2.0-or-later",   // Defines the main JavaScript file   "main": "build/index.js",   // Everything we need for building and compiling the plugin during development   "scripts": {     "build": "wp-scripts build",     "format": "wp-scripts format",     "lint:css": "wp-scripts lint-style",     "lint:js": "wp-scripts lint-js",     "packages-update": "wp-scripts packages-update",     "plugin-zip": "wp-scripts plugin-zip",     "start": "wp-scripts start"   },   // Defines which version of the scripts packages are used (24.1.0 at time of writing)   // https://developer.wordpress.org/block-editor/reference-guides/packages/packages-scripts/   "devDependencies": {     "@wordpress/scripts": "^24.1.0"   } }
View without comments
{   "name": "block-example",   "version": "0.1.0",   "description": "Example block scaffolded with Create Block tool.",   "author": "The WordPress Contributors",   "license": "GPL-2.0-or-later",   "main": "build/index.js",   "scripts": {     "build": "wp-scripts build",     "format": "wp-scripts format",     "lint:css": "wp-scripts lint-style",     "lint:js": "wp-scripts lint-js",     "packages-update": "wp-scripts packages-update",     "plugin-zip": "wp-scripts plugin-zip",     "start": "wp-scripts start"   },   "devDependencies": {     "@wordpress/scripts": "^24.1.0"   } }

The @wordpress/scripts package is the main dependency here. As you can see, it’s a devDependency meaning that it aids in development. How so? It exposes the wp-scripts binary that we can use to compile our code, from the src directory to the build directory, among other things.

There are a number of other packages that WordPress maintains for various purposes. For example, the @wordpress/components package provides several pre-fab UI components for the WordPress Block Editor that can be used for creating consistent user experiences for your block that aligns with WordPress design standards.

You don’t actually need to install these packages, even if you want to use them. This is because these @wordpress dependencies aren’t bundled with your block code. Instead, any import statements referencing code from utility packages — like @wordpress/components — are used to construct an “assets” file, during compilation. Moreover, these import statements are converted to statements mapping the imports to properties of a global object. For example, import { __ } from "@wordpress/i18n" is converted to a minified version of const __ = window.wp.i18n.__. (window.wp.i18n being an object that is guaranteed to be available in the global scope, once the corresponding i18n package file is enqueued).

During block registration in the plugin file, the “assets” file is implicitly used to tell WordPress the package dependencies for the block. These dependencies are automatically enqueued. All of this is taken care of behind the scenes, granted you are using the scripts package. That being said, you can still choose to locally install dependencies for code completion and parameter info in your package.json file:

// etc. "devDependencies": {   "@wordpress/scripts": "^24.1.0" }, "dependencies": {   "@wordpress/components": "^19.17.0" }

Now that package.json is set up, we should be able to install all those dependencies by navigating to the project folder in the command line and running npm install.

Terminal output after running the install command. 1,296 packages were installed.

Add the plugin header

If you’re coming from classic WordPress plugin development, then you probably know that all plugins have a block of information in the main plugin file that helps WordPress recognize the plugin and display information about it on the Plugins screen of the WordPress admin.

Here’s what @wordpress/create-block generated for me in for a plugin creatively called “Hello World”:

<?php /**  * Plugin Name:       Block Example  * Description:       Example block scaffolded with Create Block tool.  * Requires at least: 5.9  * Requires PHP:      7.0  * Version:           0.1.0  * Author:            The WordPress Contributors  * License:           GPL-2.0-or-later  * License URI:       https://www.gnu.org/licenses/gpl-2.0.html  * Text Domain:       css-tricks  *  * @package           create-block  */

That’s in the main plugin file, which you can call whatever you’d like. You might call it something generic like index.php or plugin.php. The create-block package automatically calls it whatever you provide as the project name when installing it. Since I called this example “Block Example”, the package gave us a block-example.php file with all this stuff.

You’re going to want to change some of the details, like making yourself the author and whatnot. And not all of that is necessary. If I was rolling this from “scratch”, then it might look something closer to this:

<?php /**  * Plugin Name:       Block Example  * Plugin URI:        https://css-tricks.com  * Description:       An example plugin for learning WordPress block development.  * Version:           1.0.0  * Author:            Arjun Singh  * License:           GPL-2.0-or-later  * License URI:       https://www.gnu.org/licenses/gpl-2.0.html  * Text Domain:       css-tricks  */

I won’t get into the exact purpose of each line since that’s already a well-established pattern in the WordPress Plugin Handbook.

The file structure

We’ve already looked at the required files for our block. But if you’re using @wordpress/create-block, you will see a bunch of other files in the project folder.

Here’s what’s in there at the moment:

block-example/ ├── build ├── node_modules ├── src/ │   ├── block.json │   ├── edit.js │   ├── editor.scss │   ├── index.js │   ├── save.js │   └── style.scss ├── .editorconfig ├── .gitignore ├── block-example.php ├── package-lock.json ├── package.json └── readme.txt

Phew, that’s a lot! Let’s call out the new stuff:

  • build/: This folder received the compiled assets when processing the files for production use.
  • node_modules: This holds all the development dependencies we installed when running npm install.
  • src/: This folder holds the plugin’s source code that gets compiled and sent to the build directory. We’ll look at each of the files in here in just a bit.
  • .editorconfig: This contains configurations to adapt your code editor for code consistency.
  • .gitignore: This is a standard repo file that identifies local files that should be excluded from version control tracking. Your node_modules should definitely be included in here.
  • package-lock.json: This is an auto-generated file containing for tracking updates to the required packages we installed with npm install.

Block metadata

I want to dig into the src directory with you but will focus first on just one file in it: block.json. If you’ve used create-block , it’s already there for you; if not, go ahead and create it. WordPress is leaning in hard to make this the standard, canonical way to register a block by providing metadata that provides WordPress context to both recognize the block and render it in the Block Editor.

Here’s what @wordpress/create-block generated for me:

{   "$ schema": "https://schemas.wp.org/trunk/block.json",   "apiVersion": 2,   "name": "create-block/block example",   "version": "0.1.0",   "title": "Block Example",   "category": "widgets",   "icon": "smiley",   "description": "Example block scaffolded with Create Block tool.",   "supports": {     "html": false   },   "textdomain": "css-tricks",   "editorScript": "file:./index.js",   "editorStyle": "file:./index.css",   "style": "file:./style-index.css" }

There’s actually a bunch of different information we can include here, but all that’s actually required is name and title. A super minimal version might look like this:

{   "$ schema": "https://schemas.wp.org/trunk/block.json",   "apiVersion": 2,   "name": "css-tricks/block-example",   "version": "1.0.0",   "title": "Block Example",   "category": "text",   "icon": "format-quote",   "editorScript": "file:./index.js", }
  • $ schema defines the schema formatting used to validate the content in the file. It sounds like a required thing, but it’s totally optional as it allows supporting code editors to validate the syntax and provide other additional affordances, like tooltip hints and auto-completion.
  • apiVersion refers to which version of the Block API the plugin uses. Today, Version 2 is the latest.
  • name is a required unique string that helps identify the plugin. Notice that I’ve prefixed this with css-tricks/ which I’m using as a namespace to help avoid conflicts with other plugins that might have the same name. You might choose to use something like your initials instead (e.g. as/block-example).
  • version is something WordPress suggests using as a cache-busting mechanism when new versions are released.
  • title is the other required field, and it sets the name that’s used wherever the plugin is displayed.
  • category groups the block with other blocks and displays them together in the Block Editor. Current existing categories include text, media, design, widgets, theme, and embed, and you can even create custom categories.
  • icon lets you choose something from the Dashicons library to visually represent your block in the Block Editor. I’m using the format-quote icon](https://developer.wordpress.org/resource/dashicons/#format-quote) since we’re making our own pullquote sort of thing in this example. It’s nice we can leverage existing icons rather than having to create our own, though that’s certainly possible.
  • editorScript is where the main JavaScript file, index.js, lives.

Register the block

One last thing before we hit actual code, and that’s to register the plugin. We just set up all that metadata and we need a way for WordPress to consume it. That way, WordPress knows where to find all the plugin assets so they can be enqueued for use in the Block Editor.

Registering the block is a two-fold process. We need to register it both in PHP and in JavaScript. For the PHP side, open up the main plugin file (block-example.php in this case) and add the following right after the plugin header:

function create_block_block_example_block_init() {   register_block_type( __DIR__ . '/build' ); } add_action( 'init', 'create_block_block_example_block_init' );

This is what the create-block utility generated for me, so that’s why the function is named the way it is. We can use a different name. The key, again, is avoiding conflicts with other plugins, so it’s a good idea to use your namespace here to make it as unique as possible:

function css_tricks_block_example_block_init() {   register_block_type( __DIR__ . '/build' ); } add_action( 'init', 'css_tricks_block_example_block_init' );

Why are we pointing to the build directory if the block.json with all the block metadata is in src? That’s because our code still needs to be compiled. The scripts package processes the code from files in the src directory and places the compiled files used in production in the build directory, while also copying the block.json file in the process.

Alright, let’s move over to the JavaScript side of registering the block. Open up src/index.js and make sure it looks like this:

import { registerBlockType } from "@wordpress/blocks";  import metadata from "./block.json"; import Edit from "./edit.js"; import Save from "./save.js";  const { name } = metadata;  registerBlockType(name, {   edit: Edit,   save: Save, });

We’re getting into React and JSX land! This tells WordPress to:

  • Import the registerBlockType module from the @wordpress/blocks package.
  • Import metadata from block.json.
  • Import the Edit and Save components from their corresponding files. We’ll be putting code into those files later.
  • Register the the block, and use the Edit and Save components for rendering the block and saving its content to the database.

What’s up with the edit and save functions? One of the nuances of WordPress block development is differentiating the “back end” from the “front end” and these functions are used to render the block’s content in those contexts, where edit handles back-end rendering and save writes the content from the Block Editor to the database for rendering the content on the front end of the site.

A quick test

We can do some quick work to see our block working in the Block Editor and rendered on the front end. Let’s open index.js again and use the edit and save functions to return some basic content that illustrates how they work:

import { registerBlockType } from "@wordpress/blocks"; import metadata from "./block.json";  const { name } = metadata;  registerBlockType(name, {   edit: () => {     return (       "Hello from the Block Editor"     );   },   save: () => {     return (       "Hello from the front end"     );   } });

This is basically a stripped-down version of the same code we had before, only we’re pointing directly to the metadata in block.json to fetch the block name, and left out the Edit and Save components since we’re running the functions directly from here.

We can compile this by running npm run build in the command line. After that, we have access to a block called “Block Example” in the Block Editor:

If we drop the block into the content area, we get the message we return from the edit function:

The WordPress Block Editor with the block inserter panel open and the pullquote block inserted into the content area. It reads hello from the back end.

If we save and publish the post, we should get the message we return from the save function when viewing it on the front end:

The pullquote block rendered on the front end of the website. It says hello from the front end.

Creating a block

Looks like everything is hooked up! We can revert back to what we had in index.js before the test now that we’ve confirmed things are working:

import { registerBlockType } from "@wordpress/blocks";  import metadata from "./block.json"; import Edit from "./edit.js"; import Save from "./save.js";  const { name } = metadata;  registerBlockType(name, {   edit: Edit,   save: Save, });

Notice that the edit and save functions are tied to two existing files in the src directory that @wordpress/create-block generated for us and includes all the additional imports we need in each file. More importantly, though, those files establish the Edit and Save components that contain the block’s markup.

Back end markup (src/edit.js)

import { useBlockProps } from "@wordpress/block-editor"; import { __ } from "@wordpress/i18n";  export default function Edit() {   return (     <p {...useBlockProps()}>       {__("Hello from the Block Editor", "block-example")}     </p>   ); }

See what we did there? We’re importing props from the @wordpress/block-editor package which allows us to generate classes we can use later for styling. We’re also importing the __ internationalization function, for dealing with translations.

The pullquote block on the back end, selected and with devtools open beside it displaying the markup.

Front-end markup (src/save.js)

This creates a Save component and we’re going to use pretty much the same thing as src/edit.js with slightly different text:

import { useBlockProps } from "@wordpress/block-editor"; import { __ } from "@wordpress/i18n";  export default function Save() {   return (     <p {...useBlockProps.save()}>       {__("Hello from the front end", "block-example")}     </p>   ); }

Again, we get a nice class we can use in our CSS:

The pullquote block on the front end, selected and with devtools open beside it displaying the markup.

Styling blocks

We just covered how to use block props to create classes. You’re reading this article on a site all about CSS, so I feel like I’d be missing something if we didn’t specifically address how to write block styles.

Differentiating front and back-end styles

If you take a look at the block.json in the src directory you’ll find two fields related to styles:

  • editorStyle provides the path to the styles applied to the back end.
  • style is the path for shared styles that are applied to both the front and back end.

Kev Quirk has a detailed article that shows his approach for making the back-end editor look like the front end UI.

Recall that the @wordpress/scripts package copies the block.json file when it processes the code in the /src directory and places compiled assets in the /build directory. It is the build/block.json file that is used to register the block. That means any path that we provide in src/block.json should be written relative to build/block.json.

Using Sass

We could drop a couple of CSS files in the build directory, reference the paths in src/block.json, run the build, and call it a day. But that doesn’t leverage the full might of the @wordpress/scripts compilation process, which is capable of compiling Sass into CSS. Instead, we place our style files in the src directory and import them in JavaScript.

While doing that, we need to be mindful of how @wordpress/scripts processes styles:

  • A file named style.css or style.scss or style.sass, imported into the JavaScript code, is compiled to style-index.css.
  • All other style files are compiled and bundled into index.css.

The @wordpress/scripts package uses webpack for bundling and @wordpress/scripts uses the PostCSS plugin for working for processing styles. PostCSS can be extended with additional plugins. The scripts package uses the ones for Sass, SCSS, and Autoprefixer, all of which are available for use without installing additional packages.

In fact, when you spin up your initial block with @wordpress/create-block, you get a nice head start with SCSS files you can use to hit the ground running:

  • editor.scss contains all the styles that are applied to the back-end editor.
  • style.scss contains all the styles shared by both the front and back end.

Let’s now see this approach in action by writing a little Sass that we’ll compile into the CSS for our block. Even though the examples aren’t going to be very Sass-y, I’m still writing them to the SCSS files to demonstrate the compilation process.

Front and back-end styles

OK, let’s start with styles that are applied to both the front and back end. First, we need to create src/style.scss (it’s already there if you’re using @wordpress/create-block) and make sure we import it, which we can do in index.js:

import "./style.scss";

Open up src/style.scss and drop a few basic styles in there using the class that was generated for us from the block props:

.wp-block-css-tricks-block-example {   background-color: rebeccapurple;   border-radius: 4px;   color: white;   font-size: 24px; }

That’s it for now! When we run the build, this gets compiled into build/style.css and is referenced by both the Block Editor and the front end.

Back-end styles

You might need to write styles that are specific to the Block Editor. For that, create src/editor.scss (again, @wordpress/create-block does this for you) and drop some styles in there:

.wp-block-css-tricks-block-example {   background-color: tomato;   color: black; }

Then import it in edit.js, which is the file that contains our Edit component (we can import it anywhere we want, but since these styles are for the editor, it’s more logical to import the component here):

import "./editor.scss";

Now when we run npm run build, the styles are applied to the block in both contexts:

The pullquote block in the WordPress Block Editor with an applied tomoato-colored background. behind black text.
The pullquote block ion the front end with an applied rebecca purple-colored background behind black text.

Referencing styles in block.json

We imported the styling files in the edit.js and index.js, but recall that the compilation step generates two CSS files for us in the build directory: index.css and style-index.css respectively. We need to reference these generated files in the block metadata.

Let’s add a couple of statements to the block.json metadata:

{   "$ schema": "https://schemas.wp.org/trunk/block.json",   "apiVersion": 2,   "name": "css-tricks/block-example",   "version": "1.0.0",   "title": "Block Example",   "category": "text",   "icon": "format-quote",   "editorScript": "file:./index.js",   "editorStyle": "file:./index.css",   "style": "file:./style-index.css" }

Run npm run build once again, install and activate the plugin on your WordPress site, and you’re ready to use it!

You can use npm run start to run your build in watch mode, automatically compiling your code every time you make a change in your code and save.

We’re scratching the surface

Actual blocks make use of the Block Editor’s Settings sidebar and other features WordPress provides to create rich user experiences. Moreover, the fact that there’s essentially two versions of our block — edit and save — you also need to give thought to how you organize your code to avoid code duplication.

But hopefully this helps de-mystify the general process for creating WordPress blocks. This is truly a new era in WordPress development. It’s tough to learn new ways of doing things, but I’m looking forward to seeing how it evolves. Tools like @wordpress/create-block help, but even then it’s nice to know exactly what it’s doing and why.

Are the things we covered here going to change? Most likely! But at least you have a baseline to work from as we keep watching WordPress blocks mature, including best practices for making them.

References

Again, my goal here is to map out an efficient path for getting into block development in this season where things are evolving quickly and WordPress documentation is having a little hard time catching up. Here are some resources I used to pull this together:


Getting Started With WordPress Block Development originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

CSS-Tricks

, , , ,

GIFs Without the .gif: The Most Performant Image and Video Options Right Now

So you want an auto-playing looping video without sound? In popular vernacular this is the very meaning of the word GIF. The word has stuck around but the image format itself is ancient and obsolete. Twitter, for example, has a “GIF” button that actually inserts a <video> element with an MP4 file into your tweet — no .gif in sight. There are a beguiling amount of ways to achieve the same outcome but one thing is clear: there’s really no good reason to use the bulky .gif file format anymore.

Use a HTML <video> element

It’s easy to recreate the behavior of a GIF using the HTML video element.

<video autoplay loop muted playsinline src="cats.mp4"></video>

With this code the video will play automatically in a continuous loop with no audio. playsinline means that mobile browsers will play the video where it is on the page rather than opening in fullscreen.

While the HTML video element itself has been supported for many years, the same can’t be said for the wide variety of video formats.

Videos are made up of two parts: the container and the video codec. (If your video contains audio then it is made up of three parts, the third being the audio codec.) Containers can store video, audio, subtitles and meta information. The two most common containers for video on the web are MP4 and WebM. The container is the same as the file type — if a file ends with a .mp4 extension, that means it’s using an MP4 container. The file extension doesn’t tell you the codec though. Examples of video codecs commonly used on the web include VP8, VP9, H.264 and HEVC (H.265). For your video to play online, the browser needs to support both the video container and the codec.

Browser support for video is a labyrinthine mess, which is part of the reason YouTube embeds are ubiquitous, but that doesn’t work for our use case. Let’s look at the video formats that are worth considering.

Containers

  • MP4 was originally released in 2001. It is supported by all web browsers and has been for quite some time.
  • WebM was released in 2010. It works in all browsers except for iOS Safari.

Codecs

  • The H.264 codec works in all browsers.
  • HEVC/H.265, the successor of H.264, is supported by Safari, Edge, and Chrome (as of version 105).
  • VP9 is the successor to the VP8 codec. VP9 is supported by all the browsers that support WebM.
  • The AV1 codec has been supported in Chrome since 2018 and Firefox since 2019. It has not yet shipped in Edge or Safari.

An MP4 file using the H.264 codec will work everywhere, but it doesn’t deliver the best quality or the smallest file size.

AV1 doesn’t have cross-browser support yet but, released in 2018, it’s the most modern codec around. It’s already being used, at least for some videos and platforms, by Netflix, YouTube and Vimeo. AV1 is a royalty-free video codec designed specifically for the internet. AV1 was created by the Alliance for Open Media (AOM), a group founded by Google, Mozilla, Cisco, Microsoft, Netflix, Amazon, and Intel. Apple is now also a member, so it’s safe to assume all browsers will support AV1 eventually. Edge is “still evaluating options to support AVIF and AV1.”

The recently redesigned website from development consultancy Evil Martians is a testament to the file-size reduction that AV1 is capable of.

If you want to use newer video formats with fallbacks for older browsers, you can use multiple <source> elements. The order of the source elements matter. Specify the ideal source at the top, and the fallback after.

<video autoplay loop muted playsinline>   <source src="cats.webm" type="video/webm"> <!-- ideal -->   <source src="cats.mp4" type="video/mp4"> <!-- fallhack --> </video>

Given the above code, cats.webm will be used unless the browser does not support that format, in which case the MP4 will be displayed instead.

What if you want to include multiple MP4 files, but with each using a different codec? When specifying the type you can include a codecs parameter. The syntax is horrifically complicated for anybody who isn’t some kind of hardcore codec nerd, but it looks something like this:

<video autoplay loop muted playsinline>   <source src="cats.mp4" type="video/mp4; codecs=av01.0.05M.08" >   <source src="cats.mp4" type="video/mp4" > </video>

Using the above code the browser will select AV1 if it can play that format and fallback to the universally-supported H.264 if not. For AV1, the codecs parameter always starts with av01. The next number is either 0 (for main profile), 1 (for high profile) or 2 (for professional profile). Next comes a two-digit level number. This is followed either by the letter M (for main tier) or H (for high tier). It’s difficult to understand what any those things mean, so you could provide your AV1 video in a WebM container and avoid specifying the codec entirely.

Most video editing software does not allow you to export as AV1, or even as WebM. If you want to use one of those formats you’ll need to export your video as something else, like a .mov, and then convert it using the command-line tool FFmpeg:

ffmpeg -i yourSourceFile.mov -map_metadata -1 -c:a libopus -c:v librav1e -qp 80 -tile-columns 2 -tile-rows 2 -pix_fmt yuv420p -movflags +faststart -vf &quot;scale=trunc(iw/2)*2:trunc(ih/2)*2&quot; videoTitle.mp4

You should use the most high-resolution source file you can. Obviously, once image quality is lost you can’t improve it through conversion to a superior format. Using a .gif as a source file isn’t ideal because the visual quality of .gif isn’t great, but you’ll still get the benefit of a large reduction in file size:

ffmpeg -i cats.gif -map_metadata -1 -an opus -c:v librav1e -qp 80 -tile-columns 2 -tile-rows 2 -pix_fmt yuv420p -movflags +faststart -vf &quot;scale=trunc(iw/2)*2:trunc(ih/2)*2&quot; cats.mp4

On Mac, you can download FFmpeg using Homebrew:

brew install ffmpeg

Here’s a nice example of video in web design on the masterfully designed Oxide website:

If you want to use the video as a background and place other elements on top of it, working with <video> is slightly more challenging than a CSS background-image, and requires code that goes something like this:

.video-parent {   position: relative;   width: 100vw;   height: 100vh; }   .video-parent video {   object-fit: cover;   position: absolute;   inset: 0;   z-index: -1;   width: 100%;   height: 100%; }

The <video> element is a perfectly okay option for replacing GIFs but it does have one unfortunate side-effect: it prevents a user’s screen from going to sleep, as explained in this post from an ex- product manager on the Microsoft Edge browser.

The benefits of using an image

Whether it’s an animated WebP or animated AVIF file, using images rather than video comes with some benefits.

I’m not sure how many people actually want to art-direct their GIFs, but using the <picture> element does open up some possibilities that couldn’t easily be achieved with <video>. You could specify different animations for light and dark mode, for example:

<picture>   <source srcset="dark-animation.avifs" media="(prefers-color-scheme: dark)">   <img src="light-animation.avif" alt=""> </picture>

We might want a video on mobile to be a different aspect ratio than on desktop. We could just crop parts of the image with CSS, but that seems like a waste of bytes and somewhat haphazard. Using a media query we can display a different animated image file based on the screen size or orientation:

<picture>   <source type="image/avif" srcset="typeloop-landscape.avifs" media="(orientation: landscape)"">   <img src="typeloop-portrait.avif" alt=""> </picture>

All of this is possible with video — you can use matchMedia to do any media queries in JavaScript and programmatically change the src of a <video> element:

const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)"); if (mediaQuery.matches) {   document.querySelector("video").src = "dark-animation.mp4"; }

I believe that whenever there’s a way to do something with markup it should be preferred over doing it JavaScript.

You can use raster images inside of an SVG using the <image> element. This includes animated image formats. There’s not much you can do with an image inside an SVG that you couldn’t already do with CSS, but if you group an image with vector elements inside an SVG, then you do get the benefit that the different elements move and scale together.

The <img> element has the benefit of native lazy-loading:

<img loading="lazy" src="cats.avif" alt="cats">

If you want a background video that takes up the entire screen, it’s slightly easier to position a background-image than a HTML <video> element:

.background-video {   background-image: url("coolbackground.webp");   background-repeat: no-repeat;   background-size: cover;   height: 100vh;   width: 100vh; } 

If you want to support older browsers you could use the <picture> element with a fallback of either an animated WebP or, just for Safari, an img with a video src, or if you care about ancient browsers, maybe an APNG (animated PNG) or a GIF. Using multiple image formats this way might be impractical if you’re optimizing images manually; but it is relatively trivial if you’re using a service like Cloudinary.

<picture>   <source type="image/avif" srcset="cats.avif">   <img src="cats.webp"> </picture>

There’s still no well-supported way to specify fallback images for CSS backgrounds. image-set is an equivalent of the <picture> element, [but for background-image. Unfortunately, only Firefox currently supports the type attribute of image-set.

.box {   background-image: image-set(     url("cats.avif") type("image/avif"),     url("cats.webp") type("image/webp")); }

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 Firefox IE Edge Safari
108* 89 No 105* TP

Mobile / Tablet

Android Chrome Android Firefox Android iOS Safari
105* 104 105* 16.1

Use animated WebP

The WebP image format was introduced by Google in 2010. WebP, including animated WebP, has broad browser support.

A cat flying through space leaving a rainbow trail
<img src="nyancat.webp" alt="A cat flying through space leaving a rainbow trail">

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 Firefox IE Edge Safari
32 65 No 18 16.0

Mobile / Tablet

Android Chrome Android Firefox Android iOS Safari
105 104 4.2-4.3 14.0-14.4

Use animated AVIF

WebP is now twelve years old. The more modern AV1 Image File Format (AVIF), released in 2019, is the best image format for most use cases on the web. Converting a .gif file to AVIF can reduce bytes by over 90%.

<img src="nyancat.avif" alt="A cat flying through space leaving a rainbow trail">

As its name suggests, AVIF is based on the the AV1 video codec. Like WebP, AVIF can be used for both still images and animation. There’s not much difference between an animated AVIF file and an AV1 video in an MP4 container.

You can put a shadow on AVIF animation, e.g.:

filter: drop-shadow(2px 4px 6px black);

AVIF is already supported by Safari, Firefox, Samsung Internet, and Chrome. Firefox only shipped support for still images, not animated AVIF. Safari supports animation as of version 16.1. Unfortunately, because Firefox does support AVIF, just not animated AVIF, it’s impossible to successfully use the <picture> element to display AVIF only to browsers that support animation. Given the following code, Firefox would display the AVIF, but as a static image, rather than showing the animated WebP version:

<picture>   <source srcset="https://fonts.gstatic.com/s/e/notoemoji/latest/1f4a9/512.avif" type="image/avif">   <img src="https://fonts.gstatic.com/s/e/notoemoji/latest/1f4a9/512.webp" alt="💩" width="32" height="32"> </picture>

Tooling for AVIF is still improving. Video editing software does not enable you to export footage as animated AVIF or animated WebP. You’ll need to export it in some other format and then convert it. On the website ezgif.com you can upload a video file or a .gif and convert it to AVIF or WebP. You could also use FFmpeg. Using Cloudinary you can upload a video file or an old .gif and convert it to pretty much any format you want — including animated WebP and animated AVIF. As of time of writing, Squoosh, an image conversion app, doesn’t support animated AVIF.

Adoption remains lacking in design software. When viewing a prototype, Figma will play any animated GIFs included in the design. For AVIF, by contrast, you can’t even import or export a still image.

An error in Figma that says files failed to import.

Use a video with an <img> element

In 2018, Safari 11.1 gave developers the ability to use a video file as the source of the HTML <img> element. This works in Safari:

<img src="cat.mp4" alt="A Siamese cat walking in a circle">

All the same codecs that Safari supports for <video> are supported by <img>. This means you can use MP4, H.264, and HEVC.

In Safari, video files will also work anyplace in CSS where you could use an image, like background-image or border-image:

.video-border {     border: 40px solid transparent;   border-image: url(abstract_bg_animation.mp4) 100 round; }

One strange consequence of this feature in Safari is that the poster image of a <video> element can also be a video. The poster will autoplay even if you have blocked video’s from auto-playing. Safari claimed this feature came with performance benefits, not just over using .gif files but also over using the <video> element. According to Apple:

By placing your videos in <img> elements, the content loads faster, uses less battery power, and gets better performance.

Colin Bendell, co-author of O‘Reilly’s High Performance Images, wrote about the shortcomings of the <video> tag for our use case:

Unlike <img> tags, browsers do not preload <video> content. Generally preloaders only preload JavaScript, CSS, and image resources because they are critical for the page layout. Since <video> content can be any length – from micro-form to long-form – <video> tags are skipped until the main thread is ready to parse its content. This delays the loading of <video> content by many hundreds of milliseconds.

[…]

Worse yet, many browsers assume that <video> tags contain long-form content. Instead of downloading the whole video file at once, which would waste your cell data plan in cases where you do not end up watching the whole video, the browser will first perform a 1-byte request to test if the server supports HTTP Range Requests. Then it will follow with multiple range requests in various chunk sizes to ensure that the video is adequately (but not over-) buffered. The consequence is multiple TCP round trips before the browser can even start to decode the content and significant delays before the user sees anything. On high-latency cellular connections, these round trips can set video loads back by hundreds or thousands of milliseconds.

Chrome has marked this as “WontFix” — meaning they don’t intend to ever support this feature, for various reasons. There is, however, an open issue on GitHub to add it to the HTML spec, which would force Google’s hand.

Respecting user preferences

Video has the benefit of automatically respecting a users preferences. Firefox and Safari allow users to block videos from automatically playing, even if they don’t have any audio. Here are the settings in Firefox, for example:

firefox autoplay settings open in a modal.

The user can still decide to watch a certain video by right-clicking and pressing play in the menu, or enable autoplay for all videos on a specific website.

Contextual menu for a video.

For users who haven’t disabled autoplay, it’s nice to have the option to pause an animation if you happen to find it annoying or distracting (a user can still right-click to bring up the pause option in a menu when video controls aren’t shown). Success Criterion 2.2.2 Pause, Stop, Hide of the WCAG accessibility guidelines states:

For any moving, blinking or scrolling information that (1) starts automatically, (2) lasts more than five seconds, and (3) is presented in parallel with other content, there is a mechanism for the user to pause, stop, or hide it unless the movement, blinking, or scrolling is part of an activity where it is essential.

With the <video> element, you’ll achieve that criterion without any additional development.

There’s also a “reduce motion” user setting that developers can respect by reducing or removing CSS and JavaScript web animations.

macOS settings window for display accessibility with rediced motion checked.

You can also use it to display a still image instead of an animation. This takes extra code to implement — and you need to host a still image in additional to your animated image.

<picture>   <source     srcset="nyancat.avifs"     type="image/avif"     media="(prefers-reduced-motion: no-preference)"   />   <img src="nyancat.png" alt="Nyan cat" width="250" height="250" /> </picture>

There’s another downside. When using the <picture> element in this way if the user has checked “reduce motion”there’s no way for them to see the animation. Just because a user prefers less animation, doesn’t mean they never want any — they might still want to be able to opt-in and watch one every now and then. Unlike the <video> element, displaying a still image takes away that choice.

Checking for progressive enhancement

If you want to check that your <picture> code is properly working and fallback images are being displayed, you can use the Rendering tab in Chrome DevTools to turn off support for AVIF and WebP image formats. Seeing as all browsers now support WebP, this is a pretty handy feature.

Chrome DevTools with Rendering panel open optons for disabling AVIF and WebP images.

While it’s usually the best option to create animations with CSS, JavaScript, DOM elements, canvas and SVG, as new image and video formats offer smaller files than what was previously possible, they become a useful option for UI animation (rather than just nyancat loops). For one-off animations, an AVIF file is probably going to be more performant than importing an entire animation library.

Circular badge that reads Match Accepted with an animated blue progress highlight going around it.
Here’s a fun example of using video for UI from all the way back in 2017 for the League of Legends website.

Lottie

After Effects is a popular animation tool from Adobe. Using an extension called Bodymovin, you can export animation data from After Effects as a JSON file.

Then there’s Lottie, an open-source animation library from Airbnb that can take that JSON file and render it as an animation on different platforms. The library is available for native iOS, Android, and React Native applications, as well as for the web. You can see examples from Google Home, Target, and Walgreens, among others.

Once you’ve included the dependency you need to write a small amount of JavaScript code to get the animation to run:

<div id="lottie"></div>
const animation = bodymovin.loadAnimation({   container: document.getElementById('lottie'),   path: 'myAnimation.json',   renderer: 'svg',   loop: true,   autoplay: true, })

You can optionally change those settings to only play after an event:

const lottieContainer = document.getElementById('lottie'); const animation = bodymovin.loadAnimation({   container: lottieContainer,    path: 'myAnimation.json',   renderer: 'svg',   loop: true,   autoplay: false,   }) // Play the animation on hover lottieContainer.addEventListener('mouseover', () => {   animation.play(); }); // Stop the animation after playing once animation.addEventListener('loopComplete', function() {   animation.stop(); });

Here’s a cute example of a cat typing on a keyboard I took from Lottiefiles.com (the website is a useful website for previewing your own Lottie JSON file animations, rather than needing to install After Effects, as well finding animations from other creatives):

You can also programmatically play an animation backwards and change the playback rate.

If you do choose to use Lottie, there’s a Figma plugin for Lottie but all it does is convert JSON files to .gif so that they can be previewed in prototyping mode.

Abd what about Lottie’s performance? There’s size of the library — 254.6KB (63.8 gzipped) — and the size of the JSON file to consider. There’s also the amount of DOM elements that get created for the SVG parts. If you run into this issue, Lottie has the option to render to a HTML <canvas>, but you’ll need to use a different version of the JavaScript library.

const animation = bodymovin.loadAnimation({   container: document.getElementById('lottie'),    path: 'myAnimation.json',   renderer: 'canvas', })

Lottie isn’t a full replacement for gifs. While After Effects itself is often used with video clips, and Lottie can render to a HTML <canvas>, and a canvas can play video clips, you wouldn’t use a Lottie file for that purpose. Lottie is for advanced 2D animations, not so much for video. There are other tools for creating complex web animations with a GUI like SVGator and Rive, but I haven’t tried them myself. 🤷‍♂️


I wish there was a TL;DR for this article. For now, at least, there’s no clear winner…


GIFs Without the .gif: The Most Performant Image and Video Options Right Now originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

CSS-Tricks

, , , , , , , ,
[Top]

Named Element IDs Can Be Referenced as JavaScript Globals

Did you know that DOM elements with IDs are accessible in JavaScript as global variables? It’s one of those things that’s been around, like, forever but I’m really digging into it for the first time.

If this is the first time you’re hearing about it, brace yourself! We can see it in action simply by adding an ID to an element in HTML:

<div id="cool"></div>

Normally, we’d define a new variable using querySelector("#cool") or getElementById("cool") to select that element:

var el = querySelector("#cool");

But we actually already have access to #cool without that rigamorale:

So, any id — or name attribute, for that matter — in the HTML can be accessed in JavaScript using window[ELEMENT_ID]. Again, this isn’t exactly “new” but it’s really uncommon to see.

As you may guess, accessing the global scope with named references isn’t the greatest idea. Some folks have come to call this the “global scope polluter.” We’ll get into why that is, but first…

Some context

This approach is outlined in the HTML specification, where it’s described as “named access on the Window object.”

Internet Explorer was the first to implement the feature. All other browsers added it as well. Gecko was the only browser at the time to not support it directly in standards mode, opting instead to make it an experimental feature. There was hesitation to implement it at all, but it moved ahead in the name of browser compatibility (Gecko even tried to convince WebKit to move it out of standards mode) and eventually made it to standards mode in Firefox 14.

One thing that might not be well known is that browsers had to put in place a few precautionary measures — with varying degrees of success — to ensure generated globals don’t break the webpage. One such measure is…

Variable shadowing

Probably the most interesting part of this feature is that named element references don’t shadow existing global variables. So, if a DOM element has an id that is already defined as a global, it won’t override the existing one. For example:

<head>   <script>     window.foo = "bar";   </script> </head> <body>   <div id="foo">I won't override window.foo</div>   <script>     console.log(window.foo); // Prints "bar"   </script> </body>

And the opposite is true as well:

<div id="foo">I will be overridden :(</div> <script>   window.foo = "bar";   console.log(window.foo); // Prints "bar" </script>

This behavior is essential because it nullifies dangerous overrides such as <div id="alert" />, which would otherwise create a conflict by invalidating the alert API. This safeguarding technique may very well be the why you — if you’re like me — are learning about this for the first time.

The case against named globals

Earlier, I said that using global named elements as references might not be the greatest idea. There are lots of reasons for that, which TJ VanToll has covered nicely over at his blog and I will summarize here:

  • If the DOM changes, then so does the reference. That makes for some really “brittle” (the spec’s term for it) code where the separation of concerns between HTML and JavaScript might be too much.
  • Accidental references are far too easy. A simple typo may very well wind up referencing a named global and give you unexpected results.
  • It is implemented differently in browsers. For example, we should be able to access an anchor with an id — e.g. <a id="cool"> — but some browsers (namely Safari and Firefox) return a ReferenceError in the console.
  • It might not return what you think. According to the spec, when there are multiple instances of the same named element in the DOM — say, two instances of <div class="cool"> — the browser should return an HTMLCollection with an array of the instances. Firefox, however, only returns the first instance. Then again, the spec says we ought to use one instance of an id in an element’s tree anyway. But doing so won’t stop a page from working or anything like that.
  • Maybe there’s a performance cost? I mean, the browser’s gotta make that list of references and maintain it. A couple of folks ran tests in this StackOverflow thread, where named globals were actually more performant in one test and less performant in a more recent test.

Additional considerations

Let’s say we chuck the criticisms against using named globals and use them anyway. It’s all good. But there are some things you might want to consider as you do.

Polyfills

As edge-case-y as it may sound, these types of global checks are a typical setup requirement for polyfills. Check out the following example where we set a cookie using the new CookieStore API, polyfilling it on browsers that don’t support it yet:

<body>   <img id="cookieStore"></img>   <script>     // Polyfill the CookieStore API if not yet implemented.     // https://developer.mozilla.org/en-US/docs/Web/API/CookieStore     if (!window.cookieStore) {       window.cookieStore = myCookieStorePolyfill;     }     cookieStore.set("foo", "bar");   </script> </body>

This code works perfectly fine in Chrome, but throws the following error in Safari.:

TypeError: cookieStore.set is not a function

Safari lacks support for the CookieStore API as of this writing. As a result, the polyfill is not applied because the img element ID creates a global variable that clashes with the cookieStore global.

JavaScript API updates

We can flip the situation and find yet another issue where updates to the browser’s JavaScript engine can break a named element’s global references.

For example:

<body>   <input id="BarcodeDetector"></input>   <script>     window.BarcodeDetector.focus();   </script> </body>

That script grabs a reference to the input element and invokes focus() on it. It works correctly. Still, we don’t know how long it will continue to work.

You see, the global variable we’re using to reference the input element will stop working as soon as browsers start supporting the BarcodeDetector API. At that point, the window.BarcodeDetector global will no longer be a reference to the input element and .focus() will throw a “window.BarcodeDetector.focus is not a function” error.

Bonus: Not all named elements generate global references

Want to hear something funny? To add insult to the injury, named elements are accessible as global variables only if the names contain nothing but letter. Browsers won’t create a global reference for an element with a ID that contains special characters and numbers, like hello-world and item1.

Conclusion

Let’s sum up how we got here:

  • All major browsers automatically create global references to each DOM element with an id (or, in some cases, a name attribute).
  • Accessing these elements through their global references is unreliable and potentially dangerous. Use querySelector or getElementById instead.
  • Since global references are generated automatically, they may have some side effects on your code. That’s a good reason to avoid using the id attribute unless you really need it.

At the end of the day, it’s probably a good idea to avoid using named globals in JavaScript. I quoted the spec earlier about how it leads to “brittle” code, but here’s the full text to drive the point home:

As a general rule, relying on this will lead to brittle code. Which IDs end up mapping to this API can vary over time, as new features are added to the web platform, for example. Instead of this, use document.getElementById() or document.querySelector().

I think the fact that the HTML spec itself recommends to staying away from this feature speaks for itself.


Named Element IDs Can Be Referenced as JavaScript Globals originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

CSS-Tricks

, , , ,
[Top]

How to Create Wavy Shapes & Patterns in CSS

The wave is probably one of the most difficult shapes to make in CSS. We always try to approximate it with properties like border-radius and lots of magic numbers until we get something that feels kinda close. And that’s before we even get into wavy patterns, which are more difficult.

“SVG it!” you might say, and you are probably right that it’s a better way to go. But we will see that CSS can make nice waves and the code for it doesn’t have to be all crazy. And guess what? I have an online generator to make it even more trivial!

If you play with the generator, you can see that the CSS it spits out is only two gradients and a CSS mask property — just those two things and we can make any kind of wave shape or pattern. Not to mention that we can easily control the size and the curvature of the waves while we’re at it.

Some of the values may look like “magic numbers” but there’s actually logic behind them and we will dissect the code and discover all the secrets behind creating waves.

This article is a follow-up to a previous one where I built all kinds of different zig-zag, scoped, scalloped, and yes, wavy border borders. I highly recommend checking that article as it uses the same technique we will cover here, but in greater detail.

The math behind waves

Strictly speaking, there isn’t one magic formula behind wavy shapes. Any shape with curves that go up and down can be called a wave, so we are not going to restrict ourselves to complex math. Instead, we will reproduce a wave using the basics of geometry.

Let’s start with a simple example using two circle shapes:

Two gray circles.

We have two circles with the same radius next to each other. Do you see that red line? It covers the top half of the first circle and the bottom half of the second one. Now imagine you take that line and repeat it.

A squiggly red line in the shape of waves.

We already see the wave. Now let’s fill the bottom part (or the top one) to get the following:

Red wave pattern.

Tada! We have a wavy shape, and one that we can control using one variable for the circle radii. This is one of the easiest waves we can make and it’s the one I showed off in this previous article

Let’s add a bit of complexity by taking the first illustration and moving the circles a little:

Two gray circles with two bisecting dashed lines indicating spacing.

We still have two circles with the same radii but they are no longer horizontally aligned. In this case, the red line no longer covers half the area of each circle, but a smaller area instead. This area is limited by the dashed red line. That line crosses the point where both circles meet.

Now take that line and repeat it and you get another wave, a smoother one.

A red squiggly line.
A red wave pattern.

I think you get the idea. By controlling the position and size of the circles, we can create any wave we want. We can even create variables for them, which I will call P and S, respectively.

You have probably noticed that, in the online generator, we control the wave using two inputs. They map to the above variables. S is the “Size of the wave” and P is the “curvature of the wave”.

I am defining P as P = m*S where m is the variable you adjust when updating the curvature of the wave. This allows us to always have the same curvature, even if we update S.

m can be any value between 0 and 2. 0 will give us the first particular case where both circles are aligned horizontally. 2 is a kind of maximum value. We can go bigger, but after a few tests I found that anything above 2 produces bad, flat shapes.

Let’s not forget the radius of our circle! That can also be defined using S and P like this:

R = sqrt(P² + S²)/2

When P is equal to 0, we will have R = S/2.

We have everything to start converting all of this into gradients in CSS!

Creating gradients

Our waves use circles, and when talking about circles we talk about radial gradients. And since two circles define our wave, we will logically be using two radial gradients.

We will start with the particular case where P is equal to 0. Here is the illustration of the first gradient:

This gradient creates the first curvature while filling in the entire bottom area —the “water” of the wave so to speak.

.wave {   --size: 50px;    mask: radial-gradient(var(--size) at 50% 0%, #0000 99%, red 101%)      50% var(--size)/calc(4 * var(--size)) 100% repeat-x; }

The --size variable defines the radius and the size of the radial gradient. If we compare it with the S variable, then it’s equal to S/2.

Now let’s add the second gradient:

The second gradient is nothing but a circle to complete our wave:

radial-gradient(var(--size) at 50% var(--size), blue 99%, #0000 101%)    calc(50% - 2*var(--size)) 0/calc(4 * var(--size)) 100%

If you check the previous article you will see that I am simply repeating what I already did there.

I followed both articles but the gradient configurations are not the same.

That’s because we can reach the same result using different gradient configurations. You will notice a slight difference in the alignment if you compare both configurations, but the trick is the same. This can be confusing if you are unfamiliar with gradients, but don’t worry. With some practice, you get used to them and you will find by yourself that different syntax can lead to the same result.

Here is the full code for our first wave:

.wave {   --size: 50px;    mask:     radial-gradient(var(--size) at 50% var(--size),#000 99%, #0000 101%)        calc(50% - 2*var(--size)) 0/calc(4 * var(--size)) 100%,     radial-gradient(var(--size) at 50% 0px, #0000 99%, #000 101%)        50% var(--size)/calc(4 * var(--size)) 100% repeat-x; }

Now let’s take this code and adjust it to where we introduce a variable that makes this fully reusable for creating any wave we want. As we saw in the previous section, the main trick is to move the circles so they are no more aligned so let’s update the position of each one. We will move the first one up and the second down.

Our code will look like this:

.wave {   --size: 50px;   --p: 25px;    mask:     radial-gradient(var(--size) at 50% calc(var(--size) + var(--p)), #000 99%, #0000 101%)        calc(50% - 2*var(--size)) 0/calc(4 * var(--size)) 100%,     radial-gradient(var(--size) at 50% calc(-1*var(--p)), #0000 99%, #000 101%)        50% var(--size) / calc(4 * var(--size)) 100% repeat-x; }

I have introduced a new --p variable that’s used it to define the center position of each circle. The first gradient is using 50% calc(-1*var(--p)), so its center moves up while the second one is using calc(var(--size) + var(--p)) to move it down.

A demo is worth a thousand words:

The circles are neither aligned nor touch one another. We spaced them far apart without changing their radii, so we lost our wave. But we can fix things up by using the same math we used earlier to calculate the new radius. Remember that R = sqrt(P² + S²)/2. In our case, --size is equal to S/2; the same for --p which is also equal to P/2 since we are moving both circles. So, the distance between their center points is double the value of --p for this:

R = sqrt(var(--size) * var(--size) + var(--p) * var(--p))

That gives us a result of 55.9px.

Our wave is back! Let’s plug that equation into our CSS:

.wave {   --size: 50px;   --p: 25px;   --R: sqrt(var(--p) * var(--p) + var(--size)*var(--size));    mask:     radial-gradient(var(--R) at 50% calc(var(--size) + var(--p)), #000 99%, #0000 101%)        calc(50% - 2*var(--size)) 0 / calc(4 * var(--size)) 100%,     radial-gradient(var(--R) at 50% calc(-1*var(--p)), #0000 99%, #000 101%)        50% var(--size)/calc(4 * var(--size)) 100% repeat-x; }

This is valid CSS code. sqrt() is part of the specification, but at the time I’m writing this, there is no browser support for it. That means we need a sprinkle of JavaScript or Sass to calculate that value until we get broader sqrt() support.

This is pretty darn cool: all it takes is two gradients to get a cool wave that you can apply to any element using the mask property. No more trial and error — all you need is to update two variables and you’re good to go!

Reversing the wave

What if we want the waves going the other direction, where we’re filling in the “sky” instead of the “water”. Believe it or not, all we have to do is to update two values:

.wave {   --size: 50px;   --p: 25px;   --R: sqrt(var(--p) * var(--p) + var(--size) * var(--size));    mask:     radial-gradient(var(--R) at 50% calc(100% - (var(--size) + var(--p))), #000 99%, #0000 101%)       calc(50% - 2 * var(--size)) 0/calc(4 * var(--size)) 100%,     radial-gradient(var(--R) at 50% calc(100% + var(--p)), #0000 99%, #000 101%)        50% calc(100% - var(--size)) / calc(4 * var(--size)) 100% repeat-x; }

All I did there is add an offset equal to 100%, highlighted above. Here’s the result:

We can consider a more friendly syntax using keyword values to make it even easier:

.wave {   --size: 50px;   --p: 25px;   --R: sqrt(var(--p)*var(--p) + var(--size) * var(--size));    mask:     radial-gradient(var(--R) at left 50% bottom calc(var(--size) + var(--p)), #000 99%, #0000 101%)        calc(50% - 2 * var(--size)) 0/calc(4 * var(--size)) 100%,     radial-gradient(var(--R) at left 50% bottom calc(-1 * var(--p)), #0000 99%, #000 101%)        left 50% bottom var(--size) / calc(4 * var(--size)) 100% repeat-x; }

We’re using the left and bottom keywords to specify the sides and the offset. By default, the browser defaults to left and top — that’s why we use 100% to move the element to the bottom. In reality, we are moving it from the top by 100%, so it’s really the same as saying bottom. Much easier to read than math!

With this updated syntax, all we have to do is to swap bottom for top — or vice versa — to change the direction of the wave.

And if you want to get both top and bottom waves, we combine all the gradients in a single declaration:

.wave {   --size: 50px;   --p: 25px;   --R: sqrt(var(--p)*var(--p) + var(--size)*var(--size));    mask:     /* Gradient 1 */     radial-gradient(var(--R) at left 50% bottom calc(var(--size) + var(--p)), #000 99%, #0000 101%)        left calc(50% - 2*var(--size)) bottom 0 / calc(4 * var(--size)) 51% repeat-x,     /* Gradient 2 */     radial-gradient(var(--R) at left 50% bottom calc(-1 * var(--p)), #0000 99%, #000 101%)        left 50% bottom var(--size) / calc(4 * var(--size)) calc(51% - var(--size)) repeat-x,     /* Gradient 3 */     radial-gradient(var(--R) at left 50% top calc(var(--size) + var(--p)), #000 99%, #0000 101%)        left calc(50% - 2 * var(--size)) top 0 / calc(4 * var(--size)) 51% repeat-x,     /* Gradient 4 */     radial-gradient(var(--R) at left 50% top calc(-1 * var(--p)), #0000 99%, #000 101%)        left 50% top var(--size) / calc(4 * var(--size)) calc(51% - var(--size)) repeat-x; }

If you check the code, you will see that in addition to combining all the gradients, I have also reduced their height from 100% to 51% so that they both cover half of the element. Yes, 51%. We need that little extra percent for a small overlap that avoid gaps.

What about the left and right sides?

It’s your homework! Take what we did with the top and bottom sides and try to update the values to get the right and left values. Don’t worry, it’s easy and the only thing you need to do is to swap values.

If you have trouble, you can always use the online generator to check the code and visualize the result.

Wavy lines

Earlier, we made our first wave using a red line then filled the bottom portion of the element. How about that wavy line? That’s a wave too! Even better is if we can control its thickness with a variable so we can reuse it. Let’s do it!

We are not going to start from scratch but rather take the previous code and update it. The first thing to do is to update the color stops of the gradients. Both gradients start from a transparent color to an opaque one, or vice versa. To simulate a line or border, we need to start from transparent, go to opaque, then back to transparent again:

#0000 calc(99% - var(--b)), #000 calc(101% - var(--b)) 99%, #0000 101%

I think you already guessed that the --b variable is what we’re using to control the line thickness. Let’s apply this to our gradients:

Yeah, the result is far from a wavy line. But looking closely, we can see that one gradient is correctly creating the bottom curvature. So, all we really need to do is rectify the second gradient. Instead of keeping a full circle, let’s make partial one like the other gradient.

Still far, but we have both curvatures we need! If you check the code, you will see that we have two identical gradients. The only difference is their positioning:

.wave {   --size: 50px;   --b: 10px;   --p: 25px;   --R: sqrt(var(--p)*var(--p) + var(--size)*var(--size));    --_g: #0000 calc(99% - var(--b)), #000 calc(101% - var(--b)) 99%, #0000 101%;   mask:     radial-gradient(var(--R) at left 50% bottom calc(-1*var(--p)), var(--_g))        calc(50% - 2*var(--size)) 0/calc(4*var(--size)) 100%,     radial-gradient(var(--R) at left 50% top    calc(-1*var(--p)), var(--_g))        50% var(--size)/calc(4*var(--size)) 100%; }

Now we need to adjust the size and position for the final shape. We no longer need the gradient to be full-height, so we can replace 100% with this:

/* Size plus thickness */ calc(var(--size) + var(--b))

There is no mathematical logic behind this value. It only needs to be big enough for the curvature. We will see its effect on the pattern in just a bit. In the meantime, let’s also update the position to vertically center the gradients:

.wave {   --size: 50px;   --b: 10px;   --p: 25px;   --R: sqrt(var(--p)*var(--p) + var(--size)*var(--size));    --_g: #0000 calc(99% - var(--b)), #000 calc(101% - var(--b)) 99%, #0000 101%;     mask:     radial-gradient(var(--R) at left 50% bottom calc(-1*var(--p)), var(--_g))        calc(50% - 2*var(--size)) 50%/calc(4 * var(--size)) calc(var(--size) + var(--b)) no-repeat,     radial-gradient(var(--R) at left 50% top calc(-1 * var(--p)), var(--_g)) 50%       50%/calc(4 * var(--size)) calc(var(--size) + var(--b)) no-repeat; }

Still not quite there:

One gradient needs to move a bit down and the other a bit up. Both need to move by half of their height.

We are almost there! We need a small fix for the radius to have a perfect overlap. Both lines need to offset by half the border (--b) thickness:

We got it! A perfect wavy line that we can easily adjust by controlling a few variables:

.wave {   --size: 50px;   --b: 10px;   --p: 25px;   --R: calc(sqrt(var(--p) * var(--p) + var(--size) * var(--size)) + var(--b) / 2);    --_g: #0000 calc(99% - var(--b)), #000 calc(101% - var(--b)) 99%, #0000 101%;   mask:     radial-gradient(var(--R) at left 50% bottom calc(-1 * var(--p)), var(--_g))       calc(50% - 2*var(--size)) calc(50% - var(--size)/2 - var(--b)/2) / calc(4 * var(--size)) calc(var(--size) + var(--b)) repeat-x,     radial-gradient(var(--R) at left 50% top calc(-1*var(--p)),var(--_g))       50%  calc(50% + var(--size)/2 + var(--b)/2) / calc(4 * var(--size)) calc(var(--size) + var(--b)) repeat-x; }

I know that the logic takes a bit to grasp. That’s fine and as I said, creating a wavy shape in CSS is not easy, not to mention the tricky math behind it. That’s why the online generator is a lifesaver — you can easily get the final code even if you don’t fully understand the logic behind it.

Wavy patterns

We can make a pattern from the wavy line we just created!

Oh no, the code of the pattern will be even more difficult to understand!

Not at all! We already have the code. All we need to do is to remove repeat-x from what we already have, and tada. 🎉

A nice wavy pattern. Remember the equation I said we’d revisit?

/* Size plus thickness */ calc(var(--size) + var(--b))

Well, this is what controls the distance between the lines in the pattern. We can make a variable out of it, but there’s no need for more complexity. I’m not even using a variable for that in the generator. Maybe I’ll change that later.

Here is the same pattern going in a different direction:

I am providing you with the code in that demo, but I’d for you to dissect it and understand what changes I made to make that happen.

Simplifying the code

In all the previous demos, we always define the --size and --p independently. But do you recall how I mentioned earlier that the online generator evaluates P as equal to m*S, where m controls the curvature of the wave? By defining a fixed multiplier, we can work with one particular wave and the code can become easier. This is what we will need in most cases: a specific wavy shape and a variable to control its size.

Let’s update our code and introduce the m variable:

.wave {   --size: 50px;   --R: calc(var(--size) * sqrt(var(--m) * var(--m) + 1));    mask:     radial-gradient(var(--R) at 50% calc(var(--size) * (1 + var(--m))), #000 99%, #0000 101%)        calc(50% - 2*var(--size)) 0/calc(4 * var(--size)) 100%,     radial-gradient(var(--R) at 50% calc(-1 * var(--size) * var(--m)), #0000 99%, #000 101%)        50% var(--size) / calc(4 * var(--size)) 100% repeat-x;   }

As you can see, we no longer need the --p variable. I replaced it with var(--m)*var(--size), and optimized some of the math accordingly. Now, If we want to work with a particular wavy shape, we can omit the --m variable and replace it with a fixed value. Let’s try .8 for example.

--size: 50px; --R: calc(var(--size) * 1.28);  mask:   radial-gradient(var(--R) at 50% calc(1.8 * var(--size)), #000 99%, #0000 101%)      calc(50% - 2*var(--size)) 0/calc(4 * var(--size)) 100%,   radial-gradient(var(--R) at 50% calc(-.8 * var(--size)), #0000 99%, #000 101%)      50% var(--size) / calc(4 * var(--size)) 100% repeat-x;

See how the code is easier now? Only one variable to control your wave, plus you no more need to rely on sqrt() which has no browser support!

You can apply the same logic to all the demos we saw even for the wavy lines and the pattern. I started with a detailed mathmatical explanation and gave the generic code, but you may find yourself needing easier code in a real use case. This is what I am doing all the time. I rarely use the generic code, but I always consider a simplified version especially that, in most of the cases, I am using some known values that don’t need to be stored as variables. (Spoiler alert: I will be sharing a few examples at the end!)

Limitations to this approach

Mathematically, the code we made should give us perfect wavy shapes and patterns, but in reality, we will face some strange results. So, yes, this method has its limitations. For example, the online generator is capable of producing poor results, especially with wavy lines. Part of the issue is due to a particular combination of values where the result gets scrambled, like using a big value for the border thickness compared to the size:

For the other cases, it’s the issue related to some rounding that will results in misalignment and gaps between the waves:

That said, I still think the method we covered remains a good one because it produces smooth waves in most cases, and we can easily avoid the bad results by playing with different values until we get it perfect.

Wrapping up

I hope that after this article, you will no more to fumble around with trial and error to build a wavy shape or pattern. In addition to the online generator, you have all the math secrets behind creating any kind of wave you want!

The article ends here but now you have a powerful tool to create fancy designs that use wavy shapes. Here’s inspiration to get you started…

What about you? Use my online generator (or write the code manually if you already learned all the math by heart) and show me your creations! Let’s have a good collection in the comment section.


How to Create Wavy Shapes & Patterns in CSS originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

CSS-Tricks

, , ,
[Top]

How To Customize WordPress Block Theme Cover Templates with Dynamic Post Feature Images

If we browse the WordPress theme directory, a majority of themes showcase cover images. It is a feature in popular demand. The cover page trend is true even in the block theme directory screenshots as well.

Let’s consider the following example from Twenty Twenty (a classic theme) which includes a cover template that can be used to display both in single post and page, where the post’s featured image displays at the top that stretches across the browser screen, with post title and other desired meta data below. Cover templates allow creating content that stands out from the traditional constraints of displaying content.

Screenshot showing a single post with Twenty Twenty cover template.

Creating cover templates currently requires writing PHP code as captured here in the Twenty Twenty default theme’s cover template. If we look at the template-parts/content-cover.php file, it contains the code for displaying content when the cover-template is used.

Thus, it is not possible to create a customized cover page if you do not possess a deep knowledge of PHP. For many ordinary WordPress users, the only option is to use plugin like Custom Post Type UI as described in this short video.

Cover sections in block themes

Since WordPress 5.8, theme authors could create custom templates (like single post, author, category, and others) with a top hero section using block editor cover block and bundled into their themes with minimal or no code.

Before diving into how top large cover sections are created in block themes templates, let’s briefly look at the two block themes Twenty Twenty-Two and Wabi by Rich Tabor (full review here).

Screenshot showing cover page thumbnails of Twenty Twenty-Two (left) and Wabi (right) themes.

Behind-the-scenes, Twenty Twenty-Two implements a large header by adding a hidden image stored as a pattern in the header-dark-large parts. Whereas, in the Wabi theme, the large header background color in a single post is implemented with accent background colors and a 50px height spacer block (lines: 5-9). The accent colors are managed by the assets/js/accent-colors.js file.

Many others chose to create a top cover section by using cover block, which allowed users to change the background color and add a static image from Media Library or upload from media devices – without writing any code. With this approach, images from the post featured image block had to be added manually to each single post if you wanted to have the post featured image as the background image in single posts.

Cover Blocks with dynamic post featured image

WordPress 6.0 made available another cool featured image cover blocks feature, which allows use of the featured image of any post or page as the background image in the cover block.

In the following short video, Automattic engineers discuss adding featured images to cover blocks with an example from Archeo theme:

The image block including post featured image block can be further customized using duotone color in theme.json as discussed in this short Connecting The Dots YouTube video (Automattic’s Anne McCarthy).

Use case examples (Wei, Bright Mode)

If we browse the thumbnail images in the block theme directory, we see a majority of them include large cover header sections. If we dig into their template files, they make use of cover blocks with static image background.

Some recently developed themes are using cover blocks with the dynamic post featured image background (e.g., Archeo, Wei, Frost, Bright Mode, etc.). A brief overview of the new feature is available in this short GitHub video.

Screenshot showing cover page thumbnails of Wei (left) and Bright-mode (right) themes.

Combining dynamic accent colors features of Wabi theme with cover and post featured image blocks, Rich Tabor further expands his creativity in his new Wei theme (full review available here) to display dynamic cover images from a single post.

In his Wei announcement post, Rich Tabor writes: “Behind-the-scenes, the single.html template is using a Cover block that leverages the post’s featured image. Then the duotone is applied by the color scheme assigned to the post. This way, just about any image will look fine”.

If you would like to dig deeper into the Wei theme’s header cover block and learn how to create your own, here is a short video from Fränk Klein (WP Development Courses) who explains step-by-step how it was created.

Similar to the Wei theme, Brian Gardner also makes use of cover block with post featured image block in his recent Bright Mode theme to display standout contents with vibrant colors.

Brian told WPTavern: “he loves most about the theme is the way the Cover Block is used on single pages. It pulls the featured image into the Cover block and also offers custom block styles for shadows and full-height options. […] I feel as though this really presents what’s possible with modern WordPress.”

For more detail, here is its demo site and full review of Brian’s Bright Mode theme.

Designing complex layouts with block editor

Recently, WordPress launched a new block editor designed landing homepage and a download page. The announcement attracted mixed reactions from its readers, including from Matt Mullenweg (Automattic) who commented on the 33-days taken to design and launch such a “simple page”. You can find additional behind the scene discussions here.

In response, Jamie Marsland of Pootlepress created this YouTube video where he reproduces a nearly identical homepage in nearly 20 minutes.

Commenting on Marsland video, Sarah Gooding of WP Travern writes: “He is what one might describe as a power user with the block editor. He can quickly shuffle rows, columns, and groups around, adjusting padding and margins as necessary, and assign each section the corresponding color for the design. At this point, this is not something most average WordPress users could do.”

Though the block editor has come a long way, there are still growing pain points to most theme developers and ordinary users to create and design complex layouts with it.

Adding enhancement to TT2 Gopher blocks

In this section, I will walk you through how I added enhancements to the TT2 Gopher Blocks theme that I referenced in my previous article. Inspired by cover blocks from themes that I described earlier, I wanted to add three cover templates (author, category, and single-cover) to the theme.

While browsing websites, we notice two types of cover headers. The mostly observed header is cover section blended with the site header (site title and top navigation) into the cover block (e.g., Twenty Twenty, Twenty Twenty-Two, Wei, Wabi, Frost, Bright Mode, etc.). We also find header cover section which is not blended with site header and positioned just underneath, such as this BBC Future website. For TT2 Gopher blocks theme, I opted for the latter.

Creating cover header patterns

First, let’s create cover header patterns for author, single, and others (categories, tags) templates using cover blocks. Then we will convert them into patterns (as described here previously) and call the respective header cover patterns into the templates.

If you are familiar to working with the block editor, design your header section using cover blocks in the site editor and then convert the cover header code into patterns. However, if you are not familiar with FSE editor, then the easiest way is to copy patterns from the patterns directory in a post, make necessary modification and convert it into a pattern.

In my previous CSS-Tricks article, I discussed in detail on creating and using block patterns. Here is a brief overview of the workflow that I am using to create the single post cover header pattern:

Single post cover header pattern

Step 1: Using FSE interface, let’s create a new blank file and start building block structure as shown on the left panel.

Screenshot of the WordPress UI with the Full Site Editor. A block is being assembled with post date, categories, and post title.

Alternatively, this could be done in a post or page first, and then copy and paste the markup into a pattern file, later.

Step 2: Next, to covert the above markup into a pattern, first we should copy its code markup and paste into a new /patterns/header-single-cover.php in our code editor. We should also add required pattern file header markup (e.g., title, slug, categories, inserter, etc.).

Here is the entire code of the /patterns/header-single-cover.php file:

<?php     /**      * Title: Header cover single      * Slug: tt2gopher/header-cover-single      * Categories: tt2gopher-header      * Block Types: core/template-part/header      * inserter: yes      */ ?>     <!-- wp:cover {"url":"https://pd.w.org/2022/08/15062ed5f5707b5c5.85694718-2048x1536.jpg","id":100,"dimRatio":0,"overlayColor":"foreground","focalPoint":{"x":"0.40","y":"0.37"},"minHeight":50,"minHeightUnit":"vh","isDark":false,"align":"full","style":{"color":{"duotone":["#000000","#00a5ff"]},"spacing":{"margin":{"top":"0px","bottom":"0px"}}}} -->     <div class="wp-block-cover alignfull is-light" style="margin-top:0px;margin-bottom:0px;min-height:50vh"><span aria-hidden="true" class="wp-block-cover__background has-foreground-background-color has-background-dim-0 has-background-dim"></span><img class="wp-block-cover__image-background wp-image-100" alt="" src="https://pd.w.org/2022/08/15062ed5f5707b5c5.85694718-2048x1536.jpg" style="object-position:40% 37%" data-object-fit="cover" data-object-position="40% 37%"/><div class="wp-block-cover__inner-container"><!-- wp:group {"style":{"elements":{"link":{"color":{"text":"var:preset|color|base"}}},"spacing":{"blockGap":"10px"}},"textColor":"base","layout":{"wideSize":"800px"}} -->     <div class="wp-block-group has-base-color has-text-color has-link-color"><!-- wp:group {"style":{"spacing":{"blockGap":"10px"}},"textColor":"primary","layout":{"type":"flex","flexWrap":"nowrap","justifyContent":"center"},"fontSize":"small"} -->     <div class="wp-block-group has-primary-color has-text-color has-small-font-size"><!-- wp:post-date {"textColor":"foreground"} /-->          <!-- wp:paragraph -->     <p>|</p>     <!-- /wp:paragraph -->          <!-- wp:post-terms {"term":"category","style":{"elements":{"link":{"color":{"text":"var:preset|color|foreground"}}}}} /--></div>     <!-- /wp:group -->          <!-- wp:post-title {"textAlign":"center","level":1,"style":{"typography":{"fontStyle":"normal","fontWeight":"400"}},"textColor":"foreground","fontSize":"max-60"} /--></div>     <!-- /wp:group --></div></div>     <!-- /wp:cover -->

Step 3: For this demo, I have used this image from photos directory as a filler background image, and applied the Midnight duotone color. To use post featured image dynamically, we should add "useFeaturedImage":true in the cover block by replacing the above filler image link just before the "dimRatio":50 such that the line 10 should look like the following:

<!-- wp:cover {"useFeaturedImage":true,"dimRatio":0,"overlayColor":"foreground","focalPoint":{"x":"0.40","y":"0.37"},"minHeight":50,"minHeightUnit":"vh","isDark":false,"align":"full","style":{"color":{"duotone":["#000000","#00a5ff"]},"spacing":{"margin":{"top":"0px","bottom":"0px"}}}} -->

Alternatively, the filler image could also be changed by clicking Replace and selecting Use featured image option:

Screenshot of the WordPress UI with ‘Replace’ and ‘Use featured image’ selected.

Now, the header cover patterns should be visible in the patterns inserter panel for use anywhere in the templates, posts, and pages.

Archive cover headers

Inspired by this WP Tavern post and a step-by-step walkthrough to create an author template header, I wanted to create a similar cover header and add to TT2 Gopher theme, too.

First, let’s create the archive cover header pattern for author.html the template as well, following the above workflow. In this case, I am creating this in a new blank page, by adding blocks (as shown below in list view):

Screenshot of the WordPress UI for an Author page using a single post header cover.

In the background for the cover, I used the same image used in the single post header cover.

Because we would like to display a short author biography on the author block, a biographical statement should also be added to the user profile page, or else a blank space will be displayed in the front-end.

The following is the markup code of the header-author-cover, that we will use pattern, in the next step:

    <!-- wp:cover {"url":"https://pd.w.org/2022/03/8256241eff74ef542.61868565.jpeg","id":226,"dimRatio":10,"focalPoint":{"x":"0.50","y":"0.75"},"minHeight":200,"minHeightUnit":"px","isDark":false,"align":"full","style":{"color":{"duotone":["#000000","#00a5ff"]}}} -->     <div class="wp-block-cover alignfull is-light" style="min-height:200px"><span aria-hidden="true" class="wp-block-cover__background has-background-dim-10 has-background-dim"></span><img class="wp-block-cover__image-background wp-image-226" alt="" src="https://pd.w.org/2022/03/8256241eff74ef542.61868565.jpeg" style="object-position:50% 75%" data-object-fit="cover" data-object-position="50% 75%"/><div class="wp-block-cover__inner-container"><!-- wp:group {"layout":{"inherit":true}} -->     <div class="wp-block-group"><!-- wp:group {"style":{"spacing":{"padding":{"top":"1rem","right":"2rem","bottom":"1rem","left":"2rem"}}},"layout":{"type":"flex","flexWrap":"nowrap"}} -->     <div class="wp-block-group" style="padding-top:1rem;padding-right:2rem;padding-bottom:1rem;padding-left:2rem"><!-- wp:avatar {"size":70,"isLink":true,"align":"right","style":{"border":{"radius":"9999px"}}} /-->          <!-- wp:group -->     <div class="wp-block-group"><!-- wp:group {"style":{"spacing":{"blockGap":"6px"}},"layout":{"type":"flex"},"fontSize":"large"} -->     <div class="wp-block-group has-large-font-size"><!-- wp:paragraph {"textColor":"foreground","fontSize":"large"} -->     <p class="has-foreground-color has-text-color has-large-font-size">Published by:</p>     <!-- /wp:paragraph -->          <!-- wp:post-author-name {"isLink":true,"style":{"typography":{"fontStyle":"large","fontWeight":"600"},"elements":{"link":{"color":{"text":"var:preset|color|background"}}}},"textColor":"foreground"} /--></div>     <!-- /wp:group -->          <!-- wp:post-author-biography {"textColor":"foreground","fontSize":"small"} /-->          <!-- wp:separator {"backgroundColor":"foreground"} -->     <hr class="wp-block-separator has-text-color has-foreground-color has-alpha-channel-opacity has-foreground-background-color has-background"/>     <!-- /wp:separator --></div>     <!-- /wp:group --></div>     <!-- /wp:group --></div>     <!-- /wp:group --></div></div>     <!-- /wp:cover -->

To covert the markup into a header-author-cover pattern, we should add the required pattern file header markup as described earlier. By editing the header-author-cover.php pattern, we can create similar header covers for tags, taxonomy, and other custom templates.

The header-category-cover.php pattern for my category.html template is available on GitHub.

Creating Templates with header cover blocks

WordPress 6.0 and the recent Gutenberg 13.7 extended template creating features into the block editor, thus making it possible for many WordPress users, without deep knowledge of coding, to create their customized templates.

For more detailed information and use cases, here is a thorough customization note by Justin Tadlock.

Block editor allows creating various types of templates, including cover templates. Let’s briefly overview how combining cover block and post featured image block with new template UI makes easy to create various types of cover custom templates even with no or low coding skills.

Screenshot of the WordPress UI displaying available templates provided by TT2 Gopher Blocks – Single, Page, Index, Home, 404, Blank, and Archive.

Creating templates has been made much easier with Gutenberg 13.7. How to create block templates with codes and in site editor is described in the Theme handbook and in my previous article.

Author template with cover block

Top (header section) markup of the author.html template is shown below (line 6):

    <!-- wp:template-part {"slug":"header-small-dark","theme":"TT2-GOPHER-V2","tagName":"header"} /-->          <!-- wp:group {"tagName":"main","style":{"spacing":{"margin":{"top":"0","bottom":"0px"},"padding":{"bottom":"80px"},"blockGap":"0px"}},"className":"site-content"} -->     <main class="wp-block-group site-content" style="margin-top:0;margin-bottom:0px;padding-bottom:80px">              <!-- wp:pattern {"slug":"tt2gopher/header-author-cover"} /-->          ...     ...     ...     <!-- /wp:group -->     ...

Here are screenshots of cover headers for the author.html and category.html templates:

Screenshot of Author Page header (left) with author name, avatar, and biography. And screenshot of Category Page header (right).

The entire code for both templates is available on GitHub.

Single post with cover block

To display cover block in our single post, we have to call the header-cover-single pattern below the header section (line 3):

    <!-- wp:template-part {"slug":"header-small-dark","tagName":"header"} /-->           <!-- wp:pattern {"slug":"tt2gopher/header-cover-single"} /-->          <!-- wp:spacer {"height":32} -->     <div style="height:32px" aria-hidden="true" class="wp-block-spacer"></div>     <!-- /wp:spacer -->     ....     ....     ....

Here is a screen capture showing the front-end view of the single post with the header cover section:

Screenshot of TT2 Gopher Blocks Single Post with Header Cover Section Pattern.

The entire single-cover.html template is available on GitHub.

You can find additional step-by-step walkthrough tutorials on creating a hero header post section and using post featured image background cover blocks on WP Tavern and Full Site Editing website.

There you have it!

Helpful Resources

Blog posts


Even though the block themes, in general, are getting lots of pushback from WordPress community members, in my opinion, they are the future of WordPress, too. With block themes, amateur theme authors, without the deep coding skills and mastery of PHP and JavaScript languages, can now create themes with complex layouts with a hero cover section as described in this article combined with patterns and style variations.

As an early Gutenberg user, I couldn’t be more excited with the new theming tools like create block theme plugin and others which allow theme authors to achieve the following directly from block editor UI without writing any code:

  • (i) create
  • (ii) overwrite theme files and export
  • (iii) generate blank or a child theme, and
  • (iv) modify and save style variation of the current theme

Additionally, the recent iterations of the Gutenberg plugin allow enabling fluid typography and layout alignments and other stylistic controls using only theme.json file without JavaScript and a line of CSS rules.

Thank you for reading and share your comments and thoughts below!


How To Customize WordPress Block Theme Cover Templates with Dynamic Post Feature Images originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

CSS-Tricks

, , , , , , , , ,
[Top]

The Web is Good Now

The video of Chris Coyier’s talk at CascadiaJS 2022 is now available. It’s his first in-person talk in more than two years, so it’s great to see our good friend back on stage slinging gems on what makes the web good these days.

Container Queries! WAAPI! Scroll Timelines! offset-path! FLIP! Variable fonts! Fluid type! We really are all-powerful front-end developers these days.

Chris really packs a bunch into a 25-minute slot. It feels good to pause for that brief amount of time to reflect on the great new things for building websites and celebrate the fact that we get to use them.

And there’s nothing better than watching Chris greet the enture room as a bunch of “web nerds”. 🤓

To Shared LinkPermalink on CSS-Tricks


The Web is Good Now originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

CSS-Tricks

[Top]

How I Made an Icon System Out of CSS Custom Properties

SVG is the best format for icons on a website, there is no doubt about that. It allows you to have sharp icons no matter the screen pixel density, you can change the styles of the SVG on hover and you can even animate the icons with CSS or JavaScript.

There are many ways to include an SVG on a page and each technique has its own advantages and disadvantages. For the last couple of years, I have been using a Sass function to import directly my icons in my CSS and avoid having to mess up my HTML markup.

I have a Sass list with all the source codes of my icons. Each icon is then encoded into a data URI with a Sass function and stored in a custom property on the root of the page.

TL;DR

What I have for you here is a Sass function that creates a SVG icon library directly in your CSS.

The SVG source code is compiled with the Sass function that encodes them in data URI and then stores the icons in CSS custom properties. You can then use any icon anywhere in your CSS like as if it was an external image.

This is an example pulled straight from the code of my personal site:

.c-filters__summary h2:after {   content: var(--svg-down-arrow);   position: relative;   top: 2px;   margin-left: auto;   animation: closeSummary .25s ease-out; }

Demo

Sass structure

/* All the icons source codes */ $  svg-icons: (   burger: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0...' );  /* Sass function to encode the icons */ @function svg($  name) {   @return url('data:image/svg+xml, #{$  encodedSVG} '); }  /* Store each icon into a custom property */ :root {   @each $  name, $  code in $  svg-icons {     --svg-#{$  name}: #{svg($  name)};   } }  /* Append a burger icon in my button */ .menu::after {   content: var(--svg-burger); }		

This technique has both pros and cons, so please take them into account before implementing this solution on your project:

Pros

  • There are no HTTP requests for the SVG files.
  • All of the icons are stored in one place.
  • If you need to update an icon, you don’t have to go over each HTML templates file.
  • The icons are cached along with your CSS.
  • You can manually edit the source code of the icons.
  • It does not pollute your HTML by adding extra markup.
  • You can still change the color or some aspect of the icon with CSS.

Cons

  • You cannot animate or update a specific part of the SVG with CSS.
  • The more icons you have, the heavier your CSS compiled file will be.

I mostly use this technique for icons rather than logos or illustrations. An encoded SVG is always going to be heavier than its original file, so I still load my complex SVG with an external file either with an <img> tag or in my CSS with url(path/to/file.svg).

Encoding SVG into data URI

Encoding your SVG as data URIs is not new. In fact Chris Coyier wrote a post about it over 10 years ago to explain how to use this technique and why you should (or should not) use it.

There are two ways to use an SVG in your CSS with data URI:

  • As an external image (using background-image,border-image,list-style-image,…)
  • As the content of a pseudo element (e.g. ::before or ::after)

Here is a basic example showing how you how to use those two methods:

The main issue with this particular implementation is that you have to convert the SVG manually every time you need a new icon and it is not really pleasant to have this long string of unreadable code in your CSS.

This is where Sass comes to the rescue!

Using a Sass function

By using Sass, we can make our life simpler by copying the source code of our SVG directly in our codebase, letting Sass encode them properly to avoid any browser error.

This solution is mostly inspired by an existing function developed by Threespot Media and available in their repository.

Here are the four steps of this technique:

  • Create a variable with all your SVG icons listed.
  • List all the characters that needs to be skipped for a data URI.
  • Implement a function to encode the SVGs to a data URI format.
  • Use your function in your code.

1. Icons list

/** * Add all the icons of your project in this Sass list */ $  svg-icons: (   burger: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24.8 18.92" width="24.8" height="18.92"><path d="M23.8,9.46H1m22.8,8.46H1M23.8,1H1" fill="none" stroke="#000" stroke-linecap="round" stroke-width="2"/></svg>' );

2. List of escaped characters

/** * Characters to escape from SVGs * This list allows you to have inline CSS in your SVG code as well */ $  fs-escape-chars: (   ' ': '%20',   '\'': '%22',   '"': '%27',   '#': '%23',   '/': '%2F',   ':': '%3A',   '(': '%28',   ')': '%29',   '%': '%25',   '<': '%3C',   '>': '%3E',   '\': '%5C',   '^': '%5E',   '{': '%7B',   '|': '%7C',   '}': '%7D', );

3. Encode function

/** * You can call this function by using `svg(nameOfTheSVG)` */ @function svg($  name) {   // Check if icon exists   @if not map-has-key($  svg-icons, $  name) {     @error 'icon “#{$  name}” does not exists in $  svg-icons map';     @return false;   }    // Get icon data   $  icon-map: map-get($  svg-icons, $  name);    $  escaped-string: '';   $  unquote-icon: unquote($  icon-map);   // Loop through each character in string   @for $  i from 1 through str-length($  unquote-icon) {     $  char: str-slice($  unquote-icon, $  i, $  i);      // Check if character is in symbol map     $  char-lookup: map-get($  fs-escape-chars, $  char);      // If it is, use escaped version     @if $  char-lookup != null {         $  char: $  char-lookup;     }      // Append character to escaped string     $  escaped-string: $  escaped-string + $  char;   }    // Return inline SVG data   @return url('data:image/svg+xml, #{$  escaped-string} '); }		

4. Add an SVG in your page

button {   &::after {     /* Import inline SVG */     content: svg(burger);   } }

If you have followed those steps, Sass should compile your code properly and output the following:

button::after {   content: url("data:image/svg+xml, %3Csvg%20xmlns=%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox=%270%200%2024.8%2018.92%27%20width=%2724.8%27%20height=%2718.92%27%3E%3Cpath%20d=%27M23.8,9.46H1m22.8,8.46H1M23.8,1H1%27%20fill=%27none%27%20stroke=%27%23000%27%20stroke-linecap=%27round%27%20stroke-width=%272%27%2F%3E%3C%2Fsvg%3E "); }		

Custom properties

The now-implemented Sass svg() function works great. But its biggest flaw is that an icon that is needed in multiple places in your code will be duplicated and could increase your compiled CSS file weight by a lot!

To avoid this, we can store all our icons into CSS variables and use a reference to the variable instead of outputting the encoded URI every time.

We will keep the same code we had before, but this time we will first output all the icons from the Sass list into the root of our webpage:

/**   * Convert all icons into custom properties   * They will be available to any HTML tag since they are attached to the :root   */  :root {   @each $  name, $  code in $  svg-icons {     --svg-#{$  name}: #{svg($  name)};   } }

Now, instead of calling the svg() function every time we need an icon, we have to use the variable that was created with the --svg prefix.

button::after {   /* Import inline SVG */   content: var(--svg-burger); }

Optimizing your SVGs

This technique does not provide any optimization on the source code of the SVG you are using. Make sure that you don’t leave unnecessary code; otherwise they will be encoded as well and will increase your CSS file size.

You can check this great list of tools and information on how to optimize properly your SVG. My favorite tool is Jake Archibald’s SVGOMG — simply drag your file in there and copy the outputted code.

Bonus: Updating the icon on hover

With this technique, we cannot select with CSS specific parts of the SVG. For example, there is no way to change the fill color of the icon when the user hovers the button. But there are a few tricks we can use with CSS to still be able to modify the look of our icon.

For example, if you have a black icon and you want to have it white on hover, you can use the invert() CSS filter. We can also play with the hue-rotate() filter.

That’s it!

I hope you find this little helper function handy in your own projects. Let me know what you think of the approach — I’d be interested to know how you’d make this better or tackle it differently!


How I Made an Icon System Out of CSS Custom Properties originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

CSS-Tricks

, , , ,
[Top]

CSS Rules vs. CSS Rulesets

The latest spec:

style rule is a qualified rule that associates a selector list with a list of property declarations and possibly a list of nested rules. They are also called rule sets in CSS2.

Louis Lazaris:

As the above quote from W3C indicates, it seems like the W3C considers “rule set” to be a bit of an outdated term, preferring the term “style rule” (or possibly “rule” for short).

I never noticed that! “Rule set” is so gosh darned branded on my brain that it’s gonan take losing a lot of muscle memory to start using “style rule” instead. I didn’t see a specific note in the spec’s Changes section, but you can see the change in the table of contents between versions:

Side-by-side screenshot comparing the table of contents for both the CSS 2 and CSS 3 specifications.

Louis nicely sums up the parts of a style rule as well:

/* Everything below is a style rule (or rule set, or just rule) */ section { /* Everything between the braces is a declaration block */   margin: 0 20px; /* This line is an individual declaration */   color: #888; /* Another declaration */ }

I know nothing of the context and, at first, I was gonna poo-poo the change, but “style rule” really makes sense the more I sit with it. If the property:value pairs are declarations that sit in a declaration block, then we’ve got something less like a set of rules and more like one rule that defines the styles for a selector with a block of style declarations. 👌

Once again, naming things is hard.

To Shared LinkPermalink on CSS-Tricks


CSS Rules vs. CSS Rulesets originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

CSS-Tricks

,
[Top]

CSS Checkerboard Background… But With Rounded Corners and Hover Styles

On one hand, creating simple checkered backgrounds with CSS is easy. On the other hand, though, unless we are one of the CSS-gradient-ninjas, we are kind of stuck with basic patterns.

At least that’s what I thought while staring at the checkered background on my screen and trying to round those corners of the squares just a little…until I remembered my favorite bullet point glyph — — and figured that if only I could place it over every intersection in the pattern, I’ll surely get the design I want.

Turns out it’s possible! Here’s the proof.

Let’s start with the basic pattern:

<div></div>
div {  background:    repeating-linear-gradient(     to right, transparent,      transparent 50px,      white 50px,      white 55px   ),   repeating-linear-gradient(     to bottom, transparent,       transparent 50px,      white 50px,      white 55px   ),   linear-gradient(45deg, pink, skyblue);   /* more styles */ }

What that gives us is a repeating background of squares that go from pink to blue with 5px white gaps between them. Each square is fifty pixels wide and transparent. This is created using repeating-linear-gradient, which creates a linear gradient image where the gradient repeats throughout the containing area.

In other words, the first gradient in that sequence creates white horizontal stripes and the second gradient creates white vertical stripes. Layered together, they form the checkered pattern, and the third gradient fills in the rest of the space.

Now we add the star glyph I mentioned earlier, on top of the background pattern. We can do that by including it on the same background property as the gradients while using an encoded SVG for the shape:

div {   background:      repeat left -17px top -22px/55px 55px     url("data:image/svg+xml,     <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 35px 35px'>       <foreignObject width='35px' height='35px'>         <div xmlns='http://www.w3.org/1999/xhtml' style='color: white; font-size: 35px'>✦</div>       </foreignObject>     </svg>"     ),      repeating-linear-gradient(       to right, transparent,       transparent 50px,       white 50px,       white 55px     ),     repeating-linear-gradient(       to bottom, transparent,       transparent 50px,       white 50px,       white 55px     ),     linear-gradient(45deg, pink, skyblue);   /* more style */ }

Let’s break that down. The first keyword, repeat, denotes that this is a repeating background image. Followed by that is the position and size of each repeating unit, respectively (left -17px top -22px/55px 55px). This offset position is based on the glyph and pattern’s size. You’ll see below how the glyph size is given. The offset is added to re-position the repeating glyph exactly over each intersection in the checkered pattern.

The SVG has an HTML <div> carrying the glyph. Notice that I declared a font-size on it. That ultimately determines the border radius of the squares in the checkerboard pattern — the bigger the glyph, the more rounded the squares. The unrolled SVG from the data URL looks like this:

<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 35px 35px'>   <foreignObject width='35px' height='35px'>     <div xmlns='http://www.w3.org/1999/xhtml' style='color:white;font-size:35px'>✦</div>   </foreignObject> </svg>

Now that a CSS pattern is established, let’s add a :hover effect where the glyph is removed and the white lines are made slightly translucent by using rgb() color values with alpha transparency.

div:hover {   background:     repeating-linear-gradient(       to right, transparent,       transparent 50px,       rgb(255 255 255 / 0.5) 50px,       rgb(255 255 255 / 0.5) 55px     ),     repeating-linear-gradient(       to bottom, transparent,       transparent 50px,       rgb(255 255 255 / 0.5) 50px,       rgb(255 255 255 / 0.5) 55px     ),   linear-gradient(45deg, pink, skyblue);   box-shadow: 10px 10px 20px pink; }

There we go! Now, not only do we have our rounded corners, but we also have more control control over the pattern for effects like this:

Again, this whole exercise was an attempt to get a grid of squares in a checkerboard pattern that supports rounded corners, a background gradient that serves as an overlay across the pattern, and interactive styles. I think this accomplishes the task quite well, but I’m also interested in how you might’ve approached it. Let me know in the comments!


CSS Checkerboard Background… But With Rounded Corners and Hover Styles originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

CSS-Tricks

, , , , ,
[Top]

Making a Real-Time Clock With a Conic Gradient Face

Gradients have been a part of the CSS spectrum for quite some time now. We see a lot of radial and linear gradients in a lot of projects, but there is one type of gradient that seems to be a bit lonely: the conic gradient. We’re going to make a watch face using this type of gradient.

Working with conic gradients

What we’re making consists of a gradient with color transitions rotated around a center point and can have multiple color values. For this clock to work, we will also be using the angle value of a conic gradient which defines the rotation or starting point. The angle is defined by using a from value.

background-image: conic-gradient(from 45deg, #6e7dab, #5762d5);

What is interesting about this, is that a starting angle can have a negative value in CSS, which will come in handy later.

A simple elegant example of a conical gradient:

Building our basic clock

Let’s start by adding some HTML for the clock and the hands:

Let’s create some default styling for our clock. For this to work properly, we will update CSS variables with JavaScript later on, so let’s scope these variables inside our .clock selector. For easy tweaking, let’s add the colors of the hands as well.

.clock {   /* general clock vars */   --hour-hand-color: #000;   --hour-hand-degrees: 0deg;   --minute-hand-color: #000;   --minute-hand-degrees: 0deg;   --second-hand-color: hotpink;   --second-hand-degrees: 0deg;    position: relative;   min-width: 320px;   width: 25vw;   height: 25vw;   min-height: 320px;   border-radius: 50%;   margin: 0 auto;   border: 7px solid #000; }  /* clock hands */ .hand {   position: absolute;   left: 50%;   bottom: 50%;   height: 45%;   width: 4px;   margin-left: -2px;   background: var(--second-hand-color);   border-radius: 6px;   transform-origin: bottom center;   transition-timing-function: cubic-bezier(0.1, 2.7, 0.58, 1); } .second-hand {   transform: rotate(var(--second-hand-degrees)); } .hour-hand {   height: 35%;   border-radius: 40px;   background-color: var(--hour-hand-color);   transform: rotate(var(--hour-hand-degrees)); } .minute-hand {   height: 50%;   background: var(--minute-hand-color);   transform: rotate(var(--minute-hand-degrees)); }

This sets us up with the general styling we need for the clock. We’ve set transform-origin on the hands so that they properly rotate around the face of the clock. There are also a few custom properties in there to set angles on the hands that we’ll update with JavaScript to get the timing just right so that each hand maps to seconds, minutes, and hours accordingly.

Here’s what we have so far:

Alright, let’s move on to updating those custom properties!

Adding the JavaScript for our basic clock

First off, we’re going to target our clock and create a function:

const clock = document.getElementById("clock"); function setDate() {   // Code to set the current time and hand angles. } setDate();

Inside of our function we’re going to fetch the current time using the Date() function to calculate the correct angle of the hands:

const now = new Date(); const secondsAngle = now.getSeconds() * 6;  const minsAngle = now.getMinutes() * 6 + secondsAngle / 60; const hourAngle = ((now.getHours() % 12) / 12) * 360 + minsAngle / 12;

Here is how this calculation works:

  • Seconds: We take 60 seconds and multiply it by 6, which happens to be 360, the perfect number of angles in a full circle.
  • Minutes: Same as seconds, but now we add the seconds angle and divide it by 60 to increase the angle just a little bit within the minute for a more accurate result.
  • Hours: First, we calculate the remainder of the hour and divide it by 12. Then we divide that remainder by 12 again to get a decimal value we can multiply by 360. For example, when we’re at the 23rd hour, 23 / 12 = remain 11. Divide this by 12 and we get 0.916 which then gets multiplied by 360 for a grand total of 330. Here, we will do the same thing we did with the minutes and add the minutes angle, divided by 12, for a more accurate result.

Now that we have our angles, the only thing left to do is to update the variables of our clock by adding the following at the end of our function:

clock.style.setProperty("--second-hand-degrees", secondsAngle + "deg"); clock.style.setProperty("--minute-hand-degrees", minsAngle + "deg"); clock.style.setProperty("--hour-hand-degrees", hourAngle + "deg");

Last, but not least, we will trigger the function with an interval of a second to get a working clock:

const clock = document.getElementById("clock"); function setDate() {   // etc. } // Tick tick tick setInterval(setDate, 1000); setDate();

See the working demo of our basic clock:

Applying this to a conical gradient

OK, so the hands of our clock are working. What we really want is to map them to a conical gradient that updates as the time changes. You may have seen the same effect if you have an Apple Watch with the “Gradient” face active:

Black Apple Watch on a person's wrist showing a deep purple conic gradient face.
Credit: Macworld

To do this, let’s start by updating our .clock element with a conic gradient and two custom properties that control the starting and ending angles :

.clock {   /* same as before */    /* conic gradient vars */   --start: 0deg;   --end: 0deg;    /* same as before */    background:      conic-gradient(       from var(--start),       rgb(255 255 255) 2deg,       rgb(0 0 0 / 0.5) var(--end),       rgb(255 255 255) 2deg,       rgb(0 0 0 / 0.7)   ); }

You can play around with this a bit to style it just the way you like it. I added some extra colors in the gradient to my liking, but as long as you have a starting point and an ending point, you’re good to go.

Next up, we will update our setDate() function so that it updates the variables for our starting and ending points on the conic gradient. The starting point will be our seconds hand, which is easy to find because it will be the same as the angle of our minutes. To make this end at the hours hand, we should make our ending point the same as the hourAngle variable in the script, but subtract our starting point from it.

let startPosition = minsAngle; let endPosition = hourAngle - minsAngle;

Now we can update our variables with JavaScript again:

clock.style.setProperty("--start", startPosition + "deg"); clock.style.setProperty("--end", endPosition + "deg");

It looks like we could be done at this point, but there is a catch! This calculation works fine as long as the minutes hand has a smaller angle than the hours hand. Our conic gradient will get messy the moment when the minutes hand has moved past it. To fix this, we will use a negative value as a starting point. Luckily, it’s easy to spot when this happens. Before updating our variables we’ll add the following:

if (minsAngle > hourAngle) {   startPosition = minsAngle - 360;   endPosition = hourAngle - startPosition; }

By subtracting 360 from our minutes angle, we are able to set a negative value for our startposition variable. Because of this negative starting point, our end position should be updated by the hour angle, subtracted by the starting position.

There we go — now the hour and minute hands are set to gradient angles:

That’s it! But don’t let that stop you from taking this even further. Create your own styles and share them with me in the comments so I can check them out.. Here is a little inspiration to get you going:


Making a Real-Time Clock With a Conic Gradient Face originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

CSS-Tricks

, , , , ,
[Top]