Using CSS Clip Path to Create Interactive Effects, Part II

This is a follow up to my previous post looking into clip paths. Last time around, we dug into the fundamentals of clipping and how to get started. We looked at some ideas to exemplify what we can do with clipping. We’re going to take things a step further in this post and look at different examples, discuss alternative techniques, and consider how to approach our work to be cross-browser compatible.

One of the biggest drawbacks of CSS clipping, at the time of writing, is browser support. Not having 100% browser coverage means different experiences for viewers in different browsers. We, as developers, can’t control what browsers support — browser vendors are the ones who implement the spec and different vendors will have different agendas.

One thing we can do to overcome inconsistencies is use alternative technologies. The feature set of CSS and SVG sometimes overlap. What works in one may work in the other and vice versa. As it happens, the concept of clipping exists in both CSS and SVG. The SVG clipping syntax is quite different, but it works the same. The good thing about SVG clipping compared to CSS is its maturity level. Support is good all the way back to old IE browsers. Most bugs are fixed by now (or at least one hope they are).

This is what the SVG clipping support looks like:

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

Desktop

Chrome Opera Firefox IE Edge Safari
4 9 3 9 12 3.2

Mobile / Tablet

iOS Safari Opera Mobile Opera Mini Android Android Chrome Android Firefox
3.2 10 all 4.4 67 60

Clipping as a transition

A neat use case for clipping is transition effects. Take The Silhouette Slideshow demo on CodePen:

See the Pen Silhouette zoom slideshow by Mikael Ainalem (@ainalem) on CodePen.

A “regular” slideshow cycles though images. Here, to make it a bit more interesting, there’s a clipping effect when switching images. The next image enters the screen through a silhouette of of the previous image. This creates the illusion that the images are connected to one another, even if they are not.

The transitions follow this process:

  1. Identify the focal point (i.e., main subject) of the image
  2. Create a clipping path for that object
  3. Cut the next image with the path
  4. The cut image (silhouette) fades in
  5. Scale the clipping path until it’s bigger than the viewport
  6. Complete the transition to display the next image
  7. Repeat!

Let’s break down the sequence, starting with the first image. We’ll split this up into multiple pens so we can isolate each step.

<svg> ... <image class="..." xlink:href="..." /> ... </svg>

For this image, we then want to create a mask of the focal point — in this case, the person’s silhouette. If you’re unsure how to go about creating a clip, check out my previous article for more details because, generally speaking, making cuts in CSS and SVG is fundamentally the same:

  1. Import an image into the SVG editor
  2. Draw a path around the object
  3. Convert the path to the syntax for SVG clip path. This is what goes in the SVG’s <defs> block.
  4. Paste the SVG markup into the HTML

If you’re handy with the editor, you can do most of the above in the editor. Most editors have good support for masks and clip paths. I like to have more control over the markup, so I usually do at least some of the work by hand. I find there’s a balance between working with an SVG editor vs. working with markup. For example, I like to organize the code, rename the classes and clean up any cruft the editor may have dropped in there.

Mozilla Developer Network does a fine job of documenting SVG clip paths. Here’s a stripped-down version of the markup used by the original demo to give you an idea of how a clip path fits in:

<svg> <defs> <clipPath id="clip"> <!-- Clipping defined --> <path class="clipPath clipPath2" d="..." /> </clipPath> </defs> ... <path ... clip-path="url(#clip)"/> <!-- Clipping applied -->
</svg>

Let’s use a colored rectangle as a placeholder for the next image in the slideshow. This helps to clearly visualize the shape that part that’s cut out and will give a clearer idea of the shape and its movement.

.clipPath { transition: transform 1200ms 500ms; /* Delayed transform transition */ transform-origin: 50%; } .clipPath.active { transform: translateX(-30%) scale(15); /* Upscaling and centering mask */ } .image { transition: opacity 1000ms; /* Fade-in, starts immediately */ opacity: 0; } .image.active { opacity: 1; }

Here’s what we get — an image that transitions to the rectangle!

remove = (remove + 1) % images.length; current = (current + 1) % images.length

Note that this examples is not supported by Firefox at the time of writing because is lacks support for scaling clip paths. I hope this is something that will be addressed in the near future.

Clipping to emerge foreground objects into the background

Another interesting use for clipping is for revealing and hiding effects. We can create parts of the view where objects are either partly or completely hidden making for a fun way to make background images interact with foreground content. For instance, we could have objects disappear behind elements in the background image, say a building or a mountain. It becomes even more interesting when we pair that idea up with animation or scrolling effects.

See the Pen Parallax clip by Mikael Ainalem (@ainalem) on CodePen.

This example uses a clipping path to create an effect where text submerges into the photo — specifically, floating behind mountains as a user scrolls down the page. To make it even more interesting, the text moves with a parallax effect. In other words, the different layers move at different speeds to enhance the perspective.

We start with a simple div and define a background image for it in the CSS:

window.addEventListener('scroll', function() { logo.setAttribute('transform',`translate(0 ${html.scrollTop / 10 + 5})`); clip.setAttribute('transform',`translate(0 -${html.scrollTop / 10 + 5})`); });

Don’t pay too much attention to the + 5 used when calculating the distance. It’s only there as a sloppy way to offset the element. The important part is where things are divided by 10, which creates the parallax effect. Scrolling a certain amount will proportionally move the element and the clip path. Template literals convert the calculated value to a string which is used for the transform property value as an offset to the SVG nodes.

Combining clipping and masking

Clipping and masking are two interesting concepts. One lets you cut out pieces of content whereas the other let’s you do the opposite. Both techniques are useful by themselves but there is no reason why we can’t combine their powers!

When combining clipping and masking, you can split up objects to create different visual effects on different parts. For example:

See the Pen parallax logo blend by Mikael Ainalem (@ainalem) on CodePen.

I created this effect using both clipping and masking on a logo. The text, split into two parts, blends with the background image, which is a beautiful monochromatic image of the New York’s Statue of Liberty. I use different colors and opacities on different parts of the text to make it stand out. This creates an interesting visual effect where the text blends in with the background when it overlaps with the statue — a splash of color to an otherwise grey image. There is, besides clipping and masking, a parallax effect here as well. The text moves in a different speed relative to the image when the user hovers or moves (touch) over the image.

To illustrate the behavior, here is what we get when the masked part is stripped out:

See the Pen parallax logo blend by Mikael Ainalem (@ainalem) on CodePen.

Wrapping up

Clipping is a fun way to create interactions and visual effects. It can enhance slide-shows or make objects stand out of images, among other things. Both SVG and CSS provide the ability to apply clip paths and masks to elements, though with different syntaxes. We can pretty much cut any web content nowadays. It is only your imagination that sets the limit.

If you happen to create anything cool with the things we covered here, please share them with me in the comments!

The post Using CSS Clip Path to Create Interactive Effects, Part II appeared first on CSS-Tricks.

::before vs :before

Note the double-colon ::before versus the single-colon :before. Which one is correct?

Technically, the correct answer is ::before. But that doesn’t mean you should automatically use it.

The situation is that:

  • double-colon selectors are pseudo-elements.
  • single-colon selectors are pseudo-selectors.

::before is definitely a pseudo-element, so it should use the double colon.

The distinction between a pseudo-element and pseudo-selector is already confusing. Fortunately, ::after and ::before are fairly straightforward. They literally add something new to the page, an element.

But something like ::first-letter is also a pseudo-element. The way I reason that out in my brain is that it’s selecting a part of something in which there is no existing HTML element for. There is no <span> around that first letter you’re targeting, so that first letter is almost like a new element you’re adding on the page. That differs from pseudo-selectors which are selecting things that already exist, like the :nth-child(2) or whatever.

Even though ::before is a pseudo-element and a double-colon is the correct way to use pseudo-elements, should you?

There is an argument that perhaps you should use :before, which goes like this:

  1. Internet Explorer 8 and below only supported :before, not ::before
  2. All modern browsers support it both ways, since tons of sites use :before and browsers really value backwards compatibility.
  3. Hey it’s one less character as a bonus.

I’ve heard people say that they have a CSS linter that requires (or automates) them to be single-colon. Personally, I’m OK with people doing that. Seems fine. I’d value consistency over which way you choose to go.

On the flip side, there’s an argument for going with ::before that goes like this:

  1. Single-colon pseudo-elements were a mistake. There will never be any more pseudo-elements with a single-colon.
  2. If you have the distinction straight in your mind, might as well train your fingers to do it right.
  3. This is already confusing enough, so let’s just follow the correctly specced way.

I’ve got my linter set up to force me to do double-colons. I don’t support Internet Explorer 8 anyway and it feels good to be doing things the “right” way.

The post ::before vs :before appeared first on CSS-Tricks.

A Basic WooCommerce Setup to Sell T-Shirts

WooCommerce is a powerful eCommerce solution for WordPress sites. If you’re like me, and like working with WordPress and have WordPress-powered sites already, WooCommerce is a no-brainer for helping you sell things online on those sites. But even if you don’t already have a WordPress site, WooCommerce is so good I think it would make sense to spin up a WordPress site so you could use it for your eCommerce solution.

Personally, I’ve used WooCommerce a number of times to sell things. Most recently, I’ve used it to sell T-Shirts (and hats) over on CodePen. We use WordPress already to power our blog, documentation, and podcast. Makes perfect sense to use WordPress for the store as well!

What I think is notable about our WooCommerce installation at CodePen is how painless it was, while doing everything we need it to do. I’d say it was a half-day job with maybe a half-day of maintenance every few months, partially based on us wanting to change something.

The first step is installing the plugin, and immediately you get a Products post type you can use to add new products. We’re selling a T-Shirt, so that looks like this:

Note the variations in use for size. We even track inventory at the size level so our T-Shirt printing company knows when to re-print different sizes.

What is somewhat astounding about WooCommerce is that you might need to do very little else. You could set a price, flip on the basic PayPal integration and enter your email, publish the product, and start taking orders.

Or, you could start customizing things and do as much or as little as you want:

  • You could add as many different payment processors as you like. We like using Stripe for credit card processing at CodePen, but also offer PayPal.
  • You could customize the template of every different page involved, or just use the defaults. At CodePen we have very lightly customized templates for the store homepage and product page.
  • You could get very detailed with calculating shipping costs, or use flat rates. We use a flat rate shipping cost at CodePen almost as marketing: same shipping cost anywhere in the world!
  • You could get into integrations, like connecting it with your MailChimp account for further email marketing or Slack account to notify your team of sales.

If you can dream it, you can do it with WooCommerce.

At CodePen, we work with a company called RealThread that actually prints and ships the T-Shirts.

They work great with WooCommerce of course, and the way we set that up is that we use the ShipStation integration and blast the orders into their account there and they handle all the fulfillment from there. There are all sorts of shipping method plugins though for anything you can think of.

Within WooCommerce, we have a dashboard of all the orders, their status, and even tracking information should we need to look something up.

So essentially:

  1. We use WooCommerce
  2. We use the Stripe plugin to take our credit card payments that way
  3. We use the PayPal plugin to take PayPal payments over Braintree
  4. We use the ShipStation plugin to send orders to that system for our fulfillment company to handle

It was quite easy to set up and works great, and it’s comforting to know that we could do tons more with it if we needed to and support is there to help.

The post A Basic WooCommerce Setup to Sell T-Shirts appeared first on CSS-Tricks.

Using feature detection to write CSS with cross-browser support

In early 2017, I presented a couple of workshops on the topic of CSS feature detection, titled CSS Feature Detection in 2017.

A friend of mine, Justin Slack from New Media Labs, recently sent me a link to the phenomenal Feature Query Manager extension (available for both Chrome and Firefox), by Nigerian developer Ire Aderinokun. This seemed to be a perfect addition to my workshop material on the subject.

However, upon returning to the material, I realized how much my work on the subject has aged in the last 18 months.

The CSS landscape has undergone some tectonic shifts:

  • The Atomic CSS approach, although widely hated at first, has gained some traction through libraries like Tailwind, and perhaps influenced the addition of several new utility classes to Bootstrap 4.
  • CSS-in-JS exploded in popularity, with Styled Components at the forefront of the movement.
  • The CSS Grid Layout spec has been adopted by browser vendors with surprising speed, and was almost immediately sanctioned as production ready.

The above prompted me to not only revisit my existing material, but also ponder the state of CSS feature detection in the upcoming 18 months.

In short:

  1. ❓ Why do we need CSS feature detection at all?
  2. 🛠️ What are good (and not so good) ways to do feature detection?
  3. 🤖 What does the future hold for CSS feature detection?

Cross-browser compatible CSS

When working with CSS, it seems that one of the top concerns always ends up being inconsistent feature support among browsers. This means that CSS styling might look perfect on my browsers of choice, but might be completely broken on another (perhaps an even more popular) browser.

Luckily, dealing with inconsistent browser support is trivial due to a key feature in the design of the CSS language itself. This behavior, called fault tolerance, means that browsers ignore CSS code they don’t understand. This is in stark contrast to languages like JavaScript or PHP that stop all execution in order to throw an error.

The critical implication here is that if we layer our CSS accordingly, properties will only be applied if the browser understands what they mean. As an example, you can include the following CSS rule and the browser will just ignore it— overriding the initial yellow color, but ignoring the third nonsensical value:

background-color: yellow;
background-color: blue; /* Overrides yellow */
background-color: aqy8godf857wqe6igrf7i6dsgkv; /* Ignored */

To illustrate how this can be used in practice, let me start with a contrived, but straightforward situation:

A client comes to you with a strong desire to include a call-to-action (in the form of a popup) on his homepage. With your amazing front-end skills, you are able to quickly produce the most obnoxious pop-up message known to man:

Unfortunately, it turns out that his wife has an old Windows XP machine running Internet Explorer 8. You’re shocked to learn that what she sees no longer resembles a popup in any shape or form.

But! We remember that by using the magic of CSS fault tolerance, we can remedy the situation. We identify all the mission-critical parts of the styling (e.g., the shadow is nice to have, but does not add anything useful usability-wise) and buffer prepend all core styling with fallbacks.

This means that our CSS now looks something like the following (the overrides are highlighted for clarity):

.overlay { background: grey; background: rgba(0, 0, 0, 0.4); border: 1px solid grey; border: 1px solid rgba(0, 0, 0, 0.4); padding: 64px; padding: 4rem; display: block; display: flex; justify-content: center; /* if flex is supported */ align-items: center; /* if flex is supported */ height: 100%; width: 100%;
} .popup { background: white; background-color: rgba(255, 255, 255, 1); border-radius: 8px; border: 1px solid grey; border: 1px solid rgba(0, 0, 0, 0.4); box-shadow: 0 7px 8px -4px rgba(0,0, 0, 0.2), 0 13px 19px 2px rgba(0, 0, 0, 0.14), 0 5px 24px 4px rgba(0, 0, 0, 0.12); padding: 32px; padding: 2rem; min-width: 240px;
} button { background-color: #e0e1e2; background-color: rgba(225, 225, 225, 1); border-width: 0; border-radius: 4px; border-radius: 0.25rem; box-shadow: 0 1px 3px 0 rgba(0,0,0,.2), 0 1px 1px 0 rgba(0,0,0,.14), 0 2px 1px -1px rgba(0,0,0,.12); color: #5c5c5c; color: rgba(95, 95, 95, 1); cursor: pointer; font-weight: bold; font-weight: 700; padding: 16px; padding: 1rem;
} button:hover { background-color: #c8c8c8; background-color: rgb(200,200,200); }

The above example generally falls under the broader approach of Progressive Enhancement. If you’re interested in learning more about Progressive Enhancement check out Aaron Gustafson’s second edition of his stellar book on the subject, titled Adaptive Web Design: Crafting Rich Experiences with Progressive Enhancement (2016).

If you’re new to front-end development, you might wonder how on earth does one know the support level of specific CSS properties. The short answer is that the more you work with CSS, the more you will learn these by heart. However, there are a couple of tools that are able to help us along the way:

  • Can I Use is a widely used directory that contains searchable, up to date support matrices for all CSS features.
  • Stylelint has a phenomenal plugin-called called No Unsupported Browser Features that gives scours errors for unsupported CSS (defined via Browserslist) either in your editor itself or via a terminal command.
  • There are several tools like BrowserStack or Cross Browser Testing that allow you to remotely test your website on different browsers. Note that these are paid services, although BrowserStack has a free tier for open source projects.

Even with all the above at our disposal, learning CSS support by heart will help us plan our styling up front and increase our efficiency when writing it.

Limits of CSS fault tolerance

The next week, your client returns with a new request. He wants to gather some feedback from users on the earlier changes that were made to the homepage—again, with a pop-up:

Once again it will look as follows in Internet Explorer 8:

Being more proactive this time, you use your new fallback skills to establish a base level of styling that works on Internet Explorer 8 and progressive styling for everything else. Unfortunately, we still run into a problem…

In order to replace the default radio buttons with ASCII hearts, we use the ::before pseudo-element. However this pseudo-element is not supported in Internet Explorer 8. This means that the heart icon does not render; however the display: none property on the <input type="radio"> element still triggers on Internet Explorer 8. The implication being that neither the replacement behavior nor the default behavior is shown.

Credit to John Faulds for pointing out that it is actually possible to get the ‘::before’ pseudo-element to work in Internet Explorer 8 if you replace the official double colon syntax with a single colon.

In short, we have a rule (display: none) whose execution should not be bound to its own support (and thus its own fallback structure), but to the support level of a completely separate CSS feature (::before).

For all intents and purposes, the common approach is to explore whether there are more straightforward solutions that do not rely on ::before. However, for the sake of this example, let’s say that the above solution is non-negotiable (and sometimes they are).

Enter User Agent Detection

A solution might be to determine what browser the user is using and then only apply display: none if their browser supports the ::before pseudo-element.

In fact, this approach is almost as old as the web itself. It is known as User Agent Detection or, more colloquially, browser sniffing.

It is usually done as follows:

  • All browsers add a JavaScript property on the global window object called navigator and this object contains a userAgent string property.
  • In my case, the userAgent string is: Mozilla/5.0 (Windows NT10.0;Win64;x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.9 Safari/537.36.
  • Mozilla Developer Network has a comprehensive list of how the above can be used to determine the browser.
  • If we are using Chrome, then the following should return true: (navigator.userAgent.indexOf("chrome") !== -1).
  • However, under the Internet Explorer section on MDN, we just get Internet Explorer. IE doesn’t put its name in the BrowserName/VersionNumber format.
  • Luckily, Internet Explorer provides its own native detection in the form of Conditional Comments.

This means that adding the following in our HTML should suffice:

<!--[if lt IE 9]> <style> input { display: block; } </style>
<![endif]-->

This means that the above will be applied, should the browser be a version of Internet Explorer lower than version 9 (IE 9 supports ::before)—effectively overriding the display: none property.
Seems straightforward enough?

Unfortunately, over time, some critical flaws emerged in User Agent Detection. So much so that Internet Explorer stopped supporting Conditional Comments from version 10 onward. You will also notice that in the Mozilla Developer Network link itself, the following is presented in an orange alert:

It’s worth re-iterating: it’s very rarely a good idea to use user agent sniffing. You can almost always find a better, more broadly compatible way to solve your problem!

The biggest drawback of User Agent Detection is that browser vendors started spoofing their user agent strings over time due to the following:

  • Developer adds CSS feature that is not supported in the browser.
  • Developer adds User Agent Detection code to serve fallbacks to the browser.
  • Browser eventually adds support for that specific CSS feature.
  • Original User Agent Detection code is not updated to take this into consideration.
  • Code always displays the fallback, even if the browser now supports the CSS feature.
  • Browser uses a fake user agent string to give users the best experience on the web.

Furthermore, even if we were able to infallibly determine every browser type and version, we would have to actively maintain and update our User Agent Detection to reflect the feature support state of those browsers (notwithstanding browsers that have not even been developed yet).

It is important to note that although there are superficial similarities between feature detection and User Agent Detection, feature detection takes a radically different approach than User Agent Detection. According to the Mozilla Developer Network, when we use feature detection, we are essentially doing the following:

  1. 🔎 Testing whether a browser is actually able to run a specific line (or lines) of HTML, CSS or JavaScript code.
  2. 💪 Taking a specific action based on the outcome of this test.

We can also look to Wikipedia for a more formal definition (emphasis mine):

Feature detection (also feature testing) is a technique used in web development for handling differences between runtime environments (typically web browsers or user agents), by programmatically testing for clues that the environment may or may not offer certain functionality. This information is then used to make the application adapt in some way to suit the environment: to make use of certain APIs, or tailor for a better user experience.

While a bit esoteric, this definition does highlight two important aspects of feature detection:

  • Feature detection is a technique, as opposed to a specific tool or technology. This means that there are various (equally valid) ways to accomplish feature detection.
  • Feature detection programmatically tests code. This means that browsers actually run a piece of code to see what happens, as opposed to merely using inference or comparing it against a theoretical reference/list as done with User Agent Detection.

CSS feature detection with @supports

The core concept is not to ask “What browser is this?” It’s to ask “Does your browser support the feature I want to use?”.

—Rob Larson, The Uncertain Web: Web Development in a Changing Landscape (2014)

Most modern browsers support a set of native CSS rules called CSS conditional rules. These allow us to test for certain conditions within the stylesheet itself. The latest iteration (known as module level 3) is described by the Cascading Style Sheets Working Group as follows:

This module contains the features of CSS for conditional processing of parts of style sheets, conditioned on capabilities of the processor or the document the style sheet is being applied to. It includes and extends the functionality of CSS level 2 [CSS21], which builds on CSS level 1 [CSS1]. The main extensions compared to level 2 are allowing nesting of certain at-rules inside ‘@media’, and the addition of the ‘@supports’ rule for conditional processing.

If you’ve used @media, @document or @import before, then you already have experience working with CSS conditional rules. For example when using CSS media queries we do the following:

  • Wrap a single or multiple CSS declarations in a code block with curly brackets, { }.
  • Prepend the code block with a @media query with additional information.
  • Include an optional media type. This can either be all, print, speech or the commonly used screen type.
  • Chain expressions with and/or to determine the scope. For example, if we use (min-width: 300px) and (max-width: 800px), it will trigger the query if the screen size is wider than 300 pixels and smaller than 800 pixels.

The feature queries spec (editor’s draft) prescribes behavior that is conveniently similar to the above example. Instead of using a query expression to set a condition based on the screen size, we write an expression to scope our code block according to a browser’s CSS support (emphasis mine):

The ‘@supports rule allows CSS to be conditioned on implementation support for CSS properties and values. This rule makes it much easier for authors to use new CSS features and provide good fallback for implementations that do not support those features. This is particularly important for CSS features that provide new layout mechanisms, and for other cases where a set of related styles needs to be conditioned on property support.

In short, feature queries are a small built-in CSS tool that allow us to only execute code (like the display: none example above) when a browser supports a separate CSS feature—and much like media queries, we are able to chain expressions as follows: @supports (display: grid) and ((animation-name: spin) or (transition: transform(rotate(360deg)).

So, theoretically, we should be able to do the following:

@supports (::before) { input { display: none; }
}

Unfortunately, it seems that in our example above the display: none property did not trigger, in spite of the fact that your browser probably supports ::before.

That’s because there are some caveats to using @supports:

  • First and foremost, CSS feature queries only support CSS properties and not CSS pseudo-element, like ::before.
  • Secondly, you will see that in the above example our @supports (transform: scale(2)) and (animation-name: beat) condition fires correctly. However if we were to test it in Internet Explorer 11 (which supports both transform: scale(2) and animation-name: beat) it does not fire. What gives? In short, @supports is a CSS feature, with a support matrix of its own.

CSS feature detection with Modernizr

Luckily, the fix is fairly easy! It comes in the form of an open source JavaScript library named Modernizr, initially developed by Faruk Ateş (although it now has some pretty big names behind it, like Paul Irish from Chrome and Alex Sexton from Stripe).

Before we dig into Modernizr, let’s address a subject of great confusion for many developers (partly due to the name “Modernizr” itself). Modernizr does not transform your code or magically enable unsupported features. In fact, the only change Modernzr makes to your code is appending specific CSS classes to your <html> tag.

This means that you might end up with something like the following:

<html class="js flexbox flexboxlegacy canvas canvastext webgl no-touch geolocation postmessage websqldatabase indexeddb hashchange history draganddrop websockets rgba hsla multiplebgs backgroundsize borderimage borderradius boxshadow textshadow opacity cssanimations csscolumns cssgradients cssreflections csstransforms csstransforms3d csstransitions fontface generatedcontent video audio localstorage sessionstorage webworkers applicationcache svg inlinesvg smil svgclippaths">

That is one big HTML tag! However, it allows us do something super powerful: use the CSS descendant selector to conditionally apply CSS rules.

When Modernizr runs, it uses JavaScript to detect what the user’s browser supports, and if it does support that feature, Modernizr injects the name of it as a class to the <html>. Alternatively, if the browser does not support the feature, it prefixes the injected class with no- (e.g., no-generatedcontent in our ::before example). This means that we can write our conditional rule in the stylesheet as follows:

.generatedcontent input { display: none
}

In addition, we are able to replicate the chaining of @supports expressions in Modernizr as follows:

/* default */
.generatedcontent input { } /* 'or' operator */
.generatedcontent input, .csstransforms input { } /* 'and' operator */
.generatedcontent.csstransformsinput { } /* 'not' operator */
.no-generatedcontent input { }

Since Modernizr runs in JavaScript (and does not use any native browser APIs), it’s effectively supported on almost all browsers. This means that by leveraging classes like generatedcontent and csstransforms, we are able to cover all our bases for Internet Explorer 8, while still serving bleeding-edge CSS to the latest browsers.

It is important to note that since the release of Modernizr 3.0, we are no longer able to download a stock-standard modernizr.js file with everything except the kitchen sink. Instead, we have to explicitly generate our own custom Modernizr code via their wizard (to copy or download). This is most likely in response to the increasing global focus on web performance over the last couple of years. Checking for more features contributes to more loading, so Modernizr wants us to only check for what we need.

So, I should always use Modernizr?

Given that Modernizr is effectively supported across all browsers, is there any point in even using CSS feature queries? Ironically, I would not only say that we should but that feature queries should still be our first port of call.

First and foremost, the fact that Modernizr does not plug directly into the browser API is it’s greatest strength—it does not rely on the availability of a specific browser API. However, this benefit comes a cost, and that cost is additional overhead to something most browsers support out of the box through @supports—especially when you’re delivering this additional overhead to all users indiscriminately in order to a small amount of edge users. It is important to note that, in our example above, Internet Explorer 8 currently only stands at 0.18% global usage).

Compared to the light touch of @supports, Modernizr has the following drawbacks:

  • The approach underpinning development of Modernizr is driven by the assumption that Modernizr was “meant from day one to eventually become unnecessary.”
  • In the majority of cases, Modernizr needs to be render blocking. This means that Modernizr needs to be downloaded and executed in JavaScript before a web page can even show content on the screen—increasing our page load time (especially on mobile devices)!
  • In order to run tests, Modernizr often has to actually build hidden HTML nodes and test whether it works. For example, in order to test for <canvas> support, Modernizr executes the follow JavaScript code: return !!(document.createElement('canvas').getContext && document.createElement('canvas').getContext('2d'));. This consumes CPU processing power that could be used elsewhere.
  • The CSS descendant selector pattern used by Modernizr increases CSS specificity. (See Harry Roberts’ excellent article on why “specificity is a trait best avoided.”)
  • Although Modernizr covers a lot of tests (150+), it still does not cover the entire spectrum of CSS properties like @support does. The Modernizr team actively maintains a list of these undetectables.

Given that feature queries have already been widely implemented across the browser landscape, (covering about 93.42% of global browsers at the time of writing), it’s been a good while since I’ve used Modernizr. However, it is good to know that it exists as an option should we run into the limitations of @supports or if we need to support users still locked into older browsers or devices for a variety of potential reasons.

Furthermore, when using Modernizr, it is usually in conjunction with @supports as follows:

.generatedcontent input { display: none;
} label:hover::before { color: #c6c8c9;
} input:checked + label::before { color: black;
} @supports (transform: scale(2)) and (animation-name: beat) { input:checked + label::before { color: #e0e1e2; animation-name: beat; animation-iteration-count: infinite; animation-direction: alternate; }
}

This triggers the following to happen:

  • If ::before is not supported, our CSS will fallback to the default HTML radio select.
  • If neither transform(scale(2)) nor animation-name: beat are supported but ::before is, then the heart icon will change to black instead of animate when selected.
  • If transform(scale(2), animation-name: beat and ::before are supported, then the heart icon will animate when selected.

The future of CSS feature detection

Up until this point, I’ve shied away from talking about feature detection in a world being eaten by JavaScript, or possibly even a post-JavaScript world. Perhaps even intentionally so, since current iterations at the intersection between CSS and JavaScript are extremely contentious and divisive.

From that moment on, the web community was split in two by an intense debate between those who see CSS as an untouchable layer in the “separation of concerns” paradigm (content + presentation + behaviour, HTML + CSS + JS) and those who have simply ignored this golden rule and found different ways to style the UI, typically applying CSS styles via JavaScript. This debate has become more and more intense every day, bringing division in a community that used to be immune to this kind of “religion wars”.

—Cristiano Rastelli, Let there be peace on CSS (2017)

However, I think exploring how to apply feature detection in the modern CSS-in-JS toolchain might be of value as follows:

  • It provides an opportunity to explore how CSS feature detection would work in a radically different environment.
  • It showcases feature detection as a technique, as opposed to a specific technology or tool.

With this in mind, let us start by examining an implementation of our pop-up by means of the most widely-used CSS-in-JS library (at least at the time of writing), Styled Components:

This is how it will look in Internet Explorer 8:

In our previous examples, we’ve been able to conditionally execute CSS rules based on the browser support of ::before (via Modernizr) and transform (via @supports). However, by leveraging JavaScript, we are able to take this even further. Since both @supports and Modernizr expose their APIs via JavaScript, we are able to conditionally load entire parts of our pop-up based solely on browser support.

Keep in mind that you will probably need to do a lot of heavy lifting to get React and Styled Components working in a browser that does not even support ::before (checking for display: grid might make more sense in this context), but for the sake of keeping with the above examples, let us assume that we have React and Styled Components running in Internet Explorer 8 or lower.

In the example above, you will notice that we’ve created a component, called ValueSelection. This component returns a clickable button that increments the amount of likes on click. If you are viewing the example on a slightly older browser, you might notice that instead of the button you will see a dropdown with values from 0 to 9.

In order to achieve this, we’re conditionally returning an enhanced version of the component only if the following conditions are met:

if ( CSS.supports('transform: scale(2)') && CSS.supports('animation-name: beat') && Modernizr.generatedcontent
) { return ( <React.Fragment> <Modern type="button" onClick={add}>{string}</Modern> <input type="hidden" name="liked" value={value} /> </React.Fragment> )
} return ( <Base value={value} onChange={select}> { [1,2,3,4,5,6,7,8,9].map(val => ( <option value={val} key={val}>{val}</option> )) } </Base>
);

What is intriguing about this approach is that the ValueSelection component only exposes two parameters:

  • The current amount of likes
  • The function to run when the amount of likes are updated
<Overlay> <Popup> <Title>How much do you like popups?</Title> <form> <ValueInterface value={liked} change={changeLike} /> <Button type="submit">Submit</Button> </form> </Popup>
</Overlay>

In other words, the component’s logic is completely separate from its presentation. The component itself will internally decide what presentation works best given a browser’s support matrix. Having the conditional presentation abstracted away inside the component itself opens the door to exciting new ways of building cross-browser compatible interfaces when working in a front-end and/or design team.

Here’s the final product:

…and how it should theoretically look in Internet Explorer 8:

Additional Resources

If you are interested in diving deeper into the above you can visit the following resources:

  • Mozilla Developer Network article on feature detection
  • Mozilla Developer Network article on user agent detection
  • Mozilla Developer Network article on CSS feature queries
  • Official feature queries documentation by the CSSWG
  • Modernizr Documentation

Schalk is a South African front-end developer/designer passionate about the role technology and the web can play as a force for good in his home country. He works full time with a group of civic tech minded developers at a South African non-profit called OpenUp.

He also helps manage a collaborative space called Codebridge where developers are encouraged to come and experiment with technology as a tool to bridge social divides and solve problems alongside local communities.

The post Using feature detection to write CSS with cross-browser support appeared first on CSS-Tricks.

ABeamer: a frame-by-frame animation framework

In a recent post, Zach Saucier demonstrated the awesome things that the DOM allows us to do, thanks to the <canvas> element. Taking a snapshot of an element and manipulating it to create an exploding animation is pretty slick and a perfect example of how far complex animations have come in the last few years.

ABeamer is a new animation ecosystem that takes advantage of these new concepts. At the core of the ecosystem is the web browser animation library. But, it’s not just another animation engine. ABeamer is designed to build frame-by-frame animations in the web browser and use a render server to generate a PNG file sequence, which can ultimately be used to create an animated GIF or imported into a video editor.

First, a little about what ABeamer can do

A key feature is its ability to hook into remote sources. This allows us to build an animation by using the web browser and “beam” it to the cloud to be remotely rendered—hence the name “ABeamer.”

ABeamer doesn’t only distinguish itself from other animation frameworks by its capacity to render elements in a file sequence, but it also includes a rich and extensible toolset that is still growing, avoiding the need to constantly rewrite common animations.

ABeamer’s frame-by-frame design allows it to create overlays without dropping frames. (Demo)

The purpose isn’t to be another Velocity or similar real-time web browser animation library, but to use the web technologies that have become mainstream and allow us to create pure animations, image overlays and video edits from the browser.

I have plans to create an interface for ABeamer that acts as an animation editor. This will abstract the need to write code, making the technology accessible to folks at places like ad networks and e-commerce companies who might want to provide their customers a simple tool to build rich, animated content instead of static images for ad placements. It can create titles, filter effects, transitions, and ultimately build videos directly from image slideshows without having to install any software.

In other words, taking advantage of all these effects and features will require no coding skills whatsoever, which opens this up to new use cases and a wider audience.

Create animated GIFs like this out of images. (Demo)

But if JavaScript is used, what about security? ABeamer has two modes of server rendering: one for trusted environments such as company intranets that renders the HTML/CSS/JavaScript as it was built by sending the files; and another for untrusted environments such as cloud render servers that renders teleported stories by sending them by AJAX along with the assets. Teleportation sanitizes the content both on the client side and the server side. The JavaScript that is used during interpolation process is not allowed, nor is any plug-in that isn’t on an authorization list. ABeamer supports expressions, which are safe, teleportable, and in many cases, it can replace the need of JavaScript code.

Example of an advertisement made with ABeamer (Demo)

The last key feature is decoupling. ABeamer doesn’t operate directly with the document DOM, but instead uses adapters as a middleman, allowing us to animate SVG, canvas, WebGL, or any other virtual element.

Several examples of the chart animations built into ABeamer. (Demo)

Getting started with ABeamer

Now that we’ve covered a lot of ground for what ABeamer is capable of doing, let’s dive into what it takes to get up and running with it.

Installation

The ABeamer animation library can be downloaded or cloned on GitHub, but in order to generate animated GIFs, movies, or simplify the process of getting started, you’ll want to install it with npm:

# 1. install nodejs: https://www.nodejs.org # 2. install abeamer
$ npm install -g abeamer # 2. learn how to configure puppeteer to use chrome instead of chromium
$ abeamer check # 3. install a render server (requires chrome web browser) $ npm install -g puppeteer # 4. install imagemagick: https://www.imagemagick.org # 5. install ffmpeg: https://www.ffmpeg.org/

Puppeteer is installed separately, since other server renders are also supported, like PhantomJS. Still, Puppeteer running on Chrome will produce the best results.

Spinning up a new project

The best way to get started it’s to use the ABeamer CLI to create a new project:

abeamer create my-project --width 640 --height 480

This will create a project with the following files:

  • abeamer.ini – Change this file to modify the frame dimensions and recompile main.scss. This file will be used by the server render and main.scss.
$abeamer-width: 640;
$abeamer-height: 480;
  • css/main.scss – CSS can also be used instead of SCSS, but it requires to change the dimensions in two places.
@import "./../abeamer.ini"; body,
html,
.abeamer-story,
.abeamer-scene { width: $abeamer-width + px; height: $abeamer-height + px;
} #hello { position: absolute; color: red; left: 50px; top: 40px;
}

ABeamer content is defined inside a story, much like a theater play. Each story can have multiple scenes.

  • index.html – This is inside a scene where the animation happens:
<div class="abeamer-story" id=story> <div class="abeamer-scene" id=scene1> <div id=hello>Hello <span id=world>World</span> </div> </div>
</div>
  • js/main.ts – ABeamer was built using TypeScript, but you can use plain JavaScript. However, using TypeScript allows you to tap into ABeamer type definitions and Visual Studio Code IntelliSense:
$(window).on("load", () => { const story: ABeamer.Story = ABeamer.createStory(/*FPS:*/25); const scene1 = story.scenes[0]; scene1.addAnimations([{ selector: '#hello', duration: '2s', props: [{ // pixel property animation. // uses CSS property `left` to determine the start value. prop: 'left', // this is the end value. it must be numeric. value: 100, }, { // formatted numerical property animation. prop: 'transform', valueFormat: 'rotate(%fdeg)', // this is the start value, // it must be always defined for the property `transform`. valueStart: 10, // this is the end value. it must be numeric. value: 100, }], }, { selector: '#world', duration: '2s', props: [{ // textual property animation. prop: 'text', valueText: ['World', 'Mars', 'Jupiter'], }], }]); story.render(story.bestPlaySpeed());
});

Live Demo

You may notice some differences between ABeamer and other web animation libraries:

  • ABeamer uses load instead of a ready event. This is due the fact that the app was designed to generate frame files, and unlike real-time animation, it requires all assets to be loaded before the process begins.
  • It sets CORS, if it required to load a JSON, it needs a live server.

    To solve this, ABeamer has included a live server. Spin it up with this:

    # 1. runs a live server on port 9000
    $ abeamer serve

    This will assign your project to: http://localhost:9000/my-project/

    The render command then becomes:

    $ abeamer render my-project --url http://localhost:9000/my-project/

    Cloud Rendering

    At the moment, there is no third-party cloud rendering. But as the project gains traction, I’m hoping that cloud companies see the potential and provide it as a service in the same manner as Google provides computation of Big Data that server farms can use as cloud render servers.

    The benefits of cloud rendering would be huge:

    • It wouldn’t require any software installation on the client machine. Instead, it can all be done on the web browser. While there is currently no ABeamer UI, online code editors can be used, like CodePen.
    • Heavy render processes could be designed in a client machine and then sent to be rendered in the cloud.
    • Hybrid apps would be able to use ABeamer to build animations and then send them to the cloud to generate movies or animated GIFs on demand.

    That said, cloud rendering is more restrictive than the server render since it doesn’t send the files, but rather a sanitized version of the story:

    • Interactive JavaScript code isn’t allowed, so case expressions are required
    • All animations are sanitized.
    • The animation can only use plugins that are allowed by the cloud server provider.

    Setting up a cloud render server

    If you are working in an environment where installing software locally isn’t allowed, or you have multiple users building animations, then it might be worth setting your own cloud render server.

    Due to CORS, an animation must either be in a remote URL or have a live server in order to be sent to the cloud server.

    The process of preparing, sending, and rebuilding on remote server side it is called teleportation. Animations requires changes to be teleported:

    $(window).on("load", () => { const story: ABeamer.Story = ABeamer.createStory(/*FPS:*/25, { toTeleport: true }); // the rest of the animation code // .... const storyToTeleport = story.getStoryToTeleportAsConfig(); // render is no longer needed // story.render(story.bestPlaySpeed());
    });

    By setting toTeleport=true, ABeamer starts recording every animation in a way that it can be sent to the server. The storyToTeleport method will hold an object containing the animations, CSS, HTML and metadata. You need to send this by AJAX along with the required assets to the cloud.

    On the server side, a web server will receive the data and the assets and it will execute ABeamer to generate the resulting files.

    To prepare the server:

    • Create a simple project named remote-server using the command abeamer create remote-server.
    • Download the latest remote server code, extract the files, and override them with the ones existing in remote-server.
    • Save the received object from AJAX as remote-server/story.json and save all assets in the project.
    • Start a live server as you normally would using the abeamer serve command.
    • Render the teleported story:
    abeamer render \
    --url http://localhost:9000/remote-server/ \
    --allowed-plugins remote-server/.allowed-plugins.json \
    --inject-page remote-server/index.html \
    --config remote-server/story.json

    This will generate the PNG file sequence of the teleported story. For GIFs and movies you can run the same commands as before:

    $ abeamer gif remote-server
    $ abeamer movie remote-server

    For more details, here’s the full documentation for the ABeamer teleporter.

    Happy animating!

    Hopefully this post gives you a good understanding of ABeamer, what it can do, and how to use it. The ability to use new animation techniques and render the results as images opens up a lot of possibilities, from commercial uses to making your own GIF generator and lots of things in between.

    If you have any questions at all or have trouble setting up, leave a comment. In the meantime, enjoy exploring! I’d love to see how you put ABeamer to use.

    The post ABeamer: a frame-by-frame animation framework appeared first on CSS-Tricks.

Level up your .filter game

.filter is a built-in array iteration method that accepts a predicate which is called against each of its values, and returns a subset of all values that return a truthy value.

That is a lot to unpack in one statement! Let’s take a look at that statement piece-by-piece.

  • “Built-in” simply means that it is part of the language—you don’t need to add any libraries to get access to this functionality.
  • “Iteration methods” accept a function that are run against each item of the array. Both .map and .reduce are other examples of iteration methods.
  • A “predicate” is a function that returns a boolean.
  • A “truthy value” is any value that evaluates to true when coerced to a boolean. Almost all values are truthy, with the exceptions of: undefined, null, false, 0, NaN, or "" (empty string).

To see .filter in action, let’s take a look at this array of restaurants.

const restaurants = [ { name: "Dan's Hamburgers", price: 'Cheap', cuisine: 'Burger', }, { name: "Austin's Pizza", price: 'Cheap', cuisine: 'Pizza', }, { name: "Via 313", price: 'Moderate', cuisine: 'Pizza', }, { name: "Bufalina", price: 'Expensive', cuisine: 'Pizza', }, { name: "P. Terry's", price: 'Cheap', cuisine: 'Burger', }, { name: "Hopdoddy", price: 'Expensive', cuisine: 'Burger', }, { name: "Whataburger", price: 'Moderate', cuisine: 'Burger', }, { name: "Chuy's", cuisine: 'Tex-Mex', price: 'Moderate', }, { name: "Taquerias Arandina", cuisine: 'Tex-Mex', price: 'Cheap', }, { name: "El Alma", cuisine: 'Tex-Mex', price: 'Expensive', }, { name: "Maudie's", cuisine: 'Tex-Mex', price: 'Moderate', },
];

That’s a lot of information. I’m currently in the mood for a burger, so let’s filter that array down a bit.

const isBurger = ({cuisine}) => cuisine === 'burger';
const burgerJoints = restaurants.filter(isBurger);

isBurger is the predicate, and burgerJoints is a new array which is a subset of restaurants. It is important to note that restaurants remained unchanged from the .filter.

Here is a simple example of two lists being rendered—one of the original restaurants array, and one of the filtered burgerJoints array.

See the Pen .filter – isBurger by Adam Giese (@AdamGiese) on CodePen.

Negating Predicates

For every predicate there is an equal and opposite negated predicate.

A predicate is a function that returns a boolean. Since there are only two possible boolean values, that means it is easy to “flip” the value of a predicate.

A few hours have passed since I’ve eaten my burger, and now I’m hungry again. This time, I want to filter out burgers to try something new. One option is to write a new isNotBurger predicate from scratch.

const isBurger = ({cuisine}) => cuisine === 'burger';
const isNotBurger = ({cuisine}) => cuisine !== 'burger';

However, look at the amount of similarities between the two predicates. This is not very DRY code. Another option is to call the isBurger predicate and flip the result.

const isBurger = ({cuisine}) => cuisine === 'burger';
const isNotBurger = ({cuisine}) => !isBurger(cuisine);

This is better! If the definition of a burger changes, you will only need to change the logic in one place. However, what if we have a number of predicates that we’d like to negate? Since this is something that we’d likely want to do often, it may be a good idea to write a negate function.

const negate = predicate => function() { return !predicate.apply(null, arguments);
} const isBurger = ({cuisine}) => cuisine === 'burger';
const isNotBurger = negate(isBurger); const isPizza = ({cuisine}) => cuisine === 'pizza';
const isNotPizza = negate(isPizza);

You may have some questions.

What is .apply?

MDN:

apply() method calls a function with a given this value, and arguments provided as an array (or an array-like object).

What is arguments?

MDN:

The arguments object is a local variable available within all (non-arrow) functions. You can refer to a function’s arguments within the function by using the arguments object.

Why return an old-school function instead of a newer, cooler arrow function?

In this case, returning a traditional function is necessary because the arguments object is only available on traditional functions.

Returning Predicates

As we saw with our negate function, it is easy for a function to return a new function in JavaScript. This can be useful for writing “predicate creators.” For example, let’s look back at our isBurger and isPizza predicates.

const isBurger = ({cuisine}) => cuisine === 'burger';
const isPizza = ({cuisine}) => cuisine === 'pizza';

These two predicates share the same logic; they only differ in comparisons. So, we can wrap the shared logic in an isCuisine function.

const isCuisine = comparison => ({cuisine}) => cuisine === comparison;
const isBurger = isCuisine('burger');
const isPizza = isCuisine('pizza');

This is great! Now, what if we want to start checking the price?

const isPrice = comparison => ({price}) => price === comparison;
const isCheap = isPrice('cheap');
const isExpensive = isPrice('expensive');

Now the isCheap and isExpensive are DRY, and isPizza and isBurger are DRY—but isPrice and isCuisine share their logic! Luckily, there are no rules for how many functions deep you can return.

const isKeyEqualToValue = key => value => object => object[key] === value; // these can be rewritten
const isCuisine = isKeyEqualToValue('cuisine');
const isPrice = isKeyEqualToValue('price'); // these don't need to change
const isBurger = isCuisine('burger');
const isPizza = isCuisine('pizza');
const isCheap = isPrice('cheap');
const isExpensive = isPrice('expensive');

This, to me, is the beauty of arrow functions. In a single line, you can elegantly create a third-order function. isKeyEqualToValue is a function that returns the function isPrice which returns the function isCheap.

See how easy it is to create multiple filtered lists from the original restaurants array?

See the Pen .filter – returning predicates by Adam Giese (@AdamGiese) on CodePen.

Composing Predicates

We can now filter our array by burgers or by a cheap price… but what if you want cheap burgers? One option is to simply chain to filters together.

const cheapBurgers = restaurants.filter(isCheap).filter(isBurger);

Another option is to “compose” the two predicates into a single one.

const isCheapBurger = restaurant => isCheap(restaurant) && isBurger(restaurant);
const isCheapPizza = restaurant => isCheap(restaurant) && isPizza(restaurant);

Look at all of that repeated code. We can definitely wrap this into a new function!

const both = (predicate1, predicate2) => value => predicate1(value) && predicate2(value); const isCheapBurger = both(isCheap, isBurger);
const isCheapPizza = both(isCheap, isPizza); const cheapBurgers = restaurants.filter(isCheapBurger);
const cheapPizza = restaurants.filter(isCheapPizza);

What if you are fine with either pizza or burgers?

const either = (predicate1, predicate2) => value => predicate1(value) || predicate2(value); const isDelicious = either(isBurger, isPizza);
const deliciousFood = restaurants.filter(isDelicious);

This is a step in the right direction, but what if you have more than two foods you’d like to include? This isn’t a very scalable approach. There are two built-in array methods that come in handy here. .every and .some are both predicate methods that also accept predicates. .every checks if each member of an array passes a predicate, while .some checks to see if any member of an array passes a predicate.

const isDelicious = restaurant => [isPizza, isBurger, isBbq].some(predicate => predicate(restaurant)); const isCheapAndDelicious = restaurant => [isDelicious, isCheap].every(predicate => predicate(restaurant));

And, as always, let’s wrap them up into some useful abstraction.

const isEvery = predicates => value => predicates.every(predicate => predicate(value)); const isAny = predicates => value => predicates.some(predicate => predicate(value)); const isDelicious = isAny([isBurger, isPizza, isBbq]);
const isCheapAndDelicious = isEvery([isCheap, isDelicious]);

isEvery and isAny both accept an array of predicates and return a single predicate.

Since all of these predicates are easily created by higher order functions, it isn’t too difficult to create and apply these predicates based on a user’s interaction. Taking all of the lessons we have learned, here is an example of an app that searches restaurants by applying filters based on button clicks.

See the Pen .filter – dynamic filters by Adam Giese (@AdamGiese) on CodePen.

Wrapping up

Filters are an essential part of JavaScript development. Whether you’re sorting out bad data from an API response or responding to user interactions, there are countless times when you would want a subset of an array’s values. I hope this overview helped with ways that you can manipulate predicates to write more readable and maintainable code.

The post Level up your .filter game appeared first on CSS-Tricks.

Working with refs in React

Refs make it possible to access DOM nodes directly within React. This comes in handy in situations where, just as one example, you want to change the child of a component. Let’s say you want to change the value of an <input> element, but without using props or re-rendering the whole component.

That’s the sort of thing refs are good for and what we’ll be digging into in this post.

How to create a ref

createRef() is a new API that shipped with React 16.3. You can create a ref by calling React.createRef() and attaching a React element to it using the ref attribute on the element.

class Example extends React.Component { constructor(props) { super(props) // Create the ref this.exampleRef = React.createRef() } render() { return ( <div> // Call the ref with the `ref` attribute <input type="text" ref={this.exampleRef} /> </div> ) }
}

We can “refer” to the node of the ref created in the render method with access to the current attribute of the ref. From the example above, that would be this.exampleRef.current.

Here’s an example:

See the Pen React Ref – createRef by Kingsley Silas Chijioke (@kinsomicrote) on CodePen.

class App extends React.Component { constructor(props) { super(props) // Create the ref this.textInput = React.createRef(); this.state = { value: '' } } // Set the state for the ref handleSubmit = e => { e.preventDefault(); this.setState({ value: this.textInput.current.value}) }; render() { return ( <div> <h1>React Ref - createRef</h1> // This is what will update <h3>Value: {this.state.value}</h3> <form onSubmit={this.handleSubmit}> // Call the ref on <input> so we can use it to update the <h3> value <input type="text" ref={this.textInput} /> <button>Submit</button> </form> </div> ); }
}
How a conversation between a child component and an element containing the ref might go down.

This is a component that renders some text, an input field and a button. The ref is created in the constructor and then attached to the input element when it renders. When the button is clicked, the value submitted from the input element (which has the ref attached) is used to update the state of the text (contained in an H3 tag). We make use of this.textInput.current.value to access the value and the new state is then rendered to the screen.

Passing a callback function to ref

React allows you to create a ref by passing a callback function to the ref attribute of a component. Here is how it looks:

<input type="text" ref={element => this.textInput = element} />

The callback is used to store a reference to the DOM node in an instance property. When we want to make use of this reference, we access it using:

this.textInput.value

Let’s see how that looks in the same example we used before.

See the Pen React Ref – Callback Ref by Kingsley Silas Chijioke (@kinsomicrote) on CodePen.

class App extends React.Component { state = { value: '' } handleSubmit = e => { e.preventDefault(); this.setState({ value: this.textInput.value}) }; render() { return ( <div> <h1>React Ref - Callback Ref</h1> <h3>Value: {this.state.value}</h3> <form onSubmit={this.handleSubmit}> <input type="text" ref={element => this.textInput = element} /> <button>Submit</button> </form> </div> ); }
}

When you make use of callback like we did above, React will call the ref callback with the DOM node when the component mounts, when the component un-mounts, it will call it with null.

It is also possible to pass ref from a parent component to a child component using callbacks.

See the Pen React Ref – Callback Ref 2 by Kingsley Silas Chijioke (@kinsomicrote) on CodePen.

Let’s create our “dumb” component that will render a simple input:

const Input = props => { return ( <div> <input type="text" ref={props.inputRef} /> </div> );
};

This component is expecting inputRef props from its parent component which is then used to create a ref to the DOM node.

Here’s the parent component:

class App extends React.Component { state = { value: '' }; handleSubmit = event => { this.setState({ value: this.inputElement.value }); }; render() { return ( <div> <h1>React Ref - Callback Ref</h1> <h3>Value: {this.state.value}</h3> <Input inputRef={el => (this.inputElement = el)} /> <button onClick={this.handleSubmit}>Submit</button> </div> ); }
}

In the App component, we want to obtain the text that is entered in the input field (which is in the child component) so we can render it. The ref is created using a callback like we did in the first example of this section. The key lies in how we access the DOM of the input element in the Input component from the App component. If you look closely, we access it using this.inputElement. So, when updating the state of value in the App component, we get the text that was entered in the input field using this.inputElement.value.

The ref attribute as a string

This is the old way of creating a ref and it will likely be removed in a future release because of some issues associated with it. The React team advises against using it, going so far as to label it as “legacy” in the documentation. We’re including it here anyway because there’s a chance you could come across it in a codebase.

See the Pen React Ref – String Ref by Kingsley Silas Chijioke (@kinsomicrote) on CodePen.

Going back to our example of an input whose value is used to update text value on submit:

class App extends React.Component { state = { value: '' } handleSubmit = e => { e.preventDefault(); this.setState({ value: this.refs.textInput.value}) }; render() { return ( <div> <h1>React Ref - String Ref</h1> <h3>Value: {this.state.value}</h3> <form onSubmit={this.handleSubmit}> <input type="text" ref="textInput" /> <button>Submit</button> </form> </div> ); }
}

The component is initialized and we start with a default state value set to an empty string (value='’). The component renders the text and form, as usual and, like before, the H3 text updates its state when the form is submitted with the contents entered in the input field.

We created a ref by setting the ref prop of the input field to textInput. That gives us access to the value of the input in the handleSubmit() method using this.refs.textInput.value.

Forwarding a ref from one component to another

**Ref forwarding is the technique of passing a ref from a component to a child component by making use of the React.forwardRef() method.

See the Pen React Ref – forward Ref by Kingsley Silas Chijioke (@kinsomicrote) on CodePen.

Back to our running example of a input field that updates the value of text when submitted:

class App extends React.Component { constructor(props) { super(props) this.inputRef = React.createRef(); this.state = { value: '' } } handleSubmit = e => { e.preventDefault(); this.setState({ value: this.inputRef.current.value}) }; render() { return ( <div> <h1>React Ref - createRef</h1> <h3>Value: {this.state.value}</h3> <form onSubmit={this.handleSubmit}> <Input ref={this.inputRef} /> <button>Submit</button> </form> </div> ); }
}

We’ve created the ref in this example with inputRef, which we want to pass to the child component as a ref attribute that we can use to update the state of our text.

const Input = React.forwardRef((props, ref) => ( <input type="text" ref={ref} />
));

Here is an alternative way to do it by defining the ref outside of the App component:

const Input = React.forwardRef((props, ref) => ( <input type="text" ref={ref} />
)); const inputRef = React.createRef(); class App extends React.Component { constructor(props) { super(props) this.state = { value: '' } } handleSubmit = e => { e.preventDefault(); this.setState({ value: inputRef.current.value}) }; render() { return ( <div> <h1>React Ref - createRef</h1> <h3>Value: {this.state.value}</h3> <form onSubmit={this.handleSubmit}> <Input ref={inputRef} /> <button>Submit</button> </form> </div> ); }
}

Using ref for form validation

We all know that form validation is super difficult but something React is well-suited for. You know, things like making sure a form cannot be submitted with an empty input value. Or requiring a password with at least six characters. Refs can come in handy for these types of situations.

See the Pen React ref Pen – validation by Kingsley Silas Chijioke (@kinsomicrote) on CodePen.

class App extends React.Component { constructor(props) { super(props); this.username = React.createRef(); this.password = React.createRef(); this.state = { errors: [] }; } handleSubmit = (event) => { event.preventDefault(); const username = this.username.current.value; const password = this.password.current.value; const errors = this.handleValidation(username, password); if (errors.length > 0) { this.setState({ errors }); return; } // Submit data }; handleValidation = (username, password) => { const errors = []; // Require username to have a value on submit if (username.length === 0) { errors.push("Username cannot be empty"); } // Require at least six characters for the password if (password.length < 6) { errors.push("Password should be at least 6 characters long"); } // If those conditions are met, then return error messaging return errors; }; render() { const { errors } = this.state; return ( <div> <h1>React Ref Example</h1> <form onSubmit={this.handleSubmit}> // If requirements are not met, then display errors {errors.map(error => <p key={error}>{error}</p>)} <div> <label>Username:</label> // Input for username containing the ref <input type="text" ref={this.username} /> </div> <div> <label>Password:</label> // Input for password containing the ref <input type="text" ref={this.password} /> </div> <div> <button>Submit</button> </div> </form> </div> ); }
}

We used the createRef() to create refs for the inputs which we then passed as parameters to the validation method. We populate the errors array when either of the input has an error, which we then display to the user.

That’s a ref… er, a wrap!

Hopefully this walkthrough gives you a good understanding of how powerful refs can be. They’re an excellent way to update part of a component without the need to re-render the entire thing. That’s convenient for writing leaner code and getting better performance.

At the same time, it’s worth heeding the advice of the React docs themselves and avoid using ref too much:

Your first inclination may be to use refs to “make things happen” in your app. If this is the case, take a moment and think more critically about where state should be owned in the component hierarchy. Often, it becomes clear that the proper place to “own” that state is at a higher level in the hierarchy.

Get it? Got it? Good.

The post Working with refs in React appeared first on CSS-Tricks.

Building Battleship in CSS

This is an experiment to see how far into an interactive experience I can get using only CSS. What better project to attempt than a game? Battleship seemed like a good challenge and a step up from the CSS games I’ve seen so far because it has the complexity of multiple areas that have to interact with two players.

Wanna see the complete game?

View Repo View Demo

Oh, you wanna learn how it works? Let’s dig in.

I could tell right away there was going to be a lot of repetitive HTML and very long CSS selectors coming, so I set up Pug to compile HTML and Less to compile CSS. This is what all the code from here on is going to be written in.

Interactive elements in CSS

In order to get the game mechanics working, we need some interactive elements. We’re going to walk through each one.

HTML checkboxes and :checked

Battleship involves a lot of checking to see whether a field contains a ship or not, so we’re going to use a boatload of checkboxes.

[type*='checkbox'] { // inactive style &:checked { // active style }
}

To style checkboxes, we would first need to reset them using appearance: none; which is only poorly supported right now and needs browser prefixes. The better solution here is to add helper elements. <input> tags can’t have children, including pseudo elements (even though Chrome will render them anyway), so we need to work around that using the adjacent sibling selector.

[type*='checkbox'] { position: relative; opacity: none; + .check-helper { position: absolute; top: 0; left: 0; pointer-events: none; // further inactive styles } &:checked { + .check-helper { // active styles } }
}

If you use a <label> for the helper element, you will also extend the click area of the checkbox onto the helper, allowing you to position it more freely. Also, you can use multiple labels for the same checkbox. Multiple checkboxes for the same label are not supported, however, since you would have to assign the same ID for each checkbox.

Targets

We’re making a local multiplayer game, so we need to hide one player’s battlefield from the other and we need a pause mode allowing for a player to switch without glancing at the other player’s ships. A start screen explaining the rules would also be nice.

HTML already gives us the option to link to a given ID in the document. Using :target we can select the element that we just jumped to. That allows us to create an Single Page Application-like behavior in a completely static document (and without even breaking the back button).

- var screens = ['screen1', 'screen2', 'screen3'];
body nav each screen in screens a(href='#' + screen) each screen in screens .screen(id=screen) p #{screen}
.screen { display: none; &:target { display: block; }
}

Visibility and pointer events

Rendering elements inactive is usually done by using pointer-events: none; The cool thing about pointer-events is that you can reverse it on the child elements. That will leave only the selected child clickable, but the parent stays click-through. This will come in handy later in combination with checkbox helper elements.

The same goes for visibility: hidden; While display: none; and opacity: 0; make the element and all it’s children disappear, visibility can be reversed.

Note that a hidden visibility also disables any pointer events, unlike opacity: 0;, but stays in the document flow, unlike display: none;.

.foo { display: none; // invisible and unclickable .bar { display: block; // invisible and unclickable }
} .foo { visibility: hidden; // invisible and unclickable .bar { visibility: visible; // visible and clickable }
} .foo { opacity: 0; pointer-evens: none; // invisible and unclickable .bar { opacity: 1; pointer-events: all; // still invisible, but clickable }
}
CSS Rule Reversible opacity Reversible pointer events
display: none;
visibility: hidden;
opacity: 0;
pointer-events: none;

OK, now that we’ve established the strategy for our interactive elements, let’s turn to the setup of the game itself.

Setting up

We have some global static variables and the size of our battlefields to define before we actually start:

@gridSize: 12;
@zSea: 1;
@zShips: 1000;
@zAbove: 2000;
@seaColor: #123;
@enemyColor: #f0a;
@playerColor: #0c8;
@hitColor: #f27; body { --grid-measurements: 70vw; @media (min-aspect-ratio: 1/2) { --grid-measurements: 35vh; }
}

The grid size is the size of the battlefield: 12×12 fields in this case. Next, we define some z-indexes and colors.

Here’s the Pug skeleton:

doctype html head title Ships! link(rel="stylesheet", href="style.css") meta(charset="UTF-8") meta(name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no") meta(name="theme-color" content="#000000") body

Everything HTML from this point on will be in the body.

Implementing the states

We need to build the states for Player 1, Player 2, pause, and a start screen. We’ll do this like it was explained above with target selectors. Here’s a little sketch of what we’re trying to achieve:

We have a few modes, each in its own container with an ID. Only one mode is to be displayed in the viewport—the others are hidden via display: none;, except for player modes. If one player is active, the other needs to be outside of the viewport, but still have pointer events so the players can interact with each other.

.mode#pause each party in ['p1', 'p2'] .mode(id=party) .mode#start .status each party in ['p1', 'p2'] a.player-link.switch(href='#' + party) a.status-link.playpause(href='#pause') End Turn h1 Ships!

The .status div contains the main navigation. Its entries will change depending on the active mode, so in order to select it properly, we’ll need put it after our .mode elements. The same goes for the <h1>, so it ends up at the end of the document (don’t tell the SEO people).

.mode { opacity: 0; pointer-events: none; &:target, &#start { opacity: 1; pointer-events: all; z-index: 1; } &#p1, &#p2 { position: absolute; transform: translateX(0); opacity: 1; z-index: 2; } &#p1:target { transform: translateX(50vw); +#p2 { transform: translateX(50vw); z-index: 2; } } &#p2 { transform: translateX(50vw); z-index: 1; } &#pause:target { ~ #p1, ~ #p2 { opacity: 0; } }
} #start { .mode:target ~ & { display: none; }
}

The .mode div never has pointer events and always is fully transparent (read: inactive), except for the start screen, which is enabled by default and the currently targeted screen. I don’t simply set it to display: none; because I still need it to be in the document flow. Hiding the visibility won’t work because I need to activate pointer events individually later on, when hitting enemy ships.

I need #p1 and #p2 to be next to each other because that’s what’s going to enable the interaction between one players hits and the other players ships.

Implementing the battlefields

We need two sets of two battlefields for a total of four battlefields. Each set contains one battlefield for the current player and another for the opposite player. One set is going to be in #p1 and the other one in #p2. Only one of the players will be in the viewport, but both retain their pointer events and their flow in the document. Here’s a little sketch:

Now we need lots of HTML. Each player needs two battlefields, which need to have 12×12 fields. That’s 576 fields in total, so we’re going to loop around a bit.

The fields are going to have their own class declaring their position in the grid. Also, fields in the first row or line get a position indicator, so you get to say something cool like “Fire at C6.”

each party in 'p1', 'p2'] .mode(id=party) each faction in 'enemy', 'player'] .battlefield(class=faction, class=party) each line in 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] each col, colI in 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L'] div(class='field-x' + (colI+1) + '-y' + line) if (col === 'A') .indicator-col #{line} if (line === 1) .indicator-line #{col}

The battlefield itself is going to be set in a CSS grid, with its template and measurements coming from the variables we set before. We’ll position them absolutely within our .mode divs and switch the enemy position with the player. In the actual board game, you have your own ships on the bottom as well. Note that we need to escape the calc on the top value, or Less will try to calculate it for you and fail.

.battlefield { position: absolute; display: grid; grid-template-columns: repeat(@gridSize, 1fr); width: var(--grid-measurements); height: var(--grid-measurements); margin: 0 auto 5vw; border: 2px solid; transform: translate(-50%, 0); z-index: @zSea; &.player { top: calc(var(--grid-measurements) ~"+" 150px); border-color: transparent; :target & { border-color: @playerColor; } } &.enemy { top: 100px; border-color: transparent; :target & { border-color: @enemyColor; } }
}

We want the tiles of the battlefield to be a nice checkerboard pattern. I wrote a mixin to calculate the colors, and since I like my mixins separated from the rest, this is going into a components.less file.

.checkerboard(@counter) when (@counter > 0) { .checkerboard(@counter - 2); &[class^='field-'][class$='-y@{counter}'] { &:nth-of-type(odd) { background-color: transparent; :target & { background-color: darken(@seaColor, 3%); } } &:nth-of-type(even) { background-color: transparent; :target & { background-color: darken(@seaColor, 4%); } } }
}

When we call it with .checkerboard(@gridSize);, it will iterate through every second line of the grid and set background colors for odd and even instances of the current element. We can color the remaining fields with an ordinary :odd and :even.

Next, we place the indicators outside of the battlefields.

[class^='field-'] { position: relative; display: flex; justify-content: center; align-items: center; width: 100%; height: 100%; background-color: transparent; .checkerboard(@gridSize); :target &:nth-of-type(even) { background-color: darken(@seaColor, 2%); } :target &:nth-of-type(odd) { background-color: darken(@seaColor, 1%); } [class^='indicator-'] { display: none; :target & { position: absolute; display: flex; justify-content: center; width: calc(var(--grid-measurements)~"/"@gridSize); height: calc(var(--grid-measurements)~"/"@gridSize); color: lighten(@seaColor, 10%); pointer-events: none; } &.indicator-line { top: -1.5em; align-items: flex-start; } &.indicator-col { left: -2.3em; align-items: center; } }
}

Implementing the ships

Let’s get to the tricky part and place some ships. Those need to be clickable and interactive, so they’re going to be checkboxes. Actually, we need two checkboxes for one ship: miss and hit.

  • Miss is the bottom one. If nothing else is on that field, your shot hits the water and triggers a miss-animation. The exception is when a player clicks on their own battlefield. In that case, the ship animation plays.
  • When an own ships spawns, it activates a new checkbox. This one is called hit. It’s placed at the exact same coordinates as its corresponding ship, but in the other players attack field and above the checkbox helper for the miss. If a hit is activated, it displays a hit animation on the current player’s attack field as well as well as on the opponent’s own ship.

This is why we need to position our battlefields absolutely next to each other. We need them aligned at all times in order to let them interact with each other.

First, we’re going to set some styles that apply to both checkboxes. We still need the pointer events, but want to visually hide the checkbox and work with helper elements instead.

.check { position: absolute; top: 0; left: 0; width: 100%; height: 100%; margin: 0; opacity: 0; + .check-helper { position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; }
}

We’ll also write some classes for our events for later use right now. This will also go into components.less:

.hit-obj { position: absolute; visibility: visible; left: 0; top: 0; width: 100%; height: 100%; border-radius: 50%; animation: hit 1s forwards;
} .ship-obj { position: absolute; left: 0; top: 0; width: 90%; height: 90%; border-radius: 15%; animation: setShip 0.5s forwards;
} .miss-obj { position: absolute; left: 0; top: 0; width: 100%; height: 100%; border-radius: 50%; animation: miss 1s forwards;
}

Spawning and missing ships

Those two events are basically the same. If you hit the sea in your own battlefield, you create a ship. If you hit the sea in the enemy battlefield, you trigger a miss. That happens by calling the respective class from our components.less file within a pseudo element of the helper class. We use pseudo elements here because we need to place two objects in one helper later on.

If you spawn a ship, you shouldn’t be able to un-spawn it, so we make it lose its pointer events after being checked. However, the next hit-checkbox gains it pointer events, enabling the enemy to hit spawned ships.

.check { &.ship { &:checked { pointer-events: none; } &:checked + .check-helper { :target .player & { &::after { content: ""; .ship-obj; // set own ship } } :target .enemy & { &::after { content: ""; .miss-obj; // miss enemy ship } } } &:checked ~ .hit { pointer-events: all; } }
}

Hitting ships

That new hit checkbox is positioned absolutely on top of the other player’s attack field. For Player 1 that means by 50vw to the right and by the grid height + 50px margin to the top. It has no pointer events by default, they are going to be overwritten by those set in .ship:check ~ .hit, so only ships that are actually set, can be hit.

To display a hit event, we need two pseudo elements: one that confirms the hit on the attack field; and one that shows the victim where they have been hit. :checked + .check-helper::after calls a .hit-obj from components.less onto the attacker’s field and the corresponding ::before pseudo element gets translated back to the victim’s own battlefield.

Since the display of hit events isn’t scoped to the active player, we need to remove all unnecessary instances manually using display: none;.

.check { &.hit { position: absolute; top: ~"calc(-1 * (var(--grid-measurements) + 50px))"; left: 50vw; width: 100%; height: 100%; pointer-events: none; #p2 &, #p1:target & { left: 0; } #p1:not(:target) & + .check-helper::before { left: 50vw; } &:checked { opacity: 1; visibility: hidden; pointer-events: none; + .check-helper { &::before { content: ""; .hit-obj; // hit enemy ships top: ~"calc(-1 * (var(--grid-measurements) + 50px))"; } &::after { content: ""; .hit-obj; // hit own ships top: -2px; left: -2px; } #p1:target &::before, #p1:target ~ #p2 &::after, #p1:not(:target) &::after, #p2:target &::before { display: none; } } } #p1:target .battlefield.p1 &, #p2:target .battlefield.p2 & { display: none; } }
}

Animating the events

While we did style our miss, ship and hit objects, there’s nothing to be seen yet. That’s because we are still missing the animations making those objects visible. Those are simple keyframe animations that I put into a new Less file called animations.less.

@keyframes setShip { 0% { transform: scale(0, 0); background-color: transparent; } 100% { transform: scale(1, 1); background-color: @playerColor; }
} @keyframes hit { 0% { transform: scale(0, 0); opacity: 0; background-color: transparent; } 10% { transform: scale(1.2, 1.2); opacity: 1; background-color: spin(@hitColor, 40); box-shadow: 0 0 0 0.5em var(--shadowColor); } 100% { transform: scale(.7, .7); opacity: .7; background-color: @hitColor; box-shadow: 0 0 0 0.5em var(--shadowColor); }
} @keyframes miss { 0% { transform: scale(0, 0); opacity: 1; background-color: lighten(@seaColor, 50); } 100% { transform: scale(1, 1); opacity: .8; background-color: lighten(@seaColor, 10); }
}

Add customizable player names

This isn’t really necessary for functionality, but it’s a nice little extra. Instead of being called “Player 1” and “Player 2,” you can enter your own name. We do this by adding two <input type="text"> to .status, one for each player. They have placeholders in case the players don’t want to enter their names and want to skip to the game right away.

.status input(type="text" placeholder="1st Player").player-name#name1 input(type="text" placeholder="2nd Player").player-name#name2 each party in ['p1', 'p2'] a.player-link.switch(href='#' + party) a.status-link.playpause(href='#pause') End Turn

Because we put them into .status, we can display them on every screen. On the start screen, we leave them as normal input fields, for the players to enter their names. We style their placeholders to look like the actual text input, so it doesn’t really matter if players enter their names or not.

.status { .player-name { position: relative; padding: 3px; border: 1px solid @enemyColor; background: transparent; color: @playerColor; &::placeholder { color: @playerColor; opacity: 1; // Reset Firefox user agent styles } }
}

On the other screens, we remove their typical input field styles as well as their pointer events, making they appear as normal, non-changeable text. .status also contains empty links to select players. We style those links to have actual measurements and display the name inputs without pointer events above them. Clicking a name triggers the link now, targeting the corresponding mode.

.status { .mode#pause:target ~ & { top: 40vh; width: calc(100% ~"-" 40px); padding: 0 20px; text-align: center; z-index: @zAbove; .player-name, .player-link { position: absolute; display: block; width: 80%; max-width: 500px; height: 40px; margin: 0; padding: 0; &:nth-of-type(even) { top: 60px; } } .player-name { border: 0; text-align: center; pointer-events: none; } }
}

The player screens only need to display the active player, so we remove the other one.

.status { .mode#p1:target ~ & #name2 { display: none; } .mode#p2:target ~ & #name1 { display: none; }
}

Some notes on the Internet Explorer and Edge: Microsoft browsers haven’t implemented the ::placeholder pseudo element. While they do support :-ms-input-placeholder for IE and ::-ms-input-placeholder, as well as the webkit-prefix for Edge, those prefixes only work if ::placeholder is not set. As far as I played around with placeholders, I only managed to style them properly in either the Microsoft browsers, or all the other ones. If someone else has a workaround, please share it!

Putting it all together

What we have so far is a functional, but not very handsome game. I use the start screen to clarify some basic rules. Since we don’t have a hard-coded win condition and nothing to prevent players to place their ships wildly all over the place, I created a “Play fair” note that encourages the good ol’ honor system.

.mode#start .battlefield.enemy ol li span You are this color. li span Your enemy is span this span color li span You may place your ships as follows: ul li 1 x 5 blocks li 2 x 4 blocks li 3 x 3 blocks li 4 x 2 blocks

I’m not going into the detail of how I went about getting things exactly to my liking since most of that is very basic CSS. You can go through the end result to pick them out.

When we finally connect all the pieces, we get this:

See the Pen CSS Game: Battleships by Daniel Schulz (@iamschulz) on CodePen.

Wrapping up

Let’s look back at what we’ve accomplished.

HTML and CSS may not be programming languages, but they are mighty tools in their own domain. We can manage states with pseudo classes and manipulate the DOM with pseudo elements.

While most of us use :hover and :focus all the time, :checked goes by largely unnoticed, only for styling actual checkboxes and radio buttons at best. Checkboxes are handy little tools that can help us to get rid of unnecessary JavaScript in our more simple front end features. I wouldn’t hesitate to build dropdown or off-canvas menus in pure CSS in real life projects, as long as the requirements don’t get too complicated.

I’d be a bit more cautious when using the :target selector. Since it uses the URL hash value, it’s only usable for a global value. I think I’d use it for, say, highlighting the current paragraph on a content page, but not for reusable elements like a slider or an accordion menu. It can also quickly get messy on larger projects, especially when other parts of it start controlling the hash value.

Building the game was a learning experience for me, dealing with pseudo selectors interacting with each other and playing around with lots of pointer events. If I had to build it again, I’d surely choose another path, which is a good outcome for me. I definitely don’t see it as a production-ready or even clean solution, and those super specific selectors are a nightmare to maintain, but it has some good parts in it that I can transition to real life projects.

Most importantly though, it was a fun thing to do.

The post Building Battleship in CSS appeared first on CSS-Tricks.

Practical CSS Scroll Snapping

CSS scroll snapping allows you to lock the viewport to certain elements or locations after a user has finished scrolling. It’s great for building interactions like this one:

Live Demo

Browser support and basic usage

Browser support for CSS scroll snapping has improved significantly since it was introduced in 2016, with Google Chrome (69+), Firefox, Edge, and Safari all supporting some version of it.

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

Desktop

Chrome Opera Firefox IE Edge Safari
69 No 63 11* 18* 11

Mobile / Tablet

iOS Safari Opera Mobile Opera Mini Android Android Chrome Android Firefox
11.0-11.2 No No No No 60

Scroll snapping is used by setting the scroll-snap-type property on a container element and the scroll-snap-align property on elements inside it. When the container element is scrolled, it will snap to the child elements you’ve defined. In its most basic form, it looks like this:

<div class='container'> <section class='child'></section> <section class='child'></section> <section class='child'></section> ...
</div>
.container { scroll-snap-type: y mandatory;
} .child { scroll-snap-align: start;
}

This is different to the first version of the spec, which allowed you to set snap-points manually using the repeat keyword:

.container { scroll-snap-points-y: repeat(300px);
}

This method is pretty limited. Since it only allows evenly-spaced snap points, you can’t really build an interface that snaps to different-sized elements. If elements change their shape across different screen sizes, you’re also bound to run into issues.

At the time of this writing, Firefox, Internet Explorer, and Edge support the older version of the spec, while Chrome (69+) and Safari support the newer, element-based method.

You can use both methods alongside each other (if your layout allows it) to support both groups of browsers:

.container { scroll-snap-type: mandatory; scroll-snap-points-y: repeat(300px); scroll-snap-type: y mandatory;
} .child { scroll-snap-align: start;
}

I’d argue a more flexible option is to use the element-based syntax exclusively and loading a polyfill to support browsers that don’t yet support it. This is the method I’m using in the examples below.

Unfortunately the polyfill doesn’t come with a browser bundle, so it’s a bit tricky to use if you’re not using a build process. The easiest way around this I’ve found is to link to the script on bundle.run and initializing it using cssScrollSnapPolyfill() once the DOM is loaded. It’s also worth pointing out that this polyfill only supports the element-based syntax, not the repeat-method.

Parent container properties

As with any property, it’s a good idea to get familiar with the values they accept. Scroll snap properties are applied to both parent and child elements, with specific values for each. Sort of the same way flexbox and grid do, where the parent becomes a “flex” or “grid” container. In this case, the parent becomes a snap container, if you will.

Here are the properties and values for the parent container and how they work.

scroll-snap-type “mandatory” vs. “proximity”

The mandatory value means the browser has to snap to a snap point whenever the user stops scrolling. The proximity property is less strict—it means the browser may snap to a snap point if it seems appropriate. In my experience this tends to kick in when you stop scrolling within a few hundred pixels of a snap point.

See the Pen Scroll-snap-type “Mandatory” vs “Proximity” by Max Kohler (@maxakohler) on CodePen.

In my own work, I’ve found that mandatory makes for a more consistent user experience, but it can also be dangerous, as the spec points out. Picture a scenario where an element inside a scrolling container is taller than the viewport:

If that container is set to scroll-snap-type: mandatory, it will always snap either to the top of the element or the top of the one below, making the middle part of the tall element impossible to scroll to.

scroll-padding

By default content will snap to the very edges of the container. You can change that by setting the scroll-padding property on the container. It follows the same syntax as the regular padding property.

This can be useful if your layout has elements that could get in the way of the content, like a fixed header.

Properties on the children

Now let’s move on over to the properties for child elements.

scroll-snap-align

This lets you specify which part of the element is supposed to snap to the container. It has three possible values: start, center, and end.

These are relative to the scroll direction. If you’re scrolling vertically, start refers to the top edge of the element. If you’re scrolling horizontally, it refers to the left edge. center and end follow the same principle. You can set a different values for each scroll direction separated by a space.

scroll-snap-stop “normal” vs. “always”

By default, scroll snapping only kicks in when the user stops scrolling, meaning they can skip over several snap points before coming to a stop.

You can change this by setting scroll-snap-stop: always on any child element. This forces the scroll container to stop on that element before the user can continue to scroll.

At the time of this writing no browser supports scroll-snap-stop natively, though there is a tracking bug for Chrome.

Let’s look at some examples of scroll snap in use.

Example 1: Vertical list

To make a vertical list snap to each list element only takes a few lines of CSS. First, we tell the container to snap along its vertical axis:

.container { scroll-snap-type: y mandatory;
}

Then, we define the snap points. Here, we’re specifying that the top of each list element is going to be a snap point:

.child { scroll-snap-align: start;
}

See the Pen Vertical List by Max Kohler (@maxakohler) on CodePen.

Example 2: Horizontal slider

To make a horizontal slider, we tell the container to snap along its x-axis. We’re also using scroll-padding to make sure the child elements snap to the center of the container.

.container { scroll-snap-type: x mandatory; scroll-padding: 50%;
}

Then, we tell the container which points to snap to. To center the gallery, we define the center point of each element as a snap point.

.child { scroll-snap-align: center;
}

See the Pen Horizontal, different sized images by Max Kohler (@maxakohler) on CodePen.

Example 3: Vertical full screen

We can set the snap points directly on the <body> element:

body { scroll-snap-type: y mandatory;
}

Then, we make each section the size of the viewport and define the top edge as a snap point:

section { height: 100vh; width: 100vw; scroll-snap-align: start;
}

See the Pen Vertical Full-Screen by Max Kohler (@maxakohler) on CodePen.

Example 4: Horizontal full screen

This is the same sort of concept as the vertical version, but with the snap point on the x-axis instead.

body { scroll-snap-type: x mandatory;
} section { height: 100vh; width: 100vw; scroll-snap-align: start;
}

See the Pen Horizontal Full-Screen by Max Kohler (@maxakohler) on CodePen.

Example 5: 2D image grid

Scroll snapping can work in two directions at the same time. Again, we can set scroll-snap-type directly on the <body> element:

.container { scroll-snap-type: both mandatory;
}

Then, we define the top-left corner of each tile as a snap point:

.tile { scroll-snap-align: start;
}

See the Pen 2d Snapping by Max Kohler (@maxakohler) on CodePen.

Some thoughts on user experience

Messing with scrolling is risky business. Since it’s such a fundamental part of interacting with the web, changing it in any way can feel jarring—the term scrolljacking used to get thrown around to describe that sort of experience.

The great thing about CSS-based scroll snapping is that you’re not taking direct control over the scroll position. Instead you’re just giving the browser a list of positions to snap in a way that is appropriate to the platform, input method, and user preferences. This means a scrolling interface you build is going to feel just like the native interface (i.e using the same animations, etc.) on whatever platform it’s viewed on.

To me, this is the key advantage of CSS scroll snapping over JavaScript libraries that offer similar functionality.

This works fairly well in my experience, especially on mobile. Maybe this is because scroll snapping is already part of the native UI on mobile platforms. (Picture the home screens on iOS and Android—they’re essentially horizontal sliders with snap points.) The interaction on Chrome on Android is particularly nice because it feels like regular scrolling, but the viewport always happens to come to a stop at a snap point:

There’s definitely some fancy maths going on to make this happen. Thanks to CSS scroll snapping, we’re getting it for free.

Of course, we shouldn’t start throwing snap points onto everything. Things like article pages do just fine without them. But I think they can be a nice enhancement in the right situation—image galleries, slideshows seem like good candidates, but maybe there’s potential beyond that.

Conclusion

If done thoughtfully, scroll snapping can be a useful design tool. CSS snap points allow you to hook into the browser’s native scrolling interaction, so your interface feel seamless and smooth. With a JavaScript API potentially on the horizon, these are going to become even more powerful. Still, a light touch is probably the way to go.

The post Practical CSS Scroll Snapping appeared first on CSS-Tricks.

The possibilities of the color-adjust property

The Open Web continues to show up in places we would have never originally expected to find it: our phones, televisions, watches, books, video game consoles, fast food menus, gas pumps, elevators, cars—even our refrigerators.

By not making too many or too strict assumptions about how the web should be used, it remains flexible and adaptable. These qualities have allowed it to outperform closed technologies like Flash and Silverlight.

With the web’s growth comes new features to better accommodate its new form factors and use cases. One feature I’m excited about is the color-adjust property, proposed in CSS Color Module Level 4. It is an acknowledgement that the web will continue to show up on devices that have less-than-stellar displays.

There are two values for color-adjust: economy and exact. A value of exact tells the browser it shouldn’t make adjustments to the colors declared in the stylesheet:

.card { background-color: #98b3c7; border-bottom: 0.25rem solid #7c92a3; color: #f3f3f3; color-adjust: exact; ...
}

The color-adjust: exact; declaration in this example forces the browser to render all colors as accurately as possible on anything with a class of card applied to it. Accurate meaning being as close as possible based on the host device’s best ability.

The description for the economy value in the specification reads as, “The user agent should make adjustments to the page’s styling as it deems necessary and prudent for the output device.” It places trust in the browser’s hands, allowing it to make adjustments to color values as it sees fit.

Best ability

Handing control over to a browser might seem a little scary at first. As an industry, we’re really great at bikeshedding the heck out of color systems. And that’s a great thing! The use of color, including proper contrast ratios, is an incredibly important aspect of design, and can oftentimes make or break a product.

But we need to understand that our platonically ideal design might not be able to be experienced in the real world as intended. Not everyone owns a device that outputs to a Retina display with a luxurious DCI-P3 color space; nor do they always have perfect vision or ideal lighting conditions. In these kinds of circumstances, it’s better to bend, not break.

We now live in a Mobile, Small, Portrait, Slow, Interlace, Monochrome, Coarse, Non-Hover, First world. Limited color displays aren’t as rare as you think, and are probably only going to get more commonplace as time goes on. I’d especially like to call attention to the rise of internet use by low income populations and emerging markets. With that comes cheaper devices with lower-quality displays.

Browser support

At the time of this article’s publishing, color-adjust has been supported since Firefox 48 (and Android Firefox 60):

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

Desktop

Chrome Opera Firefox IE Edge Safari
19* 15* 48 No 18 6*

Mobile / Tablet

iOS Safari Opera Mobile Opera Mini Android Android Chrome Android Firefox
12 No No 67 67 60

Chrome and Safari, both WebKit browsers, require a vendor-prefixed declaration of -webkit-print-color-adjust. Curiously, -webkit-print signals that their implementation of this property is only intended for print. While the W3C documentation does mention use cases for printing, it is phrased in such a way as to not be limited to it.

People still print webpages! Paper doesn’t require a data plan, nor does its connection drop when you go underground. Just yesterday, I saw someone on the train who was using a collection of printed sites to study for their next exam. And here’s your galaxy brain moment: printed pages are just limited color displays.

I’d also be remiss if I didn’t mention situations where print styles are missing or poorly-authored, potentially forcing a printer to waste ink in an attempt to do what the stylesheet asks of it. Printer ink is hideously expensive—minding this does your (or your IT department’s) budget a solid.

Potential uses

Before we get any further, I want to state the following is all personal theorizing based off the phrasing in the W3C specification—targeting, but not limited to printing.

I think color-adjust could be one of those properties that could find a home explicitly declared in the body selector, where it can best take advantage of the Cascade:

body { color-adjust: economy; ...
}

This declaration says, “Every time I declare color in this website, use the values I specify. If you can’t, that’s cool—do what you think is best.” That’s a lot better than the browser trying to literally interpret styling instructions at all cost, potentially rendering the page as completely illegible.

You could declare color-adjust in a more specific way, say nested in a @supports at-rule inside of a print media query, but that’s unnecessary extra work. It would fail to accommodate things like High Contrast Mode and the upcoming color gamut media feature. Better to embrace the unknown and cast a wide net.

I’m also very curious to see how color-adjust could work in conjunction with other browser capabilities, say the Ambient Light Sensor API (RIP Battery Status API). It’d be neat if there were opportunities to experiment with other specialized display modes—macOS’ Night Shift, Increased Contrast, Grayscale, and Reduce Transparency options all come to mind.

A note about accessibility

I’m wondering if software (browser preference toggle or extension, bookmarklet, etc.) could be written to override what the device’s hardware reports itself as being. Much like User Agent spoofing, it could “trick” a browser into thinking it has a limited color display, using economy to force better contrast between text and background color. This would be a lot like some browser’s reading modes, only page layout would be better preserved.

That being said, I don’t think color-adjust is a silver bullet for all color-related accessibility concerns. We can’t always know the device and context our websites and web apps will show up in, including what colors color-adjust would ultimately render as. Because of this, it’s still important to mind your color contrast ratios.

Bending, not breaking

color-adjust feels like a natural extension of Jen Simmons’ Intrinsic Web Design: fluid and squishy UI, proportional sizing, media queries as needed, and simple declarations that do the heavy lifting.

The beauty of the CSS Cascade means you can gracefully create intent, then adjust as needed. color-adjust‘s documentation specifically mentions a situation where it could be useful to ensure a table’s zebra striping is is retained when printed to make it easier to read. Such a tweak can be scoped to a single selector, without having to spend time undoing it for every other component.

body { color-adjust: economy; ...
} tr { color-adjust: exact;
}

The beauty of CSS’ fault tolerance means browsers that don’t understand this declaration will ignore it and continue parsing the rest of of the stylesheet. Browsers that do support it can take advantage of it, without any complicated build tool configuration or dangerous User Agent sniffing.

It is important to make our web sites and web apps design adapt to the user’s environment and circumstances, and not the other way around. Good user experiences meet the user where they are, not where we hope they’ll be.

The post The possibilities of the color-adjust property appeared first on CSS-Tricks.