Tag: Scope

The Power (and Fun) of Scope with CSS Custom Properties

You’re probably already at least a little familiar with CSS variables. If not, here’s a two-second overview: they are really called custom properties, you set them in declaration blocks like --size: 1em and use them as values like font-size: var(--size);, they differ from preprocessor variables (e.g. they cascade), and here’s a guide with way more information.

But are we using them to their full potential? Do we fall into old habits and overlook opportunities where variables could significantly reduce the amount of code we write?

This article was prompted by a recent tweet I made about using CSS variables to create dynamic animation behavior.

Let’s look at a couple of instances where CSS variables can be used to do some pretty cool things that we may not have considered.

Basic scoping wins

The simplest and likely most common example would be scoped colors. And what’s our favorite component to use with color? The button. 😅

Consider the standard setup of primary and secondary buttons. Let’s start with some basic markup that uses a BEM syntax.

<button class="button button--primary">Primary</button> <button class="button button--secondary">Secondary</button>

Traditionally, we might do something like this to style them up:

.button {   padding: 1rem 1.25rem;   color: #fff;   font-weight: bold;   font-size: 1.25rem;   margin: 4px;   transition: background 0.1s ease; }  .button--primary {   background: hsl(233, 100%, 50%);   outline-color: hsl(233, 100%, 80%); }  .button--primary:hover {   background: hsl(233, 100%, 40%); }  .button--primary:active {   background: hsl(233, 100%, 30%); }  .button--secondary {   background: hsl(200, 100%, 50%);   outline-color: hsl(200, 100%, 80%); }  .button--secondary:hover {   background: hsl(200, 100%, 40%); }  .button--secondary:active {   background: hsl(200, 100%, 30%); }

See the Pen
Basic buttons
by Jhey (@jh3y)
on CodePen.

That’s an awful lot of code for something not particularly complex. We haven’t added many styles and we’ve added a lot of rules to cater to the button’s different states and colors. We could significantly reduce the code with a scoped variable.

In our example, the only differing value between the two button variants is the hue. Let’s refactor that code a little then. We won’t change the markup but cleaning up the styles a little, we get this:

.button {   padding: 1rem 1.25rem;   color: #fff;   font-weight: bold;   font-size: 1.25rem;   margin: 1rem;   transition: background 0.1s ease;   background: hsl(var(--hue), 100%, 50%);   outline-color: hsl(var(--hue), 100%, 80%);  } .button:hover {   background: hsl(var(--hue), 100%, 40%); }  .button:active {   background: hsl(var(--hue), 100%, 30%); }  .button--primary {   --hue: 233; }  .button--secondary {   --hue: 200; }

See the Pen
Refactoring styles with a scoped variable
by Jhey (@jh3y)
on CodePen.

This not only reduces the code but makes maintenance so much easier. Change the core button styles in one place and it will update all the variants! 🙌

I’d likely leave it there to make it easier for devs wanting to use those buttons. But, we could take it further. We could inline the variable on the actual element and remove the class declarations completely. 😲

<button class="button" style="--hue: 233;">Primary</button> <button class="button" style="--hue: 200;">Secondary</button>

Now we don’t need these. 👍

.button--primary {   --hue: 233; }  .button--secondary {   --hue: 200; }

See the Pen
Scoping w/ inline CSS variables
by Jhey (@jh3y)
on CodePen.

Inlining those variables might not be best for your next design system or app but it does open up opportunities. Like, for example, if we had a button instance where we needed to override the color.

button.button.button--primary(style=`--hue: 20;`) Overridden

See the Pen
Overridden with inline scope
by Jhey (@jh3y)
on CodePen.

Having fun with inline variables

Another opportunity is to have a little fun with it. This is a technique I use for many of the Pens I create over on CodePen. 😉

You may be writing straightforward HTML, but in many cases, you may be using a framework, like React or a preprocessor like Pug, to write your markup. These solutions allow you to leverage JavaScript to create random inline variables. For the following examples, I’ll be using Pug. Pug is an indentation-based HTML templating engine. If you aren’t familiar with Pug, do not fear! I’ll try to keep the markup simple.

Let’s start by randomizing the hue for our buttons:

button.button(style=`--hue: $ {Math.random() * 360}`) First

With Pug, we can use ES6 template literals to inline randomized CSS variables. 💪

See the Pen
Random inline CSS variable hues
by Jhey (@jh3y)
on CodePen.

Animation alterations

So, now that we have the opportunity to define random characteristics for an element, what else could we do? Well, one overlooked opportunity is animation. True, we can’t animate the variable itself, like this:

@keyframes grow {   from { --scale: 1; }   to   { --scale: 2; } }

But we can create dynamic animations based on scoped variables. We can change the behavior of animation on the fly! 🤩

Example 1: The excited button

Let’s create a button that floats along minding its own business and then gets excited when we hover over it.

Start with the markup:

button.button(style=`--hue: $ {Math.random() * 360}`) Show me attention

A simple floating animation may look like this:

@keyframes flow {   0%, 100% {     transform: translate(0, 0);   }   50% {     transform: translate(0, -25%);   } }

This will give us something like this:

See the Pen
The excited button foundation
by Jhey (@jh3y)
on CodePen.

I’ve added a little shadow as an extra but it’s not vital. 👍

Let’s make it so that our button gets excited when we hover over it. Now, we could simply change the animation being used to something like this:

.button:hover {   animation: shake .1s infinite ease-in-out; }  @keyframes shake {   0%, 100% {     transform: translate(0, 0) rotate(0deg);   }   25% {     transform: translate(-1%, 3%) rotate(-2deg);   }   50% {     transform: translate(1%, 2%) rotate(2deg);   }   75% {     transform: translate(1%, -2%) rotate(-1deg);   } }

And it works:

See the Pen
The excited button gets another keyframes definition
by Jhey (@jh3y)
on CodePen.

But, we need to introduce another keyframes definition. What if we could merge the two animations into one? They aren’t too far off from each other in terms of structure.

We could try:

@keyframes flow-and-shake {   0%, 100% {     transform: translate(0, 0) rotate(0deg);   }   25%, 75% {     transform: translate(0, -12.5%) rotate(0deg);   }   50% {     transform: translate(0, -25%) rotate(0deg);   } }

Although this works, we end up with an animation that isn’t quite as smooth because of the translation steps. So what else could we do? Let’s find a compromise by removing the steps at 25% and 75%.

@keyframes flow-and-shake {   0%, 100% {     transform: translate(0, 0) rotate(0deg);   }   50% {     transform: translate(0, -25%) rotate(0deg);   } }

It works fine, as we expected, but here comes the trick: Let’s update our button with some variables.

.button {   --y: -25;   --x: 0;   --rotation: 0;   --speed: 2; }

Now let’s plug them into the animation definition, along with the button’s animation properties.

.button {   animation-name: flow-and-shake;   animation-duration: calc(var(--speed) * 1s);   animation-iteration-count: infinite;   animation-timing-function: ease-in-out; }  @keyframes flow-and-shake {   0%, 100% {     transform: translate(calc(var(--x) * -1%), calc(var(--y) * -1%))       rotate(calc(var(--rotation) * -1deg));   }   50% {     transform: translate(calc(var(--x) * 1%), calc(var(--y) * 1%))       rotate(calc(var(--rotation) * 1deg));   } }

All is well. 👍

Let’s change those values when the button is hovered:

.button:hover {   --speed: .1;   --x: 1;   --y: -1;   --rotation: -1; }

See the Pen
The excited button with refactored keyframes & scoped variables
by Jhey (@jh3y)
on CodePen.

Nice! Now our button has two different types of animations but defined via one set of keyframes. 🤯

Let’s have a little more fun with it. If we take it a little further, we can make the button a little more playful and maybe stop animating altogether when it’s active. 😅

See the Pen
The Excited Button w/ dynamic animation 🤓
by Jhey (@jh3y)
on CodePen.

Example 2: Bubbles

Now that we’ve gone through some different techniques for things we can do with the power of scope, let’s put it all together. We are going to create a randomly generated bubble scene that heavily leverages scoped CSS variables.

Let’s start by creating a bubble. A static bubble.

.bubble {   background: radial-gradient(100% 115% at 25% 25%, #fff, transparent 33%),     radial-gradient(15% 15% at 75% 75%, #80dfff, transparent),     radial-gradient(100% 100% at 50% 25%, transparent, #66d9ff 98%);   border: 1px solid #b3ecff;   border-radius: 100%;   height: 50px;   width: 50px; }

We are using background with multiple values and a border to make the bubble effect — but it’s not very dynamic. We know the border-radius will always be the same. And we know the structure of the border and background will not change. But the values used within those properties and the other property values could all be random.

Let’s refactor the CSS to make use of variables:

.bubble {   --size: 50;   --hue: 195;   --bubble-outline: hsl(var(--hue), 100%, 50%);   --bubble-spot: hsl(var(--hue), 100%, 75%);   --bubble-shade: hsl(var(--hue), 100%, 70%);   background: radial-gradient(100% 115% at 25% 25%, #fff, transparent 33%),     radial-gradient(15% 15% at 75% 75%, var(--bubble-spot), transparent),     radial-gradient(100% 100% at 50% 25%, transparent, var(--bubble-shade) 98%);   border: 1px solid var(--bubble-outline);   border-radius: 100%;   height: calc(var(--size) * 1px);   width: calc(var(--size) * 1px); }

That’s a good start. 👍

See the Pen
Bubbles foundation
by Jhey (@jh3y)
on CodePen.

Let’s add some more bubbles and leverage the inline scope to position them as well as size them. Since we are going to start randomizing more than one value, it’s handy to have a function to generate a random number in range for our markup.

- const randomInRange = (max, min) => Math.floor(Math.random() * (max - min + 1)) + min

With Pug, we can utilize iteration to create a large set of bubbles:

- const baseHue = randomInRange(0, 360) - const bubbleCount = 50 - let b = 0 while b < bubbleCount   - const size = randomInRange(10, 50)   - const x = randomInRange(0, 100)   .bubble(style=`--x: $ {x}; --size: $ {size}; --hue: $ {baseHue}`)   - b++

Updating our .bubble styling allows us to make use of the new inline variables.

.bubble {   left: calc(var(--x) * 1%);   position: absolute;   transform: translate(-50%, 0); }

Giving us a random set of bubbles:

See the Pen
Adding bubbles
by Jhey (@jh3y)
on CodePen.

Let’s take it even further and animate those bubbles so they float from top to bottom and fade out.

.bubble {   animation: float 5s infinite ease-in-out;   top: 100%; }  @keyframes float {   from {     opacity: 1;     transform: translate(0, 0) scale(0);   }   to {     opacity: 0;     transform: translate(0, -100vh) scale(1);   } }

See the Pen
Bubbles rising together
by Jhey (@jh3y)
on CodePen.

That’s pretty boring. They all do the same thing at the same time. So let’s randomize the speed, delay, end scale and distance each bubble is going to travel.

- const randomInRange = (max, min) => Math.floor(Math.random() * (max - min + 1)) + min - const baseHue = randomInRange(0, 360) - const bubbleCount = 50 - let b = 0 while b < bubbleCount   - const size = randomInRange(10, 50)   - const delay = randomInRange(1, 10)   - const speed = randomInRange(2, 20)   - const distance = randomInRange(25, 150)   - const scale = randomInRange(100, 150) / 100   - const x = randomInRange(0, 100)   .bubble(style=`--x: $ {x}; --size: $ {size}; --hue: $ {baseHue}; --distance: $ {distance}; --speed: $ {speed}; --delay: $ {delay}; --scale: $ {scale}`)   - b++

And now, let’s update our styles

.bubble {   animation-name: float;   animation-duration: calc(var(--speed) * 1s);   animation-delay: calc(var(--delay) * -1s);   animation-iteration-count: infinite;   animation-timing-function: ease-in-out; }  @keyframes float {   from {     opacity: 1;     transform: translate(-50%, 0) scale(0);   }   to {     opacity: 0;     transform: translate(-50%, calc(var(--distance) * -1vh)) scale(var(--scale));   } }

And we will get this:

See the Pen
Random bubble scene using variable scope 😎
by Jhey (@jh3y)
on CodePen.

With around 50 lines of code, you can create a randomly generated animated scene by honing the power of the scope! 💪

That’s it!

We can create some pretty cool things with very little code by putting CSS variables to use and leveraging some little tricks.

I do hope this article has raised some awareness for the power of CSS variable scope and I do hope you will hone the power and pass it on 😎

All the demos in this article are available in this CodePen collection.

The post The Power (and Fun) of Scope with CSS Custom Properties appeared first on CSS-Tricks.

CSS-Tricks

, , ,

Weekly Platform News: HTML Inspection in Search Console, Global Scope of Scripts, Babel env Adds defaults Query

In this week’s look around the world of web platform news, Google Search Console makes it easier to view crawled markup, we learn that custom properties aren’t computing hogs, variables defined at the top-level in JavaScript are global to other page scripts, and Babel env now supports the defaults query — plus all of last month’s news compiled into a single package for you.

Easier HTML inspection in Google Search Console

The URL Inspection tool in Google Search Console now includes useful controls for searching within and copying the HTML code of the crawled page.

Note: The URL Inspection tool provides information about Google’s indexed version of a specific page. You can access Google Search Console at https://search.google.com/search-console.

(via Barry Schwartz)

CSS properties are computed once per element

The value of a CSS custom property is computed once per element. If you define a custom property --func on the <html> element that uses the value of another custom property --val, then re-defining the value of --val on a nested DOM element that uses --func won’t have any effect because the inherited value of --func is already computed.

html {   --angle: 90deg;   --gradient: linear-gradient(var(--angle), blue, red); }  header {   --angle: 270deg; /* ignored */   background-image: var(--gradient); /* inherited value */ }

(via Miriam Suzanne)

The global scope of scripts

JavaScript variables created via let, const, or class declarations at the top level of a script (<script> element) continue to be defined in subsequent scripts included in the page.

Note:Axel Rauschmayer calls this the global scope of scripts.”)

(via Surma)

Babel env now supports the defaults query

Babel’s env preset (@babel/preset-env) now allows you to target browserslist’s default browsers (which are listed at browsersl.ist). Note that if you don’t specify your target browsers, Babel env will run every syntax transform on your code.

{   "presets": [     [       "@babel/preset-env",       {         "targets": { "browsers": "defaults" }       }     ]   ] }

(via Nicolò Ribaudo)

All the June 2019 news that’s fit to… print

For your convenience, I have compiled all 59 news items that I’ve published throughout June into one 10-page PDF document.

Download PDF

The post Weekly Platform News: HTML Inspection in Search Console, Global Scope of Scripts, Babel env Adds defaults Query appeared first on CSS-Tricks.

CSS-Tricks

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

Regarding CSS’s Global Scope

html {   font-family: Roboto, sans-serif; }

With the except of some form elements, you’ve just set a font on every bit of text on a site! Nice! That’s probably what you were trying to do, because of the probably hundreds of elements all over your site, setting that font-family every time would be tedious and error-prone.

CSS is global by nature. On purpose!

I like how David Khourshid put it:

You ever stop and think about why CSS has a global scope? Maybe we want to use consistent typography, colors, sizing, spacing, layout, transitions, etc. and have our websites & apps feel like one cohesive unit?

Love the cascade, the cascade is your friend.

And yet. The global nature of CSS is perhaps the most-pointed-at anti-feature of CSS. Some people really don’t like it. We all know it’s very easy to write a single CSS rule that has implications all over a site, breaking things you really didn’t want to break.

There are whole new categories of testing to assist with these problems.

Scoped styles aren’t the only reason there is such interest and adoption in the landscape of tools that is CSS-in-JS, but it’s a big one. There are loads of sites that don’t directly author any CSS at all — even preprocessed styles — and go for a JavaScript library instead where styles are authored quite literally in JavaScript. There is a playground demonstrating the syntax of the various options. Here’s how styled-components works:

import React from 'react'; import styled from 'styled-components';  const Container = styled.main`   display: flex;   flex-direction: column;   min-height: 100%;   width: 100%;   background-color: #f6f9fc; `;  export default function Login() {   return (     <Container>       ... Some stuff ....     </Container>   ); }

There are literally dozens of options, each doing things a bit differently while offering slightly different syntaxes and features. Vue even offers scoped CSS directly in .vue files:

<style scoped> .example {   color: red; } </style>  <template>   <div class="example">hi</div> </template>

Unfortunately, <style scoped> never quite made it as a native web platform feature. There is shadow DOM, though, where a style block can be injected in a template and those styles will be isolated from the rest of the page:

let myElement = document.querySelector('.my-element');  let shadow = myElement.attachShadow({   mode: 'closed' }); shadow.innerHTML = `   <style>     p {        color: red;     }   </style>    <p>Element with Shadow DOM</p> `;

No styles will leak into or out of that shadow DOM boundary. That’s pretty cool for people seeking this kind of isolation, but it could be tricky. You’d likely have to architect the CSS to have certain global styles that can be imported with the shadow DOM’d web component so it can achieve some styling cohesion in your site. Personally, I wish it was possible to make the shadow DOM one-way permeable: styles can leak in, but styles defined inside can’t leak out.

CSS-in-JS stuff is only one way to scope styles. There are actually two sides to the spectrum. You could call CSS-in-JS total isolation, whereas you could author CSS directly with total abstraction:

Total abstraction might come from a project, like Tachyons, that gives you a fixed set of class names to use for styling (Tailwind is like a configurable version of that), or a programmatic tool (like Atomizer) that turns specially named HTML class attributes into a stylesheet with exactly what it needs.

Even adhering 100% to BEM across your entire site could be considered total CSS isolation, solving the problems that the global scope may bring.

Personally, I’d like to see us move to this kind of future:

When we write styles, we will always make a choice. Is this a global style? Am I, on purpose, leaking this style across the entire site? Or, am I writing CSS that is specific to this component? CSS will be split in half between these two. Component-specific styles will be scoped and bundled with the component and used as needed.

Best of both worlds, that.

Anyway, it’s tricky.

Maybe this will be the hottest CSS topic in 2019.

The post Regarding CSS’s Global Scope appeared first on CSS-Tricks.

CSS-Tricks

, , ,
[Top]