Friday, 30 August 2019

Styling Links with Real Underlines

Working with Attributes on DOM Elements

The DOM is just a little weird about some things, and the way you deal with attributes is no exception. There are a number of ways to deal with the attributes on elements. By attributes, I mean things like the id in <div id="cool"></div>. Sometimes you need to set them. Sometimes you need to get them. Sometimes you get fancy helpers. Sometimes you don't.

For this article, I'll assume el is a DOM element in your JavaScript. Let's say you've done something like const el = document.querySelector("#cool"); and matched <div id="cool"> or whatever.

Some attributes are also attributes of the DOM object itself, so iff you need to set an id or title, you can do:

el.id; // "cool"
el.title = "my title";
el.title; // "my title";

Others that work like that are lang, align, and all the big events, like onclick.

Then there are attributes that work similarly to that but are nested deeper. The style attribute is like that. If you log el.style you'll see a ton of CSS style declarations. You can get and set them easily:

el.style.color = "red";
module.style.backgroundColor = "black";

You can get computed colors this way too. If you do module.style.color hoping to get the color of an element out of the gate, you probably won't get it. For that, you'd have to do:

let style = window.getComputedStyle(el);
style.color; // whatever in CSS won out

But not all attributes are like first-class attributes like that.

el['aria-hidden'] = true; // nope

That "works" in that it sets that as a property, but it doesn't set it in the DOM the proper way. Instead, you'll have to use the generic setter and getter functions that work for all attributes, like:

el.setAttribute("aria-hidden", true);
el.getAttribute("aria-hidden");

Some attributes have fancy helpers. The most fancy is classList for class attributes. On an element like:

<div class="module big"></div>

You'd have:

el.classList.value; // "module big"
el.classList.length; // 2
el.classList.add("cool"); // adds the class "cool", so "module big cool"
el.classList.remove("big"); // removes "big", so "module cool"
el.classList.toggle("big"); // adds "big" back, because it was missing (goes back and forth)
el.classList.contains("module"); // true

There's even more, and classList itself behaves like an array so you can forEach it and such. That's a pretty strong reason to use classes, as the DOM API around them is so handy.

Another attribute type that has a somewhat fancy help is data-*. Say you've got:

<div data-active="true" data-placement="top right" data-extra-words="hi">test</div> 

You've got dataset:

el.dataset;
/*
{
  active: "true",
  "placement", "top right"
*/

el.dataset.active; // "true"
el.dataset.extraWords; // "hi", note the conversion to camelCase

el.dataset.active = "false"; // setters work like this

The post Working with Attributes on DOM Elements appeared first on CSS-Tricks.



from CSS-Tricks https://ift.tt/2ZG6NBG
via IFTTT

Refurbishing Top Content - Best of Whiteboard Friday

Thursday, 29 August 2019

The Best (GraphQL) API is One You Write

Listen, I am no GraphQL expert but I do enjoy working with it. The way it exposes data to me as a front-end developer is pretty cool. It's like a menu of available data and I can ask for whatever I want. That's a massive improvement over REST and highly empowering for me as a front-end developer who desires to craft components with whatever data I think is best for a UI without having to make slew of calls for data or ask a back-end developer to help make me a new bespoke API for my new needs.

But... who builds that menu of data? Somebody does.

If that somebody is a person or team at your own company because you've built out your own GraphQL API for your own needs, that's great. Now you've got control over what goes in there (and when and how).

But sometimes GraphQL APIs are just handed to you. Perhaps that is how your CMS delivers its data. Still cool and useful, but that control is at the mercy of the CMS. You still have a menu of options, but the menu just is what it is. No substitutes, to continue the metaphor. If the menu doesn't have what you need, you can't go back into the kitchen and add extra sauerkraut to that reuben or have the steak fries come with fried mushrooms.

This came up in a discussion with Simen Skogsrud and Knut Melvær on an episode of ShopTalk. Their product, Sanity, is like cloud storage for JSON data, and a CMS if you need it. A modern product like this, you'd think a GraphQL API would be a no-brainer, and indeed, they have a beta for it.

But instead of GraphQL being the main first-class citizen way of querying for and mutating data, they have their own special language: GROQ. At first glance, I'm like: eeeeeesh, there's a way to shoot yourself in the foot. Invent some special language that people have to learn that's unique to your product instead of the emerging industry standard.

But Simen and Knut made a good point about some of the limitations of GraphQL in the context of a third-party handing you an API: you get what you get. Say a GraphQL API offers a way to retrieve authors. A generic API for that is probably designed something like this:

{
  allAuthors {
    author {
      name
      username
      avatar
    }
  }
}

But what I actually want is just how many authors we have on the site. Perhaps I wish I could do this:

{
  allAuthors {
    count
  }
}

But that's not in the API I was given. Well, too bad. I guess I'll have to request all the authors and count them myself. I might not control the API.

This means that something like a CMS that offers a GraphQL endpoint needs to make a choice. They are either very strict and you just get-what-you-get. Or, they offer not only a GraphQL API but a way to control and augment what goes into that API.

In Santiy's case, rather than offer the later, they offer GROQ, which is a query language that is powerful enough you can get whatever you want out of the (JSON) data. And rather than making it this proprietary Sanity-only thing, they've open sourced it.

With GROQ, I don't need any permission or alterations to the API to ask how many authors there are. I'd do something like...

{ "totalAuthors": count(*[* in authors]) }

(I actually have no idea if the above code is accurate, and of course, it depends on the JSON it is querying, but it's just conceptual anyway.)

By giving someone a query language that is capable of selecting any possible data in the data store, it has a big benefit:

  • You can query for literally anything
  • You don't need a middle layer of configuration

But it comes at a cost:

  • Complexity of syntax
  • No middle layer means less opportunity for connecting multiple APIs, exposing only certain data based on permissions, etc.

The post The Best (GraphQL) API is One You Write appeared first on CSS-Tricks.



from CSS-Tricks https://ift.tt/2L7oDd2
via IFTTT

Maskable Icons: Android Adaptive Icons for Your PWA

A Glassy (and Classy) Text Effect

Wednesday, 28 August 2019

Nested Gradients with background-clip

Creating a Maintainable Icon System with Sass

One of my favorite ways of adding icons to a site is by including them as data URL background images to pseudo-elements (e.g. ::after) in my CSS. This technique offers several advantages:

  • They don't require any additional HTTP requests other than the CSS file.
  • Using the background-size property, you can set your pseudo-element to any size you need without worrying that they will overflow the boundaries (or get chopped off).
  • They are ignored by screen readers (at least in my tests using VoiceOver on the Mac) so which is good for decorative-only icons.

But there are some drawbacks to this technique as well:

  • When used as a background-image data URL, you lose the ability to change the SVG's colors using the "fill" or "stroke" CSS properties (same as if you used the filename reference, e.g. url( 'some-icon-file.svg' )). We can use filter() as an alternative, but that might not always be a feasible solution.
  • SVG markup can look big and ugly when used as data URLs, making them difficult to maintain when you need to use the icons in multiple locations and/or have to change them.

We're going to address both of these drawbacks in this article.

The situation

Let's build a site that uses a robust iconography system, and let's say that it has several different button icons which all indicate different actions:

  • A "download" icon for downloadable content
  • An "external link" icon for buttons that take us to another website
  • A "right caret" icon for taking us to the next step in a process

Right off the bat, that gives us three icons. And while that may not seem like much, already I'm getting nervous about how maintainable this is going to be when we scale it out to more icons like social media networks and the like. For the sake of this article, we're going to stop at these three, but you can imagine how in a sophisticated icon system this could get very unwieldy, very quickly.

It's time to go to the code. First, we'll set up a basic button, and then by using a BEM naming convention, we'll assign the proper icon to its corresponding button. (At this point, it's fair to warn you that we'll be writing everything out in Sass, or more specifically, SCSS. And for the sake of argument, assume I'm running Autoprefixer to deal with things like the appearance property.)

.button {
  appearance: none;
  background: #d95a2b;
  border: 0;
  border-radius: 100em;
  color: #fff;
  cursor: pointer;
  display: inline-block;
  font-size: 18px;
  font-weight: 700;
  line-height: 1;
  padding: 1em calc( 1.5em + 32px ) 0.9em 1.5em;
  position: relative;
  text-align: center;
  text-transform: uppercase;
  transition: background-color 200ms ease-in-out;

  &:hover,
  &:focus,
  &:active {
    background: #8c3c2a;
  }
}

This gives us a simple, attractive, orange button that turns to a darker orange on the hover, focused, and active states. It even gives us a little room for the icons we want to add, so let's add them in now using pseudo-elements:

.button {

  /* everything from before, plus... */

  &::after {
    background: center / 24px 24px no-repeat; // Shorthand for: background-position, background-size, background-repeat
    border-radius: 100em;
    bottom: 0;
    content: '';
    position: absolute;
    right: 0;
    top: 0;
    width: 48px;
  }

  &--download {

    &::after {
      background-image: url( 'data:image/svg+xml;utf-8,<svg xmlns="http://www.w3.org/2000/svg" width="30.544" height="25.294" viewBox="0 0 30.544 25.294"><g transform="translate(-991.366 -1287.5)"><path d="M1454.5,1298.922l6.881,6.881-6.881,6.881" transform="translate(2312.404 -157.556) rotate(90)" fill="none" stroke="%23fff" stroke-width="3"/><path d="M8853.866,5633.57v9.724h27.544v-9.724" transform="translate(-7861 -4332)" fill="none" stroke="%23fff" stroke-linejoin="round" stroke-width="3"/><line y2="14" transform="translate(1006.5 1287.5)" fill="none" stroke="%23fff" stroke-width="3"/></g></svg>' );
      }
    }

  &--external {

    &::after {
      background-image: url( 'data:image/svg+xml;utf-8,<svg xmlns="http://www.w3.org/2000/svg" width="31.408" height="33.919" viewBox="0 0 31.408 33.919"><g transform="translate(-1008.919 -965.628)"><g transform="translate(1046.174 2398.574) rotate(-135)"><path d="M0,0,7.879,7.879,0,15.759" transform="translate(1025.259 990.17) rotate(90)" fill="none" stroke="%23fff" stroke-width="3"/><line y2="16.032" transform="translate(1017.516 980.5)" fill="none" stroke="%23fff" stroke-width="3"/></g><path d="M10683.643,5322.808v10.24h-20.386v-21.215h7.446" transform="translate(-9652.838 -4335)" fill="none" stroke="%23fff" stroke-width="3"/></g></svg>' );
    }
  }

  &--caret-right {

    &::after {
      background-image: url( 'data:image/svg+xml;utf-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 19.129 34.016"><path d="M1454.5,1298.922l15.947,15.947-15.947,15.947" transform="translate(-1453.439 -1297.861)" fill="none" stroke="%23fff" stroke-width="3"/></svg>' );
    }
  }
}

Let's pause here. While we're keeping our SCSS as tidy as possible by declaring the properties common to all buttons, and then only specifying the background SVGs on a per-class basis, it's already starting to look a bit unwieldy. That's that second downside to SVGs I mentioned before: having to use big, ugly markup in our CSS code.

Also, note how we're defining our fill and stroke colors inside the SVGs. At some point, browsers decided that the octothorpe ("#") that we all know and love in our hex colors was a security risk, and declared that they would no longer support data URLs that contained them. This leaves us with three options:

  1. Convert our data URLs from markup (like we have here) to base-64 encoded strings, but that makes them even less maintainable than before by completely obfuscating them; or
  2. Use rgba() or hsla() notation, not always intuitive as many developers have been using hex for years; or
  3. Convert our octothorpes to their URL-encoded equivalents, "%23".

We're going to go with option number three, and work around that browser limitation. (I will mention here, however, that this technique will work with rgb(), hsla(), or any other valid color format, even CSS named colors. But please don't use CSS named colors in production code.)

Moving to maps

At this point, we only have three buttons fully declared. But I don't like them just dumped in the code like this. If we needed to use those same icons elsewhere, we'd have to copy and paste the SVG markup, or else we could assign them to variables (either Sass or CSS custom properties), and reuse them that way. But I'm going to go for what's behind door number three, and switch to using one of Sass' greatest features: maps.

If you're not familiar with Sass maps, they are, in essence, the Sass version of an associative array. Instead of a numerically-indexed array of items, we can assign a name (a key, if you will) so that we can retrieve them by something logical and easily remembered. So let's build a Sass map of our three icons:

$icons: (
  'download':    '<svg xmlns="http://www.w3.org/2000/svg" width="30.544" height="25.294" viewBox="0 0 30.544 25.294"><g transform="translate(-991.366 -1287.5)"><path d="M1454.5,1298.922l6.881,6.881-6.881,6.881" transform="translate(2312.404 -157.556) rotate(90)" fill="none" stroke="%23fff" stroke-width="3"/><path d="M8853.866,5633.57v9.724h27.544v-9.724" transform="translate(-7861 -4332)" fill="none" stroke="%23fff" stroke-linejoin="round" stroke-width="3"/><line y2="14" transform="translate(1006.5 1287.5)" fill="none" stroke="%23fff" stroke-width="3"/></g></svg>',
  'external':    '<svg xmlns="http://www.w3.org/2000/svg" width="31.408" height="33.919" viewBox="0 0 31.408 33.919"><g transform="translate(-1008.919 -965.628)"><g transform="translate(1046.174 2398.574) rotate(-135)"><path d="M0,0,7.879,7.879,0,15.759" transform="translate(1025.259 990.17) rotate(90)" fill="none" stroke="%23fff" stroke-width="3"/><line y2="16.032" transform="translate(1017.516 980.5)" fill="none" stroke="%23fff" stroke-width="3"/></g><path d="M10683.643,5322.808v10.24h-20.386v-21.215h7.446" transform="translate(-9652.838 -4335)" fill="none" stroke="%23fff" stroke-width="3"/></g></svg>',
  'caret-right': '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 19.129 34.016"><path d="M1454.5,1298.922l15.947,15.947-15.947,15.947" transform="translate(-1453.439 -1297.861)" fill="none" stroke="%23fff" stroke-width="3"/></svg>',
);

There are two things to note here: We didn't include the data:image/svg+xml;utf-8, string in any of those icons, only the SVG markup itself. That string is going to be the same every single time we need to use these icons, so why repeat ourselves and run the risk of making a mistake? Let's instead define it as its own string and prepend it to the icon markup when needed:

$data-svg-prefix: 'data:image/svg+xml;utf-8,';

The other thing to note is that we aren't actually making our SVG any prettier; there's no way to do that. What we are doing is pulling all that ugliness out of the code we're working on a day-to-day basis so we don't have to look at all that visual clutter as much. Heck, we could even put it in its own partial that we only have to touch when we need to add more icons. Out of sight, out of mind!

So now, let's use our map. Going back to our button code, we can now replace those icon literals with pulling them from the icon map instead:

&--download {

  &::after {
    background-image: url( $data-svg-prefix + map-get( $icons, 'download' ) );
  }
}

&--external {

  &::after {
    background-image: url( $data-svg-prefix + map-get( $icons, 'external' ) );
  }
}

&--next {

  &::after {
    background-image: url( $data-svg-prefix + map-get( $icons, 'caret-right' ) );
  }
}

Already, that's looking much better. We've started abstracting out our icons in a way that keeps our code readable and maintainable. And if that were the only challenge, we'd be done. But in the real-world project that inspired this article, we had another wrinkle: different colors.

Our buttons are a solid color which turn to a darker version of that color on their hover state. But what if we want "ghost" buttons instead, that turn into solid colors on hover? In this case, white icons would be invisible for buttons that appear on white backgrounds (and probably look wrong on non-white backgrounds). What we're going to need are two variations of each icon: the white one for the hover state, and one that matches button's border and text color for the non-hover state.

Let's update our button's base CSS to turn it in from a solid button to a ghost button that turns solid on hover. And we'll need to adjust the pseudo-elements for our icons, too, so we can swap them out on hover as well.

.button {
  appearance: none;
  background: none;
  border: 3px solid #d95a2b;
  border-radius: 100em;
  color: #d95a2b;
  cursor: pointer;
  display: inline-block;
  font-size: 18px;
  font-weight: bold;
  line-height: 1;
  padding: 1em calc( 1.5em + 32px ) 0.9em 1.5em;
  position: relative;
  text-align: center;
  text-transform: uppercase;
  transition: 200ms ease-in-out;
  transition-property: background-color, color;

  &:hover,
  &:focus,
  &:active {
    background: #d95a2b;
    color: #fff;
  }
}

Now we need to create our different-colored icons. One possible solution is to add the color variations directly to our map... somehow. We can either add new different-colored icons as additional items in our one-dimensional map, or make our map two-dimensional.

One-Dimensional Map:

$icons: (
  'download-white':  '<svg xmlns="http://www.w3.org/2000/svg" width="30.544" height="25.294" viewBox="0 0 30.544 25.294"><g transform="translate(-991.366 -1287.5)"><path d="M1454.5,1298.922l6.881,6.881-6.881,6.881" transform="translate(2312.404 -157.556) rotate(90)" fill="none" stroke="%23fff" stroke-width="3"/><path d="M8853.866,5633.57v9.724h27.544v-9.724" transform="translate(-7861 -4332)" fill="none" stroke="%23fff" stroke-linejoin="round" stroke-width="3"/><line y2="14" transform="translate(1006.5 1287.5)" fill="none" stroke="%23fff" stroke-width="3"/></g></svg>',
  'download-orange': '<svg xmlns="http://www.w3.org/2000/svg" width="30.544" height="25.294" viewBox="0 0 30.544 25.294"><g transform="translate(-991.366 -1287.5)"><path d="M1454.5,1298.922l6.881,6.881-6.881,6.881" transform="translate(2312.404 -157.556) rotate(90)" fill="none" stroke="%23d95a2b" stroke-width="3"/><path d="M8853.866,5633.57v9.724h27.544v-9.724" transform="translate(-7861 -4332)" fill="none" stroke="%23d95a2b" stroke-linejoin="round" stroke-width="3"/><line y2="14" transform="translate(1006.5 1287.5)" fill="none" stroke="%23d95a2b" stroke-width="3"/></g></svg>',
);

Two-Dimensional Map:

$icons: (
  'download': (
    'white':  '<svg xmlns="http://www.w3.org/2000/svg" width="30.544" height="25.294" viewBox="0 0 30.544 25.294"><g transform="translate(-991.366 -1287.5)"><path d="M1454.5,1298.922l6.881,6.881-6.881,6.881" transform="translate(2312.404 -157.556) rotate(90)" fill="none" stroke="%23fff" stroke-width="3"/><path d="M8853.866,5633.57v9.724h27.544v-9.724" transform="translate(-7861 -4332)" fill="none" stroke="%23fff" stroke-linejoin="round" stroke-width="3"/><line y2="14" transform="translate(1006.5 1287.5)" fill="none" stroke="%23fff" stroke-width="3"/></g></svg>',
    'orange': '<svg xmlns="http://www.w3.org/2000/svg" width="30.544" height="25.294" viewBox="0 0 30.544 25.294"><g transform="translate(-991.366 -1287.5)"><path d="M1454.5,1298.922l6.881,6.881-6.881,6.881" transform="translate(2312.404 -157.556) rotate(90)" fill="none" stroke="%23d95a2b" stroke-width="3"/><path d="M8853.866,5633.57v9.724h27.544v-9.724" transform="translate(-7861 -4332)" fill="none" stroke="%23d95a2b" stroke-linejoin="round" stroke-width="3"/><line y2="14" transform="translate(1006.5 1287.5)" fill="none" stroke="%23d95a2b" stroke-width="3"/></g></svg>',
  ),
);

Either way, this is problematic. By just adding one additional color, we're going to double our maintenance efforts. Need to change the existing download icon with a different one? We need to manually create each color variation to add it to the map. Need a third color? Now you've just tripled your maintenance costs. I'm not even going to get into the code to retrieve values from a multi-dimensional Sass map because that's not going to serve our ultimate goal here. Instead, we're just going to move on.

Enter string replacement

Aside from maps, the utility of Sass in this article comes from how we can use it to make CSS behave more like a programming language. Sass has built-in functions (like map-get(), which we've already seen), and it allows us to write our own.

Sass also has a bunch of string functions built-in, but inexplicably, a string replacement function isn't one of them. That's too bad, as its usefulness is obvious. But all is not lost.

Hugo Giradel gave us a Sass version of str-replace() here on CSS-Tricks in 2014. We can use that here to create one version of our icons in our Sass map, using a placeholder for our color values. Let's add that function to our own code:

@function str-replace( $string, $search, $replace: '' ) {

  $index: str-index( $string, $search );

  @if $index {
    @return str-slice( $string, 1, $index - 1 ) + $replace + str-replace( str-slice( $string, $index + str-length( $search ) ), $search, $replace);
  }

  @return $string;
}

Next, we'll update our original Sass icon map (the one with only the white versions of our icons) to replace the white with a placeholder (%%COLOR%%) that we can swap out with whatever color we call for, on demand.

$icons: (
  'download':    '<svg xmlns="http://www.w3.org/2000/svg" width="30.544" height="25.294" viewBox="0 0 30.544 25.294"><g transform="translate(-991.366 -1287.5)"><path d="M1454.5,1298.922l6.881,6.881-6.881,6.881" transform="translate(2312.404 -157.556) rotate(90)" fill="none" stroke="%%COLOR%%" stroke-width="3"/><path d="M8853.866,5633.57v9.724h27.544v-9.724" transform="translate(-7861 -4332)" fill="none" stroke="%%COLOR%%" stroke-linejoin="round" stroke-width="3"/><line y2="14" transform="translate(1006.5 1287.5)" fill="none" stroke="%%COLOR%%" stroke-width="3"/></g></svg>',
  'external':    '<svg xmlns="http://www.w3.org/2000/svg" width="31.408" height="33.919" viewBox="0 0 31.408 33.919"><g transform="translate(-1008.919 -965.628)"><g transform="translate(1046.174 2398.574) rotate(-135)"><path d="M0,0,7.879,7.879,0,15.759" transform="translate(1025.259 990.17) rotate(90)" fill="none" stroke="%%COLOR%%" stroke-width="3"/><line y2="16.032" transform="translate(1017.516 980.5)" fill="none" stroke="%%COLOR%%" stroke-width="3"/></g><path d="M10683.643,5322.808v10.24h-20.386v-21.215h7.446" transform="translate(-9652.838 -4335)" fill="none" stroke="%%COLOR%%" stroke-width="3"/></g></svg>',
  'caret-right': '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 19.129 34.016"><path d="M1454.5,1298.922l15.947,15.947-15.947,15.947" transform="translate(-1453.439 -1297.861)" fill="none" stroke="%%COLOR%%" stroke-width="3"/></svg>',
);

But if we were going to try and fetch these icons using just our new str-replace() function and Sass' built-in map-get() function, we'd end with something big and ugly. I'd rather tie these two together with one more function that makes calling the icon we want in the color we want as simple as one function with two parameters (and because I'm particularly lazy, we'll even make the color default to white, so we can omit that parameter if that's the color icon we want).

Because we're getting an icon, it's a "getter" function, and so we'll call it get-icon():

@function get-icon( $icon, $color: #fff ) {

  $icon:        map-get( $icons, $icon );
  $placeholder: '%%COLOR%%';

  $data-uri: str-replace( url( $data-svg-prefix + $icon ), $placeholder, $color );

  @return str-replace( $data-uri, '#', '%23' );
}

Remember where we said that browsers won't render data URLs that have octothorpes in them? Yeah, we're str-replace()ing that too so we don't have to remember to pass along "%23" in our color hex codes.

Side note: I have a Sass function for abstracting colors too, but since that's outside the scope of this article, I'll refer you to my get-color() gist to peruse at your leisure.

The result

Now that we have our get-icon() function, let's put it to use. Going back to our button code, we can replace our map-get() function with our new icon getter:

&--download {

  &::before {
    background-image: get-icon( 'download', #d95a2b );
  }

  &::after {
    background-image: get-icon( 'download', #fff ); // The ", #fff" isn't strictly necessary, because white is already our default
  }
}

&--external {

  &::before {
    background-image: get-icon( 'external', #d95a2b );
  }

  &::after {
    background-image: get-icon( 'external' );
  }
}

&--next {

  &::before {
    background-image: get-icon( 'arrow-right', #d95a2b );
  }

  &::after {
    background-image: get-icon( 'arrow-right' );
  }
}

So much easier, isn't it? Now we can call any icon we've defined, with any color we need. All with simple, clean, logical code.

  • We only ever have to declare an SVG in one place.
  • We have a function that gets that icon in whatever color we give it.
  • Everything is abstracted out to a logical function that does exactly what it looks like it will do: get X icon in Y color.

Making it fool-proof

The one thing we're lacking is error-checking. I'm a huge believer in failing silently... or at the very least, failing in a way that is invisible to the user yet clearly tells the developer what is wrong and how to fix it. (For that reason, I should be using unit tests way more than I do, but that's a topic for another day.)

One way we have already reduced our function's propensity for errors is by setting a default color (in this case, white). So if the developer using get-icon() forgets to add a color, no worries; the icon will be white, and if that's not what the developer wanted, it's obvious and easily fixed.

But wait, what if that second parameter isn't a color? As if, the developer entered a color incorrectly, so that it was no longer being recognized as a color by the Sass processor?

Fortunately we can check for what type of value the $color variable is:

@function get-icon( $icon, $color: #fff ) {

  @if 'color' != type-of( $color ) {

    @warn 'The requested color - "' + $color + '" - was not recognized as a Sass color value.';
    @return null;
  }

  $icon:        map-get( $icons, $icon );
  $placeholder: '%%COLOR%%';
  $data-uri:    str-replace( url( $data-svg-prefix + $icon ), $placeholder, $color );

  @return str-replace( $data-uri, '#', '%23' );
}

Now if we tried to enter a nonsensical color value:

&--download {

  &::before {
    background-image: get-icon( 'download', ce-nest-pas-un-couleur );
  }
}

...we get output explaining our error:

Line 25 CSS: The requested color - "ce-nest-pas-un-couleur" - was not recognized as a Sass color value.

...and the processing stops.

But what if the developer doesn't declare the icon? Or, more likely, declares an icon that doesn't exist in the Sass map? Serving a default icon doesn't really make sense in this scenario, which is why the icon is a mandatory parameter in the first place. But just to make sure we are calling an icon, and it is valid, we're going to add another check:

@function get-icon( $icon, $color: #fff ) {

  @if 'color' != type-of( $color ) {

    @warn 'The requested color - "' + $color + '" - was not recognized as a Sass color value.';
    @return null;
  }

  @if map-has-key( $icons, $icon ) {

    $icon:        map-get( $icons, $icon );
    $placeholder: '%%COLOR%%';
    $data-uri:    str-replace( url( $data-svg-prefix + $icon ), $placeholder, $color );

    @return str-replace( $data-uri, '#', '%23' );
  }

  @warn 'The requested icon - "' + $icon + '" - is not defined in the $icons map.';
  @return null;
}

We've wrapped the meat of the function inside an @if statement that checks if the map has the key provided. If so (which is the situation we're hoping for), the processed data URL is returned. The function stops right then and there — at the @return — which is why we don't need an @else statement.

But if our icon isn't found, then null is returned, along with a @warning in the console output identifying the problem request, plus the partial filename and line number. Now we know exactly what's wrong, and when and what needs fixing.

So if we were to accidentally enter:

&--download {

  &::before {
    background-image: get-icon( 'ce-nest-pas-une-icône', #d95a2b );
  }
}

...we would see the output in our console, where our Sass process was watching and running:

Line 32 CSS: The requested icon - "ce-nest-pas-une-icône" - is not defined in the $icons map.

As for the button itself, the area where the icon would be will be blank. Not as good as having our desired icon there, but soooo much better than a broken image graphic or some such.

Conclusion

After all of that, let's take a look at our final, processed CSS:

.button {
  -webkit-appearance: none;
      -moz-appearance: none;
          appearance: none;
  background: none;
  border: 3px solid #d95a2b;
  border-radius: 100em;
  color: #d95a2b;
  cursor: pointer;
  display: inline-block;
  font-size: 18px;
  font-weight: 700;
  line-height: 1;
  padding: 1em calc( 1.5em + 32px ) 0.9em 1.5em;
  position: relative;
  text-align: center;
  text-transform: uppercase;
  transition: 200ms ease-in-out;
  transition-property: background-color, color;
}
.button:hover, .button:active, .button:focus {
  background: #d95a2b;
  color: #fff;
}
.button::before, .button::after {
  background: center / 24px 24px no-repeat;
  border-radius: 100em;
  bottom: 0;
  content: '';
  position: absolute;
  right: 0;
  top: 0;
  width: 48px;
}
.button::after {
  opacity: 0;
  transition: opacity 200ms ease-in-out;
}
.button:hover::after, .button:focus::after, .button:active::after {
  opacity: 1;
}
.button--download::before {
  background-image: url('data:image/svg+xml;utf-8,<svg xmlns="http://www.w3.org/2000/svg" width="30.544" height="25.294" viewBox="0 0 30.544 25.294"><g transform="translate(-991.366 -1287.5)"><path d="M1454.5,1298.922l6.881,6.881-6.881,6.881" transform="translate(2312.404 -157.556) rotate(90)" fill="none" stroke="%23d95a2b" stroke-width="3"/><path d="M8853.866,5633.57v9.724h27.544v-9.724" transform="translate(-7861 -4332)" fill="none" stroke="%23d95a2b" stroke-linejoin="round" stroke-width="3"/><line y2="14" transform="translate(1006.5 1287.5)" fill="none" stroke="%23d95a2b" stroke-width="3"/></g></svg>');
}
.button--download::after {
  background-image: url('data:image/svg+xml;utf-8,<svg xmlns="http://www.w3.org/2000/svg" width="30.544" height="25.294" viewBox="0 0 30.544 25.294"><g transform="translate(-991.366 -1287.5)"><path d="M1454.5,1298.922l6.881,6.881-6.881,6.881" transform="translate(2312.404 -157.556) rotate(90)" fill="none" stroke="%23fff" stroke-width="3"/><path d="M8853.866,5633.57v9.724h27.544v-9.724" transform="translate(-7861 -4332)" fill="none" stroke="%23fff" stroke-linejoin="round" stroke-width="3"/><line y2="14" transform="translate(1006.5 1287.5)" fill="none" stroke="%23fff" stroke-width="3"/></g></svg>');
}
.button--external::before {
  background-image: url('data:image/svg+xml;utf-8,<svg xmlns="http://www.w3.org/2000/svg" width="31.408" height="33.919" viewBox="0 0 31.408 33.919"><g transform="translate(-1008.919 -965.628)"><g transform="translate(1046.174 2398.574) rotate(-135)"><path d="M0,0,7.879,7.879,0,15.759" transform="translate(1025.259 990.17) rotate(90)" fill="none" stroke="%23d95a2b" stroke-width="3"/><line y2="16.032" transform="translate(1017.516 980.5)" fill="none" stroke="%23d95a2b" stroke-width="3"/></g><path d="M10683.643,5322.808v10.24h-20.386v-21.215h7.446" transform="translate(-9652.838 -4335)" fill="none" stroke="%23d95a2b" stroke-width="3"/></g></svg>');
}
.button--external::after {
  background-image: url('data:image/svg+xml;utf-8,<svg xmlns="http://www.w3.org/2000/svg" width="31.408" height="33.919" viewBox="0 0 31.408 33.919"><g transform="translate(-1008.919 -965.628)"><g transform="translate(1046.174 2398.574) rotate(-135)"><path d="M0,0,7.879,7.879,0,15.759" transform="translate(1025.259 990.17) rotate(90)" fill="none" stroke="%23fff" stroke-width="3"/><line y2="16.032" transform="translate(1017.516 980.5)" fill="none" stroke="%23fff" stroke-width="3"/></g><path d="M10683.643,5322.808v10.24h-20.386v-21.215h7.446" transform="translate(-9652.838 -4335)" fill="none" stroke="%23fff" stroke-width="3"/></g></svg>');
}
.button--next::before {
  background-image: url('data:image/svg+xml;utf-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 19.129 34.016"><path d="M1454.5,1298.922l15.947,15.947-15.947,15.947" transform="translate(-1453.439 -1297.861)" fill="none" stroke="%23d95a2b" stroke-width="3"/></svg>');
}
.button--next::after {
  background-image: url('data:image/svg+xml;utf-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 19.129 34.016"><path d="M1454.5,1298.922l15.947,15.947-15.947,15.947" transform="translate(-1453.439 -1297.861)" fill="none" stroke="%23fff" stroke-width="3"/></svg>');
}

Yikes, still ugly, but it's ugliness that becomes the browser's problem, not ours.

I've put all this together in CodePen for you to fork and experiment. The long goal for this mini-project is to create a PostCSS plugin to do all of this. This would increase the availability of this technique to everyone regardless of whether they were using a CSS preprocessor or not, or which preprocessor they're using.

"If I have seen further it is by standing on the shoulders of Giants."
– Isaac Newton, 1675

Of course we can't talk about Sass and string replacement and (especially) SVGs without gratefully acknowledging the contributions of the others who've inspired this technique.

The post Creating a Maintainable Icon System with Sass appeared first on CSS-Tricks.



from CSS-Tricks https://ift.tt/346exQS
via IFTTT

Can you rotate the cursor in CSS?

Kinda! There is no simple or standard way to do it, but it's possible. You can change the cursor to different built-in native versions with CSS with the cursor property, but that doesn't help much here. You can also use that property to set a static image as the cursor. But again that doesn't help much because you can't rotate it once it's there.

The trick is to totally hide the cursor with cursor: none; and replace it with your own element.

Here's an example of that:

See the Pen
Move fake mouse with JavaScript
by Chris Coyier (@chriscoyier)
on CodePen.

That's not rotating yet. But now that the cursor is just some element on the page, CSS's transform: rotate(); is fully capable of that job. Some math is required.

I'll leave that to Aaron Iker's really fun demo:

See the Pen
Mouse cursor pointing to cta
by Aaron Iker (@aaroniker)
on CodePen.

Is this an accessibility problem? Something about it makes me think it might be. It's a little extra motion where you aren't expecting it and perhaps a little disorienting when an element you might rely on for a form of stability starts moving on you. It's really only something you'd do for limited-use novelty and while respecting the prefers-reduced-motion. You could also keep the original cursor and do something under it, as Jackson Callaway has done here.

The post Can you rotate the cursor in CSS? appeared first on CSS-Tricks.



from CSS-Tricks https://ift.tt/2UfTLdk
via IFTTT

How to Use Keywords in Your Blogging Strategy

Monday, 26 August 2019

Reusable Popovers to Add a Little Pop

Lead Volume vs. Lead Quality By RuthBurrReedy

Posted by RuthBurrReedy

Ruth Burr Reedy is an SEO and online marketing consultant and speaker and the Vice President of Strategy at UpBuild, a technical marketing agency specializing in SEO, web analytics, and conversion rate optimization. This is the first post in a recurring monthly series and we're excited! 


When you’re onboarding a new SEO client who works with a lead generation model, what do you do?

Among the many discovery questions you ask as you try to better understand your client’s business, you probably ask them, “What makes a lead a good lead?” That is, what are the qualities that make a potential customer more likely to convert to sale?

A business that’s given some thought to their ideal customer might send over some audience personas; they might talk about their target audience in more general terms. A product or service offering might be a better fit for companies of a certain size or budget, or be at a price point that requires someone at a senior level (such as a Director, VP, or C-level employee) to sign off, and your client will likely pass that information on to you if they know it. However, it’s not uncommon for these sorts of onboarding conversations to end with the client assuring you: “Just get us the leads. We’ll make the sales.”

Since SEO agencies often don’t have access to our clients’ CRM systems, we’re often using conversion to lead as a core KPI when measuring the success of our campaigns. We know enough to know that it’s not enough to drive traffic to a site; that traffic has to convert to become valuable. Armed with our clients’ assurances that what they really need is more leads, we dive into understanding the types of problems that our client’s product is designed to solve, the types of people who might have those problems, and the types of resources they might search for as they tend to solve those problems. Pretty soon, we’ve fixed the technical problems on our client’s site, helped them create and promote robust resources around their customers’ problems, and are watching the traffic and conversions pour in. Feels pretty good, right?

Unfortunately, this is often the point in a B2B engagement where the wheels start to come off the bus. Looking at the client’s analytics, everything seems great — traffic is up, conversions are also up, the site is rocking and rolling. Talk to the client, though, and you’ll often find that they’re not happy.

“Leads are up, but sales aren’t,” they might say, or “yes, we’re getting more leads, but they’re the wrong leads.” You might even hear that the sales team hates getting leads from SEO, because they don’t convert to sale, or if they do, only for small-dollar deals.

What happened?

At this point, nobody could blame you for becoming frustrated with your client. After all, they specifically said that all they cared about was getting more leads — so why aren’t they happy? Especially when you’re making the phone ring off the hook?

A key to client retention at this stage is to understand things from your client’s perspective — and particularly, from their sales team’s perspective. The important thing to remember is that when your client told you they wanted to focus on lead volume, they weren’t lying to you; it’s just that their needs have changed since having that conversation.

Chances are, your new B2B client didn’t seek out your services because everything was going great for them. When a lead gen company seeks out a new marketing partner, it’s typically because they don’t have enough leads in their pipeline. “Hungry for leads” isn’t a situation any sales team wants to be in: every minute they spend sitting around, waiting for leads to come in is a minute they’re not spending meeting their sales and revenue targets. It’s really stressful, and could even mean their jobs are at stake. So, when they brought you on, is it any wonder their first order of business was “just get us the leads?” Any lead is better than no lead at all.

Now, however, you’ve got a nice little flywheel running, bringing new leads to the sales team’s inbox all the livelong day, and the team has a whole new problem: talking to leads that they perceive as a waste of their time. 

A different kind of lead

Lead-gen SEO is often a top-of-funnel play. Up to the point when the client brought you on, the leads coming in were likely mostly from branded and direct traffic — they’re people who already know something about the business, and are closer to being ready to buy. They’re already toward the middle of the sales funnel before they even talk to a salesperson.

SEO, especially for a business with any kind of established brand, is often about driving awareness and discovery. The people who already know about the business know how to get in touch when they’re ready to buy; SEO is designed to get the business in front of people who may not already know that this solution to their problems exists, and hopefully sell it to them.

A fledgling SEO campaign should generate more leads, but it also often means a lower percentage of good leads. It’s common to see conversion rates, both from session to lead and from lead to sale, go down during awareness-building marketing. The bet you’re making here is that you’re driving enough qualified traffic that even as conversion rates go down, your total number of conversions (again, both to lead and to sale) is still going up, as is your total revenue.

So, now you’ve brought in the lead volume that was your initial mandate, but the leads are at a different point in their customer journey, and some of them may not be in a position to buy at all. This can lead to the perception that the sales team is wasting all of their time talking to people who will never buy. Since it takes longer to close a sale than it does to disqualify a lead, the increase in less-qualified leads will become apparent long before a corresponding uptick in sales — and since these leads are earlier in their customer journey, they may take longer to convert to sale than the sales team is used to.

At this stage, you might ask for reports from the client’s CRM, or direct access, so you can better understand what their sales team is seeing. To complicate matters further, though, attribution in most CRMs is kind of terrible. It’s often very rigid; the CRM’s definitions of channels may not match those of Google Analytics, leading to discrepancies in channel numbers; it may not have been set up correctly in the first place; it’s opaque, often relying on “secret sauce” to attribute sales per channel; and it still tends to encourage salespeople to focus on the first or last touch. So, if SEO is driving a lot of traffic that later converts to lead as Direct, the client may not even be aware that SEO is driving those leads.

None of this matters, of course, if the client fires you before you have a chance to show the revenue that SEO is really driving. You need to show that you can drive lead quality from the get-go, so that by the time the client realizes that lead volume alone isn’t what they want, you’re prepared to have that conversation.

Resist the temptation to qualify at the keyword level

When a client is first distressed about lead quality, It’s tempting to do a second round of keyword research and targeting to try to dial in their ideal decision-maker; in fact, they may specifically ask you to do so. Unfortunately, there’s not a great way to do that at the query level. Sure, enterprise-level leads might be searching “enterprise blue widget software,” but it’s difficult to target that term without also targeting “blue widget software,” and there’s no guarantee that your target customers are going to add the “enterprise” qualifier. Instead, use your ideal users’ behaviors on the site to determine which topics, messages, and calls to action resonate with them best — then update site content to better appeal to that target user

Change the onboarding conversation

We’ve already talked about asking clients, “what makes a lead a good lead?” I would argue, though, that a better question is “how do you qualify leads?” 

Sit down with as many members of the sales team as you can (since you’re doing this at the beginning of the engagement — before you’re crushing it driving leads, they should have a bit more time to talk to you) and ask how they decide which leads to focus on. If you can, ask to listen in on a sales call or watch over their shoulder as they go through their new leads. 

At first, they may talk about how lead qualification depends on a complicated combination of factors. Often, though, the sales team is really making decisions about who’s worth their time based on just one or two factors (usually budget or title, although it might also be something like company size). Try to nail them down on their most important one.

Implement a lead scoring model

There are a bunch of different ways to do this in Google Analytics or Google Tag Manager (Alex from UpBuild has a writeup of our method, here). Essentially, when a prospect submits a lead conversion form, you’ll want to:

  • Look for the value of your “most important” lead qualification factor in the form,
  • And then fire an Event “scoring” the conversion in Google Analytics as e.g. Hot, Warm, or Cold.

This might look like detecting the value put into an “Annual Revenue” field or drop-down and assigning a score accordingly; or using RegEx to detect when the “Title” field contains Director, Vice President, or CMO and scoring higher. I like to use the same Event Category for all conversions from the same form, so they can all roll up into one Goal in Google Analytics, then using the Action or Label field to track the scoring data. For example, I might have an Event Category of “Lead Form Submit” for all lead form submission Events, then break out the Actions into “Hot Lead — $5000+,” “Warm Lead — $1000–$5000,” etc.

Note: Don’t use this methodology to pass individual lead information back into Google Analytics. Even something like Job Title could be construed as Personally Identifiable Information, a big no-no where Google Analytics is concerned. We’re not trying to track individual leads’ behaviors, here; we’re trying to group conversions into ranges.

How to use scored leads

Drive the conversation around sales lifecycle. The bigger the company and the higher the budget, the more time and touches it will take before they’re ready to even talk to you. This means that with a new campaign, you’ll typically see Cold leads coming in first, then Hot and Warm trickling in overtime. Capturing this data allows you to set an agreed-upon time in the future when you and the client can discuss whether this is working, instead of cutting off campaigns/strategies before they have a chance to perform (it will also allow you to correctly set Campaign time-out in GA to reflect the full customer journey).

Allocate spend. How do your sales team’s favorite leads tend to get to the site? Does a well-timed PPC or display ad after their initial visit drive them back to make a purchase? Understanding the channels your best leads use to find and return to the site will help your client spend smarter.

Create better-targeted content. Many businesses with successful blogs will have a post or two that drives a great deal of traffic, but almost no qualified leads. Understanding where your traffic goals don’t align with your conversion goals will keep you from wasting time creating content that ranks, but won’t make money.

Build better links. The best links don’t just drive “link equity,” whatever that even means anymore — they drive referral traffic. What kinds of websites drive lots of high-scoring leads, and where else can you get those high-quality referrals?

Optimize for on-page conversion. How do your best-scoring leads use the site? Where are the points in the customer journey where they drop off, and how can you best remove friction and add nurturing? Looking at how your Cold leads use the site will also be valuable — where are the points on-site where you can give them information to let them know they’re not a fit before they convert?

The earlier in the engagement you start collecting this information, the better equipped you’ll be to have the conversation about lead quality when it rears its ugly head.


Sign up for The Moz Top 10, a semimonthly mailer updating you on the top ten hottest pieces of SEO news, tips, and rad links uncovered by the Moz team. Think of it as your exclusive digest of stuff you don't have time to hunt down but want to read!



from The Moz Blog https://ift.tt/2HsjgCR
via IFTTT

Friday, 23 August 2019

Jeremy Keith – Building the Web

I really enjoyed this interview with Jeremy Keith on the state of the web, how things have changed in recent years and why he’s a mix of optimistic and nervous for the future.

One thing that caught my attention during the interview more than anything was where Jeremy started discussing how folks think that websites are pretty crummy in general. This reminded me that I cannot count the number of times when someone has said to me “ah, I can’t view this website on my phone.”

We have websites that aren’t responsive! We have websites that litter the UI with advertisements and modals! And we have websites that are slow as all heck just when we need them the most!

Of course folks are going to start complaining about the web and working around them if they find that this is the case. I’ll even catch myself sending an email to myself when I know that the mobile experience is going to be crummy. Or I’ll Instapaper something because the design of the website is particularly difficult to read. Remember, Reader Mode is the button to beat.

My quick thought on this is that we shouldn’t become sour and pessimistic. We should roll up our sleeves and get to work because clearly there’s much left to do.

Direct Link to ArticlePermalink

The post Jeremy Keith – Building the Web appeared first on CSS-Tricks.



from CSS-Tricks https://www.youtube.com/watch?v=b2PaxNwr9nI
via IFTTT

Multiplayer Tic Tac Toe with GraphQL

Goodbye, Generic SEO Audit – Say Hello to Customization & Prioritization - Whiteboard Friday

Thursday, 22 August 2019

Weekly Platform News: Improving UX on Slow Connections, a Tip for Writing Alt Text and a Polyfill for the HTML loading attribute

In this week's roundup, how to determine a slow connection, what we should put into alt text for images, and a new polyfill for the HTML loading attribute, plus more.

Detecting users on slow connections

Algolia is using the Network Information API (see the API’s Chrome status page) to detect users on slow connections — about 9% of their users — and make the following adjustments to ensure a good user experience:

  • increase the request timeout when the user performs a search query (a static timeout can cause a false positive for users on slow connections)
  • show a notification to the user while they’re waiting for search results (e.g., "You are on a slow connection. This might take a while.")
  • request fewer search results to decrease the total response size
  • debounce queries (e.g., don’t send queries at every keystroke)
navigator.connection.addEventListener("change", () => {
  // effective round-trip time estimate in ms
  let rtt = navigator.connection.rtt;

  // effective connection type
  let effectiveType = navigator.connection.effectiveType;

  if (rtt > 500 || effectiveType.includes("2g")) {
    // slow connection
  }
});

(via Jonas Badalic)

Alt text should communicate the main point

The key is to describe what you want your audience to get out of the image rather than a simple description of what the image is.

<!-- BEFORE -->
<img alt="Graph showing the use of the phrase "Who you
          gonna call?" in popular media over time.">

<!-- AFTER -->
<img alt="Graph illustrating an 800% increase in the use
          of the phrase "Who you gonna call?" in popular
          media after the release of Ghostbusters on
          June 7th, 1984.">

(via Caitlin Cashin)

In other news...

  • There is a new polyfill for the HTML loading attribute that is used by wrapping the images and iframes that you want to lazy-load in <noscript> elements (via Maximilian Franzke).
  • WeChat, the Chinese multi-purpose app with over one billion monthly active users, hosts over one million "mini programs" that are built in a very similar fashion to web apps (essentially CSS and JavaScript) (via Thomas Steiner).
  • Microsoft has made 24 new (online) voices from 21 different languages available to the Speech Synthesis API in the preview version of Edge ("these voices are the most natural-sounding voices available today") (via Scott Low)

Read more news in my new, weekly Sunday issue. Visit webplatform.news for more information.

The post Weekly Platform News: Improving UX on Slow Connections, a Tip for Writing Alt Text and a Polyfill for the HTML loading attribute appeared first on CSS-Tricks.



from CSS-Tricks https://ift.tt/2L0Vfnm
via IFTTT

Advice for Technical Writing

Getting Netlify Large Media Going

Navbar Nudging on @keyframers

I got to be the featured guest over on The Keyframers the other day. We looked at a Dribbble shot by Björgvin Pétur Sigurjónsson and then slowly built it, taking some purposeful detours along the way to discuss various tech.

We start by considering doing it entirely in CSS, then go for some light JavaScript to alter some data attributes as state, then ultimately end up using flipping.

This is where we ended up:

See the Pen
Navbar Nudging w/ Chris Coyier | Three Person Collaborative Animation Tutorial | @keyframers 2.14.0
by @keyframers (@keyframers)
on CodePen.

The video:

(My audio goes from terrible to good at about 12 minutes.)

Other takes!

The post Navbar Nudging on @keyframers appeared first on CSS-Tricks.



from CSS-Tricks https://ift.tt/2Zd7d7t
via IFTTT

Wednesday, 21 August 2019

Using requestAnimationFrame with React Hooks

Animating with requestAnimationFrame should be easy, but if you haven’t read React’s documentation thoroughly then you will probably run into a few things that might cause you a headache. Here are three gotcha moments I learned the hard way.

TLDR: Pass an empty array as a second parameter for useEffect to avoid it running more than once and pass a function to your state’s setter function to make sure you always have the correct state. Also, use useRef for storing things like the timestamp and the request’s ID.

useRef is not only for DOM references

There are three ways to store variables within functional components:

  1. We can define a simple const or let whose value will always be reinitialized with every component re-rendering.
  2. We can use useState whose value persists across re-renderings, and if you change it, it will also trigger re-rendering.
  3. We can use useRef.

The useRef hook is primarily used to access the DOM, but it’s more than that. It is a mutable object that persists a value across multiple re-renderings. It is really similar to the useState hook except you read and write its value through its .current property, and changing its value won’t re-render the component.

For instance, the example below will always show 5 even if the component is re-rendered by its parent.

function Component() {
  let variable = 5;

  setTimeout(() => {
    variable = variable + 3;
  }, 100)

  return <div>{variable}</div>
}

...whereas this one will keep increasing the number by three and keeps re-rendering even if the parent does not change.

function Component() {
  const [variable, setVariable] = React.useState(5);

  setTimeout(() => {
    setVariable(variable + 3);
  }, 100)

  return <div>{variable}</div>
}

And finally, this one returns five and won’t re-render. However, if the parent triggers a re-render then it will have an increased value every time (assuming the re-render happened after 100 milliseconds).

function Component() {
  const variable = React.useRef(5);

  setTimeout(() => {
    variable.current = variable.current + 3;
  }, 100)

  return <div>{variable.current}</div>
}

If we have mutable values that we want to remember at the next or later renders and we don’t want them to trigger a re-render when they change, then we should use useRef. In our case, we will need the ever-changing request animation frame ID at cleanup, and if we animate based on the the time passed between cycles, then we need to remember the previous animation’s timestamp. These two variables should be stored as refs.

The side effects of useEffect

We can use the useEffect hook to initialize and cleanup our requests, though we want to make sure it only runs once; otherwise it’s really easy to end up doubling the amount of the animation frame requests with every animation cycle. Here’s a bad example:

function App() {
  const [state, setState] = React.useState(0)

  const requestRef = React.useRef()
  
  const animate = time => {
    // Change the state according to the animation
    requestRef.current = requestAnimationFrame(animate);
  }
    
  // DON’T DO THIS
  React.useEffect(() => {
    requestRef.current = requestAnimationFrame(animate);
    return () => cancelAnimationFrame(requestRef.current);
  });
  
  return <div>{state}</div>;
}

Why is it bad? If you run this, the useEffect will trigger the animate function that will both change the state and request a new animation frame. It sounds good, except that the state change will re-render the component by running the whole function again including the useEffect hook that will spin up a new request in parallel with the one that was already requested by the animate function in the previous cycle. This will ultimately end up in doubling our animation frame requests each cycle. Ideally, we only have 1 at a time. In the case above, if we assume 60 frame per second then we’ll have 1,152,921,504,606,847,000 animation frame requests in parallel after only one second.

To make sure the useEffect hook runs only once, we can pass an empty array as a second argument to it. Passing an empty array has a side-effect though, which avoids us from having the correct state during animation. The second argument is a list of changing values that the effect needs to react to. We don’t want to react to anything — we only want to initialize the animation — hence we have the empty array. But React will interpret this in a way that means this effect doesn’t have to be up to date with the state. And that includes the animate function because it was originally called from the effect. As a result, if we try to get the value of the state in the animate function, it will always be the initial value. If we want to change the state based on its previous value and the time passed, then it probably won’t work.

function App() {
  const [state, setState] = React.useState(0)

  const requestRef = React.useRef()
  
  const animate = time => {
    // The 'state' will always be the initial value here
    requestRef.current = requestAnimationFrame(animate);
  }
    
  React.useEffect(() => {
    requestRef.current = requestAnimationFrame(animate);
    return () => cancelAnimationFrame(requestRef.current);
  }, []); // Make sure the effect runs only once
  
  return <div>{state}</div>;
}

The state’s setter function also accepts a function

There’s a way to use our latest state even if the useEffect hook locked our state to its initial value. The setter function of the useState hook can also accept a function. So instead of passing a value based on the current state as you probably would do most of the time:

setState(state + delta)

... you can also pass on a function that receives the previous value as a parameter. And, yes, that’s going to return the correct value even in our situation:

setState(prevState => prevState + delta)

Putting it all together

Here’s a simple example to wrap things up. We’re going to put all of the above together to create a counter that counts up to 100 then restarts from the beginning. Technical variables that we want to persist and mutate without re-rendering the whole component are stored with useRef. We made sure useEffect only runs once by passing an empty array as its second parameter. And we mutate the state by passing on a function to the setter of useState to make sure we always have the correct state.

See the Pen
Using requestAnimationFrame with React hooks
by Hunor Marton Borbely (@HunorMarton)
on CodePen.

The post Using requestAnimationFrame with React Hooks appeared first on CSS-Tricks.



from CSS-Tricks https://ift.tt/2KMJthJ
via IFTTT

Other Ways to SPAs

That rhymed lolz.

I mentioned on a podcast the other day that I sorta think WordPress should ship with Turbolinks. It's a rather simple premise:

  1. Build a server-rendered site.
  2. Turbolinks intercepts clicks on same-origin links.
  3. It uses AJAX for the HTML of the new page and replaces the current page with the new one.

In other words, turning a server-rendered app into "Single Page App" (SPA) by way of adding this library.

Why bother? It can be a little quicker. Full page refreshes can feel slow compared to an SPA. Turbolinks is kinda "old" technology, but it's still perfectly useful. In fact, Starr Horne recently wrote a great blog post about migrating to it at Honeybadger:

Honeybadger isn't a single page app, and it probably won't ever be. SPAs just don't make sense for our technical requirements. Take a look:

  • Our app is mostly about displaying pages of static information.
  • We crunch a lot of data to generate a single error report page.
  • We have a very small team of four developers, and so we want to keep our codebase as small and simple as possible.

... There's an approach we've been using for years that lets us have our cake and eat it too ... and its big idea is that you can get SPA-like speed without all the JavaScript.

That's what I mean about WordPress. It's very good that it's server-rendered by default, but it could also benefit from SPA stuff with a simple approach like Turbolinks. You could always add it on your own though.

Just leaving your server-rendered site isn't a terrible thing. If you keep the pages light and resources cached, you're probably fine.

Chrome has started some new ideas:

I don't doubt this server-rendered but enhance-into-SPA is what has helped popularize approaches like Next and Gatsby.

I don't want to discount the power of a "real" SPA approach. The network is the main offender for slow websites, so if an app is architected to shoot across relatively tiny bits of data (rather relatively heavy huge chunks of HTML) and then calculate the smallest amount of the DOM it can re-render and do that, then that's pretty awesome. Well, that is, until the bottleneck becomes JavaScript itself.

It's just unfortunate that an SPA approach is often done at the cost of doing no server-side rendering at all. And similarly unfortunate is that the cost of "hydrating" a server-rendered app to become an SPA comes at the cost of tying up the main thread in JavaScript.

Damned if you do. Damned if you don't.

Fortunately, there is a spectrum of rendering choices for choosing an appropriate architecture.

The post Other Ways to SPAs appeared first on CSS-Tricks.



from CSS-Tricks https://ift.tt/31ULwGc
via IFTTT

FAQ, HowTo, and Q&A: Using New Schema Types to Create Interactive Rich Results

Tuesday, 20 August 2019

Fresh Features & Functionalities: A Six Month Look back at What’s New in Moz Pro

Let’s Build a JAMstack E-Commerce Store with Netlify Functions

Lazy load embedded YouTube videos

This is a very clever idea via Arthur Corenzan. Rather than use the default YouTube embed, which adds a crapload of resources to a page whether the user plays the video or not, use the little tiny placeholder webpage that is just an image you can click that is linked to the YouTube embed.

It still behaves essentially exactly the same: click, play video in place.

The trick is rooted in srcdoc, a feature of <iframe> where you can put the entire contents of an HTML document in the attribute. It's like inline styling but an inline-entire-documenting sort of thing. I've used it in the past when I embedded MailChimp-created newsletters on this site. I'd save the email into the database as a complete HTML document, retrieve it as needed, and chuck it into an <iframe> with srcdoc.

Arthur credits Remy for a tweak to get it working in IE 11 and Adrian for some accessibility tweaks.

I also agree with Hugh in the comments of that post. Now that native lazy loading has dropped in Chrome (see our coverage) we might as well slap loading="lazy" on there too, as that will mean no requests at all if it renders out of viewport.

I'll embed a demo here too:

See the Pen
Lazy Loaded YouTube Video
by Chris Coyier (@chriscoyier)
on CodePen.

Direct Link to ArticlePermalink

The post Lazy load embedded YouTube videos appeared first on CSS-Tricks.



from CSS-Tricks https://ift.tt/2M5iiQP
via IFTTT

TRCREATIVE gets creative for Nantwich Food Festival

The design agency is proud to support this non for profit organisation once again as it prepares to hold its annual food and drink celebration in the charming market town of Nantwich

 

CHESHIRE, AUGUST 2019 It is that delicious time of year again. Nantwich Food Festival is about to take over the town centre of Nantwich with a mouthwatering celebration of food and drink. To mark the occasion, TRCREATIVE has created a bumper 60-page festival brochure packed with information on activities, food demonstrations and stalls so you can plan the long weekend (30th August to 1st September) to perfection.

 

“The food festival continues to grow each year with an increasing amount of visitors drawn to this hugely popular event,” says TRCREATIVE Creative Director Lynsey Edwards. “We’re proud to support our local non for profit by creating the official brochure that celebrates a passion for good food and drink.”

 

This free event is for everyone to enjoy. Plan your weekend in advance and pick up the festival brochure from Nantwich town centre over the two weekends prior to the festival. This will give you a taste of what’s to come. Look forward to mouthwatering eats and treats, local produce, artisan products and the tastiest dishes from around the world. Celebrity chefs will also wow the crowd with live demonstrations.

 

Foodies, families and anyone with great taste will love the welcoming atmosphere, entertainment, music and, of course, the finest food and drink. We look forward to seeing you at the Nantwich Food Festival, Friday 30th August to Sunday 1st September 2019. Don’t forget to pick up your brochure beforehand!



from TRCREATIVE https://ift.tt/2PeZmRY
via IFTTT

Monday, 19 August 2019

Using rel=”preconnect” to establish network connections early and increase performance

Bounce Element Around Viewport in CSS

Let's say you were gonna bounce an element all around a screen, sorta like an old school screensaver or Pong or something.

You'd probably be tracking the X location of the element, increasing or decreasing it in a time loop and — when the element reached the maximum or minimum value — it would reverse direction. Then do that same thing with the Y location and you've got the effect we're after. Simple enough with some JavaScript and math.

Here's The Coding Train explaining it clearly:

Here's a canvas implementation. It's Pong so it factors in paddles and is slightly more complicated, but the basic math is still there:

See the Pen
Pong
by Joseph Gutierrez (@DerBaumeister)
on CodePen.

But what if we wanted to do this purely in CSS? We could write @keyframes that move the transform or left/top properties... but what values would we use? If we're trying to bounce around the entire screen (viewport), we'd need to know the dimensions of the screen and then use those values. But we never know that exact size in CSS.

Or do we?

CSS has viewport units, which are based on the size of the entire viewport. Plus, we've got calc() and we presumably know the size of our own element.

That's the clever root of Scott Kellum's demo:

See the Pen
Codepen screensaver
by Scott Kellum (@scottkellum)
on CodePen.

The extra tricky part is breaking the X animation and the Y animation apart into two separate animations (one on a parent and one on a child) so that, when the direction reverses, it can happen independently and it looks more screensaver-like.

<div class="el-wrap x">
  <div class="el y"></div>
</div>
:root {
  --width: 300px;
  --x-speed: 13s;
  --y-speed: 7s;
  --transition-speed: 2.2s;
}

.el { 
  width: var(--width);
  height: var(--width);
}

.x {
  animation: x var(--x-speed) linear infinite alternate;
}
.y {
  animation: y var(--y-speed) linear infinite alternate;
}

@keyframes x {
  100% {
    transform: translateX(calc(100vw - var(--width)));
  }
}
@keyframes y {
  100% {
    transform: translateY(calc(100vh - var(--width)));
  }
}

I stole that idea, and added some blobbiness and an extra element for this little demo:

See the Pen
Morphing Blogs with `border-radius`
by Chris Coyier (@chriscoyier)
on CodePen.

The post Bounce Element Around Viewport in CSS appeared first on CSS-Tricks.



from CSS-Tricks https://ift.tt/2ZeqNiB
via IFTTT

Can you view print stylesheets applied directly in the browser?

Friday, 16 August 2019

Draggin’ and Droppin’ in React

Accessibility and web performance are not features, they’re the baseline

This week I’ve been brooding about web performance and accessibility. It all began when Ethan Marcotte made a lot of great notes about the accessibility issues that are common with AMP:

In the recordings above, I’m trying to navigate through the AMP Story. And as I do, VoiceOver describes a page that’s impossible to understand: the arrows to go back or forward are simply announced as “button”; most images are missing text equivalents, which is why the screen reader spells out each and every character of their filenames; and when a story’s content is visible on screen, it’s almost impossible to access. I’d like to say that this one AMP Story was an outlier, but each of the nine demos listed on the AMP Stories website sound just as incomprehensible in VoiceOver.

Ethan continues to argue that these issues are so common in AMP that accessibility must not be a priority at all:

Since the beginning, Google has insisted AMP is the best solution for the web’s performance problem. And Google’s used its market dominance to force publishers to adopt the framework, going so far as to suggest that AMP’s the only format you need to publish pages on the web. But we’ve reached a point where AMP may “solve” the web’s performance issues by supercharging the web’s accessibility problem, excluding even more people from accessing the content they deserve.

I’ve been thinking a lot about this lately — about how accessibility work is often seen as an additional feature that can be tacked onto a project later — rather than accessibility work being a core principle or standard of working on the web.

And I’ve seen this sentiment expressed time and time again, in the frameworks, on Twitter, in the design process, in the development process, and so much so that arguing about the importance of accessibility can get pretty exhausting. Because at some point we’re not arguing about the importance of accessibility but the importance of front-end development itself as a series of worthy skills to have. Skills that can’t be replaced.

Similarly, this post by Craig Mod, on why software should be lightning fast, had me thinking along the same lines:

I love fast software. That is, software speedy both in function and interface. Software with minimal to no lag between wanting to activate or manipulate something and the thing happening. Lightness.

Later in the piece, Mod describes fast software as being the very definition of good software and argues that every action on a computer — whether that’s a website or an app — should feel as if you’re moving without any latency whatsoever. And I couldn’t agree more; every loading screen and wait time is in some degree a mark of failure.

Alex Russell made a similar point not so long ago when he looked at the performance of mobile phones and examined how everyone experiences the web in a very different way:

The takeaway here is that you literally can't afford desktop or iPhone levels of JS if you're trying to make good web experiences for anyone but the world's richest users, and that likely means re-evaluating your toolchain.

I’m sort of a jerk when it comes to this stuff. I don’t think a website can be good until it’s fast. The kind of fast that takes your breath away. As fast as human thought, or even faster. And so my point here is that web performance isn’t something we should aspire to, it should be the standard. The status quo. The baseline that our work is judged by. It ought to be un-shippable until the thing is fast.

The good news is that it’s easier than ever to ship a website with these base requirements of unparalleled speed and accessibility! We have Page Speed Insights, and Web Page Test, not to mention the ability to have Lighthouse perform audits with every commit in GitHub automatically as we work. Ire Aderinokun showed us how to do this not so long ago by setting up a performance budget and learning how to stick to it.

The tools to make our websites fast and accessible are here but we’re not using them. And that’s what makes me mad.


While I’m on this rant — and before I get off my particularly high horse — I think it’s important to make note of Deb Chachra’s argument that “any sufficiently advanced negligence is indistinguishable from malice.” With that in mind, it’s not just bad software design and development if a website is slow. Performance and accessibility aren’t features that can linger at the bottom of a Jira board to be considered later when it’s convenient.

Instead we must start to see inaccessible and slow websites for what they are: a form of cruelty. And if we want to build a web that is truly a World Wide Web, a place for all and everyone, a web that is accessible and fast for as many people as possible, and one that will outlive us all, then first we must make our websites something else altogether; we must make them kind.

The post Accessibility and web performance are not features, they’re the baseline appeared first on CSS-Tricks.



from CSS-Tricks https://ift.tt/2PfrG79
via IFTTT

5 Common Objections to SEO (& How to Respond) - Whiteboard Friday

Wednesday, 14 August 2019

Staggered CSS Transitions

Let's say you wanted to move an element on :hover for a fun visual effect.

@media (hover: hover) {
  .list--item {
    transition: 0.1s;
    transform: translateY(10px);
  }
  .list--item:hover,
  .list--item:focus {
    transform: translateY(0);
  }
}

Cool cool. But what if you had several list items, and you wanted them all to move on hover, but each one offset with staggered timing?

The trick lies within transition-delay and applying a slightly different delay to each item. Let's select each list item individually and apply different delays. In this case, we'll select an internal span just for fun.

@media (hover: hover) {
  .list li a span {
    transform: translateY(100px);
    transition: 0.2s;
  }
  .list:hover span {
    transform: translateY(0);
  }
  .list li:nth-child(1) span {
    transition-delay: 0.0s;
  }
  .list li:nth-child(2) span {
    transition-delay: 0.05s;
  }
  .list li:nth-child(3) span {
    transition-delay: 0.1s;
  }
  .list li:nth-child(4) span {
    transition-delay: 0.15s;
  }
  .list li:nth-child(5) span {
    transition-delay: 0.2s;
  }
  .list li:nth-child(6) span {
    transition-delay: 0.25s;
  }
}

See the Pen
Staggered Animations
by Chris Coyier (@chriscoyier)
on CodePen.

If you wanted to give yourself a little more programmatic control, you could set the delay as a CSS custom property:

@media (hover: hover) {
  .list {
    --delay: 0.05s;
  }
  .list li a span {
    transform: translateY(100px);
    transition: 0.2s;
  }
  .list:hover span {
    transform: translateY(0);
  }
  .list li:nth-child(1) span {
    transition-delay: calc(var(--delay) * 0);
  }
  .list li:nth-child(2) span {
    transition-delay: calc(var(--delay) * 1);
  }
  .list li:nth-child(3) span {
    transition-delay: calc(var(--delay) * 2);
  }
  .list li:nth-child(4) span {
    transition-delay: calc(var(--delay) * 3);
  }
  .list li:nth-child(5) span {
    transition-delay: calc(var(--delay) * 4);
  }
  .list li:nth-child(6) span {
    transition-delay: calc(var(--delay) * 5);
  }
}

This might be a little finicky for your taste. Say your lists starts to grow, perhaps to seven or more items. The staggering suddenly isn't working on the new ones because this doesn't account for that many list items.

You could pass in the delay from the HTML if you wanted:

<ul class="list">
  <li><a href="#0" style="--delay: 0.00s;">① <span>This</span></a></li>
  <li><a href="#0" style="--delay: 0.05s;">② <span>Little</span></a></li>
  <li><a href="#0" style="--delay: 0.10s;">③ <span>Piggy</span></a></li>
  <li><a href="#0" style="--delay: 0.15s;">④ <span>Went</span></a></li>
  <li><a href="#0" style="--delay: 0.20s;">⑤ <span>To</span></a></li>
  <li><a href="#0" style="--delay: 0.25s;">⑥ <span>Market</span></a></li>
</ul>
@media (hover: hover) {
  .list li a span {
    transform: translateY(100px);
    transition: 0.2s;
  }
  .list:hover span {
    transform: translateY(0);
    transition-delay: var(--delay); /* comes from HTML */
  }
}

Or if you're Sass-inclined, you could create a loop with more items than you need at the moment (knowing the extra code will gzip away pretty efficiently):

@media (hover: hover) {
 
 /* base hover styles from above */

  @for $i from 0 through 20 {
    .list li:nth-child(#{$i + 1}) span {
      transition-delay: 0.05s * $i;
    }
  }
}

That might be useful whether or not you choose to loop for more than you need.

The post Staggered CSS Transitions appeared first on CSS-Tricks.



from CSS-Tricks https://ift.tt/2Z0abLz
via IFTTT

Passkeys: What the Heck and Why?

These things called  passkeys  sure are making the rounds these days. They were a main attraction at  W3C TPAC 2022 , gained support in  Saf...