Developer News

The All-Powerful Sketch

Css Tricks - Wed, 11/08/2017 - 11:26am

Sketch is such a massive player in the screen design tooling world. Over on the Media Temple blog I take a stab at some of the reasons I think that might be.

Direct Link to ArticlePermalink

The All-Powerful Sketch is a post from CSS-Tricks

ARIA is Spackle, Not Rebar

Css Tricks - Wed, 11/08/2017 - 5:05am

Much like their physical counterparts, the materials we use to build websites have purpose. To use them without understanding their strengths and limitations is irresponsible. Nobody wants to live in an poorly-built house. So why are poorly-built websites acceptable?

In this post, I'm going to address WAI-ARIA, and how misusing it can do more harm than good.

Materials as technology

In construction, spackle is used to fix minor defects on interiors. It is a thick paste that dries into a solid surface that can be sanded smooth and painted over. Most renters become acquainted with it when attempting to get their damage deposit back.

Rebar is a lattice of steel rods used to reinforce concrete. Every modern building uses it—chances are good you'll see it walking past any decent-sized construction site.

Technology as materials

HTML is the rebar-reinforced concrete of the web. To stretch the metaphor, CSS is the interior and exterior decoration, and JavaScript is the wiring and plumbing.

Every tag in HTML has what is known as native semantics. The act of writing an HTML element programmatically communicates to the browser what that tag represents. Writing a button tag explicitly tells the browser, "This is a button. It does buttony things."

The reason this is so important is that assistive technology hooks into native semantics and uses it to create an interface for navigation. A page not described semantically is a lot like a building without rooms or windows: People navigating via a screen reader have to wander around aimlessly in the dark and hope they stumble onto what they need.

ARIA stands for Accessible Rich Internet Applications and is a relatively new specification developed to help assistive technology better communicate with dynamic, JavaScript-controlled content. It is intended to supplement existing semantic attributes by providing enhanced interactivity and context to screen readers and other assistive technology.

Using spackle to build walls

A concerning trend I've seen recently is the blind, mass-application of ARIA. It feels like an attempt by developers to conduct accessibility compliance via buckshot—throw enough of something at a target trusting that you'll eventually hit it.

Unfortunately, there is a very real danger to this approach. Misapplied ARIA has the potential to do more harm than good.

The semantics inherent in ARIA means that when applied improperly it can create a discordant, contradictory mess when read via screen reader. Instead of hearing, "This is a button. It does buttony things.", people begin to hear things along the lines of, "This is nothing, but also a button. But it's also a deactivated checkbox that is disabled and it needs to shout that constantly."

If you can use a native HTML element or attribute with the semantics and behavior you require already built in, instead of re-purposing an element and adding an ARIA role, state or property to make it accessible, then do so.
First rule of ARIA use

In addition, ARIA is a new technology. This means that browser support and behavior is varied. While I am optimistic that in the future the major browsers will have complete and unified support, the current landscape has gaps and bugs.

Another important consideration is who actually uses the technology. Compliance isn't some purely academic vanity metric we're striving for. We're building robust systems for real people that allow them to get what they want or need with as little complication as possible. Many people who use assistive technology are reluctant to upgrade for fear of breaking functionality. Ever get irritated when your favorite program redesigns and you have to re-learn how to use it? Yeah.

The power of the Web is in its universality. Access by everyone regardless of disability is an essential aspect.
– Tim Berners-Lee

It feels disingenuous to see the benefits of the DRY principal of massive JavaScript frameworks also slather redundant and misapplied attributes in their markup. The web is accessible by default. For better or for worse, we are free to do what we want to it after that.

The fix

This isn't to say we should completely avoid using ARIA. When applied with skill and precision, it can turn a confusing or frustrating user experience into an intuitive and effortless one, with far fewer brittle hacks and workarounds.

A little goes a long way. Before considering other options, start with markup that semantically describes the content it is wrapping. Test extensively, and only apply ARIA if deficiencies between HTML's native semantics and JavaScript's interactions arise.

Development teams will appreciate the advantage of terse code that's easier to maintain. Savvy developers will use a CSS-Trick™ and leverage CSS attribute selectors to create systems where visual presentation is tied to semantic meaning.

input:invalid, [aria-invalid] { border: 4px dotted #f64100; } Examples

Here are a few of the more common patterns I've seen recently, and why they are problematic. This doesn't mean these are the only kinds of errors that exist, but it's a good primer on recognizing what not to do:

<li role="listitem">Hold the Bluetooth button on the speaker for three seconds to make the speaker discoverable</li>

The role is redundant. The native semantics of the li element already describe it as a list item.

<p role="command">Type CTRL+P to print

command is an Abstract Role. They are only used in ARIA to help describe its taxonomy. Just because an ARIA attribute seems like it is applicable doesn't mean it necessarily is. Additionally, the kbd tag could be used on "CTRL" and "P" to more accurately describe the keyboard command.

<div role="button" class="button">Link to device specifications</div>

Failing to use a button tag runs the risk of not accommodating all the different ways a user can interact with a button and how the browser responds. In addition, the a tag should be used for links.

<body aria-live="assertive" aria-atomic="true">

Usually the intent behind something like this is to expose updates to the screen reader user. Unfortunately, when scoped to the body tag, any page change—including all JS-related updates—are announced immediately. A setting of assertive on aria-live also means that each update interrupts whatever it is the user is currently doing. This is a disastrous experience, especially for single page apps.

<div aria-checked="true"></div>

You can style a native checkbox element to look like whatever you want it to. Better support! Less work!

<div role="link" tabindex="40"> Link text </div>

Yes, it's actual production code. Where to begin? First, never use a tabindex value greater than 0. Secondly, the title attribute probably does not do what you think it does. Third, the anchor tag should have a destination—links take you places, after all. Fourth, the role of link assigned to a div wrapping an a element is entirely superfluous.

<h2 class="h3" role="heading" aria-level="1">How to make a perfect soufflé every time</h2>

Credit is where credit's due: Nicolas Steenhout outlines the issues for this one.

Do better

Much like content, markup shouldn't be an afterthought when building a website. I believe most people are genuinely trying to do their best most of the time, but wielding a technology without knowing its implications is dangerous and irresponsible.

I'm usually more of a honey-instead-of-vinegar kind of person when I try to get people to practice accessibility, but not here. This isn't a soft sell about the benefits of developing and designing with an accessible, inclusive mindset. It's a post about doing your job.

Every decision a team makes affects a site's accessibility.
Laura Kalbag

Get better at authoring

Learn about the available HTML tags, what they describe, and how to best use them. Same goes for ARIA. Give your page template semantics the same care and attention you give your JavaScript during code reviews.

Get better at testing

There's little excuse to not incorporate a screen reader into your testing and QA process. NVDA is free. macOS, Windows, iOS and Android all come with screen readers built in. Some nice people have even written guides to help you learn how to use them.

Automated accessibility testing is a huge boon, but it also isn't a silver bullet. It won't report on what it doesn't know to report, meaning it's up to a human to manually determine if navigating through the website makes sense. This isn't any different than other usability testing endeavors.

Build better buildings

Universal Design teaches us that websites, like buildings, can be both beautiful and accessible. If you're looking for a place to start, here are some resources:

ARIA is Spackle, Not Rebar is a post from CSS-Tricks

“a more visually-pleasing focus”

Css Tricks - Wed, 11/08/2017 - 5:03am

There is a JavaScript library, Focusingly, that says:

With Focusingly, focus styling adapts to match and fit individual elements.

No configuration required, Focusingly figures it out. The result is a pleasingly tailored effect that subtly draws the eye.

The idea is that if a link color (or whatever focusable element) is red, the outline will be red too, instead of that fuzzy light blue which might be undesirable aesthetically.

Why JavaScript? I'm not sure exactly. Matt Smith made a demo that shows that the outline color inherits from the color, which yields the same result.

a:focus { outline: 1px solid; outline-offset: .15em; }

Direct Link to ArticlePermalink

“a more visually-pleasing focus” is a post from CSS-Tricks

Building Flexible Design Systems

Css Tricks - Tue, 11/07/2017 - 11:46am

Yesenia Perez-Cruz talks about design systems that aren't just, as she puts it, Lego bricks for piecing layouts together. Yesenia is Design Director at Vox, which is a parent to many very visually different brands, so you can see how a single inflexible design system might fall over.

Successful design patterns don't exist in a vacuum.

Direct Link to ArticlePermalink

Building Flexible Design Systems is a post from CSS-Tricks

“almost everything on computers is perceptually slower than it was in 1983”

Css Tricks - Tue, 11/07/2017 - 6:59am

Good rant. Thankfully it's a tweetstorm not some readable blog post. &#x1f609;

I think about this kind of thing with cable box TV UX. At my parent's house, changing the channel takes like 4-5 seconds for the new channel to come in with all the overlays and garbage. You used to be able to turn a dial and the new channel was instantly there.

You'd like to think performance is a steady march forward. Computers are so fast these days! But it might just be a steady march backward.

Direct Link to ArticlePermalink

“almost everything on computers is perceptually slower than it was in 1983” is a post from CSS-Tricks

The Contrast Swap Technique: Improved Image Performance with CSS Filters

Css Tricks - Tue, 11/07/2017 - 4:53am

With CSS filter effects and blend modes, we can now leverage various techniques for styling images directly in the browser. However, creating aesthetic theming isn't all that filter effects are good for. You can use filters to indicate hover state, hide passwords, and now—for web performance.

While playing with profiling performance wins of using blend modes for duotone image effects (I'll write up an article on this soon), I discovered something even more exciting. A major image optimization win! The idea is to reduce image contrast in the source image, reducing its file size, then boosting the contrast back up with CSS filters!

Start with your image, then remove the contrast, and then reapply it with CSS filters. How It Works

Let's put a point on exactly how this works:

  1. Reduce image contrast using a linear transform function (Photoshop can do this)
  2. Apply a contrast filter in CSS to the image to make up for the contrast removal

Step one involves opening your image in a program that lets you linearly reduce contrast in a linear way. Photoshop's legacy mode does a good job at this (Image > Adjustments > Brightness/Contrast):

You get to this screen via Image > Adjustments > Brightness/Contrast in Photoshop CC.

Not all programs use the same functions to apply image transforms (for example, this would not work with the macOS default image editor, since it uses a different technique to reduct contrast). A lot of the work done to build image effects into the browser was initially done by Adobe, so it makes sense that Photoshop's Legacy Mode aligns with browser image effects.

Then, we apply some CSS filters to our image. The filters we'll be using are contrast and (a little bit of) brightness. With the 50% Legacy Photoshop reduction, I applied filter: contrast(1.75) brightness(1.2); to each image.

Major Savings

This technique is very effective for reducing image size and therefore the overall weight of your page. In the following study, I used 4 vibrant photos taken on an iPhone, applied a 50% reduction in contrast using Photoshop Legacy Mode, saved each photo at Maximum quality (10), and then applied filter: contrast(1.75) brightness(1.2); to each image. These are the results:

You can play with the live demo here to check it out for yourself!

In each of the above cases, we saved between 23% and 28% in image size by reducing and reapplying the contrast using CSS filters. This is with saving each of the images at maximum quality.

If you look closely, you can see some legitimate losses in image quality. This is especially true with majority-dark images. so this technique is not perfect, but it definitely proves image savings in an interesting way.

Browser Support Considerations

Be aware that browser support for CSS filters is "pretty good".

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

DesktopChromeOperaFirefoxIEEdgeSafari18*15*35No166*Mobile / TabletiOS SafariOpera MobileOpera MiniAndroidAndroid ChromeAndroid Firefox6.0-6.1*37*No4.4*6156

As you can see, Internet Explorer and Opera Mini lack support. Edge 16 (the current latest version) supports CSS filters and this technique works like a charm. You'll have to decide if a reduced-contrast image as a fallback is acceptable or not.

What About Repainting?

You may be thinking: "but while we're saving in image size, we're putting more work on the browser, wouldn't this affect performance?" That's a great question! CSS filters do trigger a repaint because they set off window.getComputedStyle(). Let's profile our example.

What I did was open an incognito window in Chrome, disable JavaScript (just to be certain for the extensions I have), set the network to "Slow 3G" and set the CPU to a 6x slowdown:

With a 6x CPU slowdown, the longest Paint Raster took 0.27 ms, AKA 0.00027 seconds.

While the images took a while to load in, the actual repaint was pretty quick. With a 6x CPU slowdown, the longest individual Rasterize Paint took 0.27 ms, AKA 0.00027 seconds.

CSS filters originated from SVG filters, and are relatively browser optimized versions of the most popular SVG filter effect transformations. So I think its pretty safe to use as progressive enhancement at this point (being aware of IE users and Opera Mini users!).

Conclusion and the Future

There are still major savings to be had when reducing image quality (again, in this small study, the images were saved at high qualities for more of a balanced result). Running images through optimizers like ImageOptim, and sending smaller image file sizes based on screen sized (like responsive images in HTML or CSS) will give you even bigger savings.

In the web performance optimization world, I find image performance the most effective thing we can do to reduce web cruft and data for our users, since images are the largest chunk of what we send on the web (by far). If we can start leveraging modern CSS to help lift some of the weight of our images, we can look into a whole new world of optimization solutions.

For example, this could potentially be taken even further, playing with other CSS filters such as saturate and brightness. We could leverage automation tools like Gulp and Webpack to apply the image effects for us, just as we use automation tools to run our images through optimizers. Blending this technique with other best practices for image optimization, can lead to major savings in the pixel-based assets we're sending our users.

The Contrast Swap Technique: Improved Image Performance with CSS Filters is a post from CSS-Tricks

Designing Tables to be Read, Not Looked At

Css Tricks - Tue, 11/07/2017 - 4:52am

Richard Rutter, in support of his new book Web Typography, shares loads of great advice on data table design. Here's a good one:

You might consider making all the columns an even width. This too does nothing for the readability of the contents. Some table cells will be too wide, leaving the data lost and detached from its neighbours. Other table cells will be too narrow, cramping the data uncomfortably. Table columns should be sized according to the data they contain.

I was excited to be reminded of the possibility for aligning numbers with decimals:

td { text-align: "." center; }

But the support for that is non-existent as best I can tell. Another tip, using font-variant-numeric: lining-nums tabular-nums; does have some support.

Tables can be beautiful but they are not works of art. Instead of painting and decorating them, design tables for your reader.

Direct Link to ArticlePermalink

Designing Tables to be Read, Not Looked At is a post from CSS-Tricks

Flexbox and Grids, your layout’s best friends

Css Tricks - Mon, 11/06/2017 - 9:53am

Eva Ferreira smacks down a few myths about CSS grid before going on to demonstrate some of the concepts of each:

? Grids arrived to kill Flexbox.
? Flexbox is Grid’s fallback.

Some more good advice about prototyping:

The best way to begin thinking about a grid structure is to draw it on paper; you’ll be able to see which are the columns, rows, and gaps you’ll be working on. Doodling on paper doesn’t take long and it will give you a better understanding of the overall grid.

These days, if you can draw a layout, you can probably get it done for real.

Direct Link to ArticlePermalink

Flexbox and Grids, your layout’s best friends is a post from CSS-Tricks

input type=’country’

Css Tricks - Mon, 11/06/2017 - 8:56am

Terence Eden looks into the complexity behind adding a new type of HTML input that would allow users to select a country from a list, as per a suggestion from Lea Verou. Lea suggested it could be as simple as this:

<input type='country'>

And then, voilà! An input with a list of all countries would appear in the browser. But Terence describes just how difficult making the user experience around that one tiny input could be for browser makers:

Let's start with the big one. What is a country? This is about as contentious as it gets! It involves national identities, international politics, and hereditary relationships. Scotland, for example, is a country. That is a (fairly) uncontentious statement - and yet in drop-down lists, I rarely see it mentioned. Why? Because it is one of the four countries which make up the country of the United Kingdom - and so it is usually (but not always) subsumed into that. Some countries don't recognize each other. Some believe that the other country is really part of their country. Some countries don't exist.

Direct Link to ArticlePermalink

input type=’country’ is a post from CSS-Tricks

Creating a Star to Heart Animation with SVG and Vanilla JavaScript

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

In my previous article, I've shown how to smoothly transition from one state to another using vanilla JavaScript. Make sure you check that one out first because I'll be referencing some things I explained there in a lot of detail, like demos given as examples, formulas for various timing functions or how not to reverse the timing function when going back from the final state of a transition to the initial one.

The last example showcased making the shape of a mouth to go from sad to glad by changing the d attribute of the path we used to draw this mouth.

Manipulating the path data can be taken to the next level to give us more interesting results, like a star morphing into a heart.

The star to heart animation we'll be coding. The idea

Both are made out of five cubic Bézier curves. The interactive demo below shows the individual curves and the points where these curves are connected. Clicking any curve or point highlights it, as well as its corresponding curve/point from the other shape.

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

Note that all of these curves are created as cubic ones, even if, for some of them, the two control points coincide.

The shapes for both the star and the heart are pretty simplistic and unrealistic ones, but they'll do.

The starting code

As seen in the face animation example, I often choose to generate such shapes with Pug, but here, since this path data we generate will also need to be manipulated with JavaScript for the transition, going all JavaScript, including computing the coordinates and putting them into the d attribute seems like the best option.

This means we don't need to write much in terms of markup:

<svg> <path id='shape'/> </svg>

In terms of JavaScript, we start by getting the SVG element and the path element - this is the shape that morphs from a star into a heart and back. We also set a viewBox attribute on the SVG element such that its dimensions along the two axes are equal and the (0,0) point is dead in the middle. This means the coordinates of the top left corner are (-.5*D,-.5*D), where D is the value for the viewBox dimensions. And last, but not least, we create an object to store info about the initial and final states of the transition and about how to go from the interpolated values to the actual attribute values we need to set on our SVG shape.

const _SVG = document.querySelector('svg'), _SHAPE = document.getElementById('shape'), D = 1000, O = { ini: {}, fin: {}, afn: {} }; (function init() { _SVG.setAttribute('viewBox', [-.5*D, -.5*D, D, D].join(' ')); })();

Now that we got this out of the way, we can move on to the more interesting part!

The geometry of the shapes

The initial coordinates of the end points and control points are those for which we get the star and the final ones are the ones for which we get the heart. The range for each coordinate is the difference between its final value and its initial one. Here, we also rotate the shape as we morph it because we want the star to point up and we change the fill to go from the golden star to the crimson heart.

Alright, but how do we get the coordinates of the end and control points in the two cases?

Star

In the case of the star, we start with a regular pentagram. The end points of our curves are at the intersection between the pentagram edges and we use the pentagram vertices as control points.

Regular pentagram with vertex and edge crossing points highlighted as control points and end points of five cubic Bézier curves (live).

Getting the vertices of our regular pentagram is pretty straightforward given the radius (or diameter) of its circumcircle, which we take to be a fraction of the viewBox size of our SVG (considered here square for simplicity, we're not going for tight packing in this case). But how do we get their intersections?

First of all, let's consider the small pentagon highlighted inside the pentagram in the illustration below. Since the pentagram is regular, the small pentagon whose vertices coincide with the edge intersections of the pentagram is also regular. It also has the same incircle as the pentagram and, therefore, the same inradius.

Regular pentagram and inner regular pentagon share the same incircle (live).

So if we compute the pentagram inradius, then we also have the inradius of the inner pentagon, which, together with the central angle corresponding to an edge of a regular pentagon, allows us to get the circumradius of this pentagon, which in turn allows us to compute its vertex coordinates and these are exactly the edge intersections of the pentagram and the endpoints of our cubic Bézier curves.

Our regular pentagram is represented by the Schläfli symbol {5/2}, meaning that it has 5 vertices, and, given these 5 vertex points equally distributed on its circumcircle, 360°/5 = 72° apart, we start from the first, skip the next point on the circle and connect to the second one (this is the meaning of the 2 in the symbol; 1 would describe a pentagon as we don't skip any points, we connect to the first). And so on - we keep skipping the point right after.

In the interactive demo below, select either pentagon or pentagram to see how they get constructed.

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

This way, we get that the central angle corresponding to an edge of the regular pentagram is twice of that corresponding to the regular pentagon with the same vertices. We have 1·(360°/5) = 1·72° = 72° (or 1·(2·?/5) in radians) for the pentagon versus 2·(360°/5) = 2·72° = 144° (2·(2·?/5) in radians) for the pentagram. In general, given a regular polygon (whether it's a convex or a star polygon doesn't matter) with the Schläfli symbol {p,q}, the central angle corresponding to one of its edges is q·(360°/p) (q·(2·?/p) in radians).

Central angle corresponding to an edge of a regular polygon: pentagram (left, 144°) vs. pentagon (right, 72°) (live).

We also know the pentagram circumradius, which we said we take as a fraction of the square viewBox size. This means we can get the pentagram inradius (which is equal to that of the small pentagon) from a right triangle where we know the hypotenuse (it's the pentagram circumradius) and an acute angle (half the central angle corresponding to the pentagram edge).

Computing the inradius of a regular pentagram from a right triangle where the hypotenuse is the pentagram circumradius and the acute angle between the two is half the central angle corresponding to a pentagram edge (live).

The cosine of half the central angle is the inradius over the circumradius, which gives us that the inradius is the circumradius multiplied with this cosine value.

Now that we have the inradius of the small regular pentagon inside our pentagram, we can compute its circumradius from a similar right triangle having the circumradius as hypotenuse, half the central angle as one of the acute angles and the inradius as the cathetus adjacent to this acute angle.

The illustration below highlights a right triangle formed from a circumradius of a regular pentagon, its inradius and half an edge. From this triangle, we can compute the circumradius if we know the inradius and the central angle corresponding to a pentagon edge as the acute angle between these two radii is half this central angle.

Computing the circumradius of a regular pentagon from a right triangle where it's the hypotenuse, while the catheti are the inradius and half the pentagon edge and the acute angle between the two radii is half the central angle corresponding to a pentagon edge (live).

Remember that, in this case, the central angle is not the same as for the pentagram, it's half of it (360°/5 = 72°).

Good, now that we have this radius, we can get all the coordinates we want. They're the coordinates of points distributed at equal angles on two circles. We have 5 points on the outer circle (the circumcircle of our pentagram) and 5 on the inner one (the circumcircle of the small pentagon). That's 10 points in total, with angles of 360°/10 = 36° in between the radial lines they're on.

The end and control points are distributed on the circumradius of the inner pentagon and on that of the pentagram respectively (live).

We know the radii of both these circles. The radius of the outer one is the regular pentagram circumradius, which we take to be some arbitrary fraction of the viewBox dimension (.5 or .25 or .32 or whatever value we feel would work best). The radius of the inner one is the circumradius of the small regular pentagon formed inside the pentagram, which we can compute as a function of the central angle corresponding to one of its edges and its inradius, which is equal to that of the pentagram and therefore we can compute from the pentagram circumradius and the central angle corresponding to a pentagram edge.

So, at this point, we can generate the path data that draws our star, it doesn't depend on anything that's still unknown.

So let's do that and put all of the above into code!

We start by creating a getStarPoints(f) function which depends on an arbitrary factor (f) that's going to help us get the pentagram circumradius from the viewBox size. This function returns an array of coordinates we later use for interpolation.

Within this function, we first compute the constant stuff that won't change as we progress through it - the pentagram circumradius (radius of the outer circle), the central (base) angles corresponding to one edge of a regular pentagram and polygon, the inradius shared by the pentagram and the inner pentagon whose vertices are the points where the pentagram edges cross each other, the circumradius of this inner pentagon and, finally, the total number of distinct points whose coordinates we need to compute and the base angle for this distribution.

After that, within a loop, we compute the coordinates of the points we want and we push them into the array of coordinates.

const P = 5; /* number of cubic curves/ polygon vertices */ function getStarPoints(f = .5) { const RCO = f*D /* outer (pentagram) circumradius */, BAS = 2*(2*Math.PI/P) /* base angle for star poly */, BAC = 2*Math.PI/P /* base angle for convex poly */, RI = RCO*Math.cos(.5*BAS) /*pentagram/ inner pentagon inradius */, RCI = RI/Math.cos(.5*BAC) /* inner pentagon circumradius */, ND = 2*P /* total number of distinct points we need to get */, BAD = 2*Math.PI/ND /* base angle for point distribution */, PTS = [] /* array we fill with point coordinates */; for(let i = 0; i < ND; i++) {} return PTS; }

To compute the coordinates of our points, we use the radius of the circle they're on and the angle of the radial line connecting them to the origin with respect to the horizontal axis, as illustrated by the interactive demo below (drag the point to see how its Cartesian coordinates change):

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

In our case, the current radius is the radius of the outer circle (pentagram circumradius RCO) for even index points (0, 2, ...) and the radius of the inner circle (inner pentagon circumradius RCI) for odd index points (1, 3, ...), while the angle of the radial line connecting the current point to the origin is the point index (i) multiplied with the base angle for point distribution (BAD, which happens to be 36° or ?/10 in our particular case).

So within the loop we have:

for(let i = 0; i < ND; i++) { let cr = i%2 ? RCI : RCO, ca = i*BAD, x = Math.round(cr*Math.cos(ca)), y = Math.round(cr*Math.sin(ca)); }

Since we've chosen a pretty big value for the viewBox size, we can safely round the coordinate values so that our code looks cleaner, without decimals.

As for pushing these coordinates into the points array, we do this twice when we're on the outer circle (the even indices case) because that's where we actually have two control points overlapping, but only for the star, so we'll need to move each of these overlapping points into different positions to get the heart.

for(let i = 0; i < ND; i++) { /* same as before */ PTS.push([x, y]); if(!(i%2)) PTS.push([x, y]); }

Next, we put data into our object O. For the path data (d) attribute, we store the array of points we get when calling the above function as the initial value. We also create a function for generating the actual attribute value (the path data string in this case - inserting commands in between the pairs of coordinates, so that the browser knows what to do with those coordinates). Finally, we take every attribute we have stored data for and we set its value to the value returned by the previously mentioned function:

(function init() { /* same as before */ O.d = { ini: getStarPoints(), afn: function(pts) { return pts.reduce((a, c, i) => { return a + (i%3 ? ' ' : 'C') + c }, `M${pts[pts.length - 1]}`) } }; for(let p in O) _SHAPE.setAttribute(p, O[p].afn(O[p].ini)) })();

The result can be seen in the Pen below:

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

This is a promising start. However, we want the first tip of the generating pentagram to point down and the first tip of the resulting star to point up. Currently, they're both pointing right. This is because we start from 0° (3 o'clock). So in order to start from 6 o'clock, we add 90° (?/2 in radians) to every current angle in the getStarPoints() function.

ca = i*BAD + .5*Math.PI

This makes the first tip of the generating pentagram and resulting star to point down. To rotate the star, we need to set its transform attribute to a half circle rotation. In order to do so, we first set an initial rotation angle to -180. Afterwards, we set the function that generates the actual attribute value to a function that generates a string from a function name and an argument:

function fnStr(fname, farg) { return `${fname}(${farg})` }; (function init() { /* same as before */ O.transform = { ini: -180, afn: (ang) => fnStr('rotate', ang) }; /* same as before */ })();

We also give our star a golden fill in a similar fashion. We set an RGB array to the initial value in the fill case and we use a similar function to generate the actual attribute value:

(function init() { /* same as before */ O.fill = { ini: [255, 215, 0], afn: (rgb) => fnStr('rgb', rgb) }; /* same as before */ })();

We now have a nice golden SVG star, made up of five cubic Bézier curves:

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

Heart

Since we have the star, let's next see how we can get the heart!

We start with two intersecting circles of equal radii, both a fraction (let's say .25 for the time being) of the viewBox size. These circles intersect in such a way that the segment connecting their central points is on the x axis and the segment connecting their intersection points is on the y axis. We also take these two segments to be equal.

We start with two circles of equal radius whose central points are on the horizontal axis and which intersect on the vertical axis (live).

Next, we draw diameters through the upper intersection point and then tangents through the opposite points of these diameters. These tangents intersect on the y axis.

Constructing diameters through the upper intersection point and tangents to the circle at the opposite ends of these diameters, tangents which intersect on the vertical axis (live).

The upper intersection point and the diametrically opposite points make up three of the five end points we need. The other two end points split the outer half circle arcs into two equal parts, thus giving us four quarter circle arcs.

Highlighting the end points of the cubic Bézier curves that make up the heart and the coinciding control points of the bottom one of these curves (live).

Both control points for the curve at the bottom coincide with the intersection of the the two tangents drawn previously. But what about the other four curves? How can we go from circular arcs to cubic Bézier curves?

We don't have a cubic Bézier curve equivalent for a quarter circle arc, but we can find a very good approximation, as explained in this article.

The gist of it is that we start from a quarter circle arc of radius R and draw tangents to the end points of this arc (N and Q). These tangents intersect at P. The quadrilateral ONPQ has all angles equal to 90° (or ?/2), three of them by construction (O corresponds to a 90° arc and the tangent to a point of that circle is always perpendicular onto the radial line to the same point) and the final one by computation (the sum of angles in a quadrilateral is always 360° and the other three angles add up to 270°). This makes ONPQ a rectangle. But ONPQ also has two consecutive edges equal (OQ and ON are both radial lines, equal to R in length), which makes it a square of edge R. So the lengths of NP and QP are also equal to R.

Approximating a quarter circle arc with a cubic Bézier curve (live).

The control points of the cubic curve approximating our arc are on the tangent lines NP and QP, at C·R away from the end points, where C is the constant the previously linked article computes to be .551915.

Given all of this, we can now start computing the coordinates of the end points and control points of the cubic curves making up our star.

Due to the way we've chosen to construct this heart, TO0SO1 (see figure below) is a square since it has all edges equal (all are radii of one of our two equal circles) and its diagonals are equal by construction (we said the distance between the central points equals that between the intersection points). Here, O is the intersection of the diagonals and OT is half the ST diagonal. T and S are on the y axis, so their x coordinate is 0. Their y coordinate in absolute value equals the OT segment, which is half the diagonal (as is the OS segment).

The TO0SO1 square (live).

We can split any square of edge length l into two equal right isosceles triangles where the catheti coincide with the square edges and the hypotenuse coincides with a diagonal.

Any square can be split into two congruent right isosceles triangles (live).

Using one of these right triangles, we can compute the hypotenuse (and therefore the square diagonal) using Pythagora's theorem: d² = l² + l². This gives us the square diagonal as a function of the edge d = ?(2?l) = l??2 (conversely, the edge as a function of the diagonal is l = d/?2). It also means that half the diagonal is d/2 = (l??2)/2 = l/?2.

Applying this to our TO0SO1 square of edge length R, we get that the y coordinate of T (which, in absolute value, equals half this square's diagonal) is -R/?2 and the y coordinate of S is R/?2.

The coordinates of the vertices of the TO0SO1 square (live).

Similarly, the Ok points are on the x axis, so their y coordinates are 0, while their x coordinates are given by the half diagonal OOk: ±R/?2.

TO0SO1 being a square also means all of its angles are 90° (?/2 in radians) angles.

The TAkBkS quadrilaterals (live).

In the illustration above, the TBk segments are diameter segments, meaning that the TBk arcs are half circle, or 180° arcs and we've split them into two equal halves with the Ak points, getting two equal 90° arcs - TAk and AkBk, which correspond to two equal 90° angles, ?TOkAk and ?AkOkBk.

Given that ?TOkS are 90° angles and ?TOkAk are also 90° angles by construction, it results that the SAk segments are also diameter segments. This gives us that in the TAkBkS quadrilaterals, the diagonals TBk and SAk are perpendicular, equal and cross each other in the middle (TOk, OkBk, SOk and OkAk are all equal to the initial circle radius R). This means the TAkBkS quadrilaterals are squares whose diagonals are 2?R.

From here we can get that the edge length of the TAkBkS quadrilaterals is 2?R/?2 = R??2. Since all angles of a square are 90° ones and the TS edge coincides with the vertical axis, this means the TAk and SBk edges are horizontal, parallel to the x axis and their length gives us the x coordinates of the Ak and Bk points: ±R??2.

Since TAk and SBk are horizontal segments, the y coordinates of the Ak and Bk points equal those of the T (-R/?2) and S (R/?2) points respectively.

The coordinates of the vertices of the TAkBkS squares (live).

Another thing we get from here is that, since TAkBkS are squares, AkBk are parallel with TS, which is on the y (vertical) axis, therefore the AkBk segments are vertical. Additionally, since the x axis is parallel to the TAk and SBk segments and it cuts the TS, it results that it also cuts the AkBk segments in half.

Now let's move on to the control points.

We start with the overlapping control points for the bottom curve.

The TB0CB1 quadrilateral (live).

The TB0CB1 quadrilateral has all angles equal to 90° (?T since TO0SO1 is a square, ?Bk by construction since the BkC segments are tangent to the circle at Bk and therefore perpendicular onto the radial lines OkBk at that point; and finally, ?C can only be 90° since the sum of angles in a quadrilateral is 360° and the other three angles add up to 270°), which makes it a rectangle. It also has two consecutive edges equal - TB0 and TB1 are both diameters of the initial squares and therefore both equal to 2?R. All of this makes it a square of edge 2?R.

From here, we can get its diagonal TC - it's 2?R??2. Since C is on the y axis, its x coordinate is 0. Its y coordinate is the length of the OC segment. The OC segment is the TC segment minus the OT segment: 2?R??2 - R/?2 = 4?R/?2 - R/?2 = 3?R/?2.

The coordinates of the vertices of the TB0CB1 square (live).

So we now have the coordinates of the two coinciding control points for the bottom curve are (0,3?R/?2).

In order to get the coordinates of the control points for the other curves, we draw tangents through their endpoints and we get the intersections of these tangents at Dk and Ek.

The TOkAkDk and AkOkBkEk quadrilaterals (live).

In the TOkAkDk quadrilaterals, we have that all angles are 90° (right) angles, three of them by construction (?DkTOk and ?DkAkOk are the angles between the radial and tangent lines at T and Ak respectively, while ?TOkAk are the angles corresponding to the quarter circle arcs TAk) and the fourth by computation (the sum of angles in a quadrilateral is 360° and the other three add up to 270°). This makes TOkAkDk rectangles. Since they have two consecutive edges equal (OkT and OkAk are radial segments of length R), they are also squares.

This means the diagonals TAk and OkDk are R??2. We already know that TAk are horizontal and, since the diagonals of a square are perpendicular, it results the OkDk segments are vertical. This means the Ok and Dk points have the same x coordinate, which we've already computed for Ok to be ±R/?2. Since we know the length of OkDk, we can also get the y coordinates - they're the diagonal length (R??2) with minus in front.

Similarly, in the AkOkBkEk quadrilaterals, we have that all angles are 90° (right) angles, three of them by construction (?EkAkOk and ?EkBkOk are the angles between the radial and tangent lines at Ak and Bk respectively, while ?AkOkBk are the angles corresponding to the quarter circle arcs AkBk) and the fourth by computation (the sum of angles in a quadrilateral is 360° and the other three add up to 270°). This makes AkOkBkEk rectangles. Since they have two consecutive edges equal (OkAk and OkBk are radial segments of length R), they are also squares.

From here, we get the diagonals AkBk and OkEk are R??2. We know the AkBk segments are vertical and split into half by the horizontal axis, which means the OkEk segments are on this axis and the y coordinates of the Ek points are 0. Since the x coordinates of the Ok points are ±R/?2 and the OkEk segments are R??2, we can compute those of the Ek points as well - they're ±3?R/?2.

The coordinates of the newly computed vertices of the TO?A?D? and A?O?B?E? squares (live).

Alright, but these intersection points for the tangents are not the control points we need to get the circular arc approximations. The control points we want are on the TDk, AkDk, AkEk and BkEk segments at about 55% (this value is given by the constant C computed in the previously mentioned article) away from the curve end points (T, Ak, Bk). This means the segments from the endpoints to the control points are C?R.

In this situation, the coordinates of our control points are 1 - C of those of the end points (T, Ak and Bk) plus C of those of the points where the tangents at the end points intersect (Dk and Ek).

So let's put all of this into JavaScript code!

Just like in the star case, we start with a getStarPoints(f) function which depends on an arbitrary factor (f) that's going to help us get the radius of the helper circles from the viewBox size. This function also returns an array of coordinates we later use for interpolation.

Inside, we compute the stuff that doesn't change throughout the function. First off, the radius of the helper circles. From that, the half diagonal of the small squares whose edge equals this helper circle radius, half diagonal which is also the circumradius of these squares. Afterwards, the coordinates of the end points of our cubic curves (the T, Ak, Bk points), in absolute value for the ones along the horizontal axis. Then we move on to the coordinates of the points where the tangents through the end points intersect (the C, Dk, Ek points). These either coincide with the control points (C) or can help us get the control points (this is the case for Dk and Ek).

function getHeartPoints(f = .25) { const R = f*D /* helper circle radius */, RC = Math.round(R/Math.SQRT2) /* circumradius of square of edge R */, XT = 0, YT = -RC /* coords of point T */, XA = 2*RC, YA = -RC /* coords of A points (x in abs value) */, XB = 2*RC, YB = RC /* coords of B points (x in abs value) */, XC = 0, YC = 3*RC /* coords of point C */, XD = RC, YD = -2*RC /* coords of D points (x in abs value) */, XE = 3*RC, YE = 0 /* coords of E points (x in abs value) */; }

The interactive demo below shows the coordinates of these points on click:

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

Now we can also get the control points from the end points and the points where the tangents through the end points intersect:

function getHeartPoints(f = .25) { /* same as before */ const /* const for cubic curve approx of quarter circle */ C = .551915, CC = 1 - C, /* coords of ctrl points on TD segs */ XTD = Math.round(CC*XT + C*XD), YTD = Math.round(CC*YT + C*YD), /* coords of ctrl points on AD segs */ XAD = Math.round(CC*XA + C*XD), YAD = Math.round(CC*YA + C*YD), /* coords of ctrl points on AE segs */ XAE = Math.round(CC*XA + C*XE), YAE = Math.round(CC*YA + C*YE), /* coords of ctrl points on BE segs */ XBE = Math.round(CC*XB + C*XE), YBE = Math.round(CC*YB + C*YE); /* same as before */ }

Next, we need to put the relevant coordinates into an array and return this array. In the case of the star, we started with the bottom curve and then went clockwise, so we do the same here. For every curve, we push two sets of coordinates for the control points and then one set for the point where the current curve ends.

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

Note that in the case of the first (bottom) curve, the two control points coincide, so we push the same pair of coordinates twice. The code doesn't look anywhere near as nice as in the case of the star, but it will have to suffice:

return [ [XC, YC], [XC, YC], [-XB, YB], [-XBE, YBE], [-XAE, YAE], [-XA, YA], [-XAD, YAD], [-XTD, YTD], [XT, YT], [XTD, YTD], [XAD, YAD], [XA, YA], [XAE, YAE], [XBE, YBE], [XB, YB] ];

We can now take our star demo and use the getHeartPoints() function for the final state, no rotation and a crimson fill instead. Then, we set the current state to the final shape, just so that we can see the heart:

function fnStr(fname, farg) { return `${fname}(${farg})` }; (function init() { _SVG.setAttribute('viewBox', [-.5*D, -.5*D, D, D].join(' ')); O.d = { ini: getStarPoints(), fin: getHeartPoints(), afn: function(pts) { return pts.reduce((a, c, i) => { return a + (i%3 ? ' ' : 'C') + c }, `M${pts[pts.length - 1]}`) } }; O.transform = { ini: -180, fin: 0, afn: (ang) => fnStr('rotate', ang) }; O.fill = { ini: [255, 215, 0], fin: [220, 20, 60], afn: (rgb) => fnStr('rgb', rgb) }; for(let p in O) _SHAPE.setAttribute(p, O[p].afn(O[p].fin)) })();

This gives us a nice looking heart:

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

Ensuring consistent shape alignment

However, if we place the two shapes one on top of the other with no fill or transform, just a stroke, we see the alignment looks pretty bad:

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

The easiest way to solve this issue is to shift the heart up by an amount depending on the radius of the helper circles:

return [ /* same coords */ ].map(([x, y]) => [x, y - .09*R])

We now have much better alignment, regardless of how we tweak the f factor in either case. This is the factor that determines the pentagram circumradius relative to the viewBox size in the star case (when the default is .5) and the radius of the helper circles relative to the same viewBox size in the heart case (when the default is .25).

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

Switching between the two shapes

We want to go from one shape to the other on click. In order to do this, we set a direction dir variable which is 1 when we go from star to heart and -1 when we go from heart to star. Initially, it's -1, as if we've just switched from heart to star.

Then we add a 'click' event listener on the _SHAPE element and code what happens in this situation - we change the sign of the direction (dir) variable and we change the shape's attributes so that we go from a golden star to a crimson heart or the other way around:

let dir = -1; (function init() { /* same as before */ _SHAPE.addEventListener('click', e => { dir *= -1; for(let p in O) _SHAPE.setAttribute(p, O[p].afn(O[p][dir > 0 ? 'fin' : 'ini'])); }, false); })();

And we're now switching between the two shapes on click:

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

Morphing from one shape to another

What we really want however is not an abrupt change from one shape to another, but a gradual one. So we use the interpolation techniques explained in the previous article to achieve this.

We first decide on a total number of frames for our transition (NF) and choose the kind of timing functions we want to use - an ease-in-out type of function for transitioning the path shape from star to heart, a bounce-ini-fin type of function for the rotation angle and an ease-out one for the fill. We only include these, though we could later add others in case we change our mind and want to explore other options as well.

/* same as before */ const NF = 50, TFN = { '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) }, 'bounce-ini-fin': function(k, s = -.65*Math.PI, e = -s) { return (Math.sin(k*(e - s) + s) - Math.sin(s))/(Math.sin(e) - Math.sin(s)) } };

We then specify which of these timing functions we use for each property we transition:

(function init() { /* same as before */ O.d = { /* same as before */ tfn: 'ease-in-out' }; O.transform = { /* same as before */ tfn: 'bounce-ini-fin' }; O.fill = { /* same as before */ tfn: 'ease-out' }; /* same as before */ })();

We move on to adding request ID (rID) and current frame (cf) variables, an update() function we first call on click, then on every refresh of the display until the transition finishes and we call a stopAni() function to exit this animation loop. Within the update() function, we... well, update the current frame cf, compute a progress k and decide whether we've reached the end of the transition and we need to exit the animation loop or we carry on.

We also add a multiplier m variable which we use so that we don't reverse the timing functions when we go from the final state (heart) back to the initial one (star).

let rID = null, cf = 0, m; function stopAni() { cancelAnimationFrame(rID); rID = null; }; function update() { cf += dir; let k = cf/NF; if(!(cf%NF)) { stopAni(); return } rID = requestAnimationFrame(update) };

Then we need to change what we do on click:

addEventListener('click', e => { if(rID) stopAni(); dir *= -1; m = .5*(1 - dir); update(); }, false);

Within the update() function, we want to set the attributes we transition to some intermediate values (depending on the progress k). As seen in the previous article, it's good to have the ranges between the final and initial values precomputed at the beginning, before even setting the listener, so that's our next step: creating a function that computes the range between numbers, whether as such or in arrays, no matter how deep and then using this function to set the ranges for the properties we want to transition.

function range(ini, fin) { return typeof ini == 'number' ? fin - ini : ini.map((c, i) => range(ini[i], fin[i])) }; (function init() { /* same as before */ for(let p in O) { O[p].rng = range(O[p].ini, O[p].fin); _SHAPE.setAttribute(p, O[p].afn(O[p].ini)); } /* same as before */ })();

Now all that's left to do is the interpolation part in the update() function. Using a loop, we go through all the attributes we want to smoothly change from one end state to the other. Within this loop, we set their current value to the one we get as the result of an interpolation function which depends on the initial value(s), range(s) of the current attribute (ini and rng), on the timing function we use (tfn) and on the progress (k):

function update() { /* same as before */ for(let p in O) { let c = O[p]; _SHAPE.setAttribute(p, c.afn(int(c.ini, c.rng, TFN[c.tfn], k))); } /* same as before */ };

The last step is to write this interpolation function. It's pretty similar to the one that gives us the range values:

function int(ini, rng, tfn, k) { return typeof ini == 'number' ? Math.round(ini + (m + dir*tfn(m + dir*k))*rng) : ini.map((c, i) => int(ini[i], rng[i], tfn, k)) };

This finally gives us a shape that morphs from star to heart on click and goes back to star on a second click!

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

It's almost what we wanted - there's still one tiny issue. For cyclic values like angle values, we don't want to go back by half a circle on the second click. Instead, we want to continue going in the same direction for another half circle. Adding this half circle from after the second click with the one traveled after the first click, we get a full circle so we're right back where we started.

We put this into code by adding an optional continuity property and tweaking the updating and interpolating functions a bit:

function int(ini, rng, tfn, k, cnt) { return typeof ini == 'number' ? Math.round(ini + cnt*(m + dir*tfn(m + dir*k))*rng) : ini.map((c, i) => int(ini[i], rng[i], tfn, k, cnt)) }; function update() { /* same as before */ for(let p in O) { let c = O[p]; _SHAPE.setAttribute(p, c.afn(int(c.ini, c.rng, TFN[c.tfn], k, c.cnt ? dir : 1))); } /* same as before */ }; (function init() { /* same as before */ O.transform = { ini: -180, fin: 0, afn: (ang) => fnStr('rotate', ang), tfn: 'bounce-ini-fin', cnt: 1 }; /* same as before */ })();

We now have the result we've been after: a shape that morphs from a golden star into a crimson heart and rotates clockwise by half a circle every time it goes from one state to the other:

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

Creating a Star to Heart Animation with SVG and Vanilla JavaScript is a post from CSS-Tricks

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.

Plus:

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>

Then:

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); }

Or...

<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.

CSS

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!

Conclusion

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

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

Syndicate content
©2003 - Present Akamai Design & Development.