There are a lot of tools that aim to help you remove „unused CSS” from your project. Never a week goes by that I don’t see a tool for this being shared or promoted. It must strike some kind of perfect chord for some developers. I care about performance, and I know that reducing file sizes is good for performance. Indeed, it is. I bet we have CSS that is unused in our stylesheets, if we removed that, that’s a performance win. Yep, it would be. We should automate that. Ehhhhhh, I’m not so sure.
There are major performance tooling players that play up this idea, like Lighthouse and how it gives you CSS and JS „Coverage”, which will surely tell you that you’re shipping code you don’t need to be.
The tools that claim to help you with unused CSS have to perform analysis to be able to tell you what is unused and what isn’t.
Here’s one way to do that analysis. Render a page of your site and get the complete DOM. Then get the complete CSSOM as well, which can give you an array of all the selectors in your CSS. Loop over those selectors and do a querySelector in the DOM and see if it matches anything. If it doesn’t, that CSS selector is unused.
Clever, right?!
I think so. But that analysis paints a rather limited picture.
Say that analysis runs two seconds after the page is complete, but there is some JavaScript that runs and injects a modal after five seconds (ughghk, I know). The analysis would have missed the HTML in that modal, which likely has styles, and thus would have incorrectly reported those styles as unused.
So, timing is one factor. Hopefully, this analysis tool has some way to configure multiple timings.
We’re also only looking at one page so far. Of course, a site might have tens, hundreds, or thousands of pages. To be entirely sure about unused styles, looking at all of them is the most sure-fire bet.
Multiple pages is another factor. Hopefully, an analysis tool has a way to look at as many pages as you tell it to. Perhaps it can look at a sitemap?
Remember the timing thing? We might think of timing as one generic form of state. There are countless other things that could be state related. Is the user logged in or not? What plan are they on? Is their credit card expired thus showing some kind of special message? Do situational things like time/date/geolocation change state? What about real-time data? Stuff from an API?
Application-level state is clearly a big factor. Hopefully, this analysis tool can trigger/set all possible combinations of state.
There is interactive state as well. What about modals that come up because something is clicked? What is the active tab? Is this menu open or closed? What scroll position are they at? There are infinite permutations of this. Imagine a warning bar that shows up seven seconds after the user logs in to warn user about their expired credit card which contains a custom styled select menu which can be in an open or closed state, but only on the user settings page.
It seems unlikely that this analysis tool can handle all those possibilities. Even with loads of configuration, mock state, and integration testing, it couldn’t cover the near-infinite possible permutations of all this.
And yet, I don’t think these tools are useless — they are just…tools. Their use can actually be a positive step toward better code. Their use says OK, I admit it, I’m a little afraid our CSS. You could use this tool to get a broad picture of what your unused CSS might be, then combine that with your own knowledge of your CSS code base to make more informed decisions. Or take another technological step and do something like add a background image to those unused selectors and check server logs to see if they get hit.
It should be said that this whole idea of unused CSS is a part of the CSS-in-JS saga that our industry is going through. If all your styles are written as part of components, there kinda is no unused CSS. Either the component gets used and the styles come with it, or it doesn’t. If you’re particularly sensitive about the danger of unused CSS, that alone might sway you toward a CSS-in-JS tool.
It also should be said that this DOM and CSSOM analysis technique is only one possible way of checking for unused styles. If you had some kind of fancy tooling that could analyze all of your templates, styles, and scripts, presumably that could determine unused styles as well. We talk about that in the recent ShopTalk Show episode with Chris Eppstein.
As I explore, learn, and most importantly, play with Vue.js, I’ve been building different types of apps as a way to get practice with and improve my use of it. A few weeks ago, I was reading about the shut down of Digg’s RSS Reader and while great alternatives exist, I thought it would be fun to build my own with Vue. In this article, I’m going to explain how I put it together and also what’s wrong with it. I knew getting into this that I was going to make some compromises, so the plan is to follow up this version with a nicer one in a follow-up post.
Article Series:
Setup and first iteration (This Post)
Refinements and final version (Coming Soon!)
Let’s start by looking at the app and explaining the various components.
When opening the application, you’re presented with some basic instructions and a prompt to add a new RSS feed.
Clicking the button opens a modal letting you enter a feed:
Once you add the button, the blog entries for that feed will be displayed:
Notice the color. I set it up so that each feed would have a unique color, making it easier to tell one site’s content from another. For example, here is how it looks with more feeds added.
The panel on the left lets you filter by clicking on a feed. Unfortunately you can’t delete a feed yet so, if you need to remove something, you’ll need to open up your DevTools and edit the cached value.
Let’s go over the tech stack!
The Components
First and foremost is the Vue library itself. I’m *not* using webpack for this application — just a simple script include with no build process.
The UI is all Vuetify, a very nice material design framework that is easy to use. I’m still learning it, so you can be sure that my design could be better, though I’m really happy with how it looks now.
Persistence is done via localStorage. I store the feed metadata retrieved from the RSS feed. This typically includes things like the name of the site, the main URL, and a description. I do not store feed items which means every time you load the site, I re-fetch items. The next version will keep items locally using IndexedDB.
So, how do I load feed information? I could just make a network request to the URL, but most RSS feeds aren’t making use of CORS which means the browser would be blocked from loading it. To get around this, I wrote a quick serverless function with Webtask. It handles both creating a CORS-friendly endpoint as well as parsing the feeds’ XML into friendly JSON.
Now that I’ve covered the various parts of the application, let’s start looking at the code!
The Layout
Let’s start with the layout. As I said, I’m using Vuetify for the UI. I started off using the dark sample layout. This is what creates the header, footer, and left column used for the menu.
Application template
I used the card component for individual feed items. I’m not quite happy with the layout here. For example, I don’t have publication dates rendered yet because I had trouble finding a nice way to render it. I decided to simply punt and wait till the next version, which we’ll **see in Part 2 of this series.
Instead of dumping the entire source code on you at once, let’s look at the individual parts. First, here’s the introductory/help text before any feeds have been added:
<div v-if="showIntro">
<p>
Welcome to the RSS Reader, a simple way to manage RSS feeds and read content. To begin using the RSS Reader, add your first feed by clicking the button below.
</p>
<p>
<v-btn color="primary" large @click="addFeed">
<v-icon>add</v-icon>
Add Feed
</v-btn>
</p>
</div>
When you do have feeds, items are displayed as a list of cards:
<v-dialog v-model="addFeedDialog" max-width="500px">
<v-card>
<v-card-title>Add Feed</v-card-title>
<v-card-text>
Add the RSS URL for a feed below, or the URL for the site and I'll try to
auto-discover the RSS feed.
<v-text-field v-model="addURL" label="URL" :error="urlError"
:rules="urlRules"></v-text-field>
</v-card-text>
<v-card-actions>
<v-btn color="primary" @click.stop="addFeedAction">Add</v-btn>
<v-btn color="primary" flat @click.stop="addFeedDialog=false">Close</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
The Logic
Now for the fun part — JavaScript! As before, I’m not going to dump the entire file on you. Instead, let’s tackle it bit by bit.
On start up, I load any existing feeds that may have defined and then display the introduction text, if needed:
Note that this method handles assigning a color (which is a simple array) and then loading feed data.
Speaking of that, how do I handle loading RSS information? Currently there are two times where this happens. First is when you initially add the feed and second when you reload the application and the feed was already defined. In both cases, I call one URL — the serverless task defined with Webtask. This task will return everything — the metadata about the feed and the items itself. I only care about the metadata on the *first* call and, in theory, I could have made the code a bit quicker by removing the metadata at the server side and strip that out but it didn’t seem like it was worth the effort.
All I’m doing here is wrapping the npm package rss-parser and that handles all the converting for me. The if statements you see in the beginning handle looking for the url parameter. When calling my webtask, I can either pass a query string variable or send it as part of a HTTP body. Either way, I simply use the rss-parser module and return the result.
You’re welcome to try it out yourself. You can see this being used in the method handling adding a feed:
addFeedAction() {
this.urlError = false;
this.urlRules = [];
//first, see if new
if(this.feeds.findIndex((feed) => {
return (feed.rsslink === this.addURL);
}) >= 0) {
this.urlError = true;
this.urlRules = ["URL already exists."];
return;
} else {
fetch(rssAPI+encodeURIComponent(this.addURL))
.then(res => res.json())
.then(res => {
// ok for now, assume no error, cuz awesome
this.addURL = '';
//assign a color first
res.feed.color = colors[this.feeds.length % (colors.length-1)];
// ok, add the items (but we append the url as a fk so we can filter later)
res.feed.items.forEach(item => {
item.feedPk = this.addURL;
item.feedColor = res.feed.color;
this.allItems.push(item);
});
// delete items
delete res.feed.items;
// add the original rss link
res.feed.rsslink = this.addURL;
this.feeds.push(res.feed);
this.addFeedDialog = false;
//always hide intro
this.showIntro = false;
//persist the feed, but not the items
this.storeFeeds();
});
}
},
This method first checks if a feed already exists and, if it doesn’t, it hits the serverless endpoint to get the details. I’ve got a bit of data duplication going on when I store items. I didn’t want to store items „under” a feed object and instead use a global Vue data value, allItems. Therefore, I copy the feed identifier and color into each item. The idea was to make it easier to do item display and filtering later. This feels „wrong” to me, but again, this is my first draft. I’m using a computed property for items and you can see that logic here:
Looking at it now, I could gather my items from each feed instead of storing one global array, though I could address this later, if I want. I love that Vue gives me options for how to solve things like this.
Where Next?
When I started this article, I explicitly thought *this* *is* a first draft. I’ve pointed out things here and there that I like and don’t like, but what exactly do I plan for the next version?
I want to move all the data access into Vuex. Vuex is described as a „state management pattern + library” for Vue. If that doesn’t make much sense to you, don’t worry. I had no idea what it meant at first either. To me, Vuex provides a way to handle more complex data in an encapsulated manner. This becomes even more important as you start building more components that need to share data.
Speaking of components, I should consider making „item” a proper Vue component. That’s an easy win.
I want to start storing feed items in IndexedDB so you’ll get content the second you open the application. This will make the application much more performant and provide basic offline support. Of course, you can’t read the full entries if you’re offline, but at least *something* could be provided.
…and anything you suggest! Take a look at the code and feel free to make suggestions and point out mistakes!
I was recently mentoring someone who had trouble with the .reduce() method in JavaScript. Namely, how you get from this:
const nums = [1, 2, 3]
let value = 0
for (let i = 0; i < nums.length; i++) {
value += nums[i]
}
…to this:
const nums = [1, 2, 3]
const value = nums.reduce((ac, next) => ac + next, 0)
They are functionally equivalent and they both sum up all the numbers in the array, but there is a bit of paradigm shift between them. Let’s explore reducers for a moment because they’re powerful, and important to have in your programming toolbox. There are literally hundreds of other articles on reducers out there, and I’ll link up some of my favorites at the end.
What is a reducer?
The first and most important thing to understand about a reducer is that it will always only return one value. The job of a reducer is to reduce. That one value can be a number, a string, an array or an object, but it will always only be one. Reducers are really great for a lot of things, but they’re especially useful for applying a bit of logic to a group of values and ending up with another single result.
That’s the other thing to mention: reducers will not, by their nature, mutate your initial value; rather they return something else. Let’s walk over that first example so you can see what’s happening here. The video below explains:
It might be helpful to watch the video to see how the progression occurs, but here’s the code we’re looking at:
const nums = [1, 2, 3]
let value = 0
for (let i = 0; i < nums.length; i++) {
value += nums[i]
}
We have our array (1, 2, 3) and the first value each number in the array will be added to (0). We walk through the amount of the array and add them to the initial value.
Now we have the same array, but this time we’re not mutating that first value. Instead, we have an initialValue that will only be used at the start. Next, we can make a function that takes an accumulator and an item. The accumulator is the collected value returned in the last invocation that informs the function what the next value will be added to. In this case of addition, you can think of it as a snowball rolling down a mountain that eats up each value in its path as it grows in size by every eaten value.
We’ll use .reduce() to apply the function and start from that initial value. This can be shortened with an arrow function:
An accumulator can be an intimidating term, so you can think of it like the current state of the array as we’re applying the logic on the callback’s invocations.
The Call Stack
In case it’s not clear what’s happening, let’s log out what’s going on for each iteration. The reduce is using a callback function that will run for each item in the array. IThe following demo will help to make this more clear. I’ve also used a different array ([1, 3, 6]) because having the numbers be the same as the index could be confusing.
It shows that the accumulator is starting at our initial value, 0
Then we have the first item, which is 1, so our return value is 1 (0 + 1 = 1)
1 becomes the accumulator on the next invocation
Now we have 1 as the accumulator and 3 is the item aince it is next in the array.
The returned value becomes 4 (1 + 3 = 4)
That, in turn, becomes the accumulator and the next item at invocation is 6
That results in 10 (4 + 6 = 10) and is our final value since 6 is the last number in the array
Simple Examples
Now that we’ve got that under our belt, let’s look at some common and useful things reducers can do.
How many of X do we have?
Let’s say you have an array of numbers and you want to return an object that reports the number of times those numbers occur in the array. Note that this could just as easily apply to strings.
Initially, we have an array and the object we’re going to put its contents into. In our reducer, we ask: does this item exist? If so, let’s increment it. If not, add it and set it to 1. At the end, please return the tally count of each item. Then, we run the reduce function, passing in both the reducer and the initial value.
Take an array and turn it into an object that shows some conditions
Let’s say we have an array and we want to create an object based on a set of conditions. Reduce can be great for this! Here, we want to create an object out of any instance of a number contained in the array and show both an odd and even version of this number. If the number is already even or odd, then that’s what we’ll have in the object.
const nums = [3, 5, 6, 82, 1, 4, 3, 5, 82]
// we're going to make an object from an even and odd
// version of each instance of a number
const result = nums.reduce((acc, item) => {
acc[item] = {
odd: item % 2 ? item : item - 1,
even: item % 2 ? item + 1 : item
}
return acc
}, {})
console.log(result)
As we’re going through every item in the array, we create a property for even and odd, and based on an inline condition with a modulus operator, we’ll either store the number or increment it by 1. The modulus operator is really good for this because it can quickly check for even or odd — if it’s divisible by two, it’s even, if not, it’s odd.
Other resources
At the top, I mentioned other posts out there that are handy resources to get more familiar with the role of reducers. Here are a few of my favorites:
The MDN documentation is wonderful for this. Seriously, it’s one of their best posts, IMO. They also describe in a bit more detail what happens if you don’t provide an initial value, which we didn’t cover in this post.
Daniel Shiffman is always amazing at explaining things on Coding Train.
Part of the job of being a front-end developer is applying different techniques and technologies to pull of the desired UI and UX. Perhaps you work with a design team and implement their designs. I know when I look at a design (heck, even if I know I’m not going to be building it), my front-end brain starts triggering all sorts of things I know will be related to the task.
It’s a very appealing design, and there is loads in there to think about from a front-end web design and development standpoint.
We’re going to mostly be talking about design pattern choices and HTML/CSS tech choices. There is much more to the job of front-end development. Accessibility! Performance! Semantics! Design systems! All important stuff as well.
Multi-line padded text
Ah yes, that look where text has a background that follows the length of the lines of text. We’ve called that Multi-Line Padded Text in the past and looked at a number of ways to do it. The easiest and most modern way to handle it is with box-decoration-break.
That header area is just begging for flexbox. It’s a single-direction layout with elements of different sizes and different space between them. Expressing that in flexbox is going to be easier than any other method and not require any fixed sizing or magic numbers — not to mention flexible!
Grid layout
The overall page layout here could be expressed nicely with CSS grid. Remember that flexbox and grid are not at odds. An element placed in a grid cell can be flexbox! Like the header above, that makes perfect sense. The main content area and footer, as grid cells, could probably go either way.
Looks like we have some truncation going on here. General performance-wise, we’d probably be wanting the data being sent only be a few lines long. But the front end can help with this too, if it has to. Three lines of text are shown here with ellipsis at the end. Perhaps the design really needs the copy to always be a maximum of three lines. That’s called line clamping.
Like most sites these days, this design is coated in custom web fonts. With a design this striking, I’d be very careful about my font loading technique. My gut tells me I’d be more into FOIT than FOUT here, and ideally I’d cache that font file as hard as I could so that we’d have neither as often as possible.
Text over images
That text „Dinner Menu” is squarely over some busy photographic imagery below. It’s still readable though, largely because of the bright white of the text over a darkened image. We’ve covered thinking this through in the past in detail. White text over a darkened image is generally the way to go, and darkened enough such that just about any image will be OK. There are other options though, like gradients and blurring (which is also in use here in the footer)
Those star ratings are probably SVG territory as well. Here’s a good collection of options. Progressively enhancing from radio buttons always seems like a smart way to go:
It might seem a little superfluous on a large screen design like this, especially as there is navigation already visible. But hey, it’s hard to avoid these days and there is something to be said about training users where site navigation can happen regardless of where you’re looking at the site.
As Brad does, he clearly outlines his struggles point-by-point:
I have invested enough time learning it
React and ES6 travel together
Syntax and conventions
Getting lost in this-land
I haven’t found sample projects or tutorials that match how i tend to work
I’m less competent at JS than HTML and CSS
It seems that Brad’s struggles resonated with others as well, inspiring empathy and help from the community. For example, Kevin Ball touches on the second and third frustrations by supplying a distinction between React and ES6 and examples of the syntax and conventions of each:
For each feature, I show a couple examples of what it might look like, identify where it is coming from, give you a quick overview of what is called and what it does, and link off to some resources that can help you learn about it.
Super awesome!
Shortly following Brad’s post was this tweet from Sara Soueidan:
Speaking of jQuery, Sarah Drasner had written a post a little while ago that showed how Vue can be used as a jQuery replacement and requires no build process at all. Well, the same can be true of React, despite the fact that both frameworks are predominantly used in complex app environments.
And, if all this talk about moving away from jQuery and into complex app environments sounds scary, then maybe this interview with Bruce Lawson will be reassuring to you. After all:
The end user doesn’t care whether your website is made with React or Angular or webpack or Broccoli or Grunt or whatever. They just want it to work in their damn browser.
But, still, there may be circumstances where React will be the right tool for the job and you’ll want it in your toolbox. For example, WordPress is using it as the basis for it’s upcoming Gutenberg editor meaning WordPress developers (and that’s a lot of us) will want to heed Matt Mullenweg’s advice to „learn JavaScript deeply.” Our guide on developing for Gutenberg might be a great place for you to start that journey.
If you’re looking for more manageable ways to create bar graphs, or in search of use cases to practice CSS Grid layout, I got you!
Before we begin working on the graph, I want to talk about coding the bars, when Grid is a good approach for graphs, and we’ll also cover some code choices you might consider before getting started.
Preface
The bar is a pretty basic shape: you can control its dimensions with CSS width, height, number of grid or table cells, etc. depending on how you’ve coded it. As far as graphs go, the main thing we want to control is the height of the bars in the graph.
Controlling height with Grid cells (like here) is convenient for designs where the height is incremental by a fixed value — no in-betweens. For example, signal bars in phones or when you don’t mind setting a lot of grid rows to better control the bar height down to its smallest value, like IRL graph paper.
For my graph, I want gradient bars as well as vertical and horizontal axes labels. So, to make it easy, I have decided to control the bar height with gradient sizing, and determine the number of grid rows based on the number of vertical axis labels I want.
Also, other than the contents for the graph — bars, axes labels, and captions — there’ll be no data present in the HTML, like data about bar colors and dimensions.
data-* attributes are used to provide that sort of information in HTML. But I didn’t want to switch back and forth between HTML and CSS while coding, and decided to completely separate the content from the design. It’s totally up to you. If you feel like using data-* might benefit your project, go for it.
I’ve created a diagram below that you might find useful to refer to while reading the code. It depicts the graph and the grid that contains it. The numbers represent grid lines.
Let’s code this thing.
The HTML
Grid can automatically place items in top-bottom and left-right directions. To take advantage of that, I’m going to add the graph contents in the order y-axis labels (top-bottom), bars, and x-axis labels (left-right). This way, I only need to write the HTML markup and the CSS will place the bars for me!
<figure aria-hidden="true">
<div class="graph">
<span class="graphRowLabel">100</span>
<span class="graphRowLabel">90</span>
<span class="graphRowLabel">80</span>
<span class="graphRowLabel">70</span>
<span class="graphRowLabel">60</span>
<span class="graphRowLabel">50</span>
<span class="graphRowLabel">40</span>
<span class="graphRowLabel">30</span>
<span class="graphRowLabel">20</span>
<span class="graphRowLabel">10</span>
<div class="graphBar"></div>
<div class="graphBar"></div>
<div class="graphBar"></div>
<div class="graphBar"></div>
<div class="graphBar"></div>
<span><sup>Y </sup>⁄<sub> X</sub></span>
<span class="graphColumnLabel">😊</span>
<span class="graphColumnLabel">😄</span>
<span class="graphColumnLabel">☺️</span>
<span class="graphColumnLabel">😁</span>
<span class="graphColumnLabel">😀</span>
</div>
<figcaption>Made with CSS Grid 💛</figcaption>
</figure>
<span class="screenreader-text">Smiling face with squinting eyes: 10%, grinning face with squinting eyes: 65%, smiling face: 52%, grinning face with smiling eyes: 100%, and grinning face: 92%.</span>
Note: If you’re interested in accessibility, know that I’m not an accessibility expert. But when I tried to make the bars accessible, screen reader experience simply sucked. Using aria-labelledby wasn’t that good either. So, I added a text description of the graph and hid it from the visual display. That made the reading much more natural.
We’ve defined eleven rows and six columns in our grid with these two little lines of CSS: ten automatically sized rows and one sized to its „maximum content”; one column sized to its „maximum content” and five automatically sized. CSS Grid is a beautiful thing.
The graph bars need to cover the grid from the first row to the second-to-last row since we are using the last one for the x-axis labels. I gave the bars 100% height, and grid-row: 1 / -2; which means „span the items from first horizontal grid line till the second last.”
The bars also have linear gradient going upwards. The size of the colored portion of the gradient is the indicator of bar’s height, which in turn is taken from each bar’s own CSS rule as a custom variable.
/* A grid item */
.graphBar{
/* Same as before */
background: palegoldenrod linear-gradient(to top, gold var(--h), transparent var(--h));
}
To control the width of the bars and the space between them, I use a fixed width and centered them with justify-self: center;. You can instead use grid-column-gap to create gaps between columns if you want. Here’ the full code that pulls everything for the bars together:
Did you notice the CSS variable (var(--h)) in there? We need to specify the exact height of each bar and we can use the variable to determine the height of the background gradient in terms of percentage:
There are a few demo-specific styles in here but everything we’ve covered so far will get you the basic framework for a bar graph. The y-axis labels I created are positioned on top of the grid lines for a slightly cleaner layout. I got the cylindrical shape and the cross-section edges of the bars by using border-radius and elliptic pseudo elements, respectively. Without them, you’ll get a straight up rectangular bar.
CSS is easy, some might argue, but that „easiness” can cause messy code. This is especially true through power of preprocessors like Sass or Less where, if you aren’t careful, your CSS can become harder to deal with instead of easier. Sass? Harder? This Gist shows a great example of Sass nesting hell.
If your Sass code looks like that, you can definitely improve your code with SEM & BIO, a CSS technique I’ll introduce you to now!
In this article, I am going to use the code example below to explain how SEM and BIO works and how they can help enhance your CSS strategy.
Generally, SEM is concerned with high level CSS philosophy whereas BIO is an actual technique to help you write better CSS to achieve SEM. The main purpose of both SEM and BIO is to better handle the CSS specificity which is one of the most important concepts you should understand for CSS.
From the CodePen example above, the „Search” button in the header looks exactly same as the ‘Link’ button in the sidebar. When we compare the HTML markup,
the „Search” button is <button> element
but the „Link” button is <a role="button" ...> element
…and even if the markup is different, the styles are identical by using the same classes: .c-btn and .c-btn--yellow.
The button styles are scalable and it allows you to add the same looking components anywhere you want as it won’t be polluted by its parents or siblings. This can save you from the big headache of not knowing why totally unrelated components are broken even if the changes are made on a different component from a totally different place.
The button in the header and in the main section look quite similar besides the 3D effect. In this case, instead of creating two different sets of buttons having totally different code bases, we could extend the plain button style by just adding the 3D effect to it.
It goes the same with the button in the footer. Even though the button has a different color and size, we could easily extend it by adding or removing new or different features.
Probably one of the biggest challenges for most front-enders is to understand CSS written by other people, or our past selves. We sometimes spend more time trying to understand the existing code than adding awesomely-written new code.
With SEM and BIO, we can definitely improve the code and save others (including ourselves!) from messy, unmaintainable code.
BIO
There are many great techniques out there to improve the way we write CSS, and from my experience, I found the following three techniques that make up the BIO acronym work very well together
A lot of developers/engineers already know those famous techniques but I would like to go through each of them and talk about the way I use those techniques.
BEM
BEM is very popular methodology and it has been helping us significantly improve the way we think about CSS and Sass/Less.
As the bad example above shows, we tend to overuse the power of Sass/Less and falling into nesting-hell. But with BEM, we start to have a very low CSS specificity by maintaining the styles in one (or two) levels of nesting.
If you’ve experienced any battles fighting higher CSS specificity, you will know how painful it is to come out a winner.
Going back to our example, the HTML markup looks like this:
When you create a class name, go with the core concept of BEM: your component is a block and all elements inside are individually attached to the block.
From our example again, I named .o-grid__form instead of .o-grid__item-form because the form itself is a separate component and doesn’t have to be tied with and be a child of o-grid__item.
Also, to more efficiently control styles, I have added another class name o-grid__header along with o-grid__item to extend the styles. Moreover, the button contains BEM-styled classes with the approach of OOCSS, which we’ll touch on next.
OOCSS
As we have already discussed, there are many great CSS methodologies and strategies helping us improve the way we write CSS. However, I see a lot of folks forcing themselves to decide on one methodology to use out of the bunch.
From my experience, combining methodologies can actually enhance their benefits by combining the best of multiple worlds. For example, I personally have found that BEM and OOCSS work very well together.
OOCSS stands for Object Oriented CSS and you can think of it working like Lego blocks:
OOCSS creates each individual part separately and then constructs them together in order to build components.
From our example, I have created buttons using the OOCSS naming convention:
.c-btn
.c-btn--yellow
.c-btn--blue
.c-btn--3d
.c-btn--large
To render the yellow search button in our example header, we combine these classes:
.c-btn
.c-btn--yellow
If we want to have the 3D button in the main section, we add in the 3D class, .c-btn--3d and call it a day.
And for the blue button in the footer, we can switch the yellow modifier to blue along with the large modifier. As you can see, the button is not depending on the header block giving us greater flexibility with how we use and repurpose components. And, by doing so, we can construct the buttons without impacting any other components or patterns while gaining the benefit of easily extending new presentational feature, like alternative colors and shapes.
Here example of a button collection created using OOCSS to create the variations:
On top of BEM and OOCSS, with the help of ITCSS, we can further improve our CSS strategy. Let’s look at that method next.
ITCSS
ITCSS stands for Inverted Triangle CSS and it helps organize CSS by applying a structure that determines how specific to get with a specific component. Lubos Kmetko has written an excellent overview of ITCSS that is worth reading.
You can see how I have put ITCSS to use by splitting styles up by grouped levels of specificity in this Gist.
Based on that example, you can see how I named components by adding a namespace to the class. So, for example, a the „button” component is prefixed with a „c” (.c-button) to indicate the component status and prevent it from being mistaken for another item. By doing so, everyone working on the project knows the function of that specific class and how changing its properties might affect other areas.
Here’s a visual that illustrates all the ITCSS levels:
Let’s go through each section.
Settings
Settings are generally a collection of variables that do not generate CSS, but are applied to classes. Some examples include:
Base
Color
Typography
Animation
Tools
Tools also will not produce any CSS yet and are typically preprocessor functions that help write or extend properties on classes:
Functions
Placeholders
Mixins
Media queries
Vendors
Vendors are third-party styles that are being used on a project. Think of things like reset.css, normalize.css, or even Foundation and Bootstrap.
The reason these styles are higher up in the structure is so we can override them if needed. As you may recall, if the same class is called twice, the cascade will render the properties of the second instance, assuming the properties are exactly the same:
.btn--large {
padding: 3em;
}
/* This one wins out */
.btn--large {
padding: 5em;
}
Just for the side note, in Sass, you can use ~ to point to the node_modules folder so you are able to import style assets from the source rather than having to move it into your own directories.
@import '~modern-normalize/modern-normalize';
Objects
Objects (namespace: o-) are used for design patterns, such as layouts, where items are being arranged rather than decorated. Object classes are used across all pages, so if you make any changes to the object classes, you should be very careful because any changes are going to affect each and every page throughout the website.
The most common object classes I use are:
.o-page: the most outer container which usually contains max-width: 100vw and overflow: hidden.
.o-main: the outer container for the main area.
.o-container: the outer container for components and it usually provides a fixed width.
.o-content: in case if any extra configuration is needed for the actual content area.
.o-grid: if a grid layout with different number of columns are required.
Do you use any other object classes? If so, please share with me. 😃
Elements
Elements (namespace: e-) are the HTML native elements which we would not style based on the class names. For example, we should provide default styles to <a> element rather than .link class.
// Do this for the default link style
a {
text-decoration: none;
&:hover {
background-color: blue;
color: white;
}
}
// Don’t provide the default link style to a class
.link {
text-decoration: none;
&:hover {
background-color: blue;
color: white;
}
}
It is because, especially in a CMS like WordPress, you wouldn’t want to add a class every single time you want to use a link in content. Hence, we provide a default style to the <a> element so without any class, the link will still have good-looking styles.
Components
A component (namespace: c-) is a small feature that makes up a part of the website. Think buttons, accordions, sliders, modal dialogs, etc. Each component is fully functional by itself and does not rely on any other components. This fact should be considered when you name the component.
For example, the button in the main section from the example above shouldn’t be called .c-main-button because main scopes it inside the main section and limits the use of it in other places, like a sidebar. Something like .c-btn is much nicer because the button is no longer tied to any other specific sections of the page.
If you need any extra features, you can always extend properties with a BEM modifier (combining powers!) or use Scope, which is coming up in a bit.
Patterns
A lot of developers/engineers use the terms component and pattern synonymously and that’s totally fine if you are more comfortable with that. It is just my preference to separate those two terms.
For a general rule of thumb, I think of a pattern (namespace: p-) as a combination of components but in a way that is not scaleable.
For example, I would consider the accordion as a component. It is scaleable and reusable on its own, meaning that it can be used in other parts of the website without making any changes even if the accordion would contain other components such as buttons.
On the other hand, the header, for example, would be a pattern because it is not scaleable (the header cannot be used in the content or sidebar area) and also contains other components such as buttons, accordions, menus, logos, search form etc.
Scope
Be warned; I only use the scope if it’s absolutely necessary. The purpose of the scope (namespace: s-) is to give us the highest specificity so we can overwrite any styles for a specific purpose.
Remember, If you find yourself using the scope class many times, you might be writing styles that are too specific and you should consider refactor your CSS structure.
Here is a simple example of the use of the scope class, .s-home.
.c-accordion {
.s-home & {
// Changing the background color specically on the homepage
background-color: tomato;
}
}
As a side note, the above example could actually be refactored by providing a modifier to the accordion (e.g., .c-accordion--bg-tomato) instead of using the scope class. That would be a much more extensible way of writing and make the component more modular.
Utility
Sometimes you may want to make changes only for a certain style in a specific place. In that case, utility (namespace: u-) classes can help us update it without changing the whole CSS structure.
For example, the font-size of the accordion heading is set to 32px.
.c-accordion__heading {
font-size: rem(32);
}
But if the font size is only different in the news section of your site and does not change anywhere else, then you might want to apply the utility class instead of setting higher specificity with the parent class or scope class.
Please note that we all know !important is BAD but I added !important to the value. It is because when we use the utility class, we are absolutely sure we want the specific style to be updated as we want. Also the utility class should overwrite any other styles, so having the !important actually works well here for the utility classes. That said, the utility classes should only play a role as a helper. It should never be used for structuring your CSS.
Like scope classes, if you are using too many utility classes, you should check with designer if the design can be more consistent across the site.
Extra Namespace
On top of the namespaces we discussed above, there are two more I often use:
is-: this indicates the state of the block or element. Most commonly used class is .is-active, like the active link in navigation.
js-: this indicates that the specific element is bound to JavaScript events. For example, js-menu-click indicates that the element is bound to the click event.
Linting
Finally making rules with .stylelint and .eslint can significantly improve the code quality.
In the frontend workflow, I don’t make it as a recommendation; I make it mandatory so that failing of the rules won’t get approved.
In this way, we can ensure that the coding quality stays at its best and provide better code to other developers, including your future self.
In Action
In this section, I’d like to discuss how we could use SEM and BIO. I have made a simple, practical example to get us started:
The main practice with the example is to build an accordion that can be used as:
a normal accordion but with different color themes in the main section
a menu in the sidebar
a block displaying social media icons in the footer
What we’re achieving is a component that is:
Scalable: as it can be added in any part of the page without any coding changes
Extensible: as it can serve different functionalities with the core functions unchanged
Maintainable: as it is organized in a way that makes sense
To achieve SEM, BIO has been used including:
BEM:.c-accordion as a block and its children as elements, also used modifiers, e.g., .c-accordion--light and .c-accordion--dark
ITCSS: The ordering/sorting of SASS files handles the CSS specificity quite well. For example, the accordion button in the sidebar contains class="c-accordion__trigger p-sidebar-menu__button" which the pattern (p-) overwrites the component (c-) with no issues.
OOCSS: the accordion is constructed with several classes, for example, class="c-accordion c-accordion--dark c-accordion--single" which creates a dark theme with opening a single panel only each time.
Final thoughts
I have used this approach for almost all of my projects including universities, government departments, commercial retailers, and many other websites. In each case, I have successfully delivered all of the projects to the clients (almost no issues during the client approval stage and delivered on time); this approach has worked for me very well so far and I think it could for you as well.
That being said, technologies always change (especially on the front end) and I am more than happy to hear and discuss any of your ideas/approaches/strategies that worked for you. Let me know in the comments!
If you ever need to hand-manipulate a color in native CSS, HSL is pretty much the only way. HSL (the hsl() and hsla() functions in CSS) stands for hue, saturation, lightness, and optionally, alpha. We’ve talked about it before but we can break it down a little more and do some interesting things with it.
Hue: Think of a color wheel. Around 0o and 360o are reds. 120o is where greens are and 240o are blues. Use anything in between 0-360. Values above and below will be modulus 360.
Saturation: 0% is completely desaturated (grayscale). 100% is fully saturated (full color).
Lightness: 0% is completely dark (black). 100% is completely light (white). 50% is average lightness.
alpha: Opacity/Transparency value. 0 is fully transparent. 1 is fully opaque. 0.5 is 50% transparent.
You can hand-manipulate any of those four values and have a decent sense of what is going to happen. Change the hue to take a trip around the color wheel. Change the saturation to get deeper or more muted colors. Change the lightness to essentially mix in black or white.
You might have some mental chops with rgb(), knowing that rgb(255, 0, 0) is clearly red or rgb(0, 0, 0) is black, but manipulating those to get to a light purple or starting with a forest green and getting a little lighter isn’t exactly mental math. You might even be the clever sort who can identify color by Hex codes. Ask David DeSandro at a party sometime. Still, nothing nearly as intuitive as HSL.
I really like HSL when playing with color in JavaScript. For example, say you want to generate some different red tones. You could randomize the H, S, and L tightly around some values:
Wanna learn more about color on the web in general? Don’t miss Sarah Drasner’s A Nerd’s Guide to Color on the Web. Lots of great goodies in there to up your understanding of working with color.
While browsing the latest award-winning websites, you may notice a lot of fancy image distortion animations or neat 3D effects. Most of them are created with WebGL, an API allowing GPU-accelerated image processing effects and animations. They also tend to use libraries built on top of WebGL such as three.js or pixi.js. Both are very powerful tools to create respectively 2D and 3D scenes.
But, you should keep in mind that those libraries were not originally designed to create slideshows or animate DOM elements. There is a library designed just for that, though, and we’re going to cover how to use it here in this post.
WebGL, CSS Positioning, and Responsiveness
Say you’re working with a library like three.js or pixi.js and you want to use it to create interactions, like mouseover and scroll events on elements. You might run into trouble! How do you position your WebGL elements relative to the document and other DOM elements? How would handle responsiveness?
This is exactly what I had in mind when creating curtains.js.
Curatins.js allows you to create planes containing images and videos (in WebGL we will call them textures) that act like plain HTML elements, with position and size defined by CSS rules. But these planes can be enhanced with the endless possibilities of WebGL and shaders.
Wait, shaders?
Shaders are small programs written in GLSL that will tell your GPU how to render your planes. Knowing how shaders work is mandatory here because this is how we will handle animations. If you’ve never heard of them, you may want to learn the basics first. There are plenty of good websites to start learning them, like The Book of Shaders.
Now that you get the idea, let’s create our first plane!
Setup of a basic plane
To display our first plane, we will need a bit of HTML, CSS, and some JavaScript to create the plane. Then our shaders will animate it.
HTML
The HTML will be really simple here. We will create a <div> that will hold our canvas, and a div that will hold our image.
<body>
<!-- div that will hold our WebGL canvas -->
<div id="canvas"></div>
<!-- div used to create our plane -->
<div class="plane">
<!-- image that will be used as a texture by our plane -->
<img src="path/to/my-image.jpg" />
</div>
</body>
CSS
We will will use CSS to make sure the <div> that wraps the canvas will be bigger than our plane, and apply any size to the plane div. (Our WebGL plane will have the exact same size and positions of this div.)
body {
/* make the body fit our viewport */
position: relative;
width: 100%;
height: 100vh;
margin: 0;
/* hide scrollbars */
overflow: hidden;
}
#canvas {
/* make the canvas wrapper fit the document */
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
.plane {
/* define the size of your plane */
width: 80%;
max-width: 1400px;
height: 80vh;
position: relative;
top: 10vh;
margin: 0 auto;
}
.plane img {
/* hide the img element */
display: none;
}
JavaScript
There’s a bit more work in the JavaScript. We need to instantiate our WebGL context, create a plane with uniform parameters, and use it.
window.onload = function() {
// pass the id of the div that will wrap the canvas to set up our WebGL context and append the canvas to our wrapper
var webGLCurtain = new Curtains("canvas");
// get our plane element
var planeElement = document.getElementsByClassName("plane")[0];
// set our initial parameters (basic uniforms)
var params = {
vertexShaderID: "plane-vs", // our vertex shader ID
fragmentShaderID: "plane-fs", // our framgent shader ID
uniforms: {
time: {
name: "uTime", // uniform name that will be passed to our shaders
type: "1f", // this means our uniform is a float
value: 0,
},
}
}
// create our plane mesh
var plane = webGLCurtain.addPlane(planeElement, params);
// use the onRender method of our plane fired at each requestAnimationFrame call
plane.onRender(function() {
plane.uniforms.time.value++; // update our time uniform value
});
}
Shaders
We need to write the vertex shader. It won’t be doing much except position our plane based on the model view and projection matrix and pass varyings to the fragment shader:
<!-- vertex shader -->
<script id="plane-vs" type="x-shader/x-vertex">
#ifdef GL_ES
precision mediump float;
#endif
// those are the mandatory attributes that the lib sets
attribute vec3 aVertexPosition;
attribute vec2 aTextureCoord;
// those are mandatory uniforms that the lib sets and that contain our model view and projection matrix
uniform mat4 uMVMatrix;
uniform mat4 uPMatrix;
// if you want to pass your vertex and texture coords to the fragment shader
varying vec3 vVertexPosition;
varying vec2 vTextureCoord;
void main() {
// get the vertex position from its attribute
vec3 vertexPosition = aVertexPosition;
// set its position based on projection and model view matrix
gl_Position = uPMatrix * uMVMatrix * vec4(vertexPosition, 1.0);
// set the varyings
vTextureCoord = aTextureCoord;
vVertexPosition = vertexPosition;
}
</script>
Now our fragment shader. This is where we will add a little displacement effect based on our time uniform and the texture coordinates.
<!-- fragment shader -->
<script id="plane-fs" type="x-shader/x-fragment">
#ifdef GL_ES
precision mediump float;
#endif
// get our varyings
varying vec3 vVertexPosition;
varying vec2 vTextureCoord;
// the uniform we declared inside our javascript
uniform float uTime;
// our texture sampler (this is the lib default name, but it could be changed)
uniform sampler2D uSampler0;
void main() {
// get our texture coords
vec2 textureCoord = vTextureCoord;
// displace our pixels along both axis based on our time uniform and texture UVs
// this will create a kind of water surface effect
// try to comment a line or change the constants to see how it changes the effect
// reminder : textures coords are ranging from 0.0 to 1.0 on both axis
const float PI = 3.141592;
textureCoord.x += (
sin(textureCoord.x * 10.0 + ((uTime * (PI / 3.0)) * 0.031))
+ sin(textureCoord.y * 10.0 + ((uTime * (PI / 2.489)) * 0.017))
) * 0.0075;
textureCoord.y += (
sin(textureCoord.y * 20.0 + ((uTime * (PI / 2.023)) * 0.023))
+ sin(textureCoord.x * 20.0 + ((uTime * (PI / 3.1254)) * 0.037))
) * 0.0125;
gl_FragColor = texture2D(uSampler0, textureCoord);
}
</script>
Et voilà! You’re all done, and if everything went well, you should be seeing something like this.
Alright, that’s pretty cool so far, but we started this post talking about 3D and interactions, so let’s look at how we could add those in.
About vertices
To add a 3D effect we would have to change the plane vertices position inside the vertex shader. However in our first example, we did not specify how many vertices our plane should have, so it was created with a default geometry containing six vertices forming two triangles :
In order to get decent 3D animations, we would need more triangles, thus more vertices:
This plane has five segments along its width and five segments along its height. As a result, we have 50 triangles and 150 total vertices.
Refactoring our JavaScript
Fortunately, it is easy to specify our plane definition as it could be set inside our initial parameters.
We are also going to listen to mouse position to add a bit of interaction. To do it properly, we will have to wait for the plane to be ready, convert our mouse document coordinates to our WebGL clip space coordinates and send them to the shaders as a uniform.
// we are using window onload event here but this is not mandatory
window.onload = function() {
// track the mouse positions to send it to the shaders
var mousePosition = {
x: 0,
y: 0,
};
// pass the id of the div that will wrap the canvas to set up our WebGL context and append the canvas to our wrapper
var webGLCurtain = new Curtains("canvas");
// get our plane element
var planeElement = document.getElementsByClassName("plane")[0];
// set our initial parameters (basic uniforms)
var params = {
vertexShaderID: "plane-vs", // our vertex shader ID
fragmentShaderID: "plane-fs", // our framgent shader ID
widthSegments: 20,
heightSegments: 20, // we now have 20*20*6 = 2400 vertices !
uniforms: {
time: {
name: "uTime", // uniform name that will be passed to our shaders
type: "1f", // this means our uniform is a float
value: 0,
},
mousePosition: { // our mouse position
name: "uMousePosition",
type: "2f", // notice this is a length 2 array of floats
value: [mousePosition.x, mousePosition.y],
},
mouseStrength: { // the strength of the effect (we will attenuate it if the mouse stops moving)
name: "uMouseStrength", // uniform name that will be passed to our shaders
type: "1f", // this means our uniform is a float
value: 0,
},
}
}
// create our plane mesh
var plane = webGLCurtain.addPlane(planeElement, params);
// once our plane is ready, we could start listening to mouse/touch events and update its uniforms
plane.onReady(function() {
// set a field of view of 35 to exagerate perspective
// we could have done it directly in the initial params
plane.setPerspective(35);
// listen our mouse/touch events on the whole document
// we will pass the plane as second argument of our function
// we could be handling multiple planes that way
document.body.addEventListener("mousemove", function(e) {
handleMovement(e, plane);
});
document.body.addEventListener("touchmove", function(e) {
handleMovement(e, plane);
});
}).onRender(function() {
// update our time uniform value
plane.uniforms.time.value++;
// continually decrease mouse strength
plane.uniforms.mouseStrength.value = Math.max(0, plane.uniforms.mouseStrength.value - 0.0075);
});
// handle the mouse move event
function handleMovement(e, plane) {
// touch event
if(e.targetTouches) {
mousePosition.x = e.targetTouches[0].clientX;
mousePosition.y = e.targetTouches[0].clientY;
}
// mouse event
else {
mousePosition.x = e.clientX;
mousePosition.y = e.clientY;
}
// convert our mouse/touch position to coordinates relative to the vertices of the plane
var mouseCoords = plane.mouseToPlaneCoords(mousePosition.x, mousePosition.y);
// update our mouse position uniform
plane.uniforms.mousePosition.value = [mouseCoords.x, mouseCoords.y];
// reassign mouse strength
plane.uniforms.mouseStrength.value = 1;
}
}
Now that our JavaScript is done, we have to rewrite our shaders so that they’ll use our mouse position uniform.
Refactoring the shaders
Let’s look at our vertex shader first. We have three uniforms that we could use for our effect:
the time which is constantly increasing
the mouse position
our mouse strength, which is constantly decreasing until the next mouse move
We will use all three of them to create a kind of 3D ripple effect.
<script id="plane-vs" type="x-shader/x-vertex">
#ifdef GL_ES
precision mediump float;
#endif
// those are the mandatory attributes that the lib sets
attribute vec3 aVertexPosition;
attribute vec2 aTextureCoord;
// those are mandatory uniforms that the lib sets and that contain our model view and projection matrix
uniform mat4 uMVMatrix;
uniform mat4 uPMatrix;
// our time uniform
uniform float uTime;
// our mouse position uniform
uniform vec2 uMousePosition;
// our mouse strength
uniform float uMouseStrength;
// if you want to pass your vertex and texture coords to the fragment shader
varying vec3 vVertexPosition;
varying vec2 vTextureCoord;
void main() {
vec3 vertexPosition = aVertexPosition;
// get the distance between our vertex and the mouse position
float distanceFromMouse = distance(uMousePosition, vec2(vertexPosition.x, vertexPosition.y));
// this will define how close the ripples will be from each other. The bigger the number, the more ripples you'll get
float rippleFactor = 6.0;
// calculate our ripple effect
float rippleEffect = cos(rippleFactor * (distanceFromMouse - (uTime / 120.0)));
// calculate our distortion effect
float distortionEffect = rippleEffect * uMouseStrength;
// apply it to our vertex position
vertexPosition += distortionEffect / 15.0;
gl_Position = uPMatrix * uMVMatrix * vec4(vertexPosition, 1.0);
// varyings
vTextureCoord = aTextureCoord;
vVertexPosition = vertexPosition;
}
</script>
As for the fragment shader, we are going to keep it simple. We are going to fake lights and shadows based on each vertex position:
<script id="plane-fs" type="x-shader/x-fragment">
#ifdef GL_ES
precision mediump float;
#endif
// get our varyings
varying vec3 vVertexPosition;
varying vec2 vTextureCoord;
// our texture sampler (this is the lib default name, but it could be changed)
uniform sampler2D uSampler0;
void main() {
// get our texture coords
vec2 textureCoords = vTextureCoord;
// apply our texture
vec4 finalColor = texture2D(uSampler0, textureCoords);
// fake shadows based on vertex position along Z axis
finalColor.rgb -= clamp(-vVertexPosition.z, 0.0, 1.0);
// fake lights based on vertex position along Z axis
finalColor.rgb += clamp(vVertexPosition.z, 0.0, 1.0);
// handling premultiplied alpha (useful if we were using a png with transparency)
finalColor = vec4(finalColor.rgb * finalColor.a, finalColor.a);
gl_FragColor = finalColor;
}
</script>
With these two simple examples, we’ve seen how to create a plane and interact with it.
Videos and displacement shaders
Our last example will create a basic fullscreen video slideshow using a displacement shader to enhance the transitions.
Displacement shader concept
The displacement shader will create a nice distortion effect. It will be written inside our fragment shader using a grayscale picture and will offset the pixel coordinates of the videos based on the texture RGB values. Here’s the image we will be using:
The effect will be calculated based on each pixel RGB value, with a black pixel being [0, 0, 0] and a white pixel [1, 1, 1] (GLSL equivalent for [255, 255, 255]). To simplify, we will use only the red channel value, as with a grayscale image red, green and blue are always equal.
You can try to create your own grayscale image (it works great with geometric shape ) to get your unique transition effect.
Multiple textures and videos
A plane can have more than one texture simply by adding multiple image tags. This time, instead of images we want to use videos. We just have to replace the <img /> tags with a <video /> one. However there are two things to know when it comes to video:
The video will always fit the exact size of the plane, which means your plane has to have the same width/height ratio as your video. This is not a big deal tho because it is easy to handle with CSS.
On mobile devices, we can’t autoplay videos without a user gesture, like a click event. It is therefore safer to add a „enter site” button to display and launch our videos.
HTML
The HTML is still pretty straightforward. We will create our canvas div wrapper, our plane div containing the textures and a button to trigger the video autoplay. Just notice the use of the data-sampler attribute on the image and video tags—it will be useful inside our fragment shader.
<body>
<div id="canvas"></div>
<!-- this div will handle the fullscreen video sizes and positions -->
<div class="plane-wrapper">
<div class="plane">
<!-- notice here we are using the data-sampler attribute to name our sampler uniforms -->
<img src="path/to/displacement.jpg" data-sampler="displacement" />
<video src="path/to/video.mp4" data-sampler="firstTexture"></video>
<video src="path/to/video-2.mp4" data-sampler="secondTexture"></video>
</div>
</div>
<div id="enter-site-wrapper">
<span id="enter-site">
Click to enter site
</span>
</div>
</body>
CSS
The stylesheet will handle a few things: display the button and hide the canvas before the user has entered the site, size and position our plane-wrapper div to handle fullscreen responsive videos.
@media screen {
body {
margin: 0;
font-size: 18px;
font-family: 'PT Sans', Verdana, sans-serif;
background: #212121;
line-height: 1.4;
height: 100vh;
width: 100vw;
overflow: hidden;
}
/*** canvas ***/
#canvas {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 10;
/* hide the canvas until the user clicks the button */
opacity: 0;
transition: opacity 0.5s ease-in;
}
/* display the canvas */
.video-started #canvas {
opacity: 1;
}
.plane-wrapper {
position: absolute;
/* center our plane wrapper */
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
z-index: 15;
}
.plane {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
/* tell the user he can click the plane */
cursor: pointer;
}
/* hide the original image and videos */
.plane img, .plane video {
display: none;
}
/* center the button */
#enter-site-wrapper {
display: flex;
justify-content: center;
align-items: center;
align-content: center;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 30;
/* hide the button until everything is ready */
opacity: 0;
transition: opacity 0.5s ease-in;
}
/* show the button */
.curtains-ready #enter-site-wrapper {
opacity: 1;
}
/* hide the button after the click event */
.curtains-ready.video-started #enter-site-wrapper {
opacity: 0;
pointer-events: none;
}
#enter-site {
padding: 20px;
color: white;
background: #ee6557;
max-width: 200px;
text-align: center;
cursor: pointer;
}
}
/* fullscreen video responsive */
@media screen and (max-aspect-ratio: 1920/1080) {
.plane-wrapper {
height: 100vh;
width: 177vh;
}
}
@media screen and (min-aspect-ratio: 1920/1080) {
.plane-wrapper {
width: 100vw;
height: 56.25vw;
}
}
JavaScript
As for the JavaScript, we will go like this:
Set a couple variables to store our slideshow state
Create the Curtains object and add the plane to it
When the plane is ready, listen to a click event to start our videos playback (notice the use of the playVideos() method). Add another click event to switch between the two videos.
Update our transition timer uniform inside the onRender() method
window.onload = function() {
// here we will handle which texture is visible and the timer to transition between images
var activeTexture = 1;
var transitionTimer = 0;
// set up our WebGL context and append the canvas to our wrapper
var webGLCurtain = new Curtains("canvas");
// get our plane element
var planeElements = document.getElementsByClassName("plane");
// some basic parameters
var params = {
vertexShaderID: "plane-vs",
fragmentShaderID: "plane-fs",
imageCover: false, // our displacement texture has to fit the plane
uniforms: {
transitionTimer: {
name: "uTransitionTimer",
type: "1f",
value: 0,
},
},
}
var plane = webGLCurtain.addPlane(planeElements[0], params);
// create our plane
plane.onReady(function() {
// display the button
document.body.classList.add("curtains-ready");
// when our plane is ready we add a click event listener that will switch the active texture value
planeElements[0].addEventListener("click", function() {
if(activeTexture == 1) {
activeTexture = 2;
}
else {
activeTexture = 1;
}
});
// click to play the videos
document.getElementById("enter-site").addEventListener("click", function() {
// display canvas and hide the button
document.body.classList.add("video-started");
// play our videos
plane.playVideos();
}, false);
}).onRender(function() {
// increase or decrease our timer based on the active texture value
// at 60fps this should last one second
if(activeTexture == 2) {
transitionTimer = Math.min(60, transitionTimer + 1);
}
else {
transitionTimer = Math.max(0, transitionTimer - 1);
}
// update our transition timer uniform
plane.uniforms.transitionTimer.value = transitionTimer;
});
}
Shaders
This is where all the magic will occur. Like in our first example, the vertex shader won’t do much and you’ll have to focus on the fragment shader that will create a “dive in” effect:
<script id="plane-vs" type="x-shader/x-vertex">
#ifdef GL_ES
precision mediump float;
#endif
// default mandatory variables
attribute vec3 aVertexPosition;
attribute vec2 aTextureCoord;
uniform mat4 uMVMatrix;
uniform mat4 uPMatrix;
// varyings
varying vec3 vVertexPosition;
varying vec2 vTextureCoord;
// custom uniforms
uniform float uTransitionTimer;
void main() {
vec3 vertexPosition = aVertexPosition;
gl_Position = uPMatrix * uMVMatrix * vec4(vertexPosition, 1.0);
// varyings
vTextureCoord = aTextureCoord;
vVertexPosition = vertexPosition;
}
</script>
<script id="plane-fs" type="x-shader/x-fragment">
#ifdef GL_ES
precision mediump float;
#endif
varying vec3 vVertexPosition;
varying vec2 vTextureCoord;
// custom uniforms
uniform float uTransitionTimer;
// our textures samplers
// notice how it matches our data-sampler attributes
uniform sampler2D firstTexture;
uniform sampler2D secondTexture;
uniform sampler2D displacement;
void main( void ) {
// our texture coords
vec2 textureCoords = vec2(vTextureCoord.x, vTextureCoord.y);
// our displacement texture
vec4 displacementTexture = texture2D(displacement, textureCoords);
// our displacement factor is a float varying from 1 to 0 based on the timer
float displacementFactor = 1.0 - (cos(uTransitionTimer / (60.0 / 3.141592)) + 1.0) / 2.0;
// the effect factor will tell which way we want to displace our pixels
// the farther from the center of the videos, the stronger it will be
vec2 effectFactor = vec2((textureCoords.x - 0.5) * 0.75, (textureCoords.y - 0.5) * 0.75);
// calculate our displaced coordinates to our first video
vec2 firstDisplacementCoords = vec2(textureCoords.x - displacementFactor * (displacementTexture.r * effectFactor.x), textureCoords.y - displacementFactor * (displacementTexture.r * effectFactor.y));
// opposite displacement effect on the second video
vec2 secondDisplacementCoords = vec2(textureCoords.x - (1.0 - displacementFactor) * (displacementTexture.r * effectFactor.x), textureCoords.y - (1.0 - displacementFactor) * (displacementTexture.r * effectFactor.y));
// apply the textures
vec4 firstDistortedColor = texture2D(firstTexture, firstDisplacementCoords);
vec4 secondDistortedColor = texture2D(secondTexture, secondDisplacementCoords);
// blend both textures based on our displacement factor
vec4 finalColor = mix(firstDistortedColor, secondDistortedColor, displacementFactor);
// handling premultiplied alpha
finalColor = vec4(finalColor.rgb * finalColor.a, finalColor.a);
// apply our shader
gl_FragColor = finalColor;
}
</script>
Here’s our little video slideshow with a cool transition effect:
This example is a great way to show you how to create a slideshow with curtains.js: you might want to use images instead of videos, change the displacement texture, modify the fragment shader or even add more slides…
Going deeper
We’ve just scraped the surface of what’s possible with curtains.js. You could try to create multiple planes with a cool mouse over effect for your article thumbs for example. The possibilities are almost endless.
If you want to see more examples covering all those basics usages, you can check the library website or the GitHub repo.
After four hours and some twenty minutes, of which over four hours were spent on tweaking positioning, edges and highlights… I finally had the result below:
My version of the rainbow gradient infinity.
The gradient doesn’t look like in the original illustration, as I chose to generate the rainbow logically instead of using the Dev Tools picker or something like that, but other than that, I think I got pretty close—let’s see how I did that!
Markup
As you’ve probably already guessed from the title, the HTML is just one element:
<div class='∞'></div>
Styling
Deciding on the approach
The first idea that might come to mind when seeing the above would be using conic gradients as border images. Unfortunately, border-image and border-radius don’t play well together, as illustrated by the interactive demo below:
Whenever we set a border-image, border-radius just gets ignored, so using the two together is sadly not an option.
So the approach we take here is using conic-gradient() backgrounds and then getting rid of the part in the middle with the help of a mask. Let’s see how that works!
Creating the two ∞ halves
We first decide on an outer diameter.
$do: 12.5em;
We create the two halves of the infinity symbol using the ::before and ::after pseudo-elements of our .∞ element. In order to place these two pseudo-elements next to one another, we use a flex layout on their parent (the infinity element .∞). Each of these has both the width and the height equal to the outer diameter $do. We also round them with a border-radius of 50% and we give them a dummy background so we can see them.
In order to create the conic-gradient() backgrounds for the two haves, we must first understand how the conic-gradient() function works.
If inside the conic-gradient() function we have a list of stops without explicit positions, then the first is taken to be at 0% (or 0deg, same thing), the last is taken to be at 100% (or 360deg), while all those left are distributed evenly in the [0%, 100%] interval.
If we have just 2 stops, it’s simple. The first is at 0%, the second (and last) at 100% and there are no other stops in between.
If we have 3 stops, the first is at 0%, the last (third) at 100%, while the second is dead in the middle of the [0%, 100%] interval, at 50%.
If we have 4 stops, the first is at 0%, the last (fourth) at 100%, while the second and third split the [0%, 100%] interval into 3 equal intervals, being positioned at 33.(3)% and 66.(6)% respectively.
If we have 5 stops, the first is at 0%, the last (fifth) at 100%, while the second, third and fourth split the [0%, 100%] interval into 4 equal intervals being positioned at 25%, 50% and 75% respectively.
If we have 6 stops, the first is at 0%, the last (sixth) at 100%, while the second, third, fourth and fifth split the [0%, 100%] interval into 5 equal intervals being positioned at 20%, 40%, 60% and 80% respectively.
In general, if we have n stops, the first is at 0%, the last at 100%, while the ones in between split the [0%, 100%] interval into n-1 eqial intervals spanning 100%/(n-1) each. If we give the stops 0-based indices, then each one of them is positioned at i*100%/(n-1).
For the first one, i is 0, which gives us 0*100%/(n-1) = 0%.
For the last (n-th) one, i is n-1, which gives us (n-1)*100%/(n-1) = 100%.
Here, we choose to use 9 stops which means we split the [0%, 100%] interval into 8 equal intervals.
Alright, but how do we get the stop list?
The hsl() stops
Well, for simplicity, we choose to generate it as a list of HSL values. We keep the saturation and the lightness fixed and we vary the hue. The hue is an angle value that goes from 0 to 360, as we can see here:
Visual representation of the hue scale from 0 to 360 (saturation and lightness being kept constant).
With this in mind, we can construct a list of hsl() stops with fixed saturation and lightness and varying hue if we know the start hue$hue-start, the hue range$hue-range (this is the end hue minus the start hue) and the number of stops$num-stops.
Let’s say we keep the saturation and the lightness fixed at 85% and 57%, respectively (arbitrary values that can probably be tweaked for better results) and, for example, we might go from a start hue of 240 to an end hue of 300 and use 4 stops.
In order to generate this list of stops, we use a get-stops() function that takes these three things as arguments:
We create the list of stops $list which is originally empty (and which we’ll return at the end after we populate it). We also compute the span of one of the equal intervals our stops split the full start to end interval into ($unit).
@function get-stops($hue-start, $hue-range, $num-stops) {
$list: ();
$unit: $hue-range/($num-stops - 1);
/* populate the list of stops $list */
@return $list
}
In order to populate our $list, we loop through the stops, compute the current hue, use the current hue to generate the hsl() value at that stop and then then add it to the list of stops:
@for $i from 0 to $num-stops {
$hue-curr: $hue-start + $i*$unit;
$list: $list, hsl($hue-curr, 85%, 57%);
}
We can now use the stop list this function returns for any kind of gradient, as it can be seen from the usage examples for this function shown in the interactive demo below (navigation works both by using the previous/next buttons on the sides as well as the arrow keys and the PgDn/ PgUp keys):
Note how, when our range passes one end of the [0, 360] interval, it continues from the other end. For example, when the start hue is 30 and the range is -210 (the fourth example), we can only go down to 0, so then we continue going down from 360.
Conic gradients for our two halves
Alright, but how do we determine the $hue-start and the $hue-range for our particular case?
In the original image, we draw a line in between the central points of the two halves of the loop and, starting from this line, going clockwise in both cases, we see where we start from and where we end up in the [0, 360] hue interval and what other hues we pass through.
We start from the line connecting the central points of the two halves and we go around them in the clockwise direction.
To simplify things, we consider we pass through the whole [0, 360] hue scale going along our infinity symbol. This means the range for each half is 180 (half of 360) in absolute value.
Keywords to hue values correspondence for saturation and lightness fixed at 100% and 50% respectively.
On the left half, we start from something that looks like it’s in between some kind of cyan (hue 180) and some kind of lime (hue 120), so we take the start hue to be the average of the hues of these two (180 + 120)/2 = 150.
The plan for the left half.
We get to some kind of red, which is 180 away from the start value, so at 330, whether we subtract or add 180:
So… do we go up or down? Well, we pass through yellows which are around 60 on the hue scale, so that’s going down from 150, not up. Going down means our range is negative (-180).
The plan for the right half.
On the right half, we also start from the same hue in between cyan and lime (150) and we also end at the same kind of red (330), but this time we pass through blues, which are around 240, meaning we go up from our start hue of 150, so our range is positive in this case (180).
As far as the number of stops goes, 9 should suffice.
Now update our code using the values for the left half as the defaults for our function:
@function get-stops($hue-start: 150, $hue-range: -180, $num-stops: 9) {
/* same as before */
}
.∞ {
display: flex;
&:before, &:after {
/* same as before */
background: conic-gradient(get-stops());
}
&:after {
background: conic-gradient(get-stops(150, 180));
}
}
And now our two discs have conic-gradient() backgrounds:
However, we don’t want these conic gradients to start from the top.
For the first disc, we want it to start from the right—that’s at 90° from the top in the clockwise (positive) direction. For the second disc, we want it to start from the left—that’s at 90° from the top in the other (negative) direction, which is equivalent to 270° from the top in the clockwise direction (because negative angles don’t appear to work from some reason).
Angular offsets from the top for our two halves.
Let’s modify our code to achieve this:
.∞ {
display: flex;
&:before, &:after {
/* same as before */
background: conic-gradient(from 90deg, get-stops());
}
&:after {
background: conic-gradient(from 270deg, get-stops(150, 180));
}
}
The next step is to cut holes out of our two halves. We do this with a mask or, more precisely, with a radial-gradient() one. This cuts out Edge support for now, but since it’s something that’s in development, it’s probably going to be a cross-browser solution at some point in the not too far future.
Remember that CSS gradient masks are alpha masks by default (and only Firefox currently allows changing this via mask-mode), meaning that only the alpha channel matters. Overlaying the mask over our element makes every pixel of this element use the alpha channel of the corresponding pixel of the mask. If the mask pixel is completely transparent (its alpha value is 0), then so will the corresponding pixel of the element.
In order to create the mask, we compute the outer radius $ro (half the outer diameter $do) and the inner radius $ri (a fraction of the outer radius $ro).
$ro: .5*$do;
$ri: .52*$ro;
$m: radial-gradient(transparent $ri, red 0);
We then set the mask on our two halves:
.∞ {
/* same as before */
&:before, &:after {
/* same as before */
mask: $m;
}
}
This looks perfect in Firefox, but the edges of radial gradients with abrupt transitions from one stop to another look ugly in Chrome and, consequently, so do the inner edges of our rings.
Close-up of the inner edge of the right half in Chrome.
The fix here would be not to have an abrupt transition between stops, but spread it out over a small distance, let’s say half a pixel:
$m: radial-gradient(transparent calc(#{$ri} - .5px), red $ri);
Close-up of the inner edge of the right half in Chrome after spreading out the transition between stops over half a pixel.
The following step is to offset the two halves such that they actually form an infinity symbol. The visible circular strips both have the same width, the difference between the outer radius $ro and the inner radius $ri. This means we need to shift each laterally by half this difference $ri - $ri.
.∞ {
/* same as before */
&:before, &:after {
/* same as before */
margin: 0 (-.5*($ro - $ri));
}
}
We’re getting closer, but we still have a very big problem here. We don’t want the right part of the loop to be completely over the left one. Instead, we want the top half of the right part to be over that of the left part and the bottom half of the left part to be over that of the right part.
So how do we achieve that?
We take a similar approach to that presented in an older article: using 3D!
In order to better understand how this works, consider the two card example below. When we rotate them around their x axes, they’re not in the plane of the screen anymore. A positive rotation brings the bottom forward and pushes the top back. A negative rotation brings the top forward and pushes the bottom back.
So if we give the left one a positive rotation and the right one a negative rotation, then the top half of the right one appears in front of the top half of the left one and the other way around for the bottom halves.
Addiing perspective makes what’s closer to our eyes appears bigger and what’s further away appears smaller and we use way smaller angles. Without it, we have the 3D plane intersection without the 3D appearance.
Note that both our halves need to be in the same 3D context, something that’s achieved by setting transform-style: preserve-3d on the .∞ element.
.∞ {
/* same as before */
transform-style: preserve-3d;
&:before, &:after {
/* same as before */
transform: rotatex(1deg);
}
&:after {
/* same as before */
transform: rotatex(-1deg);
}
}
This still isn’t perfect though. Since the inner edges of our two rings are a bit blurry, the transition in between them and the crisp outer ones looks a bit odd, so maybe we can do better there:
A quick fix here would be to add a radial-gradient() cover on each of the two halves. This cover is transparent white for most of the unmasked part of the two halves and goes to solid white along both their inner and outer edges such that we have nice continuity:
$gc: radial-gradient(#fff $ri, rgba(#fff, 0) calc(#{$ri} + 1px),
rgba(#fff, 0) calc(#{$ro} - 1px), #fff calc(#{$ro} - .5px));
.∞ {
/* same as before */
&:before, &:after {
/* same as before */
background: $gc, conic-gradient(from 90deg, get-stops());
}
&:after {
/* same as before */
background: $gc, conic-gradient(from 270deg, get-stops(150, 180));
}
}
The benefit becomes more obvious once we add a dark background to the body:
No more sharp contrast between inner and outer edges.
The final result
Finally, we add some prettifying touches by layering some more subtle radial gradient highlights over the two halves. This was the part that took me the most because it involved the least amount of logic and the most amount of trial and error. At this point, I just layered the original image underneath the .∞ element, made the two halves semi-transparent and started adding gradients and tweaking them until they pretty much matched the highlights. And you can see when I got sick of it because that’s when the position values become rougher approximations with fewer decimals.
Another cool touch would be drop shadows on the whole thing using a filter on the body. Sadly, this breaks the 3D intersection effect in Firefox, which means we cannot add it there, too.
When I first shared this demo, I got asked about animating it. I initially thought this would be complicated, but then it hit me that, thanks to Houdini, it doesn’t have to be!
As mentioned in my previous article, we can animate in between stops, let’s say from a red to a blue. In our case, the saturation and lightness components of the hsl() values used to generate the rainbow gradient stay constant, all that changes is the hue.
For each and every stop, the hue goes from its initial value to its initial value plus 360, thus passing through the whole hue scale in the process. This is equivalent to keeping the initial hue constant and varying an offset. This offset --off is the custom property we animate.
Sadly, this means support is limited to Blink browsers with the Experimental Web Platform features flag enabled.
The Experimental Web Platform features flag enabled in Chrome.
Still, let’s see how we put it all into code!
For starters, we modify the get-stops() function such that the current hue at any time is the initial hue of the current stop $hue-curr plus our offset --off: