Web Standards

Apple’s Proposal for HTML Template Instantiation

Css Tricks - Mon, 11/06/2017 - 5:45am

I'm sure I don't have the expertise to understand the finer nuances of this, but I like the spirit:

The HTML5 specification defines the template element but doesn't provide a native mechanism to instantiate it with some parts of it substituted, conditionally included, or repeated based on JavaScript values — as popular JavaScript frameworks such as Ember.js and Angular allow. As a consequence, there are many incompatible template syntaxes and semantics to do substitution and conditionals within templates — making it hard for web developers to combine otherwise reusable components when they use different templating libraries.

Whilst previously we all decided to focus on shadow DOM and the custom-elements API first, we think the time is right — now that shadow DOM and custom-elements API have been shipping in Safari and Chrome and are in development in Firefox — to propose and standardize an API to instantiate HTML templates.

Let the frameworks compete on speed and developer convenience in other ways.

Direct Link to ArticlePermalink

Apple’s Proposal for HTML Template Instantiation is a post from CSS-Tricks

So you need to parse an email?

Css Tricks - Fri, 11/03/2017 - 1:59pm

Say you have a website with users who have accounts. Those users email you sometimes. What if you could parse that email for more context about that user, their account, and what they might want?

There are email parsing services out there. For example, Zapier offers Parser, which is free, with the idea being that you use Zapier itself to interconnect that data with other apps.

You teach it about your emails and then get programatic access to those data bits.

mailparser.io is another service just for this.

Same deal, you send the emails to them, and from within that app you set up parsers and do all the processing you need to do.

That might not be exactly what you need.

Perhaps your goal in parsing an email is to extend the data available to you right in your email client.

Gmail is a pretty huge email client. I just noticed that they have released and official way to make "Gmail Add-ons":

Gmail add-ons are developed using Apps Script, a scripting language based on JavaScript that serves as a connective platform between Google products like Docs, Sheets, Drive, and Gmail. Every Gmail add-on has a corresponding Apps Script project where you define your add-on's appearance and behavior.

That might be just the ticket for those of you looking to get your hands on email data, do stuff with it, and have a UI for doing stuff right within Gmail. There is a marketplace to check out with existing apps. The Trello one seemed pretty compelling to me.


The contextual cards you create for your add-ons work for both web and mobile versions of Gmail. This means that you don't need to create separate web and mobile versions of the add-on—the same code works everywhere!

Personally, I make pretty heavy use of Front, which is like a shared team inbox superapp.

Front offers a plugin system as well, which adds your own custom panel right into the app itself and gives you all that programatic parsing stuff you need to get into emails (or tweets or whatnot).

We use it at CodePen to figure out who's emailing us (from the perspective of our own app) and show some contextual information about them, as well as provide some quick common actions that we might need.

Another thing to consider is how the emails are being generated at all. For example, do you offer customer support by just saying "email us at xxx@yyy.com", or do you have them fill out a form which generates an email? If it's a form, that's, in a sense, parsing an email before it's even sent, meaning it has structure and potentially programattic access to individual fields.

An example of that might be using a Wufoo form for your support and then using the API to access the data as needed. Maybe you can skip email parsing entirely.

So you need to parse an email? is a post from CSS-Tricks

The CSS attr() function got nothin’ on custom properties

Css Tricks - Thu, 11/02/2017 - 5:21am

Normally, the connection between CSS and HTML is that CSS selectors match HTML elements, and the CSS styles them. CSS doesn't know about the actual content in the HTML. But there is a way CSS can get its hands on data in HTML, so long as that data is within an attribute on that HTML element.

It's like this:

div::after { content: attr(data-whatever); }

That's certainly interesting. You could use it for (rather inaccessible) tooltips, for example:

<button data-tooltip="Information only mouse-having sighted people will see."> Button </button> button:hover::after { content: attr(data-tooltip); /* positioned and styled and whatnot */ /* ya, a :focus style would buy you a tad more a11y */ }

But you can't put HTML in the attribute value, so those tooltips are limited to a string value, and couldn't have a title, link, or anything like that inside them.

Here's a better use case. There is an old print stylesheet chestnut where you use attr() to add the URL's to links, so you can actually see what a link is linking to:

@media (print) { a[href]::after { content: " (" attr(href) " )"; } }

That's clever. But what else? Could you pass a color down?

<h2 data-color="#f06d06"> Custom Colored Header </h2>

That's not invalid, but it isn't useful.

h2 { /* Not gonna work */ color: attr(data-color); }

The value from attr() is a string. Even though that string is in the same format as a hex code, it won't be used as a hex code.

Nor can you pass a URL that can actually be used in something like background-image(). Nor you can pass a unit like 3, 20px or 4rem or 0.8vw.

CSS's attr() function is only strings, and strings are only really useful as content, and content (being unselectable and somewhat inaccessible) isn't particularly useful anyway. You can't select the text of psuedo content, for example, nor search for it, making it rather inacessible.

You know what can pass any sort of value and is equally easy to implement as attributes?

CSS custom properties!

You can pop them right into the style attribute of any element. Now those values are available to that element:

<button style=" --tooltip-string: 'Ug. Tooltips.'; --tooltip-color: #f06d06; --tooltip-font-size: 11px; --tooltip-top: -10px " > Button </button>

We're passing a string to CSS above, but also a color and length values. Those values are immediately usable as-is:

button::after { content: var(--tooltip-string); color: var(--tooltip-color); font-size: var(--tooltip-font-size); }

Here's that demo with some fiddly "logic" (would need to be improved a lot to be actdually useful) to allow variations:

See the Pen CSS Custom Properies Mo' Betta' than attr() by Chris Coyier (@chriscoyier) on CodePen.

This really isn't any more accessible, for the record. If I were implementing tooltips for real, I'd probably read the heck out of this.

What about some other "good" use cases for attr()?

One that comes up a lot is responsive data tables. Imagine a table with headers along a top row and rows of data below:

<table> <thead> <tr> <th>First Name</th> <th>Last Name</th> .... </tr> </thead> <tbody> <tr> <td>Chris</td> <td>Coyier</td> ... </tr> ... </tbody> </table>

Rows of data like that might become problematic on small screens (too wide). So in a reponsive data table, we might hide that top row, and show labels on a per-cell basis instead.

@media (max-width: 500px) { thead { display: none; } /* Need to reveal another label now that we've hidden the normal labels */ }

Where does that label come from? We could do...

. ... <tr> <td data-label="First Name">Chris</td> <td data-label="Last Name">Coyier</td> ... </tr>


td::before { content: attr(data-label); /* Also display: block things and such */ }

That's a pretty good use case. If we use some kinda of accessible hiding method for that <thead>, it might even pass a11y muster.

But this same exact thing is doable with CSS custom properties...

. ... <tr> <td style="--label: 'First Name';">Chris</td> <td style="--label: 'Last Name';">Chris</td> ... </tr> td::before { content: var(--label); ... }

Eric Bidelman pointed me to a method of using psueudo content to show an input's value.

<style> input { vertical-align: middle; margin: 2em; font-size: 14px; height: 20px; } input::after { content: attr(data-value) '/' attr(max); position: relative; left: 135px; top: -20px; } </style> <input type="range" min="0" max="100" value="25"> <script> var input = document.querySelector('input'); input.dataset.value = input.value; // Set an initial value. input.addEventListener('change', function(e) { this.dataset.value = this.value; }); </script>

That feels a smidge dangerous to me since I didn't think pseudo content was supposed to work on replaced elements like an <input>. It's probably a job for output, and the JavaScript would be essentially the same. You could use pseudo content with the additional element, but there's really no need for that.

Exploiting the fact that psuedo content can't be copied is also clever. For example, GitHub does code block line numbering with data-line-number="" and ::before { content: attr(data-line-number); }.

Nobody likes selecting line numbers when they are trying to copy code! Good use here (probably even more flexible than CSS counters), but again, something that CSS custom properties could handle as well.

<td style="--line-num: 5"> ... </td>

You could argue this is better because if you did want to use CSS counters, you could use that first value to kick things off and not need it on every line.

See the Pen Line Numbering by Chris Coyier (@chriscoyier) on CodePen.

Same deal with typographic trickery involving duplicating text in CSS for stylistic reasons. Check out this cool demo by Mandy Michael using attr(). I'm sure you can imagine how --heading: "Fracture"; could do the trick there.

The CSS3 Values spec (in Candidate Recommendation) has a way to make attr() useful

I'm not sure it matters much, as I'd argue CSS custom properties are a near total replacement for attr(), but the spec does specifically cover this, presumably as an attempt to make it more useful.

The idea is to set the type of value as you grab it in CSS.

<div data-color="red">Some Words</div> div { color: attr(data-color color); }


<span data-size="50">span</span> span { font-size: attr(data-size px); }

But as far as I can tell, no browser supports this.

The CSS attr() function got nothin’ on custom properties is a post from CSS-Tricks

Manage and Protect Your Apple Devices

Css Tricks - Thu, 11/02/2017 - 5:01am

(This is a sponsored post.)

Jamf Now is a mobile device management solution for the iPad, iPhone, and Mac devices at work.

We make management tasks like deploying Wi-Fi passwords, setting up email accounts, securing company data, and enforcing passcodes, simple and affordable, so businesses can support their users. No IT required.

CSS-Tricks readers can manage their first 3 devices for free, forever! Sign up today to create your free account!

Direct Link to ArticlePermalink

Manage and Protect Your Apple Devices is a post from CSS-Tricks

Can VS Code Do Emmet?

Css Tricks - Wed, 11/01/2017 - 2:53pm

As in, does Visual Studio Code, the free code editor from Microsoft, work with Emmet, the free and open source code expansion tool? The answer is of course! In fact, you don't have to do anything at all to get it going. Emmet is built-in to VS Code.

Let's take a look at what Emmet can do and some VS Code specific stuff to make the most of it.

In this article, I'll use ? to denote ?the command key on Apple and the control key on Windows. I'll also use ?? for the shift key.

What Is Emmet?

Emmet is a code expansion tool that is designed to dramatically speed up the creation of HTML and CSS.

It works like this. Say you wanted to create a div. Normally, you would type out each character:

< ... d ... i ... v ... >

Your text editor might even help autocomplete that tag name for you. Then to close it, you might only have to type a new angle bracket (<) and it will offer to complete the closing tag.

This is not a bad way to compose HTML, but when you're working in HTML, you're probably writing many more tags than that, which means that you're opening and closing a lot of angle brackets. Those angle brackets can really slow you down and make the creation of HTML tedious.

This is where Emmet comes in.

Emmet allows you to simply type div and hit the tab key. It is automatically expanded out into a full HTML div tag.

This works for any HTML tag. You can add an id with a #, a class with a ., and attributes with [foo]. It's happy to do multiple of each!

In fact, if you are working with a div, you don't even need to specify the div tag. You can just start with the id or class and you will get a div by default. Emmet is even smart enough to change that to a <span> if you are within another inline-by-default element.

Quite complex markup can be composed this way. You can use > to specify going down a level and + to specify items at the same level. Take for example a Bootstrap Navbar. The markup a navbar might look like this:

<nav class="navbar"> <a class="navbar-brand" href="#">Navbar</a> <div class="navbar-nav"> <a class="nav-item nav-link" href="#">Home</a> <a class="nav-item nav-link" href="#">Features</a> <a class="nav-item nav-link" href="#">Pricing</a> </div> </nav>

We can generate this whole block with Emmet like this:

There's a lot going on there in terms of Emmet syntax. The best way to learn it is to grab the Emmet Syntax Cheat Sheet.

You can also incorporate Lorem Ipsum into the generated markup as needed.

You probably noticed that I was able to auto-generate 3 anchor tags and that when I expanded all of it out into HTML, I got tab stops on all my anchor tags and href attributes. This allows you to quickly create a block of HTML and then go and add specifics to each item.

There is another? (?dare I say cooler) way to do this though &#x1f60e;. It's called "Wrap Individual Lines".

Wrap Individual Lines With Abbreviation

If we back up to the example above, what we really want is a navbar with Home, Features, and Pricing links. We can add that text after we generate the markup with Emmet, or we can do it before using the "Wrap Individual Lines With Abbreviation" command.

Invoke the command pallet (? + ? + P) and select "Wrap Individual Lines". We enter the exact same Emmet syntax that we had before, but this time we stop at the * as this is where Emmet will insert our lines.

There is also a "Wrap With Abbreviation" that will wrap a single line without having to select anything. This is particularly useful when wrapping links. Simple paste a URL in, invoke the command, hit "a" and then hit enter.

&#x1f525; Hot Tip

Map a keyboard shortcut to the "Wrap With Abbreviation" command. This makes it super quick to create things like anchors.

Emmet In Other File Types

By default, Emmet works on HTML and CSS?—?and that includes preprocessors/template languages. Out of the box, you get support in html, haml, jade, slim, jsx, xml, xsl, css, scss, sass, less and stylus. The docs also mention that you get it in any file type that inherits from the above file types?—?such as php.


Emmet also works in CSS. It works with a search technique called Fuzzy search which matches what you are trying to express with few characters. For example df expands into display: flex; and w100p expands in width: 100%;.

These work in different CSS files types, like Sass, where it knows not to insert semicolons in the `.sass` syntax.

File type inheritence

Unfortunately, there is no way to know which files inherit from an existing language type. For instance, Emmet does not work in Markdown files because Markdown does not inherit from any of the known types. Even though HTML is perfectly valid in Markdown files.

To fix this, we can add a language mapping so Emmet knows to behave properly in Markdown files. Add the following line to the User Settings file.

To jump to User Settings quickly, press ?, (command comma).

"emmet.includeLanguages": { "markdown": "html" }

Now we've mapped Markdown to HTML so Emmet knows how to treat the file. Well, almost. If we open up a Markdown file, you'll notice that Emmet is working, but not like we would expect. If I type div and press tab, nothing happens. If I type div and then an *, Emmet tries to kick in via the suggestions menu.

That's not good because Markdown uses asterisks for headers. Emmet is going to intercept those every time. The reason this is happening is that VS Code is trying to prevent the tab key from clobbering some other function that the tab key might serve in a document of this type.

To get around this, we can add the following line to our User Settings:

"emmet.triggerExpansionOnTab": true

Now the div expansion will work on tab, but the asterisks still fire the Emmet suggestion window. My suggestion is to turn that window off completely with the following line in User Settings:

"emmet.showExpandedAbbreviation": "never"

This also works for languages like React and Vue. While Emmet works by default in JSX files, we can make it work in JSX in regular JavaScript too. Add this mapping to your User Settings:

"emmet.includeLanguages": { "javascript": "javascriptreact" } Creating Your Own Snippets

You can also create your own Emmet snippets.

For example, you could expand Emmet's SVG capability by creating a custom snippet like rect that maps to rect[x][y][width][height] and thus expand into:

<rect x="" y="" width="" height=""></rect>

More information about that can be found in the official VS Code documentation. This is how CodePen custom snippets work under the hood as well!

More Emmet Resources

If you'd like to learn more about Emmet, here are some other resources for getting started:

Can VS Code Do Emmet? is a post from CSS-Tricks

A Reasonable Approach for Getting Comfortable With Command Line

Css Tricks - Tue, 10/31/2017 - 8:14am

Considering how much the command line is an integral part of the developer's workflow, it should not be thought overly difficult or tedious to learn.

At one time I avoided it myself, but one day began teaching myself ways to make the difficult as easy as it should be. I got over the hurdle, and you can too. It was worth investing my time to increase my command line comfort-level, and I am going to share a few tips and resources that I found helpful in this post.

The intended reader is the type who typically avoids the command line, or perhaps uses it occasionally but not as a regular or essential tool.

Tip #1: Maintain a Pragmatic Mindset

The way to get more comfortable with the command line is this: practice. You practice, and you get better. There is no secret trick to this; study and repetition of skills will turn into understanding and mastery. There is no use in the mindset that you cannot do this; it will only keep you from your goal. You may as well discard such thoughts and get down to it.

Tip #2: Keep a Cheat sheet

Don't be afraid to keep a cheat sheet. I find that a thin, spiral-bound notebook kept next to my keyboard is perfect; writing the command down helps commit it to memory; having it in a place where I can refer to it while I am typing is convenient to the process. Do not permit yourself merely to copy and paste; you will not learn this way. Until you know the command, make yourself type it out.

Tip #3: Peruse languages outside of the one(s) you normally use
  1. Spend time looking at commands in various languages, looking at the commands even if you don't immediately absorb, use, or remember them. It is worth it to invest a bit of time regularly, looking at these commands; patterns will eventually emerge. Some of them may even come back to you at an unexpected time and give you an extra eureka moment!
  2. Skimming through books with lots of CLI commands can prove interestingly useful for recognizing patterns in commands. I even take this one step further by getting my favorites spiral-bound. I am a big fan of spiral binding; a place like FedEx offers coil binding services at a surprisingly low cost.
Tip #4: Practice... safely

When I am advising someone who is new to contributing to open source, they are inevitably a bit nervous about it. I think this is perfectly natural if only to comfort myself that my own nervousness about it was perfectly natural. A good way to practice, though, is to set up your own repository for a project and regularly commit to it. Simply using common Git commands in a terminal window to commit inconsequential changes to a project of your own, will establish the "muscle memory" so that when it does come time to actually commit code of consequence, you won't be held back by still being nervous about the commands themselves.

These are the commands I have noticed most common to use in the practical day-to-day of development. It's perfectly acceptable to expect yourself to learn these and to be able to do any of them without a second thought. Do not use a GUI tool (they make weird merge choices). Learn how to write these commands yourself.

  • Check status
  • Create a new branch and switch to it
  • Add files
    • Add all the changes
    • Just add one of the changes
  • Commit
  • Push to a remote branch
  • Get a list of your branches
  • Checkout a branch
  • Delete a branch
  • Delete a branch even if there are changes
  • Fetch and merge the changes to a branch

Syncing a fork took longer to learn- I don't often spend my work hours writing code for a repository that I don't have access to. While contributing to open source software, however, I had to learn how to do this. The GitHub article about the topic is sufficient; even now I still have it bookmarked.

Tip #5: Level Up!

I really enjoy using Digital Ocean to level up my skills. Their step-by-step guides are really useful, and for $5 USD per month, "Droplets" are a cost-effective way to do so.

Here's a suggested self-learning path (which, feel free to choose your own adventure! There are over 1700 tutorials in the Digital Ocean community):

  1. Create a Droplet with Ghost pre-installed. There is a little command line work to finalize the installation, which makes it a good candidate. It's not completely done for you but there's not so much to do that it's overwhelming. There's even an excellent tutorial already written by Melissa Anderson.
  2. Set up a GitHub repo to work on a little theming for Ghost, making small changes and practice your command line work.

It would be remiss of me to write any guide without mentioning Ember, as the ember-cli is undoubtedly one of the strongest there is. Feel free to head over to the docs and read that list!


There may be some that find this brief guide too simplistic. However, as famously said by S. Thompson in Calculus Made Easy- "What one fool can do, other fools can do also." Don't let anyone else make you think that using the command line is horribly difficult, or that they are a genius because they can. With practice, you will be able to do it, and it will soon be a simple thing.

A Reasonable Approach for Getting Comfortable With Command Line is a post from CSS-Tricks

How To Develop Goals In A Usability Test

Usability Geek - Mon, 10/30/2017 - 2:31pm
Usability is the glue that sticks your user to your web and mobile designs. A well-designed user interface (UI) does not mean much if your users do not know how to engage with it. It needs to be...
Categories: Web Standards

Intersection Observers: the beginning

QuirksBlog - Mon, 10/30/2017 - 6:10am

Today I spent about an hour in writing a few very simple Intersection Observer tests, two hours in running them in a few browsers, and now an hour in writing down the results.

I’ve only just started my research, but can already draw a few odd conclusions, which make me fear Intersection Observers are not yet ready to be deployed on a large scale, particularly on mobile.

Intersection Observers are supposed to fire whenever an element (the target) scrolls into or out of a root viewport — and that can mean a wrapper element with overflow: auto or the actual browser viewport. See this test page for the basic effect.

Those who’ve followed my blog for a long time probably know the first question I asked: “Browser viewport? Which browser viewport?” As usual, spec authors and article writers alike ignore this question entirely, while it is quite important for the mobile experience to know whether the observer uses the layout viewport or the visual viewport as its root.

And as you might have guessed, browsers use the wrong viewport here.

But I’m getting ahead of myself now. First have some useful articles:

Test 1: a wrapper div

The first test I created used a scrollable div as the root and a nested div as a target. When the target entered or exited the scrollable div’s viewport (i.e. when it became visible or invisible) the observer fired, just as one would expect.

I tested in Chrome/Mac, Chrome/Android, Samsung Internet, Firefox/Mac, Firefox/Android, and Edge. All of them handled this use case correctly. (Safari does not support Intersection Observers yet; neither on Mac nor on iOS.)

However, the first three, the Blink-based browsers, had one tiny, but telling bug. See the second test case on the page for the full details.

I currently suspect that the Blink-based browsers use the root’s padding box, and not its border box, as the actual viewport area.

That means that if the target element touches the root’s padding the browser fires an intersection observer, even though the target element is still fully visible within the box. To me, this is a bug. Not a huge one, but still a bug.

Test 2: the browser viewport

Even more interesting is the test that uses the browser viewport as root. As far as I’m concerned this is a very important use case: scrollable divs have their place in web development, but intersection observers are at their best when they tell you a certain element scrolls into the browser viewport and thus the user’s view.

Intersection observers expect an options object that may contain a root. The default value is the browser viewport (which one? crickets). So I decided to test that.

In Firefox and Chrome on Mac it worked roughly as I expected. The intersection observer fired when the target element entered or left the browser window. This is what one would expect.

I have no idea what kind of default root element Edge 15 uses. It’s not the browser viewport, since the observer does not fire when the target element enters or exits the browser window. I thought it might be the HTML element (i.e. the full document), but that would mean the observer never fires. And it does fire once you make the browser window narrow enough vertically. Weird.

Then on to mobile. On desktop the layout viewport is equal to the visual viewport, but on mobile it’s not. Which viewport would intersection observers use?

Try for yourself once you’ve zoomed in a bit — and use my visualisation app to understand why this test proves the following.

Mobile browsers (Chrome, Samsung Internet, and Firefox) all use the layout viewport as their root element. And this is the wrong viewport.

What we want to know is when the user starts seeing the target element; in other words, when it moves into or out of the visual viewport. But when you’ve zoomed in the intersection observer and the user-viewed area go out of sync, since the browsers wrongly use the layout viewport as their root.

So there you go. Unusable on mobile, badly damaged in Edge, and a small but potentially annoying bug in Blink. Intersection observers have not yet come to stay.

Make Like it Matters

Css Tricks - Mon, 10/30/2017 - 3:57am

(This is a sponsored post.)

Our sponsor Media temple is holding a contest to give away a bunch of stuff, including a nice big monitor and gift cards. Entering is easy, you just drop them an image or URL to a project you're proud of. Do it quickly though, as entries end on Tuesday. Then the top 20 will be publicly voted on. US residents only.

Direct Link to ArticlePermalink

Make Like it Matters is a post from CSS-Tricks

Emulating CSS Timing Functions with JavaScript

Css Tricks - Mon, 10/30/2017 - 3:22am

CSS animations and transitions are great! However, while recently toying with an idea, I got really frustrated with the fact that gradients are only animatable in Edge (and IE 10+). Yes, we can do all sorts of tricks with background-position, background-size, background-blend-mode or even opacity and transform on a pseudo-element/ child, but sometimes these are just not enough. Not to mention that we run into similar problems when wanting to animate SVG attributes without a CSS correspondent.

Using a lot of examples, this article is going to explain how to smoothly go from one state to another in a similar fashion to that of common CSS timing functions using just a little bit of JavaScript, without having to rely on a library, so without including a lot of complicated and unnecessary code that may become a big burden in the future.

This is not how the CSS timing functions work. This is an approach that I find simpler and more intuitive than working with Bézier curves. I'm going to show how to experiment with different timing functions using JavaScript and dissect use cases. It is not a tutorial on how to do beautiful animation.

A few examples using a linear timing function

Let's start with a left to right linear-gradient() with a sharp transition where we want to animate the first stop. Here's a way to express that using CSS custom properties:

background: linear-gradient(90deg, #ff9800 var(--stop, 0%), #3c3c3c 0);

On click, we want the value of this stop to go from 0% to 100% (or the other way around, depending on the state it's already in) over the course of NF frames. If an animation is already running at the time of the click, we stop it, change its direction, then restart it.

We also need a few variables such as the request ID (this gets returned by requestAnimationFrame), the index of the current frame (an integer in the [0, NF] interval, starting at 0) and the direction our transition is going in (which is 1 when going towards 100% and -1 when going towards 0%).

While nothing is changing, the request ID is null. We also set the current frame index to 0 initially and the direction to -1, as if we've just arrived to 0% from 100%.

const NF = 80; // number of frames transition happens over let rID = null, f = 0, dir = -1; function stopAni() { cancelAnimationFrame(rID); rID = null; }; function update() {}; addEventListener('click', e => { if(rID) stopAni(); // if an animation is already running, stop it dir *= -1; // change animation direction update(); }, false);

Now all that's left is to populate the update() function. Within it, we update the current frame index f. Then we compute a progress variable k as the ratio between this current frame index f and the total number of frames NF. Given that f goes from 0 to NF (included), this means that our progress k goes from 0 to 1. Multiply this with 100% and we get the desired stop.

After this, we check whether we've reached one of the end states. If we have, we stop the animation and exit the update() function.

function update() { f += dir; // update current frame index let k = f/NF; // compute progress document.body.style.setProperty('--stop', `${+(k*100).toFixed(2)}%`); if(!(f%NF)) { stopAni(); return } rID = requestAnimationFrame(update) };

The result can be seen in the Pen below (note that we go back on a second click):

See the Pen by thebabydino (@thebabydino) on CodePen.

The way the pseudo-element is made to contrast with the background below is explained in an older article.

The above demo may look like something we could easily achieve with an element and translating a pseudo-element that can fully cover it, but things get a lot more interesting if we give the background-size a value that's smaller than 100% along the x axis, let's say 5em:

See the Pen by thebabydino (@thebabydino) on CodePen.

This gives us a sort of a "vertical blinds" effect that cannot be replicated in a clean manner with just CSS if we don't want to use more than one element.

Another option would be not to alternate the direction and always sweep from left to right, except only odd sweeps would be orange. This requires tweaking the CSS a bit:

--c0: #ff9800; --c1: #3c3c3c; background: linear-gradient(90deg, var(--gc0, var(--c0)) var(--stop, 0%), var(--gc1, var(--c1)) 0)

In the JavaScript, we ditch the direction variable and add a type one (typ) that switches between 0 and 1 at the end of every transition. That's when we also update all custom properties:

const S = document.body.style; let typ = 0; function update() { let k = ++f/NF; S.setProperty('--stop', `${+(k*100).toFixed(2)}%`); if(!(f%NF)) { f = 0; S.setProperty('--gc1', `var(--c${typ})`); typ = 1 - typ; S.setProperty('--gc0', `var(--c${typ})`); S.setProperty('--stop', `0%`); stopAni(); return } rID = requestAnimationFrame(update) };

This gives us the desired result (click at least twice to see how the effect differs from that in the first demo):

See the Pen by thebabydino (@thebabydino) on CodePen.

We could also change the gradient angle instead of the stop. In this case, the background rule becomes:

background: linear-gradient(var(--angle, 0deg), #ff9800 50%, #3c3c3c 0);

In the JavaScript code, we tweak the update() function:

function update() { f += dir; let k = f/NF; document.body.style.setProperty( '--angle', `${+(k*180).toFixed(2)}deg` ); if(!(f%NF)) { stopAni(); return } rID = requestAnimationFrame(update) };

We now have a gradient angle transition in between the two states (0deg and 180deg):

See the Pen by thebabydino (@thebabydino) on CodePen.

In this case, we might also want to keep going clockwise to get back to the 0deg state instead of changing the direction. So we just ditch the dir variable altogether, discard any clicks happening during the transition, and always increment the frame index f, resetting it to 0 when we've completed a full rotation around the circle:

function update() { let k = ++f/NF; document.body.style.setProperty( '--angle', `${+(k*180).toFixed(2)}deg` ); if(!(f%NF)) { f = f%(2*NF); stopAni(); return } rID = requestAnimationFrame(update) }; addEventListener('click', e => { if(!rID) update() }, false);

The following Pen illustrates the result - our rotation is now always clockwise:

See the Pen by thebabydino (@thebabydino) on CodePen.

Something else we could do is use a radial-gradient() and animate the radial stop:

background: radial-gradient(circle, #ff9800 var(--stop, 0%), #3c3c3c 0);

The JavaScript code is identical to that of the first demo and the result can be seen below:

See the Pen by thebabydino (@thebabydino) on CodePen.

We may also not want to go back when clicking again, but instead make another blob grow and cover the entire viewport. In this case, we add a few more custom properties to the CSS:

--c0: #ff9800; --c1: #3c3c3c; background: radial-gradient(circle, var(--gc0, var(--c0)) var(--stop, 0%), var(--gc1, var(--c1)) 0)

The JavaScript is the same as in the case of the third linear-gradient() demo. This gives us the result we want:

See the Pen by thebabydino (@thebabydino) on CodePen.

A fun tweak to this would be to make our circle start growing from the point we clicked. To do so, we introduce two more custom properties, --x and --y:

background: radial-gradient(circle at var(--x, 50%) var(--y, 50%), var(--gc0, var(--c0)) var(--stop, 0%), var(--gc1, var(--c1)) 0)

When clicking, we set these to the coordinates of the point where the click happened:

addEventListener('click', e => { if(!rID) { S.setProperty('--x', `${e.clientX}px`); S.setProperty('--y', `${e.clientY}px`); update(); } }, false);

This gives us the following result where we have a disc growing from the point where we clicked:

See the Pen by thebabydino (@thebabydino) on CodePen.

Another option would be using a conic-gradient() and animating the angular stop:

background: conic-gradient(#ff9800 var(--stop, 0%), #3c3c3c 0%)

Note that in the case of conic-gradient(), we must use a unit for the zero value (whether that unit is % or an angular one like deg doesn't matter), otherwise our code won't work - writing conic-gradient(#ff9800 var(--stop, 0%), #3c3c3c 0) means nothing gets displayed.

The JavaScript is the same as for animating the stop in the linear or radial case, but bear in mind that this currently only works in Chrome with Experimental Web Platform Features enabled in chrome://flags.

The Experimental Web Platform Features flag enabled in Chrome Canary (63.0.3210.0).

Just for the purpose of displaying conic gradients in the browser, there's a polyfill by Lea Verou and this works cross-browser but doesn't allow using CSS custom properties.

The recording below illustrates how our code works:

Recording of how our first conic-gradient() demo works in Chrome with the flag enabled (live demo).

This is another situation where we might not want to go back on a second click. This means we need to alter the CSS a bit, in the same way we did for the last radial-gradient() demo:

--c0: #ff9800; --c1: #3c3c3c; background: conic-gradient( var(--gc0, var(--c0)) var(--stop, 0%), var(--gc1, var(--c1)) 0%)

The JavaScript code is exactly the same as in the corresponding linear-gradient() or radial-gradient() case and the result can be seen below:

Recording of how our second conic-gradient() demo works in Chrome with the flag enabled (live demo).

Before we move on to other timing functions, there's one more thing to cover: the case when we don't go from 0% to 100%, but in between any two values. We take the example of our first linear-gradient, but with a different default for --stop, let's say 85% and we also set a --stop-fin value - this is going to be the final value for --stop:

--stop-ini: 85%; --stop-fin: 26%; background: linear-gradient(90deg, #ff9800 var(--stop, var(--stop-ini)), #3c3c3c 0)

In the JavaScript, we read these two values - the initial (default) and the final one - and we compute a range as the difference between them:

const S = getComputedStyle(document.body), INI = +S.getPropertyValue('--stop-ini').replace('%', ''), FIN = +S.getPropertyValue('--stop-fin').replace('%', ''), RANGE = FIN - INI;

Finally, in the update() function, we take into account the initial value and the range when setting the current value for --stop:

document.body.style.setProperty( '--stop', `${+(INI + k*RANGE).toFixed(2)}%` );

With these changes we now have a transition in between 85% and 26% (and the other way on even clicks):

See the Pen by thebabydino (@thebabydino) on CodePen.

If we want to mix units for the stop value, things get hairier as we need to compute more things (box dimensions when mixing % and px, font sizes if we throw em or rem in the mix, viewport dimensions if we want to use viewport units, the length of the 0% to 100% segment on the gradient line for gradients that are not horizontal or vertical), but the basic idea remains the same.

Emulating ease-in/ ease-out

An ease-in kind of function means the change in value happens slow at first and then accelerates. ease-out is exactly the opposite - the change happens fast in the beginning, but then slows down towards the end.

The ease-in (left) and ease-out (right) timing functions (live).

The slope of the curves above gives us the rate of change. The steeper it is, the faster the change in value happens.

We can emulate these functions by tweaking the linear method described in the first section. Since k takes values in the [0, 1] interval, raising it to any positive power also gives us a number within the same interval. The interactive demo below shows the graph of a function f(k) = pow(k, p) (k raised to an exponent p) shown in purple and that of a function g(k) = 1 - pow(1 - k, p) shown in red on the [0, 1] interval versus the identity function id(k) = k (which corresponds to a linear timing function).

See the Pen by thebabydino (@thebabydino) on CodePen.

When the exponent p is equal to 1, the graphs of the f and g functions are identical to that of the identity function.

When exponent p is greater than 1, the graph of the f function is below the identity line - the rate of change increases as k increases. This is like an ease-in type of function. The graph of the g function is above the identity line - the rate of change decreases as k increases. This is like an ease-out type of function.

It seems an exponent p of about 2 gives us an f that's pretty similar to ease-in, while g is pretty similar to ease-out. With a bit more tweaking, it looks like the best approximation is for a p value of about 1.675:

See the Pen by thebabydino (@thebabydino) on CodePen.

In this interactive demo, we want the graphs of the f and g functions to be as close as possible to the dashed lines, which represent the ease-in timing function (below the identity line) and the ease-out timing function (above the identity line).

Emulating ease-in-out

The CSS ease-in-out timing function looks like in the illustration below:

The ease-in-out timing function (live).

So how can we get something like this?

Well, that's what harmonic functions are for! More exactly, the ease-in-out out shape is reminiscent the shape of the sin() function on the [-90°,90°] interval.

The sin(k) function on the [-90°,90°] interval (live).

However, we don't want a function whose input is in the [-90°,90°] interval and output is in the [-1,1] interval, so let's fix this!

This means we need to squish the hashed rectangle ([-90°,90°]x[-1,1]) in the illustration above into the unit one ([0,1]x[0,1]).

First, let's take the domain [-90°,90°]. If we change our function to be sin(k·180°) (or sin(k·?) in radians), then our domain becomes [-.5,.5] (we can check that -.5·180° = 90° and .5·180° = 90°):

The sin(k·?) function on the [-.5,.5] interval (live).

We can shift this domain to the right by .5 and get the desired [0,1] interval if we change our function to be sin((k - .5)·?) (we can check that 0 - .5 = -.5 and 1 - .5 = .5):

The sin((k - .5)·?) function on the [0,1] interval (live).

Now let's get the desired codomain. If we add 1 to our function making it sin((k - .5)·?) + 1 this shifts out codomain up into the [0, 2] interval:

The sin((k - .5)·?) + 1 function on the [0,1] interval (live).

Dividing everything by 2 gives us the (sin((k - .5)·?) + 1)/2 function and compacts the codomain into our desired [0,1] interval:

The (sin((k - .5)·?) + 1)/2 function on the [0,1] interval (live).

This turns out to be a good approximation of the ease-in-out timing function (represented with an orange dashed line in the illustration above).

Comparison of all these timing functions

Let's say we want to have a bunch of elements with a linear-gradient() (like in the third demo). On click, their --stop values go from 0% to 100%, but with a different timing function for each.

In the JavaScript, we create a timing functions object with the corresponding function for each type of easing:

tfn = { 'linear': function(k) { return k; }, 'ease-in': function(k) { return Math.pow(k, 1.675); }, 'ease-out': function(k) { return 1 - Math.pow(1 - k, 1.675); }, 'ease-in-out': function(k) { return .5*(Math.sin((k - .5)*Math.PI) + 1); } };

For each of these, we create an article element:

const _ART = []; let frag = document.createDocumentFragment(); for(let p in tfn) { let art = document.createElement('article'), hd = document.createElement('h3'); hd.textContent = p; art.appendChild(hd); art.setAttribute('id', p); _ART.push(art); frag.appendChild(art); } n = _ART.length; document.body.appendChild(frag);

The update function is pretty much the same, except we set the --stop custom property for every element as the value returned by the corresponding timing function when fed the current progress k. Also, when resetting the --stop to 0% at the end of the animation, we also need to do this for every element.

function update() { let k = ++f/NF; for(let i = 0; i < n; i++) { _ART[i].style.setProperty( '--stop', `${+tfn[_ART[i].id](k).toFixed(5)*100}%` ); } if(!(f%NF)) { f = 0; S.setProperty('--gc1', `var(--c${typ})`); typ = 1 - typ; S.setProperty('--gc0', `var(--c${typ})`); for(let i = 0; i < n; i++) _ART[i].style.setProperty('--stop', `0%`); stopAni(); return; } rID = requestAnimationFrame(update) };

This gives us a nice visual comparison of these timing functions:

See the Pen by thebabydino (@thebabydino) on CodePen.

They all start and finish at the same time, but while the progress is constant for the linear one, the ease-in one starts slowly and then accelerates, the ease-out one starts fast and then slows down and, finally, the ease-in-out one starts slowly, accelerates and then slows down again at the end.

Timing functions for bouncing transitions

I first came across the concept years ago, in Lea Verou's CSS Secrets talk. These happen when the y (even) values in a cubic-bezier() function are outside the [0, 1] range and the effect they create is of the animated value going outside the interval between its initial and final value.

This bounce can happen right after the transition starts, right before it finishes or at both ends.

A bounce at the start means that, at first, we don't go towards the final state, but in the opposite direction. For example, if want to animate a stop from 43% to 57% and we have a bounce at the start, then, at first, out stop value doesn't increase towards 57%, but decreases below 43% before going back up to the final state. Similarly, if we go from an initial stop value of 57% to a final stop value of 43% and we have a bounce at the start, then, at first, the stop value increases above 57% before going down to the final value.

A bounce at the end means we overshoot our final state and only then go back to it. If want to animate a stop from 43% to 57% and we have a bounce at the end, then we start going normally from the initial state to the final one, but towards the end, we go above 57% before going back down to it. And if we go from an inital stop value of 57% to a final stop value of 43% and we have a bounce at the end, then, at first, we go down towards the final state, but, towards the end, we pass it and we briefly have stop values below 43% before our transition finishes there.

If what they do is still difficult to grasp, below there's a comparative example of all three of them in action.

The three cases.

These kinds of timing functions don't have their own keywords associated, but they look cool and they are what we want in a lot of situations.

Just like in the case of ease-in-out, the quickest way of getting them is by using harmonic functions. The difference lies in the fact that now we don't start from the [-90°,90°] domain anymore.

For a bounce at the beginning, we start with the [s, 0°] portion of the sin() function, where s (the start angle) is in the (-180°,-90°) interval. The closer it is to -180°, the bigger the bounce is and the faster it will go to the final state after it. So we don't want it to be really close to -180° because the result would look too unnatural. We also want it to be far enough from -90° that the bounce is noticeable.

In the interactive demo below, you can drag the slider to change the start angle and then click on the stripe at the bottom to see the effect in action:

See the Pen by thebabydino (@thebabydino) on CodePen.

In the interactive demo above, the hashed area ([s,0]x[sin(s),0]) is the area we need move and scale into the [0,1]x[0,1] area in order to get our timing function. The part of the curve that's below its lower edge is where the bounce happens. You can adjust the start angle using the slider and then click on the bottom bar to see how the transition looks for different start angles.

Just like in the ease-in-out case, we first squish the domain into the [-1,0] interval by dividing the argument with the range (which is the maximum 0 minus the minimum s). Therefore, our function becomes sin(-k·s) (we can check that -(-1)·s = s and -0·s = 0):

The sin(-k·s) function on the [-1,0] interval (live).

Next, we shift this interval to the right (by 1, into [0,1]). This makes our function sin(-(k - 1)·s) = sin((1 - k)·s) (it checks that 0 - 1 = -1 and 1 - 1 = 0):

The sin(-(k - 1)·s) function on the [0,1] interval (live).

We then shift the codomain up by its value at 0 (sin((1 - 0)*s) = sin(s)). Our function is now sin((1 - k)·s) - sin(s) and our codomain [0,-sin(s)].

The sin(-(k - 1)·s) - sin(s) function on the [0,1] interval (live).

The last step is to expand the codomain into the [0,1] range. We do this by dividing by its upper limit (which is -sin(s)). This means our final easing function is 1 - sin((1 - k)·s)/sin(s)

The 1 - sin((1 - k)·s)/sin(s) function on the [0,1] interval (live).

For a bounce at the end, we start with the [0°, e] portion of the sin() function, where e (the end angle) is in the (90°,180°) interval. The closer it is to 180°, the bigger the bounce is and the faster it will move from the initial state to the final one before it overshoots it and the bounce happens. So we don't want it to be really close to 180° as the result would look too unnatural. We also want it to be far enough from 90° so that the bounce is noticeable.

See the Pen by thebabydino (@thebabydino) on CodePen.

In the interactive demo above, the hashed area ([0,e]x[0,sin(e)]) is the area we need to squish and move into the [0,1]x[0,1] square in order to get our timing function. The part of the curve that's below its upper edge is where the bounce happens.

We start by squishing the domain into the [0,1] interval by dividing the argument with the range (which is the maximum e minus the minimum 0). Therefore, our function becomes sin(k·e) (we can check that 0·e = 0 and 1·e = e):

The sin(k·e) function on the [0,1] interval (live).

What's still left to do is to expand the codomain into the [0,1] range. We do this by dividing by its upper limit (which is sin(e)). This means our final easing function is sin(k·e)/sin(e).

The sin(k·e)/sin(e) function on the [0,1] interval (live).

If we want a bounce at each end, we start with the [s, e] portion of the sin() function, where s is in the (-180°,-90°) interval and e in the (90°,180°) interval. The larger s and e are in absolute values, the bigger the corresponding bounces are and the more of the total transition time is spent on them alone. On the other hand, the closer their absolute values get to 90°, the less noticeable their corresponding bounces are. So, just like in the previous two cases, it's all about finding the right balance.

See the Pen by thebabydino (@thebabydino) on CodePen.

In the interactive demo above, the hashed area ([s,e]x[sin(s),sin(e)]) is the area we need to move and scale into the [0,1]x[0,1] square in order to get our timing function. The part of the curve that's beyond its horizontal edges is where the bounces happen.

We start by shifting the domain to the right into the [0,e - s] interval. This means our function becomes sin(k + s) (we can check that 0 + s = s and that e - s + s = e).

The sin(k + s) function on the [0,e - s] interval (live).

Then we shrink the domain to fit into the [0,1] interval, which gives us the function sin(k·(e - s) + s).

The sin(k·(e - s) + s) function on the [0,1] interval (live).

Moving on to the codomain, we first shift it up by its value at 0 (sin(0·(e - s) + s)), which means we now have sin(k·(e - s) + s) - sin(s). This gives us the new codomain [0,sin(e) - sin(s)].

The sin(k·(e - s) + s) - sin(s) function on the [0,1] interval (live).

Finally, we shrink the codomain to the [0,1] interval by dividing with the range (sin(e) - sin(s)), so our final function is (sin(k·(e - s) + s) - sin(s))/(sin(e - sin(s)).

The (sin(k·(e - s) + s) - sin(s))/(sin(e - sin(s)) function on the [0,1] interval (live).

So in order to do a similar comparative demo to that for the JS equivalents of the CSS linear, ease-in, ease-out, ease-in-out, our timing functions object becomes:

tfn = { 'bounce-ini': function(k) { return 1 - Math.sin((1 - k)*s)/Math.sin(s); }, 'bounce-fin': function(k) { return Math.sin(k*e)/Math.sin(e); }, 'bounce-ini-fin': function(k) { return (Math.sin(k*(e - s) + s) - Math.sin(s))/(Math.sin(e) - Math.sin(s)); } };

The s and e variables are the values we get from the two range inputs that allow us to control the bounce amount.

The interactive demo below shows the visual comparison of these three types of timing functions:

See the Pen by thebabydino (@thebabydino) on CodePen.

Alternating animations

In CSS, setting animation-direction to alternate also reverses the timing function. In order to better understand this, consider a .box element on which we animate its transform property such that we move to the right. This means our @keyframes look as follows:

@keyframes shift { 0%, 10% { transform: none } 90%, 100% { transform: translate(50vw) } }

We use a custom timing function that allows us to have a bounce at the end and we make this animation alternate - that is, go from the final state (translate(50vw)) back to the initial state (no translation) for the even-numbered iterations (second, fourth and so on).

animation: shift 1s cubic-bezier(.5, 1, .75, 1.5) infinite alternate

The result can be seen below:

See the Pen by thebabydino (@thebabydino) on CodePen.

One important thing to notice here is that, for the even-numbered iterations, our bounce doesn't happen at the end, but at the start - the timing function is reversed. Visually, this means it's reflected both horizontally and vertically with respect to the .5,.5 point.

The normal timing function (f, in red, with a bounce at the end) and the symmetrical reverse one (g, in purple, with a bounce at the start) (live)

In CSS, there is no way of having a different timing function other than the symmetrical one on going back if we are to use this set of keyframes and animation-direction: alternate. We can introduce the going back part into the keyframes and control the timing function for each stage of the animation, but that's outside the scope of this article.

When changing values with JavaScript in the fashion presented so far in this article, the same thing happens by default. Consider the case when we want to animate the stop of a linear-gradient() between an initial and a final position and we want to have a bounce at the end. This is pretty much the last example presented in the first section with timing function that lets us have a bounce at the end (one in the bounce-fin category described before) instead of a linear one.

The CSS is exactly the same and we only make a few minor changes to the JavaScript code. We set a limit angle E and we use a custom bounce-fin kind of timing function in place of the linear one:

const E = .75*Math.PI; /* same as before */ function timing(k) { return Math.sin(k*E)/Math.sin(E) }; function update() { /* same as before */ document.body.style.setProperty( '--stop', `${+(INI + timing(k)*RANGE).toFixed(2)}%` ); /* same as before */ }; /* same as before */

The result can be seen below:

See the Pen by thebabydino (@thebabydino) on CodePen.

In the initial state, the stop is at 85%. We animate it to 26% (which is the final state) using a timing function that gives us a bounce at the end. This means we go beyond our final stop position at 26% before going back up and stopping there. This is what happens during the odd iterations.

During the even iterations, this behaves just like in the CSS case, reversing the timing function, so that the bounce happens at the beginning, not at the end.

But what if we don't want the timing function to be reversed?

In this case, we need to use the symmetrical function. For any timing function f(k) defined on the [0,1] interval (this is the domain), whose values are in the [0,1] (codomain), the symmetrical function we want is 1 - f(1 - k). Note that functions whose shape is actually symmetrical with respect to the .5,.5 point, like linear or ease-in-out are identical to their symmetrical functions.

See the Pen by thebabydino (@thebabydino) on CodePen.

So what we do is use our timing function f(k) for the odd iterations and use 1 - f(1 - k) for the even ones. We can tell whether an iteration is odd or even from the direction (dir) variable. This is 1 for odd iterations and -1 for even ones.

This means we can combine our two timing functions into one: m + dir*f(m + dir*k).

Here, the multiplier m is 0 for the odd iterations (when dir is 1) and 1 for the even ones (when dir is -1), so we can compute it as .5*(1 - dir):

dir = +1 ? m = .5*(1 - (+1)) = .5*(1 - 1) = .5*0 = 0 dir = -1 ? m = .5*(1 - (-1)) = .5*(1 + 1) = .5*2 = 1

This way, our JavaScript becomes:

let m; /* same as before */ function update() { /* same as before */ document.body.style.setProperty( '--stop', `${+(INI + (m + dir*timing(m + dir*k))*RANGE).toFixed(2)}%` ); /* same as before */ }; addEventListener('click', e => { if(rID) stopAni(); dir *= -1; m = .5*(1 - dir); update(); }, false);

The final result can be seen in this Pen:

See the Pen by thebabydino (@thebabydino) on CodePen.

Even more examples

Gradient stops are not the only things that aren't animatable cross-browser with just CSS.

Gradient end going from orange to violet

For a first example of something different, let's say we want the orange in our gradient to animate to a kind of violet. We start with a CSS that looks something like this:

--c-ini: #ff9800; --c-fin: #a048b9; background: linear-gradient(90deg, var(--c, var(--c-ini)), #3c3c3c)

In order to interpolate between the initial and final values, we need to know the format we get when reading them via JavaScript - is it going to be the same format we set them in? Is it going to be always rgb()/ rgba()?

Here is where things get a bit hairy. Consider the following test, where we have a gradient where we've used every format possible:

--c0: hsl(150, 100%, 50%); // springgreen --c1: orange; --c2: #8a2be2; // blueviolet --c3: rgb(220, 20, 60); // crimson --c4: rgba(255, 245, 238, 1); // seashell with alpha = 1 --c5: hsla(51, 100%, 50%, .5); // gold with alpha = .5 background: linear-gradient(90deg, var(--c0), var(--c1), var(--c2), var(--c3), var(--c4), var(--c5))

We read the computed values of the gradient image and the individual custom properties --c0 through --c5 via JavaScript.

let s = getComputedStyle(document.body); console.log(s.backgroundImage); console.log(s.getPropertyValue('--c0'), 'springgreen'); console.log(s.getPropertyValue('--c1'), 'orange'); console.log(s.getPropertyValue('--c2'), 'blueviolet'); console.log(s.getPropertyValue('--c3'), 'crimson'); console.log(s.getPropertyValue('--c4'), 'seashell (alpha = 1)'); console.log(s.getPropertyValue('--c5'), 'gold (alpha = .5)');

The results seem a bit inconsistent.

Screenshots showing what gets logged in Chrome, Edge and Firefox (live).

Whatever we do, if we have an alpha of strictly less than 1, what we get via JavaScript seems to be always an rgba() value, regardless of whether we've set it with rgba() or hsla().

All browsers also agree when reading the custom properties directly, though, this time, what we get doesn't seem to make much sense: orange, crimson and seashell are returned as keywords regardless of how they were set, but we get hex values for springgreen and blueviolet. Except for orange, which was added in Level 2, all these values were added to CSS in Level 3, so why do we get some as keywords and others as hex values?

For the background-image, Firefox always returns the fully opaque values only as rgb(), while Chrome and Edge return them as either keywords or hex values, just like they do in the case when we read the custom properties directly.

Oh well, at least that lets us know we need to take into account different formats.

So the first thing we need to do is map the keywords to rgb() values. Not going to write all that manually, so a quick search finds this repo - perfect, it's exactly what we want! We can now set that as the value of a CMAP constant.

The next step here is to create a getRGBA(c) function that would take a string representing a keyword, a hex or an rgb()/ rgba() value and return an array containing the RGBA values ([red, green, blue, alpha]).

We start by building our regular expressions for the hex and rgb()/ rgba() values. These are a bit loose and would catch quite a few false positives if we were to have user input, but since we're only using them on CSS computed style values, we can afford to take the quick and dirty path here:

let re_hex = /^\#([a-f\d]{1,2})([a-f\d]{1,2})([a-f\d]{1,2})$/i, re_rgb = /^rgba?\((\d{1,3},\s){2}\d{1,3}(,\s((0|1)?\.?\d*))?\)/;

Then we handle the three types of values we've seen we might get by reading the computed styles:

if(c in CMAP) return CMAP[c]; // keyword lookup, return rgb if([4, 7].indexOf(c.length) !== -1 && re_hex.test(c)) { c = c.match(re_hex).slice(1); // remove the '#' if(c[0].length === 1) c = c.map(x => x + x); // go from 3-digit form to 6-digit one c.push(1); // add an alpha of 1 // return decimal valued RGBA array return c.map(x => parseInt(x, 16)) } if(re_rgb.test(c)) { // extract values c = c.replace(/rgba?\(/, '').replace(')', '').split(',').map(x => +x.trim()); if(c.length === 3) c.push(1); // if no alpha specified, use 1 return c // return RGBA array }

Now after adding the keyword to RGBA map (CMAP) and the getRGBA() function, our JavaScript code doesn't change much from the previous examples:

const INI = getRGBA(S.getPropertyValue('--c-ini').trim()), FIN = getRGBA(S.getPropertyValue('--c-fin').trim()), RANGE = [], ALPHA = 1 - INI[3] || 1 - FIN[3]; /* same as before */ function update() { /* same as before */ document.body.style.setProperty( '--c', `rgb${ALPHA ? 'a' : ''}( ${INI.map((c, i) => Math.round(c + k*RANGE[i])).join(',')})` ); /* same as before */ }; (function init() { if(!ALPHA) INI.pop(); // get rid of alpha if always 1 RANGE.splice(0, 0, ...INI.map((c, i) => FIN[i] - c)); })(); /* same as before */

This gives us a linear gradient animation:

See the Pen by thebabydino (@thebabydino) on CodePen.

We can also use a different, non-linear timing function, for example one that allows for a bounce at the end:

const E = .8*Math.PI; /* same as before */ function timing(k) { return Math.sin(k*E)/Math.sin(E) } function update() { /* same as before */ document.body.style.setProperty( '--c', `rgb${ALPHA ? 'a' : ''}( ${INI.map((c, i) => Math.round(c + timing(k)*RANGE[i])).join(',')})` ); /* same as before */ }; /* same as before */

This means we go all the way to a kind of blue before going back to our final violet:

See the Pen by thebabydino (@thebabydino) on CodePen.

Do note however that, in general, RGBA transitions are not the best place to illustrate bounces. That's because the RGB channels are strictly limited to the [0,255] range and the alpha channel is strictly limited to the [0,1] range. rgb(255, 0, 0) is as red as red gets, there's no redder red with a value of over 255 for the first channel. A value of 0 for the alpha channel means completely transparent, there's no greater transparency with a negative value.

By now, you're probably already bored with gradients, so let's switch to something else!

Smooth changing SVG attribute values

At this point, we cannot alter the geometry of SVG elements via CSS. We should be able to as per the SVG2 spec and Chrome does support some of this stuff, but what if we want to animate the geometry of SVG elements now, in a more cross-browser manner?

Well, you've probably guessed it, JavaScript to the rescue!

Growing a circle

Our first example is that of a circle whose radius goes from nothing (0) to a quarter of the minimum viewBox dimension. We keep the document structure simple, without any other aditional elements.

<svg viewBox='-100 -50 200 100'> <circle/> </svg>

For the JavaScript part, the only notable difference from the previous demos is that we read the SVG viewBox dimensions in order to get the maximum radius and we now set the r attribute within the update() function, not a CSS variable (it would be immensely useful if CSS variables were allowed as values for such attributes, but, sadly, we don't live in an ideal world):

const _G = document.querySelector('svg'), _C = document.querySelector('circle'), VB = _G.getAttribute('viewBox').split(' '), RMAX = .25*Math.min(...VB.slice(2)), E = .8*Math.PI; /* same as before */ function update() { /* same as before */ _C.setAttribute('r', (timing(k)*RMAX).toFixed(2)); /* same as before */ }; /* same as before */

Below, you can see the result when using a bounce-fin kind of timing function:

See the Pen by thebabydino (@thebabydino) on CodePen.

Pan and zoom map

Another SVG example is a smooth pan and zoom map demo. In this case, we take a map like those from amCharts, clean up the SVG and then create this effect by triggering a linear viewBox animation when pressing the +/ - keys (zoom) and the arrow keys (pan).

The first thing we do in the JavaScript is create a navigation map, where we take the key codes of interest and attach info about what we do when the corresponding keys are pressed (note that we need different key codes for + and - in Firefox for some reason).

const NAV_MAP = { 187: { dir: 1, act: 'zoom', name: 'in' } /* + */, 61: { dir: 1, act: 'zoom', name: 'in' } /* + Firefox ¯\_(?)_/¯ */, 189: { dir: -1, act: 'zoom', name: 'out' } /* - */, 173: { dir: -1, act: 'zoom', name: 'out' } /* - Firefox ¯\_(?)_/¯ */, 37: { dir: -1, act: 'move', name: 'left', axis: 0 } /* ? */, 38: { dir: -1, act: 'move', name: 'up', axis: 1 } /* ? */, 39: { dir: 1, act: 'move', name: 'right', axis: 0 } /* ? */, 40: { dir: 1, act: 'move', name: 'down', axis: 1 } /* ? */ }

When pressing the + key, what we want to do is zoom in. The action we perform is 'zoom' in the positive direction - we go 'in'. Similarly, when pressing the - key, the action is also 'zoom', but in the negative (-1) direction - we go 'out'.

When pressing the arrow left key, the action we perform is 'move' along the x axis (which is the first axis, at index 0) in the negative (-1) direction - we go 'left'. When pressing the arrow up key, the action we perform is 'move' along the y axis (which is the second axis, at index 1) in the negative (-1) direction - we go 'up'.

When pressing the arrow right key, the action we perform is 'move' along the x axis (which is the first axis, at index 0) in the positive direction - we go 'right'. When pressing the arrow down key, the action we perform is 'move' along the y axis (which is the second axis, at index 1) in the positive direction - we go 'down'.

We then get the SVG element, its initial viewBox, set the maximum zoom out level to these initial viewBox dimensions and set the smallest possible viewBox width to a much smaller value (let's say 8).

const _SVG = document.querySelector('svg'), VB = _SVG.getAttribute('viewBox').split(' ').map(c => +c), DMAX = VB.slice(2), WMIN = 8;

We also create an empty current navigation object to hold the current navigation action data and a target viewBox array to contain the final state we animate the viewBox to for the current animation.

let nav = {}, tg = Array(4);

On 'keyup', if we don't have any animation running already and the key that was pressed is one of interest, we get the current navigation object from the navigation map we created at the beginning. After this, we handle the two action cases ('zoom'/ 'move') and call the update() function:

addEventListener('keyup', e => { if(!rID && e.keyCode in NAV_MAP) { nav = NAV_MAP[e.keyCode]; if(nav.act === 'zoom') { /* what we do if the action is 'zoom' */ } else if(nav.act === 'move') { /* what we do if the action is 'move' */ } update() } }, false);

Now let's see what we do if we zoom. First off, and this is a very useful programming tactic in general, not just here in particular, we get the edge cases that make us exit the function out of the way.

So what are our edge cases here?

The first one is when we want to zoom out (a zoom in the negative direction) when our whole map is already in sight (the current viewBox dimensions are bigger or equal to the maximum ones). In our case, this should happen if we want to zoom out at the very beginning because we start with the whole map in sight.

The second edge case is when we hit the other limit - we want to zoom in, but we're at the maximum detail level (the current viewBox dimensions are smaller or equal to the minimum ones).

Putting the above into JavaScript code, we have:

if(nav.act === 'zoom') { if((nav.dir === -1 && VB[2] >= DMAX[0]) || (nav.dir === 1 && VB[2] <= WMIN)) { console.log(`cannot ${nav.act} ${nav.name} more`); return } /* main case */ }

Now that we've handled the edge cases, let's move on to the main case. Here, we set the target viewBox values. We use a 2x zoom on each step, meaning that when we zoom in, the target viewBox dimensions are half the ones at the start of the current zoom action, and when we zoom out they're double. The target offsets are half the difference between the maximum viewBox dimensions and the target ones.

if(nav.act === 'zoom') { /* edge cases */ for(let i = 0; i < 2; i++) { tg[i + 2] = VB[i + 2]/Math.pow(2, nav.dir); tg[i] = .5*(DMAX[i] - tg[i + 2]); } }

Next, let's see what we do if we want to move instead of zooming.

In a similar fashion, we get the edge cases that make us exit the function out of the way first. Here, these happen when we're at an edge of the map and we want to keep going in that direction (whatever the direction might be). Since originally the top left corner of our viewBox is at 0,0, this means we cannot go below 0 or above the maximum viewBox size minus the current one. Note that given we're initially fully zoomed out, this also means we cannot move in any direction until we zoom in.

else if(nav.act === 'move') { if((nav.dir === -1 && VB[nav.axis] <= 0) || (nav.dir === 1 && VB[nav.axis] >= DMAX[nav.axis] - VB[2 + nav.axis])) { console.log(`at the edge, cannot go ${nav.name}`); return } /* main case */

For the main case, we move in the desired direction by half the viewBox size along that axis:

else if(nav.act === 'move') { /* edge cases */ tg[nav.axis] = VB[nav.axis] + .5*nav.dir*VB[2 + nav.axis] }

Now let's see what we need to do inside the update() function. This is going to be pretty similar to previous demos, except now we need to handle the 'move' and 'zoom' cases separately. We also create an array to store the current viewBox data in (cvb):

function update() { let k = ++f/NF, j = 1 - k, cvb = VB.slice(); if(nav.act === 'zoom') { /* what we do if the action is zoom */ } if(nav.act === 'move') { /* what we do if the action is move */ } _SVG.setAttribute('viewBox', cvb.join(' ')); if(!(f%NF)) { f = 0; VB.splice(0, 4, ...cvb); nav = {}; tg = Array(4); stopAni(); return } rID = requestAnimationFrame(update) };

In the 'zoom' case, we need to recompute all viewBox values. We do this with linear interpolation between the values at the start of the animation and the target values we've previously computed:

if(nav.act === 'zoom') { for(let i = 0; i < 4; i++) cvb[i] = j*VB[i] + k*tg[i]; }

In the 'move' case, we only need to recompute one viewBox value - the offset for the axis we move along:

if(nav.act === 'move') cvb[nav.axis] = j*VB[nav.axis] + k*tg[nav.axis];

And that's it! We now have a working pan and zoom demo with smooth linear transitions in between states:

See the Pen by thebabydino (@thebabydino) on CodePen.

From sad square to happy circle

Another example would be morphing a sad square SVG into a happy circle. We create an SVG with a square viewBox whose 0,0 point is right in the middle. Symmetrical with respect to the origin of the SVG system of coordinates we have a square (a rect element) covering 80% of the SVG. This is our face. We create the eyes with an ellipse and a copy of it, symmetrical with respect to the vertical axis. The mouth is a cubic Bézier curve created with a path element.

- var vb_d = 500, vb_o = -.5*vb_d; - var fd = .8*vb_d, fr = .5*fd; svg(viewBox=[vb_o, vb_o, vb_d, vb_d].join(' ')) rect(x=-fr y=-fr width=fd height=fd) ellipse#eye(cx=.35*fr cy=-.25*fr rx=.1*fr ry=.15*fr) use(xlink:href='#eye' transform='scale(-1 1)') path(d=`M${-.35*fr} ${.35*fr} C${-.21*fr} ${.13*fr} ${+.21*fr} ${.13*fr} ${+.35*fr} ${.35*fr}`)

In the JavaScript, we get the face and the mouth elements. We read the face width, which is equal to the height and we use it to compute the maximum corner rounding. This is the value for which we get a circle and is equal to half the square edge. We also get the mouth path data, from where we extract the initial y coordinate of the control points and compute the final y coordinate of the same control points.

const _FACE = document.querySelector('rect'), _MOUTH = document.querySelector('path'), RMAX = .5*_FACE.getAttribute('width'), DATA = _MOUTH.getAttribute('d').slice(1) .replace('C', '').split(/\s+/) .map(c => +c), CPY_INI = DATA[3], CPY_RANGE = 2*(DATA[1] - DATA[3]);

The rest is very similar to all other transition on click demos so far, with just a few minor differences (note that we use an ease-out kind of timing function):

/* same as before */ function timing(k) { return 1 - Math.pow(1 - k, 2) }; function update() { f += dir; let k = f/NF, cpy = CPY_INI + timing(k)*CPY_RANGE; _FACE.setAttribute('rx', (timing(k)*RMAX).toFixed(2)); _MOUTH.setAttribute( 'd', `M${DATA.slice(0,2)} C${DATA[2]} ${cpy} ${DATA[4]} ${cpy} ${DATA.slice(-2)}` ); /* same as before */ }; /* same as before */

And so we have our silly result:

See the Pen by thebabydino (@thebabydino) on CodePen.

Emulating CSS Timing Functions with JavaScript is a post from CSS-Tricks

Variable Fonts from Adobe Originals

Css Tricks - Mon, 10/30/2017 - 3:20am

It's one thing to see a variable fonts demo (oooooo one font can change things like weight, width, and slant?) but it feels a lot more real when fonts you see and work with all the time go variable. Adobe made six of them available: Source Sans, Source Serif, Source Code, Myriad, Acumin, and Minion. You can't serve them on the web directly through TypeKit yet, but you can download them from GitHub to start playing.

Print designers have just as much reason to be excited, or perhaps more, as so long as you have software that supports variable fonts, you can use them right now:

Direct Link to ArticlePermalink

Variable Fonts from Adobe Originals is a post from CSS-Tricks

WordPress + PWAs

Css Tricks - Sun, 10/29/2017 - 3:33pm

One of the sessions from the Chrome Dev Summit, hosted by Das Surma and Daniel Walmsley. It's not so much about WordPress as it is about CMS powered sites that aren't really "apps", if there is such a thing, and the possibility of turning that site into a Progressive Web AppSite.

I find the CMS + PWA combo interesting because:

  • If you aren't stoked about AMP, and let's face it, a lot of people are not stoked about AMP, but do like the idea of a super fast website, a PWA is likely of high interest. Whereas AMP feels like you're making an alternate version of your site, PWAs feel like you're making the website you have much better.
  • Some PWA work is generic and easy-ish (use HTTPS) and some PWA is bespoke and hard (make the site work offline). For lack of a better way to explain it, CMS's know about themselves in such a way that they can provide tooling to make PWAs way easier. For example, Jetpack just doing it for you. It's the same kind of thing we saw with responsive images. It's not trivial to handle by hand, but a CMS can just do it for you.

If this topic doesn't trip your trigger, there is a playlist of all the sessions here. Dan Fabulich watched all 10 hours of it and summarizes it as:

Google wants you to build PWAs, reduce JavaScript file size, use Web Components, and configure autofill. They announced only a handful of features around payments, authentication, Android Trusted Web Activities, Chrome Dev Tools, and the Chrome User Experience Report.

Direct Link to ArticlePermalink

WordPress + PWAs is a post from CSS-Tricks

Sketching Interfaces

Css Tricks - Sun, 10/29/2017 - 1:50pm

From the same team that worked on the incredibly wild idea of using React to make Sketch documents comes an even wilder idea:

Sketching seemed like the natural place to start. As interface designers, sketching is an intuitive method of expressing a concept. We wanted to see how it might look to skip a few steps in the product development lifecycle and instantly translate our sketches into a finished product.

In other words, a camera looks at the sketches, figures out what design patterns are being insinuated, and renders them in a browser.

I wouldn't doubt design tooling gets this sophisticated in coming years. Mostly I think: if your design team is this forward thinking and experimental, you've done a fantastic job putting a team together. Hopefully you can keep them happy designing travel websites, or somehow pivot to design tooling itself.

Direct Link to ArticlePermalink

Sketching Interfaces is a post from CSS-Tricks

Igniter: Why Startups Suck at Marketing

LukeW - Sat, 10/28/2017 - 2:00pm

At the Igniter Annual Conference in Mountain View, Rand Fishkin shared eight common mistakes startups make with their marketing/growth hacking and how to avoid them. Here's my notes from his talk: Why Startups Suck at Marketing

Most startups fail because they couldn’t find the right customers at an affordable price. But what specific mistakes do they make with marketing that could be avoided?

Terrible Name
  • Problems with names: no one can say your name (hard to pronounce), you don’t own domains or social handles, your name is used by other companies (esp. in your domain), etc.
  • Make your names easy to spell, say & remember, has no existing associations that could confuse, avoids trademark infringement, what the company does has no problematic overlaps, the .com and social media accounts are owned by you, has few or no Google search results, biases toward brand able.
Overvaluing First Exposure
  • Typical startup myths: make a great product, get some press, get customers.
  • But where people do to learn is from online lists, talking to others, using existing solutions, etc. If you only play at first exposure and/or decision time, it won’t work well.
  • Step 1: get known and trusted by your audience. Step 2: grow a presence across the channels they use to find solutions for what your product does. Step 3: be visible throughout their discovery, consideration, and decision processes.
Chasing Growth Hacks
  • Don’t fight the “law of shitty clickthrough rates”. Chasing growth hacks usually leads to death before success. A lot of exploitable short-term growth hacks start out working ok, then cease performing.
  • Great marketers focus on flywheels: systems that scale with decreasing friction. Most flywheels start out working poorly, but keep iterating and you’ll learn how to close the gaps.
  • Different types of flywheels: content, press & media, UGC/community.
  • Almost every flywheel finds points of conflict or friction. That’s where it is useful to apply a growth hack. Hacks are useful when applied to a sound marketing strategy.
  • As such, find a balance between long-term investments and short-term hacks.
  • The formula for marketing that will work for years to come: strategic roadmap + scalable flywheel + long-term investments + hacks to remove friction (in the flywheel).
Saving Marketing Until Launch
  • Seek out marketing channels & tactics at the intersection of: areas of personal passion & interest, areas where you can provide unique value to your audience, and areas the reach your potential customers and their influencers. Think about what marketing channels fits this criteria: blogs, forums, social media, video channels, events, etc.
  • Invest in 2-3 tactics long before you launch a product. So when you do, there’s a pre-existing community that wants to support & amplify you.
Relying on Paid Ads
  • Unknown brands with poor click-throughs (because they are new) have to pay more for ads to be effective. Ad networks want good click-throughs so their users get good experiences.
  • First earn brand exposure with your target audience, get organic traffic first, then advertise to those who know and like you.
Not Prioritizing an Easy-to-Reach Audience
  • Easier to harder to reach audiences: are people you know, know of you, are connected to you on email, social media, your website, have heard of your company, are reachable via ad targeting.
  • Have a strategy to increase the size of your easier to reach audience, target them first, and not aim 100% of his tactics at hard to reach groups.
  • Pro tip: having a lot of target customers in your personal network makes everything about starting up easier.
Failing at Funnels
  • Most sites & marketers are focused on optimizing conversations but startups often lack the right audience that the top of the funnel and the data they need to move people through the funnel. Get to know the people coming to your funnel so you know what they do. Once you have real customers, you can talk to them & learn from them.
  • Identify the right customers for you, find what/who influences them, then clone, expand, and repeat.
  • It is hugely helpful to visualize your funnel to show sources and see how each contributes to various levels. You can also find where people are not converting.
  • Gain a deep understanding of what makes buyers buy. Determine what makes some qualified visitors not buy. These two insights will help drive conversions. Interview customers that didn’t try the product, tried it and didn’t love it, tried and loved it.
Ignoring Brand
  • A brand is a promise that is ideally triggered when you need the solution you offer. Brand marketing reminds you of the brand’s existence, and nudges you to use the brand at the right time.
  • Before you can brand, you need a promise: we provide, we evoke feelings, we remind you of, we share values of … Everything should subtly reinforce your message.

A free guide to head elements

Css Tricks - Sat, 10/28/2017 - 7:11am

Josh Buchea rounds up all the stuff that could be put into the <head> for various reasons.

Fun fact I just learned, the only acceptable elements in the head are <link>, <script>, <meta>, <title>, and <noscript> (update: also <base> and <template>). If you put anything else in there at all, all browsers will abruptly end the <head> and start the <body>.

Also, this proliferation of stuff-in-the-head is at least in part why manifest.json is a thing.

Direct Link to ArticlePermalink

A free guide to head elements is a post from CSS-Tricks

Getting Around a Revoked Certificate in OSX

Css Tricks - Fri, 10/27/2017 - 6:13am

Let me start this off by saying this is not an ideal trick and one I hope no one else needs to use because it's a bad idea to work around a browser feature that's aimed to protect your security.

That said, I am in the process of testing a product and ran into a weird situation where our team had to revoke the SSL certificate we had assigned to our server. We're going to replace it but I have testing to do in the meantime and need access to our staging server, so waiting is kind of a blocker because, well, this message gets me nowhere.

Safari's warning for a site with a revoked certificate.

This message is different from the warnings browsers provide for sites without SSL. Those give you a built-in workaround by simply dismissing the warning. The difference is that a revoked certificate implies that the certificate's private key has been lost or compromised, making the site's security vulnerable to malware, phising, etc. No bueno!

I reached out to Zach Tirrell and he helped me get around this issue with some tinkering that, given the right situation, might be helpful for others.

One last note before we dive in is that I'm working on a Mac. I'm not sure what the equivalent steps would be for other systems, so your mileage may vary.

Step 1: View the Certificate Click the "Show Details" link in Safari to reveal an additional option to view the certificate.

Safari displays an option in the error message to view the certificate that is revoked. Click on that to open a dialogue that provides information on that certificate.

Step 2: Save the Certificate to the Desktop Drag the certificate to the desktop to save it locally.

There's a bit of hidden UI in the dialogue that allows you to save the certificate. Click the certificate icon and literally drag it to the desktop.

Step 3: Add the Certificate to Keychain Access Drag the certificate into the Keychain Access Certificates screen.

OSX's Keychain Access is typically known for storing a user's passwords, but it also manages secure notes and SSL certificates, among other protected system assets. You can open Keychain Access in your Applications, or search for it in Spotlight (CMD + Space).

Navigate to the Certificates panel and drag the certificate into it. The certificate is now installed and recognizable to Keychain Access.

Step 4: Trust the Certificate Tell Keychain Access to "Always Trust" the certificate.

Double-click on the certificate to manage the system preferences for handling it. Expand the Trust panel ans set the preference to Always Trust the certificate.

Keychain Access will likely ask you to confirm this change by entering your system password.

Revisit the Site

Now that the system has been instructed to trust the certificate, go ahead and re-visit the site. It should now load as if the certificate had not been revoked in the first place, though you may need to restart the browser to see the effect.

Both Safari and Chrome read permissions from Keychain Access, so those browsers should be well covered by these steps. Firefox has its own layer way of managing these permissions, which can be accessed on the browser's Preferences > Privacy Settings screen.

Wrapping Up

I'll state it again, but I really hope no one ever needs to use this trick. Browsers bake this security in for good reason and working around it is not only frowned upon, but downright risky. The type of scenario for needing to do this has got to be pretty darn rare and, for those, I sure hope this helps.

Getting Around a Revoked Certificate in OSX is a post from CSS-Tricks

Houdini Experiments

Css Tricks - Fri, 10/27/2017 - 6:00am

These experiments by Vincent De Oliveira are just the kind of thing Houdini needs to get developers interested. Or maybe I should say designers, as these demos are particularly good at demonstrating how allowing low-level painting to the screen unlocks just about anything you want it to.

Direct Link to ArticlePermalink

Houdini Experiments is a post from CSS-Tricks

The Output Element

Css Tricks - Thu, 10/26/2017 - 3:29am

Last night I was rooting around in the cellars of a particularly large codebase and stumbled upon our normalize.css which makes sure that all of our markup renders in a similar way across different browsers. I gave it a quick skim and found styles for a rather peculiar element called <output> that I'd never seen or even heard of before.

According to MDN, it "represents the result of a calculation or user action" typically used in forms. And rather embarrassingly for me, it isn't a new and fancy addition to the spec since Chris used it in a post all the way back in 2011.

But regardless! What does output do and how do we use it? Well, let's say we have an input with a type of range. Then we add an output element and correlate it to the input with its for attribute.

<input type="range" name="quantity" id="quantity" min="0" max="100"> <output for="quantity"></output>

See the Pen Input Output #2 by CSS-Tricks (@css-tricks) on CodePen.

It... doesn't really do anything. By default, output doesn't have any styles and doesn't render a box or anything in the browser. Also, nothing happens when we change the value of our input.

We'll have to tie everything together with JavaScript. No problem! First we need to find our input in the DOM with JavaScript, like so:

const rangeInput = document.querySelector('input');

Now we can append an event listener onto it so that whenever we edit the value (by sliding left or right on our input) we can detect a change:

const rangeInput = document.querySelector('input'); rangeInput.addEventListener('change', function() { console.log(this.value); });

this.value will always refer to the value of the rangeInput because we're using it inside our event handler and we can then return that value to the console to make sure everything works. After that we can then find our output element in the DOM:

const rangeInput = document.querySelector('input'); const output = document.querySelector('output'); rangeInput.addEventListener('change', function() { console.log(this.value); });

And then we edit our event listener to set the value of that output to change whenever we edit the value of the input:

const rangeInput = document.querySelector('input'); const output = document.querySelector('output'); rangeInput.addEventListener('change', function() { output.value = this.value; });

And voilá! There we have it, well mostly anyway. Once you change the value of the input our output will now reflect that:

See the Pen Input Output #3 by Robin Rendle (@robinrendle) on CodePen.

We should probably improve this a bit by settting a default value to our output so that it's visible as soon as you load the page. We could do that with the HTML itself and set the value inside the output:

<output for="quantity">50</output>

But I reckon that's not particularly bulletproof. What happens when we want to change the min or max of our input? We'd always have to change our output, too. Let's set the state of our output in our script. Here's a new function called setDefaultState:

function setDefaultState() { output.value = rangeInput.value; }

When the DOM has finished loading and then fire that function:

document.addEventListener('DOMContentLoaded', function(){ setDefaultState(); });

See the Pen Input Output #4 by Robin Rendle (@robinrendle) on CodePen.

Now we can style everything! But there's one more thing. The event listener change is great and all but it doesn't update the text immediately as you swipe left or right. Thankfully there's a new type of event listener called input with fairly decent browser support that we can use instead. Here's all our code with that addition in place:

const rangeInput = document.querySelector('input'); const output = document.querySelector('output'); function setDefaultState() { output.value = rangeInput.value; } rangeInput.addEventListener('input', function() { output.value = this.value; }); document.addEventListener('DOMContentLoaded', function() { setDefaultState(); });

See the Pen Input Output #5 by Robin Rendle (@robinrendle) on CodePen.

And there we have it! An input, with an output.

The Output Element is a post from CSS-Tricks

Heavy images slowing down your site?

Css Tricks - Thu, 10/26/2017 - 3:06am

(This is a sponsored post.)

Speed is an important piece of a website's user experience. Since images account for an average of 70% or more of a webpage's weight, optimizing them is crucial to creating a faster website. That's why we created Page Weight, a tool that will diagnose your site’s most problematic images and prescribe solutions on how to optimize them.

Simply enter your URL into Page Weight and we will prepare a custom report of your images performance and what you can do to improve them.

Test your site for free!

Direct Link to ArticlePermalink

Heavy images slowing down your site? is a post from CSS-Tricks

UX Case Study: HBO GO App

Usability Geek - Wed, 10/25/2017 - 1:10pm
While traditional television still commands the majority of the public’s viewership, the small screen is slowly surrendering its audience to an even smaller one: mobile devices. Major...
Categories: Web Standards
Syndicate content
©2003 - Present Akamai Design & Development.