Web Standards

Creating Reusable Base Classes in TypeScript with a Real-Life Example

Css Tricks - Thu, 04/18/2019 - 5:14am

Hey CSS-Tricksters! Bryan Hughes was kind enough to take a concept from an existing post he published on converting to TypeScript and take it a few couple steps further in this post to elaborate on creating reusable base classes. While this post doesn’t require reading the other one, it’s certainly worth checking it out because it covers the difficult task of rewriting a codebase and writing unit tests to help the process.

Johnny-Five is an IoT and robotics library for Node.js. Johnny-Five makes it easy to interact with hardware by taking a similar approach that jQuery took to the web a decade ago: it normalizes the differences between various hardware platforms. Also like jQuery, Johnny-Five provides higher-level abstractions that make it easier to work with the platform.

Johnny-Five supports a wide array of platforms via IO Plugins, from the Arduino family, to the Tessel, to the Raspberry Pi, and many more. Each IO Plugin implements a standardized interface for interacting with lower-level hardware peripherals. Raspi IO, which I first created five years ago, is the IO plugin that implements support for the Raspberry Pi.

Any time there are multiple implementations that conform to one thing, there is a chance to share code. IO Plugins are no exception, however, we have not shared any code between IO Plugins to date. We recently decided, as a group, that we wanted to change this. The timing was fortuitous since I was planning on rewriting Raspi IO in TypeScript anyways, so I agreed to take on this task.

The goals of a base class

For those who many not be familiar with using class-based inheritance to enable code sharing, let’s do a quick walk-through of what I mean when I say “creating a base class to reuse code."

A base class provides structure and common code that other classes can extend. Let’s take a look at a simplified TypeScript example base class that we will extend later:

abstract class Automobile { private _speed: number = 0; private _direction: number = 0; public drive(speed: number): void { this._speed = speed; } public turn(direction: number): void { if (direction > 90 || direction < -90) { throw new Error(`Invalid direction "${direction}"`); } this._direction = direction; } public abstract getNumDoors(): number; }

Here, we have created a base class that we call Automobile. This provides some basic functionality shared between all types of automobiles, whether it’s a car, a pickup, etc. Notice how this class is marked as abstract, and how there is one abstract method called getNumDoors. This method is dependent on the specific type of automobile. By defining it as an abstract class, we are saying that we require another class to extend this class and implement this method. We can do so with this code:

class Sedan extends Automobile { public getNumDoors(): number { return 4; } } const myCar = new Sedan(); myCar.getNumDoors(); // prints 4 myCar.drive(35); // sets _speed to 35 myCar.turn(20); // sets _direction to 20

We’re extending the Automobile class to create a four-door sedan class. We can now call all methods on both classes, as if it were a single class.

For this project, we’ll create a base class called AbstractIO that any IO plugin author can then extend to implement a platform-specific IO Plugin.

Creating the Abstract IO base class

Abstract IO is the base class I created to implement the IO Plugin spec for use by all IO Plugin authors, and is available today on npm.

Each author has their own unique style, some preferring TypeScript and others preferring vanilla JavaScript. This means that, first and foremost, this base class must conform to the intersection of TypeScript best practices and JavaScript best practices. Finding the intersection of best practices isn’t always obvious though. To illustrate, let’s consider two parts of the IO Plugin spec: the MODES property and the digitalWrite method, both of which are required in IO Plugins.

The MODES property is an object, with each key having a human readable name for a given mode type, such as INPUT, and the value being the numerical constant associated with the mode type, such as 0. These numerical values show up in other places in the API, such as the pinMode method. In TypeScript, we would want to use an enum to represent these values, but JavaScript doesn’t support it. In JavaScript, the best practice is to define a series of constants and put them into an object “container."

How do we resolve this apparent split between best practices? Create one using the other! We start by defining an enum in TypeScript and define each mode with an explicit initializer for each value:

export enum Mode { INPUT = 0, OUTPUT = 1, ANALOG = 2, PWM = 3, SERVO = 4, STEPPER = 5, UNKNOWN = 99 }

Each value is carefully chosen to map explicitly to the MODES property as defined by the spec, which is implemented as part of the Abstract IO class with the following code:

public get MODES() { return { INPUT: Mode.INPUT, OUTPUT: Mode.OUTPUT, ANALOG: Mode.ANALOG, PWM: Mode.PWM, SERVO: Mode.SERVO, UNKNOWN: Mode.UNKNOWN } }

With this, we have nice enums when we’re in TypeScript-land and can ignore the numerical values entirely. When we’re in pure JavaScript-land, we still have the MODES property we can pass around in typical JavaScript fashion so we don’t have to use numerical values directly.

But what about methods in the base class that derived classes must override? In the TypeScript world, we would use an abstract class, as we did in the example above. Cool! But what about the JavaScript side? Abstract methods are compiled out and don’t even exist in the output JavaScript, so we have nothing to ensure methods are overridden there. Another contradiction!

Unfortunately, I couldn’t come up with a way to respect both languages, so I defaulted to using the JavaScript approach: create a method in the base class that throws an exception:

public digitalWrite(pin: string | number, value: number): void { throw new Error(`digitalWrite is not supported by ${this.name}`); }

It’s not ideal because the TypeScript compiler doesn’t warn us if we don’t override the abstract base method. However, it does work in TypeScript (we still get the exception at runtime), as well as being a best practice for JavaScript.

Lessons learned

While working through the best way to design for both TypeScript and vanilla JavaScript, I determined that the best way to solve discrepancies is:

  1. Use a common best practice shared by each language
  2. If that doesn’t work, try to use the TypeScript best practice “internally" and map it to a separate vanilla JavaScript best practice “externally," like we did for the MODES property.
  3. And if that approach doesn’t work, default to using the vanilla JavaScript best practice, like we did for the digitalWrite method, and ignore the TypeScript best practice.
  4. These steps worked well for creating Abstract IO, but these are simply what I discovered. If you have any better ideas I’d love to hear them in the comments!

    I discovered that abstract classes are not useful in vanilla JavaScript projects since JavaScript doesn’t have such a concept. This means that they are an anti-pattern here, even though abstract classes are a TypeScript best practice. TypeScript also doesn’t provide mechanisms to ensure type-safety when overriding methods, such as the override keyword in C#. The lesson here is that you should pretend that abstract classes don’t exist when targeting vanilla JavaScript users as well.

    I also learned that it’s really important to have a single source of truth for interfaces shared across modules. TypeScript uses duck typing as its core type system pattern, and is very useful inside any given TypeScript project. However, synchronizing types across separate modules using duck typing turned out to be more of a maintenance headache than exporting an interface from one module and importing it into another. I had to keep publishing little changes to modules until I got them aligned on their types, leading to excess version number churn.

    The final lesson I learned isn’t a new one: get feedback throughout the design process from library consumers. Knowing this going in, I set up two primary rounds of feedback. The first round was in person at the Johnny-Five collaborator’s summit where we brainstormed as a group. The second round was a pull request I opened against myself, despite being the only collaborator on Abstract IO with permission to merge pull requests. The pull request proved invaluable as we sifted through the details as a group. The code at the beginning of the PR bared little resemblance to the code when it was merged, which is good!

    Wrapping up

    Producing base classes destined to be consumed by both TypeScript and vanilla JavaScript users is a mostly straightforward process, but there are some gotchas. Since TypeScript is a superset of JavaScript, the two mostly share the same best practices. When differences in best practices do arise, a little creativity is required to find the best compromise.

    I managed to achieve my goals with Abstract IO, and implemented a base class that serves both TypeScript and vanilla JavaScript IO Plugin authors well. Turns out, it’s (mostly) possible to have your cake and eat it too!

    The post Creating Reusable Base Classes in TypeScript with a Real-Life Example appeared first on CSS-Tricks.

An Event Apart Boston is Coming. Save Now!

Css Tricks - Thu, 04/18/2019 - 4:37am

(This is a sponsored post.)

An Event Apart Boston is almost here! We're talking, like, less than a month away. If you've been holding off from registering, this might be your last chance because time and seating are both limited. Besides, we're talking about three days of pure knowledge-dropping by an impressive lineup of speakers on a wide range of topics. Seriously, just look at the full schedule and prepare to be get stoked for a great time.

But, if a couple of weeks until show time is a little too tight for your liking, that's where An Event Apart has you totally covered because there's another one taking place in Washington D.C. this summer. And three more throughout the rest of the year in Chicago, Denver and San Francisco.

The reason an Event Apart is so near-and-dear to the CSS-Tricks family is that it scratches a common itch that we see come up around here often: how can we possibly stay on top of our careers as front-enders when our industry is changing at lightning speed and when we're all-consumed trying to keep on top of our actual jobs? An Event Apart is our chance to take intention time away from the day-to-day and invest in our careers by learning from some of the best in the business, meeting folks who do what we do, and having those micro-interactions with others in the hallways that spark the interesting types of dialogues you just can't get from Twitter.

Think about it: 17 speakers over three days covering everything from design systems and Progressive Web Apps to variable fonts and CSS Grid Layout. There's so much to gain by attending. Just look at the awesome takeaways Chris got from An Event Apart Seattle in March.

And, to sweeten the pot, An Event Apart gave us a special discount code for CSS-Tricksters like yourself. Enter the AEACP at checkout to knock $100 off the price.

Hope to see you at one of the upcoming events!

Register Today

Direct Link to ArticlePermalink

The post An Event Apart Boston is Coming. Save Now! appeared first on CSS-Tricks.

How AI Can Improve The UX Of Websites and Landing Pages

Usability Geek - Wed, 04/17/2019 - 5:55am
Artificial Intelligence (AI) is no longer a vision for the future – it is already here and is having a significant impact on how businesses can improve their practices. One big way that AI is...
Categories: Web Standards

Clever code

Css Tricks - Wed, 04/17/2019 - 4:00am

This week, Chris Ferdinandi examined a clever JavaScript snippet, one that's written creatively with new syntax features, but is perhaps less readable and performant. It's a quick read, but his callout of our industry's fixation on cleverness is worth... calling out:

...we’ve become obsessed as an industry with brevity and clever code, and it results in code that’s sometimes less performant, and typically harder to read and make sense of for most people.

He made a similar argument late last month when he wrote about code readability, noting that brevity might look cool but ultimately leads to all sorts of issues in a codebase:

Often, web developers are obsessed with brevity. There’s this thing were developers will try to write the same function in the fewest number of characters possible.

Personally, I think brevity is pointless. Readability is a lot more important.

I entirely agree with Chris on this point, however, I think there is one important distinction to make and that's the difference between code that's intended as a prototype and code that's intended for production. As Jeremy Keith argued a short while ago:

What’s interesting is that—when it comes to prototyping—our usual front-end priorities can and should go out the window. The priority now is speed. If that means sacrificing semantics or performance, then so be it. If I’m building a prototype and I find myself thinking “now, what’s the right class name for this component?”, then I know I’m in the wrong mindset. That question might be valid for production code, but it’s a waste of time for prototypes.

I agree with Chris that we should be writing code that is easy to read when we’re in production. I also think experimenting with code in this way is a good thing when it comes to prototypes. We shouldn’t ever shy away from playing with code and pushing things a little bit – so long as we’re not doing that in a giant web app with a a team of other developers working alongside us.

I’ve noticed that there are some folks that are doing genuinely ingenious things with Sass. I consistently sit back and think, "Wow, I’ve never seen anything like this before." But when it comes to a production web app that has to be understood by hundreds of people at the same time, I don’t believe that this is the reaction we want when someone looks at the code.

As a result, I’ve been trying to write Sass code that is actually so simple that it almost looks dumb. One easy way to make code a lot simpler is to reduce the amount of nesting:

.element { .heading { ... } }

This looks fine when there’s code inside — and it’s pretty easy to understand — but add a complex bit of design in the mix (say, using pseudo elements and media queries) and you suddenly have a rather complex set of rules in front of you. Creativity and cleverness can be harder to scan and identify one small part of the code that you’re looking for. Plus, in this example, we’ve unnecessarily made our .heading class a little bit more specific which might encourage us to override things in a hacky way elsewhere in the codebase.

We could write the following:

.element { ... } .element-heading { ... }

I know this looks foolishly simple but the relationship between these two classes is easier to see and easier to extend in the future. Bundling all that code into a single nested class can get out of hand real fast. Even if it happens to look a lot cooler.

(As an aside, Andy Bell's post on using the ampersand in Sass — and the resulting comments — is a great example of the clash between creativity and practicality.)

Anyway, the point I’m trying to make here is that CSS (and JavaScript for that matter) is a strange language because there are no definite rules you can make about it. It all really depends on the codebase and the project. But I think we can safely say that our code ought to be a lot more boring than our prototypes when it moves to production.

Continue to make prototypes that are wild and experimental or impossibly weird! Code quality be damned.

The post Clever code appeared first on CSS-Tricks.

The Power of Named Transitions in Vue

Css Tricks - Tue, 04/16/2019 - 4:05am

Vue offers several ways to control how an element or component visually appears when inserted into the DOM. Examples can be fading in, sliding in, or other visual effects. Almost all of this functionality is based around a single component: the transition component.

A simple example of this is with a single v-if based on a Boolean. When the Boolean is true, the element appears. When the Boolean is false, the element disappears. Normally, this element would just pop in and out of existence, but with the transition component you can control the visual effect.

<transition> <div v-if="isVisible">is this visible?</div> </transition>

Several articles have been written that cover the transition component quite well, like articles from Sarah Drasner, Nicolas Udy, and Hassan Djirdeh. Each article covers different aspects of Vue’s transition component in detail. This article will expand on the topic by focusing on one aspect of the transition component; the fact that they can be “named."

<transition name="fade"> <div v-if="isVisible">is this visible?</div> </transition>

The initial change this attribute offers is that the CSS classes injected onto the element during the transition sequence will be prefixed by the given name. Basically, it would be fade-enter instead of v-enter from the example above. This single attribute can go well beyond this simple option. It can be used to leverage certain features of Vue and CSS which allows for some interesting outcomes.

Another thing to consider is that the name attribute can be bound:

<transition v-bind:name="currentTransition"> <div v-if="isVisible">is this visible?</div> </transition>

In this example, the transition will be named the value currentTransition resolves to. This simple change provides another level of options and features to an app’s animations. With static and dynamic named transitions, a project can have a series of prebuilt transitions ready to apply throughout the entire app, components that can extend existing transitions applied to them, switch a transition being used before or after being applied, allowing users to choose transitions, and control how individual elements of a list transition into place based on the current state of that list.

This article is intended to explore these features and explain how to use them.

What happens when transitions are named?

By default, when a transition component is used, it applies specific classes in a specific sequence to the element. These classes can be leveraged in CSS. Without any CSS, these classes, in essence, do nothing for the element. Therefore, there is a need for CSS of this nature:

.v-enter, .v-leave-to { opacity: 0; } .v-enter-active, .v-leave-active { transition: 0.5s; }

This causes the element to fade in and out with a duration of half a second. A minor change to the transition provides for elegant visual feedback to the user. Still, there is an issue to consider. But first, what’s different with a named transition?

.fade-enter, .fade-leave-to { opacity: 0; } .fade-enter-active, .fade-leave-active { transition: 0.5s; }

Essentially the same CSS but with fade- prefixed instead of v-. This naming addresses the potential issue that can happen when using the default class names of the transition component. The v- prefix makes the classes global in effect, especially if the CSS is placed in the style block of the app’s root level. This would, in effect, make *all* transitions without a name attribute throughout the entire app use the same transition effect. For small apps this may suffice, but in larger, more complex apps, it may lead to undesirable visual effects, as not everything should fade in and out over half a second.

Naming transitions provides a level of control for developers throughout the project as to how different elements or components are inserted or removed visually. It is suggested that all transitions be named — even if there is just one — to establish the habit of doing so. Even if an app has only one transition effect, there may be a need to add a new one at a future point. Having already named existing transitions in the project eases the effort of adding a new one.

Building a collection of transition effects

Naming transitions provides for a simple yet very useful process. A common practice might be to create the transition classes as part of the component that is using them. If another common practice of scoping styles for a component is done, those classes will only be available to that particular component. If two different components have similar transitions in their style blocks, then we are just duplicating code.

So, let’s consider keeping CSS for transitions in the style block of the root of the app, typically the app.vue file. For most of my projects, I place them as the last section of the style block, making them easy to locate for adjustments and additions. Keeping the CSS in this location makes the transition effects available to every use of the transition component throughout the entire app. Here are examples from some of my projects.

.fade-enter, .fade-leave-to { opacity: 0; } .fade-enter-active, .fade-leave-active { transition: 0.5s; } .slide-enter { opacity: 0; transform: scale3d(2, 0.5, 1) translate3d(400px, 0, 0); } .slide-enter-to { transform: scale3d(1, 1, 1); } .slide-enter-active, .slide-leave-active { transition: 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55); } .slide-leave { transform: scale3d(1, 1, 1); } .slide-leave-to { opacity: 0; transform: scale3d(2, 0.5, 1) translate3d(-400px, 0, 0); } .rotate-enter { transform: perspective(500px) rotate3d(0, 1, 0, 90deg); } .rotate-enter-active, .rotate-leave-active { transition: 0.5s; } .rotate-leave-to { transform: perspective(500px) rotate3d(0, 1, 0, -90deg); }

There are multiple ways to store these transition classes depending on your preferences and the needs of the project. The first, as mentioned earlier, is to keep it all in the style block of the app.vue file. You can also keep a Sass partial of all the transitions in the project’s assets folder and import it into the app’s style block.

<style lang="scss"> @import "assets/_transitions.scss"; </style>

This method allows for adjustments and additions to the collection of transitions outside of the Vue files. Another benefit of this setup is that such a file can be easily transferred between projects if they share transition effects. If one project gets a new transition, then it’s easy enough to transfer the addition to another project without having to touch main project files.

If you’re using CSS instead of Sass, then you can include the file as a requirement of the project. You can accomplish this by keeping the file in the assets folder of the project and placing a require statement in the main.js file.

require("@/assets/transitions.css");

Another option is keep the transition styles in a static CSS file that can be stored elsewhere, either in the public folder of the project or just on the server itself. Since this is a regular CSS file, no building or deployment would be required — just include a link reference in the index.html file.

<link rel="stylesheet" type="text/css" href="/css/transitions.css">

This file could also potentially be stored in a CDN for all projects to share. Whenever the file is updated, the changes are immediately available everywhere it is referenced. If a new transition name is created, then existing projects can start using the new name as needed.

Now, let’s slow down a minute

While we’re building a collection of transitions to use throughout our project, let’s consider users out there who may not want abrupt animations, or who may want no animations at all. Some people could consider our animations over-the-top and unnecessary, but for some, they can actually cause problems. Some time ago, WebKit introduced the prefers-reduced-motion media query to assist with possible Vestibular Spectrum Disorder issues. Eric Bailey also posted a nice introduction to the media query as well.

In most cases, adding the media query as part of our collection of transitions is quite easy and should be considered. We can either reduce the amount of motion involved in the transition to reduce the negative effects or simply turn them off.

Here’s a simple example from one of my demos below:

.next-enter { opacity: 0; transform: scale3d(2, 0.5, 1) translate3d(400px, 0, 0); } .next-enter-to { transform: scale3d(1, 1, 1); } .next-enter-active, .next-leave-active { transition: 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55); } .next-leave { transform: scale3d(1, 1, 1); } .next-leave-to { opacity: 0; transform: scale3d(2, 0.5, 1) translate3d(-400px, 0, 0); } /* If animations are reduced at the OS level, use simpler transitions */ @media screen and (prefers-reduced-motion: reduce) { .next-enter { opacity: 0; transform: translate3d(100px, 0, 0); } .next-enter-active, .next-leave-active { transition: 0.5s; } .next-leave-to { opacity: 0; transform: translate3d(-100px, 0, 0); } }

In that example, I took what was a rather exaggerated transition and made it simpler. The animation is a slide that moves to the left with an elastic ease, then scales down and fades out as it moves away. If someone has the reduce motion preference set, then the animation becomes a much simpler transition with a shorter distance (which gives it a slower velocity) and keeps the fade. If we had wanted to turn them off, then we’d only need to reference the classes with the transition property and set their value to none.

To test this requires finding and selecting a checkbox on your respective OS. On Windows, you will find it in Control Panel > Ease of Access Center > Make the computer easier to see section; look for "Turn off all unnecessary animations (when possible)." On a Mac, look under System Preferences > Accessibility > Display; look for "Reduce motion." The latest iOS devices have a similar setting under Accessibility as well.

Let’s stay flexible with our transitions collection

With this collection of transitions, there is the potential snag of a lack of flexibility with the effects. For instance, what if one element needs a slightly slower fade time? Let’s say that everything else in the effect can stay the same, only the transition-duration needs to be different. There are ways to adjust for that without having to create a whole new transition name.

The easiest method is to use an inline style directly on the element within the transition component.

<transition name="fade"> <div style="transition-duration: 6s;" v-if="isVisible">this has a different duration</div> </transition>

Such a change can also be done through the various ways Vue offers handling styles and classes.

Let’s say you are using the component element with the is attribute for dynamic components such as this:

<transition name="fade" mode="out-in"> <component :is="currentComponent"></component> </transition>

Even with this dynamic component, we have options to adjust properties of the transition effect. Again, we can apply an inline style on the component element, which will be placed on the root element of the component. The root element also receives the transition classes, so we would directly override their properties.

<transition name="fade" mode="out-in"> <component :is="currentComponent" style="transition-duration: 6s;"></component> </transition>

Another option is to pass in props to our components. That way, the desired changes can be applied through the component’s code to its root element.

<transition name="fade" mode="out-in"> <component :is="currentComponent" duration="6s"></component> </transition> <template> <div :style="`transition-duration: ${duration}`">component one</div> </template> <script> export default { name: "component-one", props: { duration: String } }; </script>

We can also override the properties of the transition’s classes inside the component’s style block, especially if it is scoped.

<style scoped> .fade-enter-active, .fade-leave-active { transition-duration: 1s; } </style>

In this case, the component will have a fade duration of one second instead of the global duration of half-a-second. We can even take it a step further and have different durations for each side of the sequence.

<style scoped> .fade-enter-active { transition-duration: 1s; } .fade-leave-active { transition-duration: 2s; } </style>

Any of the global transition classes can be altered within the component when needed. Although this isn’t quite as flexible as changing the property outside of a class structure, it can still be quite useful in certain circumstances.

As you can see, even with our collection of prebuilt transitions, we still have options for flexibility.

Dynamic transitions

Even after all these interesting things we can do with Vue’s transition component, yet another interesting feature waits to be explored. The name attribute on the transition component can be dynamic in nature, meaning we can change the current transition in use at will.

This means that the transition can be changed to have different animation effects with different situations, based in code. For example, we could have a transition change because of the answer to a question, transitions decided from user interaction, and have a list use different transitions based on the current state of the list itself.

Let’s look into these three examples.

Example 1: Change transition based on an answer

In this example, we have a simple math question that must be answered. Two numbers are randomly selected and we are expected to provide the sum. Then the button is clicked to check the answer against the expected answer. A small notification appears above the equation that indicates whether the answer is true or false. If the answer is correct, the notification is given a transition that suggests a head nodding yes with an up and down animation. If your answer is incorrect, the notification goes side-to-side suggesting a head shaking no.

See the Pen
VueJS Dynamic Transitions: Change Transition Based on an Answer
by Travis Almand (@talmand)
on CodePen.

The logic behind this is not overly complicated, nor is the setup of the transition. Here’s the HTML:

<transition :name="currentTransition"> <div id="notification" :class="response.toString()" v-if="answerChecked">{{ response }}</div> </transition>

Rather simple in nature. We have a bound name on the transition and then a v-if on the notification div. We also apply a true or false class to decorate the notification based on the response.

Here’s the CSS for the transitions:

.positive-enter-active { animation: positive 1s; } @keyframes positive { 0% { transform: translate3d(0, 0, 0); } 25% { transform: translate3d(0, -20px, 0); } 50% { transform: translate3d(0, 20px, 0); } 75% { transform: translate3d(0, -20px, 0); } 100% { transform: translate3d(0, 0, 0); } } .negative-enter-active { animation: negative 1s; } @keyframes negative { 0% { transform: translate3d(0, 0, 0); } 25% { transform: translate3d(-20px, 0, 0); } 50% { transform: translate3d(20px, 0, 0); } 75% { transform: translate3d(-20px, 0, 0); } 100% { transform: translate3d(0, 0, 0); } }

You’ll see that I’m using CSS animations to accomplish the up-and-down and side-to-side effects.

Here’s some of the JavaScript:

methods: { randomProblem: function () { this.a = Math.floor(Math.random() * Math.floor(10)); this.b = Math.floor(Math.random() * Math.floor(10)); }, check: function () { this.response = this.a + this.b === parseInt(this.answer); this.answerChecked = true; this.currentTransition = this.response ? 'positive' : 'negative'; }, reset: function () { this.answer = null; this.answerChecked = false; this.randomProblem(); } }

There’s the randomProblem method that sets up our equation. The check method that decides on which transition effect to use based on comparing the provided answer with the correct answer. Then the simple reset method that just, well, resets everything.

This is just a simple example. Another possible example is having a notification that has two different effects based on whether the notification is important or not. If the message is not overly important, then we can have a subtle animation that doesn’t drive the user’s eyes away from the current task. If it is important, we could use an animation that is more direct in nature in an effort to force the eyes up to the notification.

Example 2: Change transition based on user interaction

Another thing we can build is a carousel of some sort. This could be presentation slides, an image gallery, or a series of instructions. The basic idea is that we have a need to present information to the user in a sequence. In this presentation, the user gets to decide when to proceed and whether to move forward or to go backward.

See the Pen
VueJS Dynamic Transitions: Change Transition Based on User Interaction
by Travis Almand (@talmand)
on CodePen.

This, again, is a rather simple setup. The example, more or less, is a slide presentation type of situation. The two buttons at the bottom shift between two components with a sliding transition. A real project would have more components or perhaps logic to change the contents of the components based on the current slide. This example shall stay simple to demonstrate the idea.

Here’s the HTML:

<transition :name="currentTransition" mode="out-in"> <component :is="slides[currentSlide]"></component> </transition>

You’ll see that we’re merely transitioning whenever the component is switched out by a bound is attribute on the component element.

Here’s the CSS:

.next-enter { opacity: 0; transform: scale3d(2, 0.5, 1) translate3d(400px, 0, 0); } .next-enter-to { transform: scale3d(1, 1, 1); } .next-enter-active, .next-leave-active { transition: 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55); } .next-leave { transform: scale3d(1, 1, 1); } .next-leave-to { opacity: 0; transform: scale3d(2, 0.5, 1) translate3d(-400px, 0, 0); } .prev-enter { opacity: 0; transform: scale3d(2, 0.5, 1) translate3d(-400px, 0, 0); } .prev-enter-to { transform: scale3d(1, 1, 1); } .prev-enter-active, .prev-leave-active { transition: 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55); } .prev-leave { transform: scale3d(1, 1, 1); } .prev-leave-to { opacity: 0; transform: scale3d(2, 0.5, 1) translate3d(400px, 0, 0); } /* If animations are reduced at the OS level, use simpler transitions */ @media screen and (prefers-reduced-motion: reduce) { .next-enter { opacity: 0; transform: translate3d(100px, 0, 0); } .next-enter-active, .next-leave-active { transition: 0.5s; } .next-leave-to { opacity: 0; transform: translate3d(-100px, 0, 0); } .prev-enter { opacity: 0; transform: translate3d(-100px, 0, 0); } .prev-enter-active, .prev-leave-active { transition: 0.5s; } .prev-leave-to { opacity: 0; transform: translate3d(100px, 0, 0); } }

Here we have two transitions, one for when the user clicks on the “next” button and the other is for the "prev" button. Each essentially slides the component in the appropriate direction with the transform property, but with a few extras to create a kind of squeezing effect for a cartoonish feel. We also make use of prefers-reduced-motion to change the animation to be a simpler fade with a small slide to the side in the appropriate direction.

Now, for the JavaScript:

methods: { changeSlide: function (dir) { this.currentSlide = dir === 'next' ? this.currentSlide + 1 : this.currentSlide - 1; this.currentTransition = dir; } }

Each button calls the changeSlide method on its click event and passes which direction it represents. Then we have some logic to keep track of what the current slide happens to be. A single line controls which transition to use. Since the “next” button passes “next” as the direction it corresponds to the “next” transition in the CSS. Same for the "prev" button. Each time the user clicks a button, the app automatically knows which transition to use. Thus, we have nice transition effects that provide context as to which direction the user is progressing through the sequence.

Example 3: Change transition based on list state

For our final example, we’ll see how to change transitions based on the current state of a list inside a transition-group component. The idea here is a list to be updated an item at a time with a different transition each time.

See the Pen
VueJS Dynamic Transitions: Change Transition Based on List State
by Travis Almand (@talmand)
on CodePen.

In this example, we are presented with a list of cities on the right and a blank list on the left. As cities are chosen on the right, they fill in the blanks on the left. The first city slides in from above while fading into view. The next cities before the last will slide in either from the right or the left, depending on the previous transition, and the last city slides in from below.

Here’s the HTML:

<transition-group :name="currentListTransition" tag="ul" class="list"> <li v-for="(item, index) in selectedItems" :key="item">{{ item }}</li> </transition-group>

As usual, a rather simple setup. Here are the transitions in CSS:

.top-enter-active, .top-leave-active { transition: 0.5s; } .top-enter, .top-leave-to { opacity: 0; transform: translate3d(0, -40px, 0); } .top-move { opacity: 0.5; transition: 0.5s; } .left-enter-active, .left-leave-active { transition: 0.5s; } .left-enter, .left-leave-to { opacity: 0; transform: translate3d(-40px, 0, 0); } .left-move { opacity: 0.5; transition: 0.5s; } .right-enter-active, .right-leave-active { transition: 0.5s; } .right-enter, .right-leave-to { opacity: 0; transform: translate3d(40px, 0, 0); } .right-move { opacity: 0.5; transition: 0.5s; } .bottom-enter-active, .bottom-leave-active { transition: 0.5s; } .bottom-enter, .bottom-leave-to { opacity: 0; transform: translate3d(0, 30px, 0); } .bottom-move { opacity: 0.5; transition: 0.5s; } /* If animations are reduced at the OS level, turn off transitions */ @media screen and (prefers-reduced-motion: reduce) { .top-enter-active, .top-leave-active { transition: none; } .top-move { transition: none; } .left-enter-active, .left-leave-active { transition: none; } .left-move { transition: none; } .right-enter-active, .right-leave-active { transition: none; } .right-move { transition: none; } .bottom-enter-active, .bottom-leave-active { transition: none; } .bottom-move { transition: none; } }

As you can see, a transition for each possible direction of the cities appearing the blank list.

Now, for our JavaScript:

methods: { chooseCity: function (index) { let selectedLength = this.selectedItems.length; let citiesLength = this.cities.length; let clt = this.currentListTransition; if (selectedLength === 0) { clt = 'top'; } else if (selectedLength > 0 && selectedLength < citiesLength - 1) { clt = clt === 'top' || clt === 'left' ? 'right' : 'left'; } else if (selectedLength === citiesLength - 1) { clt = 'bottom'; } this.currentListTransition = clt; this.selectedItems.push(this.cities[index]); document.querySelector(`.city:nth-child(${index + 1})`).classList.add('selected'); }, clearSelection: function () { this.currentListTransition = 'right'; this.selectedItems = []; document.querySelectorAll('.city.selected').forEach(element => { element.classList.remove('selected'); }); } }

The chooseCity method handles what happens as you choose each city. What we mostly care about is the series of if and if/else statements in the middle of the method. As cities are selected, the logic looks at the current length of the selectedItems array that the selected cities eventually get pushed into. If the length is zero, then that’s the first city, so the transition should have it come in from the top. If the length is between zero and the total number of our cities list, then the transition should be right or left. The new direction used is based on the direction of the previous transition direction. Then, finally, if we’re on the last city to be chosen, it’ll change to the bottom transition. Again we use prefers-reduced-motion, in this case to turn off the transitions altogether.

Another option to change transitions for a list is changing according to the type of items chosen; such as east coast versus west coast cities, each having different transitions. Consider changing the transition based on the current number of items added to the list; for instance, a different transition for every five items.

So long, and thanks for all the transitions

After all these examples and ideas, I hope that you will consider leveraging Vue’s transition component in your own projects. Exploring the possibilities of adding transitions and animations to your apps to provide context and interest for your users. In many cases, additions such as these are rather simple to implement, almost to the point of it being a shame not to add them. Vue offers an exciting and highly useful feature, the transition component, out of the box and I can only encourage its usage.

Cheers.

The post The Power of Named Transitions in Vue appeared first on CSS-Tricks.

WooCommerce in a Data-Driven World

Css Tricks - Tue, 04/16/2019 - 2:54am

(This is a sponsored post.)

WooCommerce recently made an entire overhaul of its highly visible dashboard screen in the WordPress admin available as a new plugin that can be downloaded free from the WordPress Plugin Directory. The new design is gorgeous, of course. I mean, anytime WooCommerce touches an admin screen, other plugin developers really pay attention because it influences they way many of them approach UI in WordPress.

But the real reason the new dashboard struck me is the sheer amount of data WooCommerce provides users. As someone who has worked on a fair share of WooCommerce (and non-WooCommerce) online stores, reporting is something that comes up quite frequently and it's nice to know WooCommerce not only bakes it right into their product, but designs it so well that it's easy to glean insights about sales, products and customers at a glance.

Here's a clean shot of the new dashboard from the blog post announcing it.

If you've had to integrate custom reporting into an online store a la Google Analytics or some other tooling, you'll know that it requires a fair amount of setup and know-how to make sure data is feeding into the right places, certain clicks or actions are getting tracked, and that the reports themselves are solid, including things like filtering by date and other variables. That's a lot of work considering we can get that and more, directly from the makers of the e-commerce platform.

As Woo mentions in its post, the dashboard changes contained in the feature plugin are merely a preview of what's to come and we have a lot of other fine features to look forward to, including new types of reports, activity feeds and more. There's a lot of power and flexibility to be gained if setting up an online store is in your cards, then the fact that WooCommerce and these features are completely open source and free of charge in the WordPress ecosystem practically make it a no-brainer.

Try WooCommerce

Direct Link to ArticlePermalink

The post WooCommerce in a Data-Driven World appeared first on CSS-Tricks.

You Are Creating For A Human: Here Is Some Advice

Usability Geek - Mon, 04/15/2019 - 12:15pm
You should know whom you are creating for. Your user is not an abstract substance. Think of a person. Explore your customer persona as you would like to explore the mind of your loved one. Discover...
Categories: Web Standards

A Website is a Car and Not a Book

Css Tricks - Mon, 04/15/2019 - 7:03am

I’ve been wondering for a good long while why it feels like web design and development isn’t respected as much as native app development, and why the front-end role in many organizations is seen as a nice-to-have rather than a vital part of the business. Why is it so hard to see that this gig we call "front-end development" is crucial for business and even the day-to-day lives of users?

Is it just me that feels this way?

We depend on front-end developers to help us file our taxes, buy our food and clothes, pay our bills, and entertain us. We find new music, we read stories and play games, and we fall in love... all on websites made up of nothing more than HTML, CSS, and JavaScript written by front-enders.

I’m not trying to be a jerk here, but you can see organizations everywhere that de-prioritize front-end development. There are slow websites! Ad-tech junk everywhere! Poor responsive interfaces! Divs used for buttons! Inaccessible forms! The problems on the web today are daunting and overwhelming to those who care about both good front-end development and the health of the web itself.

What's the cause? Well, I certainly don’t believe that it’s malice. Nobody wants to make slow websites or broken interfaces and nobody (I think) is intentionally trying to break the web. So, why do we all end up doing things that go against what we know to be best practices? What is it about the complexities of web design that is so hard to grasp?

Again, I’m not being mean here – this is an honest question.

I got coffee with my pal Lindsay Grizzard the other day and we were talking about this stuff, asking each other these and other really tough questions related to our work. We both see problems in this industry and it drives us both a little mad to some extent.

Anyway, I asked Lindsay that question: what is it about web design that makes it so difficult to understand? She posited that the issue is that most people believe web design is like designing a book. Heck, we still call these things web pages. But Lindsay argued that building a modern website is nothing like designing a book; it’s more like designing a car.

Lindsay and I looked at the cars parked on the street next to us: they have to be mass produced and they have to be tested. Each has to be built up of perfectly identical components that need to fit together in a very specific format. There are technical issues – limitations of physics, money, and time – that require confronting on a daily basis. You can’t point at one part of the car and have an opinion about aesthetics because that one component changes the relationships of the others. You have to understand that you’re looking at an immensely complicated system of moving parts.

I love that comparison though, even if it’s not particularly helpful to give others insight into what we do: a website is a car and not a book.

The post A Website is a Car and Not a Book appeared first on CSS-Tricks.

Simulating Mouse Movement

Css Tricks - Mon, 04/15/2019 - 4:11am

If you've ever had to display an interactive animation during a live talk or a class, then you may know that it's not always easy to interact with your slides and while talking.

This happened to me when I needed to show this particles demo to my students. I didn't want to have to stay next to my computer to move my mouse in order to show off the demo.

See the Pen
Particles (on move)
by Louis Hoebregts (@Mamboleoo)
on CodePen.

If you do not interact with the iframe, you will see nothing but a blank space. As soon as you start moving your mouse or your finger, you can see the animation.

For that reason, I created the same demo but I used some extra code to simulate someone interacting with the demo.

See the Pen
Particles (fake)
by Louis Hoebregts (@Mamboleoo)
on CodePen.

Simplex noise

The trick here is to use an algorithm that will generate "smooth" random positions. If we use a classic random function, the fake mouse will be at a purely random position on every frame. What we want is to have a position on every frame that is directly linked to the previous one. Thankfully, there is a technique that does exactly what we need: Simplex noise (or more commonly known as Perlin noise).

Let's take a look at this image where the height of each column is defined with random values on top, and values from Simplex noise algorithm below.

You can quickly notice that the bottom graph seems much smoother because every column height is connected to the previous one. Those graphs are only showing one dimension (the x-axis, from left to right) but with Simplex noise you can get values in multiples dimensions. In our case, we will need two dimensions for the X and Y coordinates of the fake mouse we're simulating.

If you are more interested to know how Simplex noise works, check out the video "I.5: Perlin Noise - The Nature of Code" by Daniel Shiffman

Get noise coordinates

The first thing we need to make our demo work is to implement a script that generates noise. In my case, I'm using this script by Seph.

Once the noise script is loaded, we can start using it on every frame to make our mouse move.

I will be using an image of a mouse for the demos that I put on position: fixed; with a class .mouse, but you could animate anything else for your own projects.

So, let's take a look at the code:

// We retrieve the image from the DOM const el = document.querySelector('.mouse'); // The render function is called on every frame function render (a) { // The a variable is the amount of milliseconds since we started our script // Get a noise value based on the elapsed time to get a new value on every frame // This noise algorithm is returning values between [-1, 1] so we need to map them to [0, 1] by adding one to the value and dividing it by 2 const noiseX = (noise.simplex2(0, a*0.0005) + 1) / 2; // We get another noise value for the y axis but because we don't want the same value than x, we need to use another value for the first parameter const noiseY = (noise.simplex2(1, a*0.0005) + 1) / 2; // Convert the noise values from [0, 1] to the size of the window const x = noiseX * window.innerWidth; const y = noiseY * window.innerHeight; // Apply the x & y coordinates on our element el.style.transform = `translate(${x}px, ${y}px)`; // Call the render function once the browser is ready to make it an infinite loop requestAnimationFrame(render); } // Ask the browser to call render to start our animation requestAnimationFrame(render);

Here is the result we get with the above script:

See the Pen
Virtual user 1
by Louis Hoebregts (@Mamboleoo)
on CodePen.

Allow interactivity

With the current code, we are not allowed to interact with our demo anymore. Let's add a bit more code to use our real mouse position when we interact with the demo and switch back to a fake mouse as soon as we stop.

const el = document.querySelector('.mouse'); let lastMove = 0; // When the mouse is being moved function onMouseMove (e) { // Get the x and y coordinates x = e.clientX; y = e.clientY; // Save the last move time lastMove = Date.now(); } // Update the mouse position based on x & y function updateMouse (x, y) { el.style.transform = `translate(${x}px, ${y}px)`; } function render (a) { // Check if last move was more than 500ms ago if (Date.now() - lastMove > 500) { // Generate a fake mouse position ... updateMouse(x, y); } } // Listen to mouse events window.addEventListener('mousemove', onMouseMove);

Now, if you move your mouse, the fake mouse will follow yours. If you stop moving for 500ms, the fake mouse will start moving again.

See the Pen
Virtual user 3
by Louis Hoebregts (@Mamboleoo)
on CodePen.

Customized movement

The speed of the mouse can be updated by changing the value of the third parameter. So far, we are setting this value by taking the elapsed time multiplied by 0.0005, which is equal to a/2000.

// Define a speed ratio const speed = a * 0.0005; // Use the speed const noiseX = (noise.simplex3(1, 0, speed) + 1) / 2;

We can also add a bit more randomness in the changes of direction by adding more noise from its position.

let random = 0; function render (a) { ... // Update the random value random += 0.1; // Compute a x random offset based on the window width const randX = noise.simplex3(1, 0, random) * window.innerWidth * 0.1; // Compute a y random offset based on the window height const randY = noise.simplex3(3, 0, random) * window.innerHeight * 0.1; // Define the x & y values based on (noise * screen) + randomness const x = noiseX * innerWidth + randX; const y = noiseY * innerHeight + randY; ... }

Play with the inputs to see how speed and randomly calculated values can influence the fake mouse movement.

See the Pen
Virtual user 4
by Louis Hoebregts (@Mamboleoo)
on CodePen.

More mice

Now that we have created one fake mouse, why not create 500 of them?

See the Pen
Virtual user 5
by Louis Hoebregts (@Mamboleoo)
on CodePen.

I now use this trick for almost all my demos. I think it's really cool to be able to display a project without using a video or being forced to keep moving the mouse randomly while trying to talk about the demo.

If you have any questions or remarks, please leave a comment below or ping me on Twitter.

The post Simulating Mouse Movement appeared first on CSS-Tricks.

CSS and accessibility

QuirksBlog - Mon, 04/15/2019 - 3:28am

I am likely going to write a “CSS for JavaScripters” book, and therefore I need to figure out how to explain CSS to JavaScripters. This series of article snippets are a sort of try-out — pre-drafts I’d like to get feedback on in order to figure out if I’m on the right track.

Today’s topic is CSS and accessibility. I want to warn people for common accessibility pitfalls, but on the other hand want to do it in a sneaky, underhand way. In order to do so I need a list of common problems.

No chapter

I have decided that the book will not have a “CSS and accessibility” chapter because I suspect the people who need it most will skip it. Accessibility is “not realistic,” after all, and complicated, and nobody who’s blind visits our site anyway etc. You know the score.

Instead, my plan is to kind-of weave accessibility issues throughout the book, and treat them together with the technique they relate to. I am even going to try to avoid the word “accessibility” altogether, though I’m not yet certain I’ll succeed.

A list

When in doubt, make a list. What I’m currently looking for is a list of CSS/accessibiklity tips and tricks that will solve the most common 80% of the problems. That’s where you come in.

I am not certain if I’m going to treat ARIA. I have the feeling that if your HTML and CSS are structured correctly you don’t need any ARIA in 90% of the cases (but please correct me if I’m wrong). So complicated ARIA use cases fall outside the scope of this list.

With all that in mind, please give me your simple, easy-to-implement CSS and accessibility tips and tricks. I’ll add them to the list below, and I’ll write them into the book in a sneaky, underhand way. (I have no clue how that will actually work in practice, but it’s worth a try.)

The master CSS/accessibility list

The book is going to treat these topics:

  • Accessibility problems with changing the order of flex and grid boxes; what happens when source order is not visual order? (auto-flow: dense for grid; see also http://adrianroselli.com/2019/04/reading-order-bookmarklet.html for reading order bookmarklet)
  • Lack of contrast and how to solve it.
  • The position: absolute; left: -9999px class of solutions. (I am assuming these are safe as long as you apply them to the correct bits of content.)
  • Keyboard focus and :focus, :focus-within etc.
  • If it’s a button or a link, make it a button or a link. (Strictly speaking this is an HTML trick. But I’ll likely be able to squeeze it in somewhere.)
  • https://medium.com/@matuzo/writing-css-with-accessibility-in-mind-8514a0007939
  • Consequences of display: none; element taken out entirely.
  • Reduced motion, high-contrast, and similar media queries. (OK if they’re browser-specific)
  • How display: content can remove semantics http://adrianroselli.com/2018/02/tables-css-display-properties-and-aria.html
  • CSS symbol fonts or other icons may be read aloud (this may be beyond the scope of the book, since I won't treat fonts)
  • Difference between display: none, visibility: hidden, and opacity: 0.
  • Viewport height MQs (personally I'm not sure they're that great because of serious mobile issues with toolbars)
  • Prevent outline: none; we need those dotted outlines!
  • CSS-only disclosures such as dropdowns; usually not keyboard accessible
  • Sometimes JavaScript is more accessible than CSS. (Need practical examples; https://gomakethings.com/i-was-wrong-about-javascript-free-dropdowns/)
  • Modals. (Is this truly a CSS issue? Currently I see it more as a design issue.)
  • [Your CSS/accessibility issue here]

Using the Web Speech API for Multilingual Translations

Css Tricks - Fri, 04/12/2019 - 5:19am

Since the early days of science fiction, we have fantasized about machines that talk to us. Today it is commonplace. Even so, the technology for making websites talk is still pretty new.

We can make our pages on the web talk using the SpeechSynthesis part of the Web Speech API. This is still considered an experimental technology but it has great support in the latest versions of Chrome, Safari, and Firefox.

The fun part for me is using this technology with foreign languages. For that, Mac OSX and most Windows installations have great support on all browsers. Chrome loads a set of voices remotely, so if your operating system does not have international voices installed, just use Chrome. We’re going to walk through a three-step process to create a page that speaks the same text in multiple languages. Some of the basic code is derived from documentation found here but the final product adds some fun features and can be viewed at my Polyglot CodePen here.

Screen shot of the completed Polyglot app with a menu of languages. Step 1: Start Simple

Let’s create a basic page with a <textarea> for the text we want the page to speak and include a button to click to trigger the speech.

<div id="wrapper"> <h1>Simple Text To Speech</h1> <p id="warning">Sorry, your browser does not support the Web Speech API.</p> <textarea id="txtFld">I love the sound of my computer-generated voice.</textarea> <label for="txtFld">Type text above. Then click the Speak button.</label> <div> <button type="button" id="speakBtn">Speak</button> <br> <p>Note: For best results on a Mac, use the latest version of Chrome, Safari, or FireFox. On Windows, use Chrome.</p> </div> </div>

The paragraph with ID warning will be shown only if the JavaScript detects no support for the Web Speech API. Also, note the ID values for the textarea and the button as we will use those in our JavaScript.

Feel free to style the HTML any way you’d like. You’re also free to work off the demo I created:

See the Pen
Text-To-Speech Part 1
by Steven Estrella (@sgestrella)
on CodePen.

Adding a style rule for the disabled state of the button is a good idea to avoid confusion for the few people who still use incompatible browsers, like the now-quaint Internet Explorer. Also, let’s use a style rule to hide the warning by default so we can control when it’s actually needed.

button:disabled { cursor: not-allowed; opacity: 0.3; } #warning { color: red; display: none; font-size: 1.4rem; }

Now on to the JavaScript! First, we add two variables to serve as references to the "Speak" button that triggers the speech and to the <textarea> element. An event listener at the bottom of the code tells the document to wait until the DOM elements load before calling the init() function. I used a handy utility function I call "qs" that is defined at the bottom of the code. It is a shortcut alternative to document.querySelector and it selects whatever selector value I pass to it and returns an object reference. Then we’ll add an event listener to the speakBtn object to make the button call the talk() function.

The talk() function creates a new instance of the SpeechSynthesisUtterance object that is part of the Web Speech API. It adds the text from the <textarea>(using ID txtFld) to the text property. Then the utterance is passed to the speechSynthesis method of the window object and we hear the spoken text. The specific voice you hear will vary by browser and operating system. On my Mac, for example, my default language is set to American English and the default voice for English is Alex. In Step 2, we will add code to create a menu to help the user choose voices for all available languages.

let speakBtn, txtFld; function init() { speakBtn = qs("#speakBtn"); txtFld = qs("#txtFld"); speakBtn.addEventListener("click", talk, false); if (!window.speechSynthesis) { speakBtn.disabled = true; qs("#warning").style.display = "block"; } } function talk() { let u = new SpeechSynthesisUtterance(); u.text = txtFld.value; speechSynthesis.speak(u); } // Reusable utility functions function qs(selectorText) { // Saves lots of typing for those who eschew jQuery return document.querySelector(selectorText); } document.addEventListener('DOMContentLoaded', function (e) { try {init();} catch (error) { console.log("Data didn't load", error); } }); Step 2: A Menu of International Voices

If we want to use anything other than the default language and speaking voice, we will have to add a bit more code. So that’s what we’re going tackle next.

We’re going to add a select element to hold the menu of voice options:

<h1>Multilingual Text To Speech</h1> <div class="uiunit"> <label for="speakerMenu">Voice: </label> <select id="speakerMenu"></select> speaks <span id="language">English.</span> <!-- etc. --> </div>

Before we create the code to populate the menu options, we should take care of the code that will help us connect language codes to their corresponding names. Each language is identified by a two-letter code such as "en" for English or "es" for Español (Spanish). We will take a simple list of these codes and their corresponding languages and make an array of objects of the form: {"code": "pt", "name": "Portuguese"}. Then we’ll need a utility function to help us search an array of objects for the value of a given property. We will use it in a few minutes to quickly find the language name that matches the language code of the selected voice. Copy the code below so that the two functions are just above and just below the // Generic Utility Functions comment.

function getLanguageTags() { let langs = ["ar-Arabic","cs-Czech","da-Danish","de-German","el-Greek","en-English","eo-Esperanto","es-Spanish","et-Estonian","fi-Finnish","fr-French","he-Hebrew","hi-Hindi","hu-Hungarian","id-Indonesian","it-Italian","ja-Japanese","ko-Korean","la-Latin","lt-Lithuanian","lv-Latvian","nb-Norwegian Bokmal","nl-Dutch","nn-Norwegian Nynorsk","no-Norwegian","pl-Polish","pt-Portuguese","ro-Romanian","ru-Russian","sk-Slovak","sl-Slovenian","sq-Albanian","sr-Serbian","sv-Swedish","th-Thai","tr-Turkish","zh-Chinese"]; let langobjects = []; for (let i=0;i<langs.length;i++) { let langparts = langs[i].split("-"); langobjects.push({"code":langparts[0],"name":langparts[1]}); } return langobjects; } // Generic Utility Functions function searchObjects(array, prop, term, casesensitive = false) { // Searches an array of objects for a given term in a given property // Returns an array of only those objects that test positive let regex = new RegExp(term, casesensitive ? "" : "i"); let newArrayOfObjects = array.filter(obj => regex.test(obj[prop])); return newArrayOfObjects; }

Now we can build out the options for the select element using JavaScript. We need to declare variables at the top of our JavaScript to hold references to the #speakerMenu select element, the #language span element, the array of synthesized voices (allVoices), an array of codes to identify the languages (langtags), and a place to keep track of the currently selected voice (voiceIndex). Add those just after the two variable declarations we created in Step 1.

let speakBtn, txtFld, speakerMenu, language, allVoices, langtags; let voiceIndex = 0;

The updated init() function sets some additional references to the #speakerMenu and the #language span and places all the language codes into an array of objects called langtags. The feature detection part of the code changes here, too. If the Web Speech API is supported, the setUpVoices() function is called. Also, for Chrome, we have to listen for changes to the loaded voices and repeat the setup when needed. Chrome polls the available voices every time you switch between one of its remote voices (the ones listed with the Google prefix while you are in Chrome) and all the other voices which are stored locally in the user’s operating system.

function init() { speakBtn = qs("#speakBtn"); txtFld = qs("#txtFld"); speakerMenu = qs("#speakerMenu"); language = qs("#language"); langtags = getLanguageTags(); speakBtn.addEventListener("click", talk, false); speakerMenu.addEventListener("change", selectSpeaker, false); if (window.speechSynthesis) { if (speechSynthesis.onvoiceschanged !== undefined) { // Chrome gets the voices asynchronously so this is needed speechSynthesis.onvoiceschanged = setUpVoices; } setUpVoices(); // For all the other browsers } else{ speakBtn.disabled = true; speakerMenu.disabled = true; qs("#warning").style.display = "block"; } }

The setUpVoices() function gets an array of what are called SpeechSynthesisVoice objects by calling the getVoices() method of the speechSynthesis object. This is done in our code using the getAllVoices() function. Unfortunately, I have found that the speechSynthesis.getVoices() method sometimes returns duplicates in the list, so I devoted nine lines of code to eliminate the those. Finally, at the end of getAllVoices(), I added a unique identifier number to each of the SpeechSynthesisVoice objects. That will help us in Step 3 when we need to filter the list of voices to only show voices for a given language. When complete, the allVoices array will contain objects that look like the ones below. Each object has id, voiceURI, name, and lang attributes. The localService attribute indicates whether the code for the voice is stored on the user’s computer or remotely on Google’s servers. Notice the lang attribute. The value consists of a two-letter language code (e.g. "es" for Spanish) followed by a dash and a region code (e.g. "MX" for Mexico). This identifies the language and regional accent of each voice.

{id:48, voiceURI:"Paulina", name:"Paulina", lang: "es-MX", localService:true}, {id:52, voiceURI:"Samantha", name:"Samantha", lang: "en-US", localService:true}, {id:72, voiceURI:"Google Deutsch", name:"Google Deutsch", lang: "de-DE", localService:false}

The last line of setUpVoices() calls a function to create the list of options that will appear in the #speakerMenu select element. The value of the id attribute for each voice is placed in the value attribute for the option. The name and lang attributes are the visible text items that appear in each option along with "(premium)" for those voices that are marked that way on some operating systems and browsers.

function setUpVoices() { allVoices = getAllVoices(); createSpeakerMenu(allVoices); } function getAllVoices() { let voicesall = speechSynthesis.getVoices(); let vuris = []; let voices = []; voicesall.forEach(function(obj,index) { let uri = obj.voiceURI; if (!vuris.includes(uri)) { vuris.push(uri); voices.push(obj); } }); voices.forEach(function(obj,index) {obj.id = index;}); return voices; } function createSpeakerMenu(voices) { let code = ; voices.forEach(function(vobj,i) { code += `<option value=${vobj.id}>`; code += `${vobj.name} (${vobj.lang})`; code += vobj.voiceURI.includes(".premium") ? ' (premium)' : ; code += `</option>`; }); speakerMenu.innerHTML = code; speakerMenu.selectedIndex = voiceIndex; }

You might recall that in the init() function, we had set up an event listener to call selectSpeaker() whenever the speakerMenu changes. The selectSpeaker() function stores the selectedIndex of the #speakerMenu select element. Next, it gets the value of the selected item which will be an integer that corresponds to the index of that voice in the allVoices() array. So, now we have retrieved the SpeechSynthesisVoice we want. We then grab the first two letters of the lang attribute (e.g. "en," "es," "ru," "de," "fr") and use that code to search the langtags array of language objects to find the appropriate language name. The searchObjects() function returns an array that will likely have only one entry. Regardless, the first entry (langcodeobj[0]) is all we need. Finally, we assign that name to the innerHTML attribute of the language span and it shows on the screen as expected.

// Code for when the user selects a speaker function selectSpeaker() { voiceIndex = speakerMenu.selectedIndex; let sval = Number(speakerMenu.value); let voice = allVoices[sval]; let langcode = voice.lang.substring(0,2); let langcodeobj = searchObjects(langtags, "code", langcode); language.innerHTML = langcodeobj[0].name; }

The only thing left for Step 2 to be complete is to make sure the talk() function works when we click the "Speak" button. Modify the talk() function to add attributes to the utterance to control which voice and language are used and how fast to speak the text. In my testing, a rate range of 0.5 to 2 works reliably well. I found that a rate below 0.5 has no effect. I think 0.8 works as a nice default for many languages, but as we’ll see in Step 3, there’s an easy way to let the user decide.

function talk() { let sval = Number(speakerMenu.value); let u = new SpeechSynthesisUtterance(); u.voice = allVoices[sval]; u.lang = u.voice.lang; u.text = txtFld.value; u.rate = 0.8; speechSynthesis.speak(u); }

That’s it for Step 2! Here’s the result of what we’ve done so far:

See the Pen
Text-To-Speech Part 2
by Steven Estrella (@sgestrella)
on CodePen.

Play around with it a bit. Sometimes it is fun to type an English phrase and then assign a French or German speaker to say it. Conversely, if you want to hear your worst first-year Spanish student, type a Spanish phrase and assign it to be spoken by an English voice.

Step 3: The Complete Polyglot

We’re in the final stretch! Some of the things we do in this step will be bits of polish to the UI but there are some functional things we need to do as well to button everything up. specifically, we’re going to:

  • Create a menu of available language options
  • Allow users to define the speed of the speech
  • Define a default phrase in the textarea that translates on language selection

Here’s what we’re looking at:

We’re adding a dropdown menu, speech rate setting, and a default phrase.

In the HTML, we’re going to add a new <select> element for the language menu and a number input (which will be used later to set the rate of speech). Notice we have deleted the #language span as it is no longer relevant once the language menu is working.

<div class="uiunit"> <label for="languageMenu">Language: </label> <select id="languageMenu"> <option selected value="all">Show All</option> </select> </div> <div class="uiunit"> <label for="speakerMenu">Voice: </label><select id="speakerMenu"></select> </div> <div class="uiunit"> <label for="rateFld">Speed: </label> <input type="number" id="rateFld" min="0.5" max="2" step="0.1" value="0.8" /> </div>

In the JavaScript, we will need to modify the variable declarations. We will keep track of all dialects in the allLanguages array and just the main languages in the primaryLanguages array. The langhash and langcodehash arrays will serve as hash tables so we can quickly get a language name when all we know is the two-letter language code and vice versa. We should only need to setup the languages menu once so a Boolean flag for initialSetup will come in handy.

let speakBtn, txtFld, speakerMenu, allVoices, langtags; let voiceIndex = 0; let allLanguages, primaryLanguages, langhash, langcodehash; let rateFld, languageMenu, blurbs; let initialSetup = true; let defaultBlurb = "I enjoy the traditional music of my native country.";

In the new init() function, let’s remove the line language = qs("#language"); then add the new code as seen here to create the blurbs, reference the rateFld number input and languageMenu select, and create hash tables for looking up language names and tags.

function init() { // ...keep existing content but delete language = qs("#language"); createBlurbs(); rateFld = qs("#rateFld"); languageMenu = qs("#languageMenu"); languageMenu.addEventListener("change", selectLanguage, false); langhash = getLookupTable(langtags, "name"); langcodehash = getLookupTable(langtags, "code"); if (window.speechSynthesis) { // ...keep existing content } else{ // ...keep existing content languageMenu.disabled = true; } }

The setUpVoices() function needs some work to accommodate the new languages menu and to trigger the filterVoices() function which we will use now to populate the #speakerMenu element. Also, we’re going to add the new functions: getAllLanguages() and getPrimaryLanguages(). The first one assembles an array of the unique values for the lang attribute found in the allVoices array of objects. Notice the return statement uses the spread operator combined with a new Set object to ensure that the returned array has no duplicates. The getPrimaryLanguages() function returns an array of the two-letter country codes. That makes a smaller list of just the main languages without reference to regional dialects.

function setUpVoices() { allVoices = getAllVoices(); allLanguages = getAllLanguages(allVoices); primaryLanguages = getPrimaryLanguages(allLanguages); filterVoices(); if (initialSetup && allVoices.length) { initialSetup = false; createLanguageMenu(); } } function getAllLanguages(voices) { let langs = []; voices.forEach(vobj => { langs.push(vobj.lang.trim()); }); return [...new Set(langs)]; } function getPrimaryLanguages(langlist) { let langs = []; langlist.forEach(vobj => { langs.push(vobj.substring(0,2)); }); return [...new Set(langs)]; }

The setUpVoices() function calls two additional functions. The filterVoices() function gets the two-letter language code from the current value of the #languageMenu select menu and uses it to filter the allVoices array and return only the available voice options for the chosen language. It then passes that array to the createSpeakerMenu() function (unchanged from Step 2) which populates the #speakerMenu with options. Then filterVoices() gets the blurb associated with the chosen language and places it in the textarea where it can be edited or replaced.

And, in case Chrome rebuilds this menu, the stored voiceIndex is used to restore the current selection. Next the createLanguageMenu() function uses our hash tables to create the needed menu options for the languageMenu select element. The selectLanguage() function is triggered whenever the user chooses a language. It then triggers filterVoices() and sets the #speakerMenu to display the first available option.

function filterVoices() { let langcode = languageMenu.value; voices = allVoices.filter(function (voice) { return langcode === "all" ? true : voice.lang.indexOf(langcode + "-") >= 0; }); createSpeakerMenu(voices); let t = blurbs[languageMenu.options[languageMenu.selectedIndex].text]; txtFld.value = t ? t : defaultBlurb; speakerMenu.selectedIndex = voiceIndex; } function createLanguageMenu() { let code = `<option selected value="all">Show All</option>`; let langnames = []; primaryLanguages.forEach(function(lobj,i) { langnames.push(langcodehash[lobj.substring(0,2)].name); }); langnames.sort(); langnames.forEach(function(lname,i) { let lcode = langhash[lname].code; code += `<option value=${lcode}>${lname}</option>`; }); languageMenu.innerHTML = code; } function selectLanguage() { filterVoices(); speakerMenu.selectedIndex = 0; }

In the utility functions section of the code toward the bottom, add the following code. This generic little utility will help you the next time you need to create a lookup table for an array of objects. In our case, we will use this to allow us to easily match a language code with its corresponding language name and vice versa.

function getLookupTable(objectsArray, propname) { return objectsArray.reduce((accumulator, currentValue) => (accumulator[currentValue[propname]] = currentValue, accumulator),{}); }

I added an array of text phrases, each of which is a translation of the English phrase, "I enjoy the traditional music of my native country." The language it’s displayed in will correspond to what’s selected in the language men.

Here we see the beauty of UTF-8 on full display. Above the getLanguagesTags() function, let’s add the code that generates all those translated blurbs. I only read Spanish, English, some Portuguese, and very little German, so I have to take on faith that Google Translate is providing accurate translations for the rest. If any of these is your native language, feel free to leave corrections in the comments.

function createBlurbs() { blurbs = { "Arabic" : "??? ?????? ????????? ????????? ????? ????.", "Chinese" : "????????????", "Czech" : "Mám rád tradi?ní hudbu mé rodné zem?.", "Danish" : "Jeg nyder den traditionelle musik i mit hjemland.", "Dutch" : "Ik geniet van de traditionele muziek van mijn geboorteland.", "English" : "I enjoy the traditional music of my native country.", "Finnish" : "Nautin kotimaassani perinteistä musiikkia.", "French" : "J'apprécie la musique traditionnelle de mon pays d'origine.", "German" : "Ich genieße die traditionelle Musik meiner Heimat.", "Greek" : "?????????? ??? ??????????? ??????? ??? ???????? ???.", "Hebrew" : "??? ???? ???????? ???????? ?? ??????.", "Hindi" : "??? ???? ??? ??? ?? ???????? ????? ?? ???? ???? ????", "Hungarian" : "Élvezem az én hazám hagyományos zenéjét.", "Indonesian" : "Saya menikmati musik tradisional negara asal saya.", "Italian" : "Mi piace la musica tradizionale del mio paese natale.", "Japanese" : "??????????????????", "Korean" : "?? ? ??? ?? ??? ???.", "Norwegian Bokmal" : "Jeg liker den tradisjonelle musikken i mitt hjemland.", "Polish" : "Lubi? tradycyjn? muzyk? mojego kraju.", "Portuguese" : "Eu gosto da música tradicional do meu país natal.", "Romanian" : "Îmi place muzica tradi?ional? din ?ara mea natal?.", "Russian" : "??? ???????? ???????????? ?????? ???? ?????? ??????.", "Slovak" : "Mám rád tradi?nú hudbu svojej rodnej krajiny.", "Spanish" : "Disfruto de la música tradicional de mi país natal.", "Swedish" : "Jag njuter av traditionell musik i mitt hemland.", "Thai" : "????????????????????????????????????????????????????", "Turkish" : "Ülkemdeki geleneksel müzikten zevk al?yorum." }; }

There’s one last thing: the numeric input for controlling the playback speed of the speech. Modify the talk() function to get the speech rate from the number input and we’re good to go!

Here’s the final product:

function talk() { ...// no changes except for the rateFld.value reference u.rate = Number(rateFld.value); speechSynthesis.speak(u); }

See the Pen
Polyglot: Text-To-Speech in Multiple Languages
by Steven Estrella (@sgestrella)
on CodePen.

A Real World Application

My interest in this technology started many years ago in 1990 when I created a 26-lesson curriculum as part of my dissertation. It was delivered using my first programming language, HyperCard, on a Macintosh Plus which had a primitive text-to-speech feature. I used that feature to provide some feedback to the user while they progressed through the material. More recently, in 2018, I created a free progressive web app called Buenos Verbos that helps Spanish language students search and filter a database of 766 verbs. The chosen verb is then fully conjugated and the user can click the forms to hear them spoken. So perhaps web pages might like to talk and with some imagination you may find reasons to encourage them. The question is: what will you make your website say next?

The post Using the Web Speech API for Multilingual Translations appeared first on CSS-Tricks.

Inline SVG… Cached

Css Tricks - Fri, 04/12/2019 - 4:28am

I wrote that using inline <svg> icons makes for the best icon system. I still think that's true. It's the easiest possible way to drop an icon onto a page. No network request, perfectly styleable.

But inlining code has some drawbacks, one of which is that it doesn't take advantage of caching. You're making the browser read and process the same code over and over as you browse around. Not that big of a deal. There are much bigger performance fish to fry, right? But it's still fun to think about more efficient patterns.

Scott Jehl wrote that just because you inline something doesn't mean you can't cache it. Let's see if Scott's idea can extend to SVG icons.

Starting with inline SVG

Like this...

<!DOCTYPE html> <html lang="en"> <head> <title>Inline SVG</title> <link rel="stylesheet" href="/styles/style.css"> </head> <body> ... <svg width="24" height="24" viewBox="0 0 24 24" class="icon icon-alarm" xmlns="http://www.w3.org/2000/svg"> <path id="icon-alarm" d="M11.5,22C11.64,22 11.77,22 11.9,21.96C12.55,21.82 13.09,21.38 13.34,20.78C13.44,20.54 13.5,20.27 13.5,20H9.5A2,2 0 0,0 11.5,22M18,10.5C18,7.43 15.86,4.86 13,4.18V3.5A1.5,1.5 0 0,0 11.5,2A1.5,1.5 0 0,0 10,3.5V4.18C7.13,4.86 5,7.43 5,10.5V16L3,18V19H20V18L18,16M19.97,10H21.97C21.82,6.79 20.24,3.97 17.85,2.15L16.42,3.58C18.46,5 19.82,7.35 19.97,10M6.58,3.58L5.15,2.15C2.76,3.97 1.18,6.79 1,10H3C3.18,7.35 4.54,5 6.58,3.58Z"></path> </svg> It's weirdly easy to toss text into browser cache as a file

In the above HTML, the selector .icon-alarm will fetch us the entire chunk of <svg> for that icon.

const iconHTML = document.querySelector(".icon-alarm").outerHTML;

Then we can plunk it into the browser's cache like this:

if ("caches" in window) { caches.open('static').then(function(cache) { cache.put("/icons/icon-wheelchair.svg", new Response( iconHTML, { headers: {'Content-Type': 'image/svg+xml'} } )); } }

See the file path /icons/icon-wheelchair.svg? That's kinda just made up. But it really will be put in the cache at that location.

Let's make sure the browser grabs that file out of the cache when it's requested

We'll register a Service Worker on our pages:

if (navigator.serviceWorker) { navigator.serviceWorker.register('/sw.js', { scope: '/' }); }

The service worker itself will be quite small, just a cache matcher:

self.addEventListener("fetch", event => { let request = event.request; event.respondWith( caches.match(request).then(response => { return response || fetch(request); }) ); }); But... we never request that file, because our icons are inline.

True. But what if other pages benefitted from that cache? For example, an SVG icon could be placed on the page like this:

<svg class="icon"> <use xlink:href="/icons/icon-alarm.svg#icon-alarm" /> </svg>

Since /icons/icon-alarm.svg is sitting there ready in cache, the browser will happily pluck it out of cache and display it.

(I was kind of amazed this works. Edge doesn't like <use> elements that link to files, but that'll be over soon enough.)

And even if the file isn't in the cache, assuming we actually chuck this file on the file system likely the result of some kind of "include" (I used Nunjucks on the demo).

But... <use> and inline SVG aren't quite the same

True. What I like about the above is that it's making use of the cache and the icons should render close to immediately. And there are some things you can style this way — for example, setting the fill on the parent icon should go through the shadow DOM that the <use> creates and colorize the SVG elements within.

Still, it's not the same. The shadow DOM is a big barrier compared to inline SVG.

So, enhance them! We could asynchronously load a script that finds each SVG icon, Ajaxs for the SVG it needs, and replaces the <use> stuff...

const icons = document.querySelectorAll("svg.icon"); icons.forEach(icon => { const url = icon.querySelector("use").getAttribute("xlink:href"); // Might wanna look for href also fetch(url) .then(response => response.text()) .then(data => { // This is probably a bit layout-thrashy. Someone smarter than me could probably fix that up. // Replace the <svg><use></svg> with inline SVG const newEl = document.createElement("span"); newEl.innerHTML = data; icon.parentNode.replaceChild(newEl, icon); // Remove the <span>s const parent = newEl.parentNode; while (newEl.firstChild) parent.insertBefore(newEl.firstChild, newEl); parent.removeChild(newEl); }); });

Now, assuming this JavaScript executes correctly, this page has inline SVG available just like the original page did.

Demo & Repo

Accessibility Events

Css Tricks - Thu, 04/11/2019 - 7:56am

“There isn't some way to know when—…?”

There is always a pause here. The client knows what they're asking, and I know what they're asking, but putting it into words—saying it out loud—turns unexpectedly difficult.

In the moments before the asking, it was a purely technical question—no different from "can we do this when a user is on their phone." But there's always a pause, because this question doesn't come easy; not like all the other questions about browsers and connection speeds did. A phrase like "in an assisted browsing context" doesn't spring to mind as readily as "on a phone," "in Internet Explorer," or "on a slow connection." The former, well, that's something I would say—a phrase squarely in the realm of accessibility consultants. The latter the client can relate to. They have a phone, they've used other browsers, they've been stuck with slow internet connections.

“There isn't some way to know when—… a user is… using something like a screen reader…?”

An easy question that begets a complicated answer is standard fare for almost any exchange with a web developer. This answer has, for a long time, been a refreshing deviation from that norm: "no, we can't."

The matter is, I'll offer, technically impossible; computers, you see, can't talk to each other that way. Often, there's a palpable relief here: "no" to the technical part; "no" to the the computers part. That is, of course, all they had meant to ask. I truly believe that.

Even if we could, I'll explain, we wouldn't really want to. Forking our codebase that way would put more burden on us maintainers, not less. There's an easy parallel to the "when they're on a phone" conversation, here; one we've surely had already. We can never know a user's browsing context for certain, and making assumptions will only get us and our users into trouble. Whenever a feature, component, or new design treatment was added or changed, we'd be left having all the same conversations around how to translate it over to the "accessible" experience. If those features aren't essential in the first place, well, are they worth having at all? If those features are essential—well, we'll still need to find a way to make them work in both contexts.

It could seem like an enticing option for our users, at first glance: an enhanced, fully-featured website, on the one hand, a fully accessible alternative experience on the other. That unravels with even the slightest examination, though: if the fully-featured website isn't accessible, the accessible website won't be fully featured. By choosing to have the "accessible experience" deviate from the "real website," we end up drawing a sharper line between those two definitions, and we nudge the "accessible experience" closer to an afterthought—limited and frustratingly out-of-sync with the "real" website, like so many dedicated mobile sites quickly became.

There's never any disagreement, here. Again: this is all relatable. We've all found ourselves inescapably opted into using the "mobile" version of a website at some point. We've been here before as users; we've made these mistakes before as developers. We know better now.

But this isn't a strictly technical question. This isn't as simple as browser features and screen sizes—a question of one privileged browsing context or another. Technical questions come easy. Partway through the asking—in the hesitation, in the pause, in the word stumbled over—what was meant to be a mundane development question became something much more fraught. Because there was a word that fit.

“Is there a way we can know when a user has a disability?”

The easy "no" felt empowering; a cop-out. "It doesn't matter; it can't be done" in response to a deeply fraught question was an unexpected balm for both the asked and the answered. There was, again, that palpable relief—"no" to the technical part; "no" to the the computers part. That was, of course, all they had meant to ask.

We no longer have that easy answer. In iOS 12.2 and MacOS 10.14.4, a toggle switch has appeared in Apple's VoiceOver preferences, innocuously labeled "accessibility events." It was rolled out to no fanfare—short of a brief mention in Apple's iPhone User Guide—and we're still not sure how it's meant to be used. The most generous interpretation of the intention behind this feature is that it was developed with the same intention as a "UA string"-style identifier for users browsing via VoiceOver.

We do know this much: when this setting is enabled—and it is, by default—your browser will identify you as using VoiceOver to help you browse the web. If you're using Apple's VoiceOver, both your phone and your computer will broadcast your assumed disability to the entire internet, unless and until you specifically tell it to stop.

If you're not furious at this change, you should be—not just for what it means for users, but what it foists upon you. Apple has burdened you with the knowledge that, now, yes, you can know whether a user has a disability. We can use this information to serve up a limited alternative version of a website, into which we can very easily opt people of a protected class. And once we choose to start listening for "accessibility events," well, we can capture that information, as anything else broadcast to the web. A user's disability can and will be reduced to a single data point—a cold, impersonal true, inexorably bound to their name, stored in a database, perhaps destined to be sold, leaked, passed along to insurance providers, reduced to a targeted marketing opportunity. All under the auspice of inclusivity.

At some point, the developers responsible for the "accessibility events" feature were, I'm certain, asked whether such a feature were possible. Their answer was "yes." I don't doubt that they meant well. I'm just as certain that, in the moment, it felt like the right answer; a technical solution to a technical problem, and a simple matter of browsing context.

Someday—not far in the future, I trust—I'll be asked a similar question. It will be asked hesitantly, haltingly. The pauses will ring all too familiar. I will no longer have the easy, familiar comfort of technical impossibility—no easy "no" to insulate me from the uncomfortable conversations I should have been having with clients all along. Now, there's no technical reason that I can't know whether a user is using "something like a screen reader." I—my clients, their databases, their organizations, their parent companies, their partners, their VC funders, their advertisers, and so on unto infinity—can absolutely know when a user is disabled.

But I won't play a part in helping to propagate the mistake Apple's developers made. I'll let my answer hang heavy and uncomfortable in the air: no. Not because we can't—we can. Not because we shouldn't, though, no, we still shouldn't. No—now, I will allow the word to become as coarse as I had always wanted it to be, because I no longer have the cold comfort of "well, technically" to hide behind.

No.

 

The post Accessibility Events appeared first on CSS-Tricks.

Edge Goes Chromium: What Does it Mean for Front-End Developers?

Css Tricks - Thu, 04/11/2019 - 4:32am

In December 2018, Microsoft announced that Edge would adopt Chromium, the open source project that powers Google Chrome. Many within the industry reacted with sadness at the loss of browser diversity. Personally, I was jubilant. An official release date has yet to be announced, but it will be at some point this year. With its release, a whole host of HTML, JavaScript and CSS features will have achieved full cross-browser support.

The preview build is now available for Windows, and coming soon for Mac.

&#x1f4e3; Calling all web devs and tinkerers! &#x1f4e3;

The first preview builds of the next version of #MicrosoftEdge are ready for you – try it out now! https://t.co/I531hfmD3G pic.twitter.com/Tvh6OGGouO

— Microsoft Edge Dev (@MSEdgeDev) April 8, 2019

Not so long ago, I penned an article titled "The Long Slow Death of Internet Explorer." Some of us are lucky enough have abandoned that browser already. But it wasn’t the only thing holding us back. Internet Explorer was the browser we all hated and Edge was meant to be its much-improved replacement. Unfortunately, Edge itself was quite the laggard. EdgeHTML is a fork of Trident, the engine that powered Internet Explorer. Microsoft significantly under-invested in Edge. The apple didn’t fall far from the tree. Edge’s User Voice website was a nice idea, allowing developers to vote for which features they wanted to be implemented. Unfortunately, as Dave Rupert put it, voting on the site was "like throwing coins in a wishing well." The most requested features were left unimplemented for years.

There are a lot of features that pre-Chromium Edge doesn’t currently support but are available in other modern browsers and, once they’ve made the switch, we’ll be able to use them. Many of them can’t be polyfilled or worked around, so this release is a big deal.

Features we can look forward to using

So just what are those features, exactly? Let’s outline them right here and start to get excited about all the new things we’ll be able to do.

Custom Elements and Shadow DOM

Together, custom elements and shadow DOM allow developers to define custom, reusable and encapsulated components. A lot of people were asking for this one. People have been voting for its implementation since 2014, and we’re finally getting it.

HTML details and summary elements

The <details> and <summary> elements are part of HTML5 and have been supported since 2011 in Chrome. Used together, the elements generate a simple widget to show and hide content. While it is trivial to implement something similar using JavaScript, the <details> and <summary> elements work even when JavaScript is disabled or has failed to load.

See the Pen
details/summary
by CSS GRID (@cssgrid)
on CodePen.

Javascript Font Loading API

This one means a lot to some people. All modern browsers now support the CSS font-display property. However, you still might want to load your fonts with JavaScript. Font-loading monomaniac Zach Leatherman has an explainer of why you might want to load fonts with JavaScript even though we now have broad support for font-display. Ditching polyfills for this API is important because this JavaScript is, according to Zach:

[...] usually inlined in the critical path. The time spent parsing and executing polyfill JavaScript is essentially wasted on browsers that support the native CSS Font Loading API."

In an article from 2018, Zach lamented:

[...] browser-provided CSS Font Loading API has pretty broad support and has been around for a long time but is confoundedly still missing from all available versions of Microsoft Edge."

No longer!

JavaScript flat and flatMap

Most easily explained with a code snippet, flat() is useful when you have an array nested inside another array.

const things = ['thing1', 'thing2', ['thing3', ['thing4']]] const flattenedThings = things.flat(2); // Returns ['thing1', 'thing2', 'thing3', 'thing4']

As its name suggests, flatMap() is equivalent to using both the map() method and flat().

These methods are also supported in Node 11. &#x1f389;

JavaScript TextEncoder and TextDecoder

TextEncoder and TextDecoder are part of the encoding spec. They look to be useful when working with streams.

JavaScript Object rest and object spread

These are just like rest and spread properties for arrays.

const obj1 = { a: 100, b: 2000 } const obj2 = { c: 11000, d: 220 } const combinedObj = {...obj1, ...obj2} // {a: 100, b: 2000, c: 11000, d: 220} JavaScript modules: dynamic import

Using a function-like syntax, dynamic imports allow you to lazy-load ES modules when a user needs them.

button.addEventListener("click", function() { import("./myModule.js").then(module => module.default()); }); CSS background-blend-mode property

background-blend-mode brings Photoshop style image manipulation to the web.

CSS prefers-reduced-motion media query

I can’t help feeling that not making people feel sick should be the default of a website, particularly as not all users will be aware that this setting exists. As animation on the web becomes more common, it’s important to recognize that animation can cause causes dizziness, nausea and headaches for some users.

CSS caret-color property

Admittedly a rather trivial feature, and one that could have safely and easily been used as progressive enhancement. It lets you style the blinking cursor in text input fields.

8-digit hex color notation

It’s nice to have consistency in a codebase. This includes sticking to either
the RGB, hexadecimal or HSL color format. If your preferred format is hex, then you had a problem because it required a switch to rgba() any time you needed to define transparency. Hex can now include an alpha (transparency) value. For example, #ffffff80 is equivalent to rgba(255, 255, 255, .5). Arguably, it’s not the most intuitive color format and has no actual benefit over rgba().

Intrinsic sizing

I’ve not seen as much hype or excitement for intrinsic sizing as some other new CSS features, but it’s the one I’m personally hankering for the most. Intrinsic sizing determines sizes based on the content of an element and introduces three new keywords into CSS: min-content, max-content and fit-content(). These keywords can be used most places that you would usually use a length, like height, width, min-width, max-width, min-height, max-height, grid-template-rows, grid-template-columns, and flex-basis.

CSS text-orientation property

Used in conjunction with the writing-mode property, text-orientation, specifies the orientation of text, as you might expect.

See the Pen
text-orientation: upright
by CSS GRID (@cssgrid)
on CodePen.

CSS :placeholder-shown pseudo-element

placeholder-shown was even available in Internet Explorer, yet somehow never made it into Edge... until now. UX research shows that placeholder text should generally be avoided. However, if you are using placeholder text, this is a handy way to apply styles conditionally based on whether the user has entered any text into the input.

CSS place-content property

place-content is shorthand for setting both the align-content and justify-content.

See the Pen
place-content
by CSS GRID (@cssgrid)
on CodePen.

CSS will-change property

The will-change property can be used as a performance optimization, informing the browser ahead of time that an element will change. Pre-Chromium Edge was actually good at handling animations performantly without the need for this property, but it will now have full cross-browser support.

CSS all property

https://css-tricks.com/almanac/properties/a/all/">all is a shorthand for setting all CSS properties at once.

For example, setting button { all: unset; } is equivalent to:

button { background: none; border: none; color: inherit; font: inherit; outline: none; padding: 0; }

Sadly, though, the revert keyword still hasn’t been implemented anywhere other than Safari, which somewhat limits the mileage we can get out of the all property.

CSS Shapes and Clip Path

Traditionally, the web has been rectangle-centric. It has a box model, after all. While we no longer need floats for layout, we can use them creatively for wrapping text around images and shapes with the shape-outside property. This can be combined with the clip-path property, which brings the ability to display an image inside a shape.

Clippy is an online clip-path editor CSS :focus-within pseudo-class

If you want to apply special styles to an entire form when any one of its inputs are in focus, then :focus-within is the selector for you.

CSS contents keyword

This is pretty much essential if you’re working with CSS grid. This had been marked as "not planned" by Edge, despite 3,920 votes from developers.

For both flexbox and grid, only direct children become flex items or grid items, respectively. Anything that is nested deeper cannot be placed using flex or grid-positioning. In the words of the spec, when display: contents is applied to a parent element, "the element must be treated as if it had been replaced in the element tree by its contents," allowing them to be laid out with a grid or with flexbox. Chris goes into a more thorough explanation that’s worth checking out.

There are, unfortunately, still some bugs with other browser implementations that affect accessibility.

The future holds so much more promise

We’ve only looked at features that will be supported by all modern browsers when Edge makes the move to Chromium. That said, the death of legacy Edge also makes a lot of other features feel a lot closer. Edge was the only browser dragging its feet on the Web Animation API and that showed no interest in any part of the Houdini specs, for example.

Credit: https://ishoudinireadyyet.com The impact on browser testing Testing in BrowserStack (left) and various browser apps on my iPhone (right)

Of course, the other huge plus for web developers is less testing. A lot of neglected Edge during cross-browser testing, so Edge users were more likely to have a broken experience. This was the main reason Microsoft decided to switch to Chromium. If your site is bug-free in one Chromium browser, then it’s probably fine in all of them. In the words of the Edge team, Chromium will provide "better web compatibility for our customers and less-fragmentation of the web for all web developers." The large variety of devices and browsers makes browser testing one of the least enjoyable tasks that we’re responsible for as front-end developers. Edge will now be available for macOS users which is great for the many of us who work on a Mac. A subscription to BrowserStack will now be slightly less necessary.

Do we lose anything?

To my knowledge, the only feature that was supported everywhere except Chrome is SVG color fonts, which will no longer work in the Edge browser. Other color font formats (COLR, SBIX, CBDT/CBLC) will continue to work though.

Uh, @GoogleChrome Are you planning to support #OpenTypeSVG soon? Supported in Safari (12+), Firefox (26+) even EdgeHTML (38+) Photoshop, Illustrator - but not Chrome
/cc @colorfontswtf pic.twitter.com/tgwJ3AqHm2

— Chris Lilley (@svgeesus) February 15, 2019

What about other browsers?

Admittedly, Edge wasn’t the last subpar browser. All the features in this article are unsupported in Internet Explorer, and always will be. If you have users in Russia, you’ll need to support Yandex. If you have users in Africa, you’ll need to support Opera Mini. If you have users in China, then UC and QQ will be important to test against. If you don’t have these regional considerations, there’s never been a better time to ditch support for Internet Explorer and embrace the features the modern web has to offer. Plenty of PC users have stuck with Internet Explorer purely out of habit. Hopefully, a revamped Edge will be enough to tempt them away. An official Microsoft blog entry titled "The perils of using Internet Explorer as your default browser" concluded that, "Internet Explorer is a compatibility solution...developers by and large just aren’t testing for Internet Explorer these days." For its remaining users, the majority of the web must look increasingly broken. It’s time to let it die.

Is Google a megalomaniac?

Life is about to get easier for web developers, yet the response to the Microsoft’s announcement was far from positive. Mozilla, for one, had a stridently pessimistic response, which accused Microsoft of "officially giving up on an independent shared platform for the internet." The statement described Google as having "almost complete control of the infrastructure of our online lives" and a "monopolistic hold on unique assets." It concluded that "ceding control of fundamental online infrastructure to a single company is terrible."

Many have harked back to the days of IE6, the last time a browser achieved such an overwhelming market share. Internet Explorer, having won the browser war, gave in to total stagnation. Chrome, by contrast, ceaselessly pushes new features. Google participates actively with the web standards bodies the W3C and the WHATWG. Arguably though, it has an oversized influence in these bodies and the power to dictate the future shape of the web. Google Developer Relations does have a tendency to hype features that have shipped only in Chrome.

From competition to collaboration

Rather than being the new IE, Edge can help innovate the web forward. While it fell behind in many areas, it did lead the way for CSS grid, CSS exclusions, CSS regions and the new HTML imports spec. In a radical departure from historical behavior, Microsoft have become one of the world’s largest supporters of open source projects. That means all major browsers are now open source. Microsoft have stated that they intend to become a significant contributor to Chromium — in fact, they’ve already racked up over 300 merges. This will help Edge users, but will also benefit users of Chrome, Opera, Brave, and other Chromium-based browsers.

The post Edge Goes Chromium: What Does it Mean for Front-End Developers? appeared first on CSS-Tricks.

Get a CSS Custom Property Value with JavaScript

Css Tricks - Thu, 04/11/2019 - 4:31am

Here’s a neat trick from Andy Bell where he uses CSS Custom Properties to check if a particular CSS feature is supported by using JavaScript.

Basically, he's using the ability CSS has to check for browser support on a particular property, setting a custom property that returns a value of either 0 or 1 (Boolean!) and then informing the JavaScript to execute based on that value.

Here's his example:

.my-component { --supports-scroll-snap: 0; } @supports (scroll-snap-type: x mandatory) { .my-component { --supports-scroll-snap: 1; } } const myComponent = document.querySelector('.my-component'); const isSnapSupported = getCSSCustomProp('--supports-scroll-snap', myComponent, 'boolean');

As Andy mentions, another common way to do this is to use pseudo elements on the body element and then access them with JavaScript, but this approach with @supports seems a whole lot cleaner and less hacky to me, personally. I wonder how many more intuitive things we’ll find we can do with CSS Custom Properties because this is an interesting case where CSS instructs JavaScript, instead of the other way around.

Direct Link to ArticlePermalink

The post Get a CSS Custom Property Value with JavaScript appeared first on CSS-Tricks.

While solving for collaboration, we built a product that our own teams love and use everyday!

Css Tricks - Thu, 04/11/2019 - 4:29am

(This is a sponsored post.)

Flock is a messaging and collaboration tool built for both designers and developers. With close-to-zero setup, it brings together all your team’s conversations, appointments, and files in one place, helping you spend more time on what you are best at — building awesome stuff!

Building software is hard. Building software that is a delight to use every day is even harder, given the exacting standards most of us in the design and development community have for our tools. So, when we set out to change how people communicate in the modern workplace, we had but one goal - build something that we would objectively love!

Today, thousands of design and development teams use Flock every day, validating our UX-led approach to building a team collaboration tool for all kinds of teams. But how did we get here? Here’s our story.

At Flock, our designers frequently share creatives and design assets with the rest of the organization and using email to share links to files gets real old, real fast. So we started by looking at one of the biggest challenges to efficient collaboration at work — the "app-juggling" one had to master even for something as simple as sharing a file.

"Emails on one platform, files on another, real-time conversations on yet another one, and we would often need to shuffle between these apps to find and share relevant files with team members. That was an invisible time-sink!"
—Aaron Durham, Designer at Flock

We realized that bringing together our files and the conversations around them in one place would save us a lot of time and effort, and built integrations for Flock with Google Drive, OneDrive, Box, and Dropbox. Now, it is incredibly easy to find and share relevant files from the sidebar and discuss them with the team immediately, with dynamic previews and permission controls.

The next challenge we tackled was the time spent in getting feedback on designs and prototypes from colleagues in our geographically distributed design and development team. We knew that it was difficult to convey visual feedback on creatives through plain text/emails because our designers often struggled to understand what part of an illustration the feedback was aimed at.

And then, we thought, "Wouldn’t it be so much easier if we could hop on a call and show colleagues exactly what we see?" So, we built a seamless video and audio conferencing experience into Flock that allows us to start a video call with one or more team members and walk them through the feedback by sharing screens.

Like most startups, we have a few irons in the fire at any given time. So, one group of designers and developers might be working on a prototype of our newest product while another group works on landing pages for a marketing campaign. Conversations around these projects need to happen simultaneously and seamlessly. But with a team of over a hundred rock-stars, it’s difficult to keep track of conversations on various projects and keep those conversations on track. We had to create a system that accomplished both.

So, for every project, the Project Lead creates a Channel on Flock (a group conversation) where everyone involved can discuss the project. We create other channels for shared interests and water-cooler chats, so conversations in project channels are focused and more efficient.

Another reason for the dreaded "app-juggling" act? Our designers and developers use a lot of apps and services that they have to check for updates at various times of the day. So we built integrations for third-party services right into Flock. Now, team members receive notifications from all their favorite apps in one place — Flock — and can choose to take action when required.

Our App Store has over 60 integrations with popular third-party business apps and services, so we can work with all our favorite tools in one place. And we can connect hundreds of applications and web services to Flock using Zapier and IFTTT. From Dribble and Asana to Jira and GitHub, we connect almost every service we use to Flock. Last but not least, developers can build custom apps and integrations using our open API.

Many early adopters of Flock were teams with designers and developers who were happy to share feedback. We found that a lot of these teams worked with external consultants or clients, particularly at creative agencies. And these conversations were, again, on email, on the phone or, sometimes, verbal instructions with no record for later reference.

To ensure all these conversations could be brought into one window, we created Guests in Flock, an incredibly simple way of adding external collaborators to team workflows while maintaining a firewall of access between conversations within the team and conversations with guest users. This makes it easier to collaborate with clients and consultants, feedback can be shared and acted upon in real-time, and the built-in image annotation feature allows designers to share visual feedback on creatives.

Our thinking from the get-go has been that effective communication is a basic utility in every workplace, and it should add to productivity way more than it does to expenses. Which is why we priced Flock starting from free, with an option to unlock all functionality for $4.50 a user per month on the Pro plan — a third as much as our competitors.

Our designers and developers have found incredible success in building Flock and becoming its first power users, and the business case for adopting a team collaboration platform has never been clearer. Whether one wants to discuss ideas, share collateral's, collect feedback from teammates and clients, or get code-push notifications from Gitlab, Flock just works.

Try Flock Now

Direct Link to ArticlePermalink

The post While solving for collaboration, we built a product that our own teams love and use everyday! appeared first on CSS-Tricks.

Using “box shadows” and clip-path together

Css Tricks - Wed, 04/10/2019 - 1:55pm

Let's do a little step-by-step of a situation where you can't quite do what seems to make sense, but you can still get it done with CSS trickery. In this case, it'll be applying a shadow to a shape.

You make a box .tag { background: #FB8C00; color: #222; font: bold 32px system-ui; padding: 2rem 3rem 2rem 4rem; } You fashion it into a nice tag shape

You use clip-path because it's great for that.

.tag { /* ... */ clip-path: polygon(30px 0%, 100% 0%, 100% 100%, 30px 100%, 0 50%) } You want a shadow on it, so you...

Try to use box-shadow.

.tag { /* ... */ box-shadow: 5px 5px 10px rgba(0, 0, 0, 0.5); }

But it doesn't work. Nothing shows up. You think you're going crazy. You assume you have the syntax wrong. You don't. The problem is that clip-path is cutting it off.

You can drop-shadow a parent element instead

There is a filter that does shadows as well: drop-shadow(). But you can't use it directly on the element because the clip-path will cut it off as well. So you make a parent:

<span class="tag-wrap"> <span class="tag"> Tag </span> </span>

You can't use box-shadow on that parent either, because the parent is still a rectangle and the shadow will look wrong. But you can use filter, and the shadow will follow the shape.

See the Pen
Shadow on Shape
by Chris Coyier (@chriscoyier)
on CodePen.

That's all.

The post Using “box shadows” and clip-path together appeared first on CSS-Tricks.

Under-Engineered Toggles

Css Tricks - Wed, 04/10/2019 - 5:39am

Toggles. Switches. Whatever you want to call them, they've been with us for some time and have been a dominant a staple for many form interfaces. They're even baked right into many CSS frameworks, including Bootstrap and Foundation.

It's easy to think of them in binary terms: on and off. Off and on. Click to change the state and call it a day, right? I mean, it's just a checkbox with a styled visual representation.

Well, Adrian Roselli shows us that there's a lot more to think about. And, not only that, but he shows how we can under-engineer them:

I am only going to provide styles to visually convert a standard checkbox into a visual toggle. No ARIA, no script, no special features. A progressively enhanced checkbox that will continue to work if the CSS file does not load

There's a lot to digest here. His approaches to accessibility run the gamut, from hover, active, focus and disabled states to contrast in both light and dark modes, and many things in between. What's particularly key is the progressive enhancement he mentions in that quote above.

I think the most interesting thing about Adrian’s post is just how flexible his approach is to handle any situation, including color schemes and writing modes. He also takes note of the indeterminate checkbox, that state that's nether on or off, but something perhaps in between. We have a CSS pseudo-selector for that and it could warrant a post its own, given that it's a purely visual state that cannot be set in the HTML and needs to be registered via JavaScript. It's interesting to think of an "in between" state for a switch and Adrian's use case for the default state Airplane Mode is pretty compelling.

It’s an awful lot of work that we have to do to ensure that the front-end is designed well and I think this post is the best example I’ve seen in a while as to why our work is not a problem to be solved and more of a challenge to better understand the tools of our craft.

Direct Link to ArticlePermalink

The post Under-Engineered Toggles appeared first on CSS-Tricks.

In Defense of the Ternary Statement

Css Tricks - Wed, 04/10/2019 - 4:31am

Some months ago I was on Hacker News (as one does) and I ran across a (now deleted) article about not using if statements. If you’re new to this idea (like I was), you’re in a for a real treat. Just search for "if statements" on Hacker News. You'll get articles proposing that you might not need them, articles that refer to them as a code smell and even the quintessential "considered harmful." Listen, you know a programming concept is legit when people start suggesting that using it is actually gonna hurt somebody.

And if that's not enough for you, there is always the "Anti-If Campaign." If you join, you get a nifty banner and your name on the website. IF you join. Oh the sweet, sweet irony.

The first time that I ran across this bizarre "if anathema" phenomenon, I thought it was interesting, but probably just more people mad on the internet. You are always one Google search away from finding someone who is mad about anything. Like this person who hates kittens. KITTENS.

Some time later, I was watching Linus Torvald's TED interview. In that interview, he shows two slides. The first slide contains code that he deems is "bad taste."

And the second is that same code, but in what Linus would consider, "good taste."

I realize that Linus is a bit of a polarizing figure, and you might not agree with the "good taste" vs. "bad taste" phrasing. But I think we can universally agree that the second slide is just easier on the old eye balls. It's concise, has fewer logical paths to follow, and contains no if statement. I want my code to look like that. It doesn't have to be some genius algorithm (it never will be), but I think it can be clean, and remember what Billy Corgan of Smashing Pumpkins said about cleanliness...

Cleanliness is godliness. And god is empty. Just like me.

- Billy Corgan, "Zero"

So dark! But what an amazing album.

Aside from making your code look cluttered, if statements, or "branching logic," requires your brain to hold and evaluate two separate paths at one time along with all of the things that might occur on those paths. If you nest if statements, the problem intensifies because you are creating and tracking a decision tree and your brain has to bounce around all over that tree like a drunk monkey. This kind of thing is what makes code hard to read. And remember, you should write your code thinking of the moron who comes after you that is going to have to maintain it. And that moron is probably you.

As my own favorite moron, I've been making a conscious effort lately to avoid writing if statements in my JavaScript. I don't always succeed, but what I've noticed is that at the very least, it forces me to think about solving the problem from an entirely different angle. It makes me a better developer because it compels me to engage a part of my brain that is otherwise sitting on a beanbag eating peanut M&M's while the if statement does all the work.

In the process of not writing if statements, I’ve discovered my love for the way JavaScript lets you compose conditional logic with ternary statements and logical operators. What I would like to propose to you now is that ternary has gotten a bad rap, and you can use it along with the && and || operators to write some pretty concise and readable code.

The much maligned ternary

When I first started as a programmer, people used to say, "Never use a ternary. They are too complex." So I didn’t use them. Ever. I never used a ternary. I never even bothered to question whether or not those people were right.

I don't think they were.

Ternaries are just one-line if statements. Suggesting that they are implicitly too complicated in any form is just... not true. I mean, I'm not the frostiest donut in the box, but I have no problems at all understanding a simple ternary. Is it possible that we are infantilizing ourselves here just a tad when we say to always avoid them. I think that a well-structured ternary beats an if statement every time.

Let’s take a simple example. Say we have an application where we want to test and see if the user is logged in. If they are, we send them to their profile page. Otherwise, we send them to the home page. Here is the standard if statement to do that...

if (isLogggedIn) { navigateTo('profile'); } else { navigateTo('unauthorized'); }

That's a damn simple operation to split out over six lines. SIX LINES. Remember that every time you move through a line of code, you have to remember the code that came above it and how it affects the code below it.

Now the ternary version...

isLoggedIn ? navigateTo('profile') : navigateTo('unauthorized');

Your brain only has to evaluate one line here, not six. You don’t have to move between lines, remembering what was on the line before.

One of the drawbacks to the ternary, though, is that you cannot evaluate for only one condition. Working from the previous example, if you wanted to navigate to the profile page if the user was logged in, but take no action at all if they weren't, this won't work...

// !! Doesn't Compile !! logggedIn ? navigateTo('profile')

You would have to write out an actual if statement here. Or would you?

There is a trick that you can use in JavaScript when you only want to evaluate one side of the condition and you don't want to use an if statement. You do this by leveraging the way JavaScript works with the || (or) and && (and) operators.

loggedIn && navigateTo('profile');

How does that work!?

What we're doing here is asking JavaScript, "Are both of these things true?" If the first item is false, there is no reason for the JavaScript virtual machine to execute the second. We already know that both of them aren't true because one of them is false. We're exploiting the fact that JavaScript won't bother to evaluate the second item if the first one is false. This is the equivalent of saying, "If the first condition is true, execute the second."

Now what if we wanted to flip this around? What if we wanted to navigate to the profile page only if the user is not logged in? You could just slap a ! in front of the loggedIn variable, but there is another way.

loggedIn || navigateTo('profile');

What this says is, "Are either of these things true?" If the first one is false, it has to evaluate the second to know for sure. If the first one is true though, it will never execute the second because it already knows that one of them is true; therefore the whole statement is true.

Now, is that better than just doing this?

if (!loggedIn) navigateTo('profile');

No. In that form, it is not. But here’s the thing: once you know that you can use the && and || operators to evaluate equality outside of if statements, you can use them to vastly simplify your code.

Here is a more complex example. Say we have a login function where we pass a user object. That object may be null, so we need to check local storage to see if the user has a saved session there. If they do, and they are an admin user, then we direct them to a dashboard. Otherwise, we send them to a page that tells them they are unauthorized. Here is what that looks like as a straight-up if statement.

function login(user) { if (!user) { user = getFromLocalStorage('user'); } if (user) { if (user.loggedIn && user.isAdmin) { navigateTo('dashboard'); } else { navigateTo('unauthorized'); } } else { navigateTo('unauthorized'); } }

Ouch. This is complicated because we're doing a lot of null condition checking on the user object. I don't want this post to be too strawman-y, so let's simplify this down since there is a lot of redundant code here that we would likely refactor into other functions.

function checkUser(user) { if (!user) { user = getFromLocalStorage('user'); } return user; } function checkAdmin(user) { if (user.isLoggedIn && user.isAdmin) { navigateTo('dashboard'); } else { navigateTo('unauthorized'); } } function login(user) { if (checkUser(user)) { checkAdmin(user); } else { navigateTo('unauthorized'); } }

The main login function is simpler, but that's actually more code and not necessarily “cleaner” when you consider the whole and not just the login function.

I would like to propose that we can do all of this in two lines if we forgo the if statements, embrace the ternary, and use logical operators to determine equality.

function login(user) { user = user || getFromLocalStorage('user'); user && (user.loggedIn && user.isAdmin) ? navigateTo('dashboard') : navigateTo('unauthorized') }

That's it. All of that noise generated by if statements collapses down into two lines. If the second line feels a bit long and unreadable to you, wrap it so that the conditions are on their own line.

function login(user) { user = user || getFromLocalStorage("user"); user && (user.loggedIn && user.isAdmin) ? navigateTo("dashboard") : navigateTo("unauthorized"); }

If you are worried that maybe the next person won't know about how the && and || operators work in JavaScript, add some comments, a little white space and a happy tree. Unleash your inner Bob Ross.

function login(user) { // if the user is null, check local storage to // see if there is a saved user object there user = user || getFromLocalStorage("user"); // Make sure the user is not null, and is also // both logged in and an admin. Otherwise, DENIED. &#x1f332; user && (user.loggedIn && user.isAdmin) ? navigateTo("dashboard") : navigateTo("unauthorized"); } Other things you can do

While we’re at it, here are some other tricks you can play with JavaScript conditionals.

Assignment

One of my favorite tricks (which I used above), is a one-liner to check if an item is null and then reassign it if it is. You do this with an || operator.

user = user || getFromLocalStorage('user');

And you can go on forever like this...

user = user || getFromLocalStorage('user') || await getFromDatabase('user') || new User();

This also works with the ternary...

user = user ? getFromLocalStorage('user') : new User(); Multiple conditions

You can provide multiple conditions to a ternary. For instance, if we want to log that the user has logged in and then navigate, we can do that without needing to abstract all of that into another function. Wrap it in some parentheses and provide a comma.

isLoggedIn ? (log('Logged In'), navigateTo('dashboard')) : navigateTo('unauthorized');

This also works with your && and || operators...

isLoggedIn && (log('Logged In'), navigateTo('dashboard')); Nesting ternary expressions

You can nest your ternary expressions. In his excellent article on the ternary, Eric Elliot demonstrates that with the following example...

const withTernary = ({ conditionA, conditionB }) => ( (!conditionA) ? valueC : (conditionB) ? valueA : valueB );

The most interesting thing Eric is doing there is negating the first condition so that you don’t end up with the question marks and colons together, which makes it harder to read. I would take this a step further and add a little indentation. I also added the curly braces and an explicit return because seeing one parenthesis and then immediately another makes my brain start to anticipate a function invocation that is never coming.

const withTernary = ({ conditionA, conditionB }) => { return ( (!conditionA) ? valueC : (conditionB) ? valueA : valueB ) }

As a general rule, I think that you should consider not nesting ternaries or if statements. Any of the above articles on Hacker News will shame you into the same conclusion. Although I’m not here to shame you, only to suggest that perhaps (and just maybe) you will thank yourself later if you don’t.

That’s my pitch on the misunderstood ternary and logical operators. I think that they help you write clean, readable code and avoid if statements entirely. Now if only we could get Linus Torvalds to sign off on all this as being “good taste.” I could retire early and and live the rest of my life in peace.

The post In Defense of the Ternary Statement appeared first on CSS-Tricks.

The Research Is in: Data-Backed Marketing Trends of 2019

Usability Geek - Tue, 04/09/2019 - 11:12am
You have read them before – probably in December and January while you were half-full of holiday food and less-than-half-focused on marketing trends. Still, you plugged through, skimming...
Categories: Web Standards
Syndicate content
©2003 - Present Akamai Design & Development.