Tag: Writing

Avoid Heavy Babel Transformations by (Sometimes) Not Writing Modern JavaScript

It’s hard to imagine writing production-ready JavaScript without a tool like Babel. It’s been an undisputed game-changer in making modern code accessible to a wide range of users. With this challenge largely out of the way, there’s not much holding us back from really leaning into the features that modern specifications have to offer.

But at the same time, we don’t want to lean in too hard. If you take an occasional peek into the code your users are actually downloading, you’ll notice that sometimes, seemingly straightforward Babel transformations can be especially bloated and complex. And in a lot of those cases, you can perform the same task using a simple, “old school” approach — without the heavy baggage that can come from preprocessing.

Let’s take a closer look at what I’m talking about using Babel’s online REPL — a great tool for quickly testing transformations. Targeting browsers that don’t support ES2015+, we’ll use it to highlight just a few of the times when you (and your users) might be better off choosing an “old school” way to do something in JavaScript, despite a “new” approach popularized by modern specifications.

As we go along, keep in mind that this is less about “old vs. new” and more about choosing the best implementation that gets the job done while bypassing any expected side effects of our build processes.

Let’s build!

Preprocessing a for..of loop

The for..of loop is a flexible, modern means of looping over iterable collections. It’s often used in a way very similar to a traditional for loop, which may lead you to think that Babel’s transformation would be simple and predictable, especially if you’re just using it with an array. Not quite. The code we write may only be 98 bytes:

function getList() {   return [1, 2, 3]; }  for (let value of getList()) {   console.log(value); }

But the output results in 1.8kb (a 1736% increase!):

 "use strict";  function _createForOfIteratorHelper(o) { if (typeof Symbol === "undefined" || o[Symbol.iterator] == null) { if (Array.isArray(o) || (o = _unsupportedIterableToArray(o))) { var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var it, normalCompletion = true, didErr = false, err; return { s: function s() { it = o[Symbol.iterator](); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it.return != null) it.return(); } finally { if (didErr) throw err; } } }; }  function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$  /.test(n)) return _arrayLikeToArray(o, minLen); }  function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }  function getList() {   return [1, 2, 3]; }  var _iterator = _createForOfIteratorHelper(getList()),     _step;  try {   for (_iterator.s(); !(_step = _iterator.n()).done;) {     var value = _step.value;     console.log(value);   } } catch (err) {   _iterator.e(err); } finally {   _iterator.f(); }

Why didn’t it just use for loop for this? It’s an array! Apparently, in this case, Babel doesn’t know it’s handling an array. All it knows is that it’s working with a function that could return any iterable (array, string, NodeList), and it needs to be ready for whatever that value could be, based on the ECMAScript specification for the for..of loop.

We could drastically slim the transformation by explicitly passing an array to it, but that’s not always easy in a real application. So, to leverage the benefits of loops (like break and continue statements), while confidently keeping bundle size slim, we might just reach for the for loop. Sure, it’s old school, but it gets the job done.

function getList() {   return [1, 2, 3]; } 
 for (var i = 0; i < getList().length; i++) {   console.log(value); }

/explanation Dave Rupert blogged about this exact situation a few years ago and found that forEach, even polyfilled, as a good solution for him.

Preprocessing Array […Spread]

Similar deal here. The spread operator can be used with more than one class of objects (not just arrays), so when Babel isn’t aware of the type of data it’s dealing with, it needs to take precautions. Unfortunately, those precautions can result in some serious byte bloat.

Here’s the input, weighing in at a slim 81 bytes:

function getList () {   return [4, 5, 6]; } 
 console.log([1, 2, 3, ...getList()]);

The output balloons to 1.3kb:

"use strict";  function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); }  function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }  function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$  /.test(n)) return _arrayLikeToArray(o, minLen); }  function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && Symbol.iterator in Object(iter)) return Array.from(iter); }  function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); }  function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }  function getList() {   return [4, 5, 6]; }  console.log([1, 2, 3].concat(_toConsumableArray(getList())));

Instead, we could cut to the chase and and just use concat(). The difference in the amount of code you need to write isn’t significant, it does exactly what it’s intended to do, and there’s no need to worry about that extra bloat.

function getList () {   return [4, 5, 6]; } 
 console.log([1, 2, 3].concat(getList()));

A more common example: Looping over a NodeList

You might have seen this more than a few times. We often need to query for several DOM elements and loop over the resulting NodeList. In order to use forEach on that collection, it’s common to spread it into an array.

[...document.querySelectorAll('.my-class')].forEach(function (node) {   // do something });

But like we saw, this makes for some heavy output. As an alternative, there’s nothing wrong with running that NodeList through a method on the Array prototype, like slice. Same result, but far less baggage:

[].slice.call(document.querySelectorAll('.my-class')).forEach(function(node) {   // do something });

A note about “loose” mode

It’s worth calling out that some of this array-related bloat can also be avoided by leveraging @babel/preset-env‘s loose mode, which compromises in staying totally true to the semantics of modern ECMAScript, but offers the benefit of slimmer output. In many situations, that might work just fine, but you’re also necessarily introducing risk into your application that you may come to regret later on. After all, you’re telling Babel to make some rather bold assumptions about how you’re using your code. 

The main takeaway here is that sometimes, it might be more suitable to be more intentional about the features you to use, rather than investing more time into tweaking your build process and potentially wrestling with unseen consequences later.

Preprocessing default parameters

This is a more predictable operation, but when it’s repeatedly used throughout a codebase, the bytes can add up. ES2015 introduced default parameter values, which tidy up a function’s signature when it accepts optional arguments. Here we are at 75 bytes:

function getName(name = "my friend") {   return `Hello, $  {name}!`; }

But Babel can be a little more verbose than expected with its transformation, resulting in 169 bytes:

"use strict"; 
 function getName() {   var name = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : "my friend";   return "Hello, ".concat(name, "!"); }

As an alternative, we could avoid using the arguments object altogether, and simply check if a parameter is undefined We lose the self-documenting nature that default parameters provide, but if we’re really pinching bytes, it might be worth it. And depending on the use case, we might even be able to get away with checking for falsey to slim it down even more.

function getName(name) {   name = name || "my friend";   return `Hello, $  {name}!`; }

Preprocessing async/await

The syntactic sugar of async/await over the Promise API is one of my favorite additions to JavaScript. Even so, out of the box, Babel can make make quite the mess out of it.

157 bytes to write:

async function fetchSomething(url) {   const response = await fetch(url);   return await response.json(); }  fetchSomething("https://google.com");

1.5kb when compiled:

"use strict";  function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }  function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }  function fetchSomething(_x) {   return _fetchSomething.apply(this, arguments); }  function _fetchSomething() {   _fetchSomething = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee(url) {     var response;     return regeneratorRuntime.wrap(function _callee$  (_context) {       while (1) {         switch (_context.prev = _context.next) {           case 0:             _context.next = 2;             return fetch(url);            case 2:             response = _context.sent;             _context.next = 5;             return response.json();            case 5:             return _context.abrupt("return", _context.sent);            case 6:           case "end":             return _context.stop();         }       }     }, _callee);   }));   return _fetchSomething.apply(this, arguments); }  fetchSomething("https://google.com");

You’ll notice that Babel doesn’t convert async code into promises out of the box. Instead, they’re transformed into generators that rely on the regenerator-runtime library, making for more a lot more code than what’s written in our IDE. Thankfully, it’s possible to go the Promise route by means of a plugin, like babel-plugin-transform-async-to-promises. Instead of that 1.5kb output, we end up with much less, at 638 bytes:

"use strict"; 
 function _await(value, then, direct) {   if (direct) {     return then ? then(value) : value;   } 
   if (!value || !value.then) {     value = Promise.resolve(value);   } 
   return then ? value.then(then) : value; } 
 var fetchSomething = _async(function (url) {   return _await(fetch(url), function (response) {     return _await(response.json());   }); }); 
 function _async(f) {   return function () {     for (var args = [], i = 0; i < arguments.length; i++) {       args[i] = arguments[i];     } 
     try {       return Promise.resolve(f.apply(this, args));     } catch (e) {       return Promise.reject(e);     }   }; }

But, like mentioned before, there’s risk in relying on a plugin to ease pain like this. When doing so, we’re impacting transformations in the entire project, and also introducing another build dependency. Instead, we could consider just sticking with the Promise API.

function fetchSomething(url) {   return new Promise(function (resolve) {     fetch(url).then(function (response) {       return response.json();     }).then(function (data) {       return resolve(data);     });   }); }

Preprocessing classes

For more syntactic sugar, there’s the class syntax introduced with ES2015, which provides a streamlined way to leverage JavaScript’s prototypical inheritance. But if we’re using Babel to transpile for older browsers, there’s nothing sweet about the output.

The input us only 120 bytes:

class Robot {   constructor(name) {     this.name = name;   } 
   speak() {      console.log(`I'm $  {this.name}!`);   } }

But the output results in 989kb:

"use strict";  function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }  function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }  function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }  var Robot = /*#__PURE__*/function () {   function Robot(name) {     _classCallCheck(this, Robot);      this.name = name;   }    _createClass(Robot, [{     key: "speak",     value: function speak() {       console.log("I'm ".concat(this.name, "!"));     }   }]);    return Robot; }();

Much of the time, unless you’re doing some fairly involved inheritance, it’s straightforward enough to use a pseudoclassical approach. It requires slightly less code to write, and the resulting interface is virtually identical to a class.

function Robot(name) {   this.name = name; 
   this.speak = function() {     console.log(`I'm $  {this.name}!`);   } } 
 const rob = new Robot("Bob"); rob.speak(); // "Bob"

Strategic considerations

Keep in mind that, depending on your application’s audience, a lot of what you’re reading here might mean that your strategies to keep bundles slim may take different shapes.

For example, your team might have already made a deliberate decision to drop support for Internet Explorer and other “legacy” browsers (which is becoming more and more common, given that the vast majority of browsers support ES2015+). If that’s the case, your time might best be spent in auditing the list of browsers your build system is targeting, or making sure you’re not shipping unnecessary polyfills.

And even if you are still obligated to support older browsers (or maybe you love some of the modern APIs too much to give them up), there are other options to enable you to ship heavy, preprocessed bundles only to the users that need them, like a differential serving implementation.

The important thing isn’t so much about which strategy (or strategies) your team chooses to prioritize, but more about intentionally making those decisions in light of the code being spit out by your build system. And that all starts by cracking open that dist directory to take a peak.

Pop open that hood

I’m a big fan of the new features modern JavaScript continues to provide. They make for applications that are easier to write, maintain, scale, and especially read. But as long as writing JavaScript means preprocessing JavaScript, it’s important to make sure that we have a finger on the pulse of what these features mean for the users that we ultimately aim to serve.

And that means popping the hood of your build process once in a while. At best, you might be able avoid especially hefty Babel transformations by using a simpler, “classic” alternative. And at worst, you’ll come to better understand (and appreciate) the work that Babel does all the more.

The post Avoid Heavy Babel Transformations by (Sometimes) Not Writing Modern JavaScript appeared first on CSS-Tricks.

CSS-Tricks

, , , , , , ,

Why does writing matter in remote work?

Talk to anyone who has an active blog and I bet they’ll tell you it’s been valuable to them. Maybe it’s opened doors. Maybe it’s got them a job. Maybe it’s got them a conference invite. Maybe they just like the thrill of knowing people have read and responded to it. Maybe they learned a lot through its creation and maintenance.

Khoi Vinh said:

It’s hard to overstate how important my blog has been, but if I were to try to distill it down into one word, it would be: “amplifier.”

But what about other kinds of writing? Just day to day writing? Is that important for web workers? “Especially now”?

Tim Casasola:

In remote work, we communicate primarily through writing. We send messages in Slack. We document projects in Notion. We send meeting invites with a written description of the purpose. We’re writing all the time.

It’s just so damn important for team work of any kind, particularly when you aren’t next to each other physically.

While writing forces people to think clearly, writing also forces teams to think clearly. In my experience, having a clearly written thing makes it easy for folks to collaborate with me. This is because people naturally enjoy poking holes in arguments, adding points that were missed, or mentioning any risks that weren’t taken into account. I’ve found it helpful to use this human tendency to my advantage. Extra opinions and poked holes are hard to surface if you didn’t write something in the first place.

Direct Link to ArticlePermalink

The post Why does writing matter in remote work? appeared first on CSS-Tricks.

CSS-Tricks

, , ,
[Top]

Advice for Writing a Technical Resume

Marco Rogers asked a very good question on Twitter:

I’ve been on both sides of the interview table for many years now, both searching for jobs and as a hiring manager. My resume skills and most salient advice for writing one is likely from my experiences looking though thousands of applications.

When it comes to writing a resume, It’s helpful to think about the human aspect first and foremost. Imagining a hiring manager’s perspective will give you an edge because it helps speak to them directly. Remember, a coveted position or reputable company commonly sifts through anywhere between tens and thousands of applications. It takes a staff that is materially impacted in the time and energy it takes to review every candidate and evaluate those who make it in to the interview stage. Even attention to minor details will help your odds of standing out.

Here are my general suggestions to make the best possible resume.

Formatting is important

Spelling, grammar and formatting are all crucial to a well-written resume. Typos, errors, and poor use of things like bold and italic styles (especially when used together) are clear red flags, so pay extra attention to what you write and how it is written. These types of mistakes give the impressions that you either lack attention to detail or are unwilling to go the extra step. As trivial as this might seem, use your spell check and get a second set of eyes on your resume before submitting it.

A few formatting tips to keep in mind:

  • Use headings to separate sections
  • Use lists to help summarize highlights and things scannable
  • Use a good font and font size that makes the content legible
  • Use line spacing that lets content breath rather than packing it close together
  • Avoid using all caps, or combining bold, italic, and underlines on the same content.

I don’t have a strong opinion on charts that show off your skills or lists of hobbies. But I will say that I’ve noticed them more frequently on the applications of junior developers, so you might unintentionally communicate you have less experience by including it.

If you don’t have a lot of work history, it’s totally OK to throw in open source projects!

Or side projects! Or working on your own site! A few folks mentioned the same thing in the Twitter thread and it’s solid advice. A good hiring manager should know that senior-level candidates don’t grow on trees — they just want to see some work that shows you have promise.

This is problematic advice in some ways, as not everyone has time on the side to devote to projects. I wouldn’t so far as to say including those things is a hard requirement for a good resume. But if you’re otherwise lacking relevant work experience, including personal projects can show the kind of work you’re capable of doing as well as the kind of work that excites you. I’ve taken chances on folks with slim-to-no work experience but with a solid combination of a portfolio site, GitHub contributions, or even a few CodePen demos that show potential.

Call out your contributions to your work experience

Each time you list a work example, answer this: what did you accomplish? This is a good way to provide valuable information without or any unnecessary fluff. Everyone is going to tout their work experience. Adding the outcomes of your work will make you stand out.

Here’s an example that would catch my attention:

Due to my team’s work refactoring the product page, we were able to meet the demands of our customers, which resulted in a 25% growth in sales. We also took the opportunity to upgrade the codebase from React.createClass to React Hooks for all of our components, ensuring a more flexible and maintainable system.

This tells me you can work on a team to deliver goals. It also tells me that you understand technical debt and how to solve it. That’s the sort of person I want to hire.

If so far your experience is limited to a code bootcamp, it’s great to talk through that.

Every job applicant is coming from a different background and from varying degrees of experience. It’s safe to assume you are not the most experienced person in the pool.

And that’s OK!

For example, let’s say your development experience is limited to online or in-person coding bootcamps rather than commercial projects. What did you learn there? What were you excited by? What was your final project? Is there a link to that work? When I’m hiring someone who’s coming in early in their career, I’m mostly looking for curiosity and enthusiasm. I’m probably not alone there.

Don’t be too long… or too short

We mentioned earlier that hiring is a time-consuming job. It’s good to keep this in mind as you’re writing by making your resume as brief as possible — ideally on a single standard page. And, yes, two pages is OK if you really need it.

Keeping everything short is a balancing act when you’re also attempting to include as much useful information as possible. Treat that constraint as a challenge to focus on the most important details. It’s a good problem if you have more to say than what fits!

At best, padding a resume into multiple pages conveys you’re unable to communicate in a succinct manner. At worst, it shows a lack of respect for a hiring manager’s time.

Make sure there’s a way to reach you

I cannot tell you how many resumes that lack the following essentials: name, email, and phone number. Seriously, it happens even on resumes that are otherwise very impressive.

Your name and contact information are hard requirements. I don’t want to search around for your email if you’re applying. To be honest, I probably won’t search at all because I’m busy and there are many other candidates to choose from.

Preparation is your friend

Make sure your accompanying cover letter (yes, you should include one) communicates you’ve done at least a little research on the company, conveys you understand what they need in a candidate, and how you fit into that need.

I will personally adjust my the descriptions in my own resume so there is a direct connection between my skills and the position.

Your work and education details should be reverse-chronological

Your most recent work is more important than your oldest work. It’s a better reflection of what you’re capable of doing today and how fresh your skills are in a particular area. The same goes for your education: lead with your most recent experience.

The person reviewing your resume can decide to continue reading further if they’re compelled by the most recent information.

Wrapping up

If you want to stand out in the crowd, make sure your resume is one that represents you well. Ask someone to help you proof and use spellcheck, and make sure you’ve put your best foot forward.

And don’t be discouraged by rejections or unreturned messages. It’s less likely to be about you personally and more likely due to the number of people applying. So keep trying!

The post Advice for Writing a Technical Resume appeared first on CSS-Tricks.

CSS-Tricks

, , ,
[Top]

Tips for Writing Animation Code Efficiently

I’ve been coding web animations and helping others do the same for years now. However, I have yet to see a concise list of tips focused on how to efficiently build animations, so here you go!

I will be using the GreenSock Animation Platform (GSAP). It provides a simple, readable API and solves cross-browser inconsistencies so that you can focus on animating. The code and concepts should be understandable even if you’ve never used GSAP. If you’d like to familiarize yourself with the basics of GSAP first so that you can get the most out of this article, the best place to begin is GSAP’s getting started page (includes a video). 

Tip #1: Use an animation library

Some developers think that using an animation library is wasteful because they can just use native browser technologies like CSS transitions, CSS animations or the Web Animations API (WAAPI) to accomplish the same thing without loading a library. In some cases, that’s true. However, here are a few other factors to consider:

  • Browser bugs, inconsistencies, and compatibility: An animation library, like GSAP, solves these for you and is universally compatible. You can even use motion paths in IE9! There are many problematic areas when it comes to cross-browser issues, including handling transform-origin on SVG elements, path stroke measurements, 3D origins in Safari, and many more that we don’t have the space to list.
  • Animation workflow: Building even moderately complex animations is much faster and more fun with a tool like GSAP. You can modularize animations, nest them as deeply as you want, and have their timing adjusted automatically. This makes it so much easier to experiment. Trust me: once you try building an animation sequence in CSS and then in GSAP, you’ll see what I mean. Night and day! Future edits are faster too.
  • Animate beyond the DOM: Canvas, WebGL, generic objects, and complex strings can’t be animated with native technologies. Using one consistent tool for all your animations is much cleaner. 
  • Runtime control: Using a good animation library can enable you to pause, resume, reverse, seek through, or even gradually change the speed of an entire animation sequence. You can control each transform component independently (rotation, scale, x, y, skew, etc.). You can also retrieve those values at any time as well. JavaScript animations give you ultimate flexibility.
  • Easing options (bounce, elastic, etc.): CSS only gives you two control points for eases. GSAP’s CustomEase lets you literally create any ease you can imagine. 
  • Lag smoothing: GSAP can prioritize absolute timing or adjust things on the fly to avoid jumps if the CPU gets bogged down.
  • Advanced capabilities: Using GSAP, it’s easy to morph SVGs, add physics/inertia, edit motion paths directly in the browser, use position-aware staggers, and more.

Most of the top animators in the industry use a tool like GSAP because they’ve learned these same things over the years. Once you get beyond very basic animations, a JavaScript library will make your life much, much easier and open up entirely new possibilities. 

Tip #2: Use timelines

A good animation library will provide some way of creating individual animations (called tweens) and a way to sequence animations in a timeline. Think of a timeline like a container for your tweens where you position them in relation to one another. 

const tl = gsap.timeline();  tl.to(".box", { duration: 1, x: 100 })   .to(".box", { duration: 1, backgroundColor: "#f38630" }, "+=0.5")    .to(".box", { duration: 1, x: 0, rotation: -360 }, "+=0.5") 

By default in GSAP, tweens added to a timeline will wait for the previous tweens to complete before running. The +=0.5 adds an additional offset or delay of a half-second as well, so the second tween will start 0.5 seconds after the first tween finishes no matter how long the first tween’s duration is.

To increase the amount of time between the tween to 1 second, all you need to do is change the +=0.5 to +=1! Super easy. With this approach, you can iterate on your animations quickly without worrying about doing the math to combine previous durations.

Tip #3: Use relative values

By “relative values” I mean three things:

  1. Animate values relative to their current value. GSAP recognizes += and -= prefixes for this. So x: "+=200" will add 200 units (usually pixels) to the current x. And x: "-=200" will subtract 200 from the current value. This is also useful in GSAP’s position parameter when positioning tweens relative to one another.
  2. Use relative units (like vw, vh and, in some cases, %) when values need to be responsive to viewport size changes.
  3. Use methods like .to() and .from() (instead of .fromTo()) whenever possible so that the start or end values are dynamically populated from their current values. That way, you don’t need to declare start and end values in every tween. Yay, less typing! For example, if you had a bunch of differently-colored elements, you could animate them all to black like gsap.to(".class", {backgroundColor: "black" }).

Tip #4: Use keyframes

If you find yourself animating the same target over and over in a row, that’s a perfect time to use keyframes! You do so like this:

gsap.to(".box", { keyframes: [   { duration: 1, x: 100 },   { duration: 1, backgroundColor: "#f38630", delay: 0.5 },    { duration: 1, x: 0, rotation: -360, delay: 0.5 } ]});

No timeline necessary! To space out the tweens we just use the delay property in each keyframe. (It can be negative to create overlaps.)

Tip #5: Use smart defaults

GSAP has default values for properties like ease ("power1.out") and duration (0.5 seconds). So, the following is a valid tween that will animate for half a second.

gsap.to(".box", { color: "black" })

To change GSAP’s global defaults, use gsap.defaults():

// Use a linear ease and a duration of 1 instead gsap.defaults({ ease: "none", duration: 1 });

This can be handy, but it’s more common to set defaults for a particular timeline so that it affects only its children. For example, we can avoid typing duration: 1 for each of the sub-tweens by setting a default on the parent timeline:

const tl = gsap.timeline({ defaults: { duration: 1 } });  tl.to(".box", { x: 100 })   .to(".box", { backgroundColor: "#f38630" }, "+=0.5")    .to(".box", { x: 0, rotation: -360 }, "+=0.5") 

Tip #6: Animate multiple elements at once

We mentioned this briefly in the third tip, but it deserves its own tip.

If you have multiple elements that share the same class of .box, the code above will animate all of the elements at the same time!

You can also select multiple elements with different selectors by using a more complex selector string:

gsap.to(".box, .circle", { ... });

Or you can pass an array of variable references as long as the elements are of the same type (selector string, variable reference, generic object, etc.):

var box = document.querySelector(".box"); var circle = document.querySelector(".circle");  // some time later… gsap.to([box, circle], { ... });

Tip #7: Use function-based values, staggers, and/or loops

Function-based values

Use a function instead of a number/string for almost any property, and GSAP will call that function once for each target when it first renders the tween. Plus, it’ll use whatever gets returned by the function as the property value! This can be really handy for creating a bunch of different animations using a single tween and for adding variance.

GSAP will pass the following parameters into the function:

  1. The index
  2. The specific element being affected
  3. An array of all of the elements affected by the tween

For example, you could set the movement direction based on the index:

Or you could choose items from an array:

Staggers

Make your animations look more dynamic and interesting by offsetting the start times with a stagger. For simple staggered offsets in a single tween, just use stagger: 0.2 to add 0.2 seconds between the start time of each animation.

You can also pass in an object to get more complex stagger effects, including ones that emanate outward from the center of a grid or randomize the timings:

For more information about GSAP’s staggers, check out the stagger documentation.

Loops

It can be helpful to loop through a list of elements to create or apply animations, particularly when they are based on some event, like a user’s interaction (which I’ll discuss later on). 

To loop through a list of items, it’s easiest to use .forEach(). But since this isn’t supported on elements selected with .querySelectorAll() in IE, you can use GSAP’s utils.toArray() function instead.

In the example below, we are looping through each container to add animations to its children that are scoped to that container.

Tip #8: Modularize your animations

Modularization is one of the key principles of programming. It allows you to build small, easy-to-understand chunks that you can combine into larger creations while still keeping things clean, reusable, and easy to modify. It also lets you to use parameters and function scope, increasing the re-usability of your code.

Functions

Use functions to return tweens or timelines and then insert those into a master timeline:

function doAnimation() {   // do something, like calculations, maybe using arguments passed into the function      // return a tween, maybe using the calculations above   return gsap.to(".myElem", { duration: 1, color: "red"}); }  tl.add( doAnimation() );

Nesting timelines can truly revolutionize the way you animate. It lets you sequence really complex animations with ease while keeping your code modular and readable.

function doAnimation() {   const tl = gsap.timeline();   tl.to(...);   tl.to(...);   // ...as many animations as you’d like!    // When you’re all done, return the timeline   return tl; }  const master = gsap.timeline(); master.add( doAnimation() ); master.add( doAnotherAnimation() ); // Add even more timelines! 

Here’s a real-world use case modified from Carl Schooff’s “Writing Smarter Animation Code” post.

Here’s a more complex demo showing the same technique using a Star Wars theme by Craig Roblewsky:

Wrapping your animation-building routines inside functions also makes recreating animations (say, on resize) a breeze!

var tl; // keep an accessible reference to our timeline  function buildAnimation() {   var time = tl ? tl.time() : 0; // save the old time if it exists    // kill off the old timeline if it exists   if (tl) {     tl.kill();   }    // create a new timeline   tl = gsap.timeline();   tl.to(...)     .to(...); // do your animation   tl.time(time); // set the playhead to match where it was }  buildAnimation(); //kick things off  window.addEventListener("resize", buildAnimation); // handle resize

Effects

With effects, you can turn a custom animation into a named effect that can be called anytime with new targets and configurations. This is especially helpful when you have standards for your animations or if you are going to be calling the same animation from different contexts.

Here’s a super-simple “fade” effect to show the concept:

// register the effect with GSAP: gsap.registerEffect({     name: "fade",     defaults: {duration: 2}, //defaults get applied to the "config" object passed to the effect below     effect: (targets, config) => {         return gsap.to(targets, {duration: config.duration, opacity:0});     } });  // now we can use it like this: gsap.effects.fade(".box"); // Or override the defaults: gsap.effects.fade(".box", {duration: 1}); 

Tip #9: Use control methods

GSAP provides many methods to control the state of a tween or timeline. They include .play(), .pause(), .reverse(), .progress(), .seek(), .restart(), .timeScale(), and several others. 

Using control methods can make transitions between animations more fluid (such as being able to reverse part way through) and more performant (by reusing the same tween/timeline instead of creating new instances each time). And by giving you finer control over the state of the animation, it can help with debugging as well.

Here’s a simple example:

One amazing use case is tweening the timeScale of a timeline!

Use case: interaction events that trigger animations

Inside of event listeners for user interaction events, we can use control methods to have fine control over our animation’s play state.

In the example below, we are creating a timeline for each element (so that it doesn’t fire the same animation on all instances), attaching a reference for that timeline to the element itself, and then playing the relevant timeline when the element is hovered, reversing it when the mouse leaves.

Use case: Animating between multiple states of a timeline

You may want a set of animations to affect the same properties of the same elements, but only in certain sequences (e.g. active/inactive states, each with mouseover/mouseout states). It may get tricky to manage. We can simplify it by using states of a timeline and control events. 

Use case: Animating based on the scroll position

We can easily fire animations based on the scroll position by using control methods. For example, this demo plays a full animation once a scroll position has been reached:

You can also attach the progress of an animation to the scroll position for more fancy scroll effects!

But if you’re going to do this, it’s best to throttle the scroll listener for performance reasons:

Hot tip: GreenSock is working on a plugin to make scroll-based animations even easier! You’re in for quite a treat. Keep your eyes peeled for news.

Bonus tip: Use GSAP’s plugins, utility methods, and helper functions

GSAP plugins add extra capabilities to GSAP’s core. Some plugins make it easier to work with rendering libraries, like PixiJS or EaselJS, while other plugins add superpowers like morphing SVG, drag and drop functionality, etc. This keeps the GSAP core relatively small and lets you add features when you need them.

Plugins

MorphSVG morphs between any two SVG shapes, no matter the number of points, and gives you fine control over how the shapes are morphed.

DrawSVG progressively reveals (or hides) the stroke of an SVG element, making it look like it’s being drawn. It works around various browser bugs that affect typical stroke-dashoffset animations.

MotionPath animates anything (SVG, DOM, canvas, generic objects, whatever) along a motion path in any browser. You can even edit the path in-browser using MotionPathHelper!

GSDevTools gives you a visual UI for interacting with and debugging GSAP animations, complete with advanced playback controls, keyboard shortcuts, global synchronization and more.

Draggable provides a surprisingly simple way to make virtually any DOM element draggable, spinnable, tossable, or even flick-scrollable using mouse or touch events. Draggable integrates beautifully (and optionally) with InertiaPlugin so the user can flick and have the motion decelerate smoothly based on momentum.

CustomEase (along with CustomBounce and CustomWiggle) add to GSAP’s already extensive easing capabilities by enabling you to register any ease that you’d like.

SplitText is an easy to use JavaScript utility that allows you to split HTML text into characters, words and lines. It’s easy to use, extremely flexible, works all the way back to IE9, and handles special characters for you.

ScrambleText scrambles the text in a DOM element with randomized characters, refreshing new randomized characters at regular intervals, while gradually revealing your new text (or the original) over the course of the tween. Visually, it looks like a computer decoding a string of text.

Physics2D lets you tween the position of elements based on velocity and acceleration as opposed to going to specific values. PhysicsProps is similar but works with any property, not just 2D coordinates.

Utility methods

GSAP has built-in utility methods that can make some common tasks easier. Most are focused on manipulating values in a particular way, which can be especially helpful when generating or modifying animation values. The ones that I use most often are .wrap(), .random, .interpolate(), .distribute(), .pipe(), and .unitize(), but there are many others you might find helpful.

Helper functions

In a similar light, but not built into GSAP’s core, are some helper functions GreenSock has created over the years to deal with specific use cases. These functions make it easy to FLIP your animations, return a random number based on an ease curve, blend two ease curves, and much more. I highly recommend checking them out!

Conclusion

You’ve made it to the end! Hopefully, you’ve learned a thing or two along the way and this article will continue to be a resource for you in the years to come.

As always, if you have any questions about GSAP, feel free to drop by the GreenSock forums. They’re incredibly helpful and welcoming! As an employee of GreenSock, that’s where I hang out often; I love helping people with animation-related challenges!

The post Tips for Writing Animation Code Efficiently appeared first on CSS-Tricks.

CSS-Tricks

, , , ,
[Top]

Google’s Technical Writing Guide

It’s good!

I’ve written up my advice (sprinkled with great advice from others), but this is way more straightforward nuts-and-bolts training on technical writing. It’s structured like an actual course, with exercises along the way.

I’m far from an expert here. But between Geoff and I, we end up doing a lot of technical article editing for the sake of clarity.

Comedy writers seek the funniest results, horror writers strive for the scariest, and technical writers aim for the clearest. In technical writing, clarity takes precedence over all other rules. 

It can be tricky to get right. Read the section on Active voice. That’s easy for anyone to get wrong.

Essentially every single rule is just an extension of “make it more clear.”

Direct Link to ArticlePermalink

The post Google’s Technical Writing Guide appeared first on CSS-Tricks.

CSS-Tricks

, , ,
[Top]

What I Like About Writing Styles with Svelte

There’s been a lot of well-deserved hype around Svelte recently, with the project accumulating over 24,000 GitHub stars. Arguably the simplest JavaScript framework out there, Svelte was written by Rich Harris, the developer behind Rollup. There’s a lot to like about Svelte (performance, built-in state management, writing proper markup rather than JSX), but the big draw for me has been its approach to CSS.

Single file components

​​

React does not have an opinion about how styles are defined
—React Documentation

​​​​

A UI framework that doesn’t have a built-in way to add styles to your components is unfinished.
—Rich Harris, creator of Svelte

In Svelte, you can write CSS in a stylesheet like you normally would on a typical project. You can also use CSS-in-JS solutions, like styled-components and Emotion, if you’d like. It’s become increasingly common to divide code into components, rather than by file type. React, for example, allows for the collocation of a components markup and JavaScript. In Svelte, this is taken one logical step further: the Javascript, markup and styling for a component can all exist together in a single `.svelte`​ file. If you’ve ever used single file components in Vue, then Svelte will look familiar.

// button.svelte <style>   button {     border-radius: 0;     background-color: aqua;   } </style>  <button>   <slot/> </button>

Styles are scoped by default

By default, styles defined within a Svelte file are scoped. Like CSS-in-JS libraries or CSS Modules, Svelte generates unique class names when it compiles to make sure the styles for one element never conflict with styles from another.

That means you can use simple element selectors like div and button in a Svelte component file without needing to work with class names. If we go back to the button styles in our earlier example, we know that a ruleset for <button> will only be applied to our <Button> component — not to any other HTML button elements within the page. If you were to have multiple buttons within a component and wanted to style them differently, you’d still need classes. Classes will also be scoped by Svelte.

The classes that Svelte generates look like gibberish because they are based on a hash of the component styles (e.g. svelte-433xyz). This is far easier than a naming convention like BEM. Admittedly though, the experience of looking at styles in DevTools is slightly worse as the class names lack meaning.

The markup of a Svelte component in DevTools.

It’s not an either/or situation. You can use Svelte’s scoped styling along with a regular stylesheet. I personally write component specific styles within .svelte files, but make use of utility classes defined in a stylesheet. For global styles to be available across an entire app — CSS custom properties, reusable CSS animations, utility classes, any ‘reset’ styles, or a CSS framework like Bootstrap — I suggest putting them in a stylesheet linked in the head of your HTML document.

It lets us create global styles

As we’ve just seen, you can use a regular stylesheet to define global styles. Should you need to define any global styles from within a Svelte component, you can do that too by using :global. This is essentially a way to opt out of scoping when and where you need to.

For example, a modal component may want to toggle a class to style the body element:

<style> :global(.noscroll) {   overflow: hidden; } </style>

Unused styles are flagged

Another benefit of Svelte is that it will alert you about any unused styles during compilation. In other words, it searches for places where styles are defined but never used in the markup.

Conditional classes are terse and effortless

If the JavaScript variable name and the class name is the same, the syntax is incredibly terse. In this example, I’m creating modifier props for a full-width button and a ghost button.

<script>   export let big = false;   export let ghost = false; </script>  <style>   .big {     font-size: 20px;     display: block;     width: 100%;   }      .ghost {     background-color: transparent;     border: solid currentColor 2px;   } </style>          <button class:big class:ghost>   <slot/> </button>

A class of ghost will be applied to the element when a ghost prop is used, and a class of big is applied when a big prop is used.

<script>   import Button from './Button.svelte'; </script>  <Button big ghost>Click Me</Button>

Svelte doesn’t require class names and prop names to be identical.

<script>   export let primary = false;   export let secondary = false; </script>  <button   class:c-btn--primary={primary}   class:c-btn--secondary={secondary}   class="c-btn">   <slot></slot> </button>

The above button component will always have a c-btn class but will include modifier classes only when the relevant prop is passed in, like this:

<Button primary>Click Me</Button>

That will generate this markup:

<button class="c-btn c-btn--primary">Click Me</button>

Any number of arbitrary classes can be passed to a component with a single prop:

<script> let class_name = ''; export { class_name as class }; </script>  <button class="c-btn {class_name}">   <slot /> </button>

Then, classes can be used much the same way you would with HTML markup:

<Button class="mt40">Click Me</Button>

From BEM to Svelte

Let’s see how much easier Svelte makes writing styles compared to a standard CSS naming convention. Here’s a simple component coded up using BEM.

.c-card {   border-radius: 3px;   border: solid 2px; }  .c-card__title {   text-transform: uppercase; }  .c-card__text {   color: gray; }  .c-card--featured {   border-color: gold; }

Using BEM, classes get long and ugly. In Svelte, things are a lot simpler.

<style> div {   border-radius: 3px;   border: solid 2px; }  h2 {   text-transform: uppercase; }  p {   color: gray; }  .featured {   border-color: gold; } </style>  <div class:featured>   <h2>What I Like About Writing Styles with Svelte</h2>   <p>     <slot />   </p> </div>

It plays well with preprocessors

CSS preprocessors feels a lot less necessary when working with Svelte, but they can work perfectly alongside one another by making use of a package called Svelte Preprocess. Support is available for Less, Stylus and PostCSS, but here we’ll look at Sass. The first thing we need to do is to install some dependencies:

npm install -D svelte-preprocess node-sass

Then we need to import autoPreprocess in rollup.config.js at the top of the file.

import autoPreprocess from 'svelte-preprocess';

Next, let’s find the plugins array and add preprocess: autoPreprocess() to Svelte:

export default {   plugins: [     svelte({       preprocess: autoPreprocess(),       ...other stuff

Then all we need to do is specify that we’re using Sass when we’re working in a component file, using type="text/scss" or lang="scss" to the style tag.

<style type="text/scss">   $ pink: rgb(200, 0, 220);   p {     color: black;     span {       color: $ pink;     }   } </style>

Dynamic values without a runtime

We’ve seen that Svelte comes with most of the benefits of CSS-in-JS out-of-the-box — but without external dependencies! However, there’s one thing that third-party libraries can do that Svelte simply can’t: use JavaScript variables in CSS.

The following code is not valid and will not work:

<script>   export let cols = 4; </script>  <style>   ul {     display: grid;     width: 100%;     grid-column-gap: 16px;     grid-row-gap: 16px;     grid-template-columns: repeat({cols}, 1fr);   } </style>  <ul>   <slot /> </ul>

We can, however, achieve similar functionality by using CSS variables.

<script>   export let cols = 4; </script>  <style>   ul {     display: grid;     width: 100%;     grid-column-gap: 16px;     grid-row-gap: 16px;     grid-template-columns: repeat(var(--columns), 1fr);   } </style>  <ul style="--columns:{cols}">   <slot /> </ul>

I’ve written CSS in all kinds of different ways over the years: Sass, Shadow DOM, CSS-in-JS, BEM, atomic CSS and PostCSS. Svelte offers the most intuitive, approachable and user-friendly styling API. If you want to read more about this topic then check out the aptly titled The Zen of Just Writing CSS by Rich Harris.

The post What I Like About Writing Styles with Svelte appeared first on CSS-Tricks.

CSS-Tricks

, , , ,
[Top]

Writing Tests for React Applications Using Jest and Enzyme

While it is important to have a well-tested API, solid test coverage is a must for any React application. Tests increase confidence in the code and helps prevent shipping bugs to users.

That’s why we’re going to focus on testing in this post, specifically for React applications. By the end, you’ll be up and running with tests using Jest and Enzyme.

No worries if those names mean nothing to you because that’s where we’re headed right now!

Installing the test dependencies

Jest is a unit testing framework that makes testing React applications pretty darn easy because it works seamlessly with React (because, well, the Facebook team made it, though it is compatible with other JavaScript frameworks). It serves as a test runner that includes an entire library of predefined tests with the ability to mock functions as well.

Enzyme is designed to test components and it’s a great way to write assertions (or scenarios) that simulate actions that confirm the front-end UI is working correctly. In other words, it seeks out components on the front end, interacts with them, and raises a flag if any of the components aren’t working the way it’s told they should.

So, Jest and Enzyme are distinct tools, but they complement each other well.

For our purposes, we will spin up a new React project using create-react-app because it comes with Jest configured right out of the box.

yarn create react-app my-app

We still need to install enzyme and enzyme-adapter-react-16 (that number should be based on whichever version of React version you’re using).

yarn add enzyme enzyme-adapter-react-16 --dev

OK, that creates our project and gets us both Jest and Enzyme in our project in two commands. Next, we need to create a setup file for our tests. We’ll call this file setupTests.js and place it in the src folder of the project.

Here’s what should be in that file:

import { configure } from 'enzyme'; import Adapter from 'enzyme-adapter-react-16'; configure({ adapter: new Adapter() });

This brings in Enzyme and sets up the adapter for running our tests.

To make things easier on us, we are going to write tests for a React application I have already built. Grab a copy of the app over on GitHub.

Taking snapshots of tests

Snapshot testing is used to keep track of changes in the app UI. If you’re wonder whether we’re dealing with literal images of the UI, the answer is no, but snapshots are super useful because they capture the code of a component at a moment in time so we can compare the component in one state versus any other possible states it might take.

The first time a test runs, a snapshot of the component code is composed and saved in a new __snapshots__ folder in the src directory. On test runs, the current UI is compared to the existing. Here’s a snapshot of a successful test of the sample project’s App component.

it("renders correctly", () => {   const wrapper = shallow(     <App />   );   expect(wrapper).toMatchSnapshot(); });

Every new snapshot that gets generated when the test suite runs will be saved in the __tests__ folder. What’s great about that Jest will check to see if the component matches is then on subsequent times when we run the test, Jest will check to see if the component matches the snapshot on subsequent tests. Here’s how that files looks.

Let’s create a conditions where the test fails. We’ll change the <h2> tag of our component from <h2>Random User</h2> to <h2>CSSTricks Tests</h2> and here’s what we get in the command line when the tests run:

If we want our change to pass the test, we either change the heading to what it was before, or we can update the snapshot file. Jest even provides instructions for how to update the snapshot right from the command line so there’s no need to update the snapshot manually:

Inspect your code changes or press `u` to update them.

So, that’s what we’ll do in this case. We press u to update the snapshot, the test passes, and we move on.

Did you catch the shallow method in our test snapshot? That’s from the Enzyme package and instructs the test to run a single component and nothing else — not even any child components that might be inside it. It’s a nice clean way to isolate code and get better information when debugging and is especially great for simple, non-interactive components.

In addition to shallow, we also have render for snapshot testing. What’s the difference, you ask? While shallow excludes child components when testing a component, render includes them while rendering to static HTML.

There is one more method in the mix to be aware of: mount. This is the most engaging type of test in the bunch because it fully renders components (like shallow and render) and their children (like render) but puts them in the DOM, which means it can fully test any component that interacts with the DOM API as well as any props that are passed to and from it. It’s a comprehensive test for interactivity. It’s also worth noting that, since it does a full mount, we’ll want to make a call to .unmount on the component after the test runs so it doesn’t conflict with other tests.

Testing Component’s Lifecycle Methods

Lifecycle methods are hooks provided by React, which get called at different stages of a component’s lifespan. These methods come in handy when handling things like API calls.
Since they are often used in React components, you can have your test suite cover them to ensure all things work as expected.

We do the fetching of data from the API when the component mounts. We can check if the lifecycle method gets called by making use of jest, which makes it possible for us to mock lifecycle methods used in React applications.

it('calls componentDidMount', () => {   jest.spyOn(App.prototype, 'componentDidMount')   const wrapper = shallow(<App />)   expect(App.prototype.componentDidMount.mock.calls.length).toBe(1) })

We attach spy to the component’s prototype, and the spy on the componentDidMount() lifecycle method of the component. Next, we assert that the lifecycle method is called once by checking for the call length.

Testing component props

How can you be sure that props from one component are being passed to another? We have a test confirm it, of course! The Enzyme API allows us to create a “mock” function so tests can simulate props being passed between components.

Let’s say we are passing user props from the main App component into a Profile component. In other words, we want the App to inform the Profile with details about user information to render a profile for that user.

First, let’s mock the user props:

const user = {   name: 'John Doe',   email: 'johndoe@gmail.com',   username: 'johndoe',   image: null }

Mock functions look a lot like other tests in that they’re wrapped around the components. However, we’re using an additional describe layer that takes the component being tested, then allows us to proceed by telling the test the expected props and values that we expect to be passed.

describe ('<Profile />', () => {   it ('contains h4', () => {     const wrapper = mount(<Profile user={user} />)     const value = wrapper.find('h4').text()     expect(value).toEqual('John Doe')   })   it ('accepts user props', () => {     const wrapper = mount(<Profile user={user} />);     expect(wrapper.props().user).toEqual(user)   }) })

This particular example contains two tests. In the first test, we pass the user props to the mounted Profile component. Then, we check to see if we can find a <h4> element that corresponds to what we have in the Profile component.

In the second test, we want to check if the props we passed to the mounted component equals the mock props we created above. Note that even though we are destructing the props in the Profile component, it does not affect the test.

Mock API calls

There’s a part in the project we’ve been using where an API call is made to fetch a list of users. And guess what? We can test that API call, too!

The slightly tricky thing about testing API calls is that we don’t actually want to hit the API. Some APIs have call limits or even costs for making making calls, so we want to avoid that. Thankfully, we can use Jest to mock axios requests. See this post for a more thorough walkthrough of using axios to make API calls.

First, we’ll create a new folder called __mock__ in the same directory where our __tests__ folder lives. This is where our mock request files will be created when the tests run.

module.exports = {   get: jest.fn(() => {     return Promise.resolve({     data: [       {         id: 1,         name: 'Jane Doe',         email: 'janedoe@gmail.com',         username: 'jdoe'       }     ]     })   }) }

We want to check and see that the GET request is made. We’ll import axios for that:

import axios from 'axios';

Just below the import statements, we need Jest to replace axios with our mock, so we add this:

jest.mock('axios')

The Jest API has a spyOn() method that takes an accessType? argument that can be used to check whether we are able to “get” data from an API call. We use jest.spyOn() to call the spied method, which we implemented in our __mock__ file, and it can be used with the shallow, render and mount tests we covered earlier.

it('fetches a list of users', () => {   const getSpy = jest.spyOn(axios, 'get')   const wrapper = shallow(     <App />   )   expect(getSpy).toBeCalled() })

We passed the test!

That’s a primer into the world of testing in a React application. Hopefully you now see the value that testing adds to a project and how relatively easy it can be to implement, thanks to the heavy lifting done by the joint powers of Jest and Enzyme.

Further reading

, , , , , ,
[Top]

Writing Animations That Bring Your Site to Life

Web animation is one of the factors that can strongly enhance your website’s look and feel. Sadly, unlike mobile apps, there aren’t as many websites using animation to their benefit as you would think. We don’t want to count yours among those, so this article is for you and anyone else looking for ways to use animation for a better user experience! Specifically, we’re going to learn how to make web interactions delightful using CSS animations.

Here’s what we’re going to build together:

Live Demo GitHub Repo

Before we move ahead, it’s worth mentioning that I’m going to assume you have at least some familiarity with modern front-end frameworks and a basic understanding of CSS animations. If you don’t, then no fear! CSS-Tricks has a great guides on React and Vue, as well as a thorough almanac post on the CSS animation property.

Good? OK, let’s talk about why we’d want to use animation in the first place and cover some baseline information about CSS animations.

Why would we to animate anything?

We could probably do an entire post on this topic alone. Oh, wait! Sarah Drasner already did that and her points are both poignant and compelling.

But, to sum things up based on my own experiences:

  • Animations enhance the way users interact with an interface. For example, smart animations can reduce cognitive load by giving users better context between page transitions.
  • They can provide clear cues to users, like where we want them to focus attention.
  • Animations serve as another design pattern in and of themselves, helping users to get emotionally attached to and engage with the interface.
  • Another benefit of using animations is that they can create a perception that a site or app loads faster than it actually does.

A couple of house rules with animations

Have you ever bumped into a site that animates all the things? Wow, those can be jarring. So, here’s a couple of things to avoid when working with animations so our app doesn’t fall into the same boat:

  • Avoid animating CSS properties other than transform and opacity. If other properties have to be animated, like width or height, then make sure there aren’t a lot of layout changes happening at the same time. There’s actually a cost to animations and you can see exactly how much by referring to CSS Triggers.
  • Also, just because animations can create perceived performance gains, there’s actually a point of diminishing return when it comes to using them. Animating too many elements at the same time may result in decreased performance.

Now we can get our hands dirty with some code!

Let’s build a music app

We’re going to build the music app we looked at earlier, which is inspired by Aurélien Salomon’s Dribbble shot. I chose this example so that we can focus on animations, not only within a component, but also between different routes. We’ll build this app using Vue and create animations using vanilla (i.e. no framework) CSS.

Animations should go hand-in-hand with UI development. Creating UI before defining their movement is likely to cost much more time. In this case, the Dribbble shot provides that scope for us.

Let’s start with the development.

Step 1: Spin up the app locally

First things first. We need to set up a new Vue project. Again, we’re assuming some base-level understanding of Vue here, so please check out the Learning Vue guide for more info on setting up.

We need a couple of dependencies for our work, notably vue-router for transitioning between views and sass-loader so we can write in Sass and compile to CSS. Here’s a detailed tutorial on using routes and Sass can be installed by pointing the command line at the project directory and using npm install -D sass-loader node-sass.

We have what we need!

Step 2: Setting up routes

For creating routes, we’re first going to create two bare minimum components — Artists.vue and Tracks.vue. We’ll drop a new file in the src folder called router.js and add routes for these components as:

import Vue from 'vue' import Router from 'vue-router' import Artists from './components/Artists.vue' import Tracks from './components/Tracks.vue'  Vue.use(Router) export default new Router({ 	mode: 'history', 	routes: [ 		{ 			path: '/', 			name: 'artists', 			component: Artists 		}, 		{ 			path: '/:id', 			name: 'tracks', 			component: Tracks 		} 	] })

Import router.js into the main.js and inject it to the Vue instance. Lastly, replace the content of your App.vue by <router-view/>.

Step 3: Create the components and content for the music app

We need two components that we’ll transition between with animation. Those are going to be:

  1. Artists.vue: a grid of artists
  2. Tracks.vue: An artist image with a back button

If you wanna jump ahead a bit, here are some assets to work with:

  1. Images and sample data in JSON format.
  2. Content for the components

When all is said and done, the two views will come out to something like this:

Artists.vue (left) and Tracks.vue (right)

Step 4: Animate!

Here we are, the part we’ve really wanted to get to all this time. The most important animation in the app is transitioning from Artists to Tracks when clicking on an artist. It should feel seamless where clicking on an artist image puts that image in focus while transitioning from one view into the next. This is exactly the type of animation that we rarely see in apps but can drastically reduce cognitive load for users.

To make sure we’re all on the same page, we’re going to refer to the first image in the sequence as the “previous” image and the second one as the “current” image. Getting the effect down is relatively easy as long as we know the dimensions and position of the previous image in the transition. We can animate the current image by transforming it as per previous image.

The formula that I’m using is transform: translate(x, y) scale(n), where n is equal to the size of previous image divided by the size of current image. Note that we can use a static value of n since the dimensions are fixed for all the images. For example, the image size in the Artists view is 190x190 and 240x240 in the Tracks view. Thus, we can replace n by 190/240 = 0.791. That means the transform value becomes translate(x, y) scale(0.791) in our equation.

Animating from Artists to Tracks

Next thing is to find x and y. We can get these values though click event in the Artists view as:

const {x, y} = event.target.getBoundingClientRect()

…and then send these values to the Tracks view, all while switching the route. Since we aren’t using any state management library, the two components will communicate via their parent component, which is the top level component, App.vue. In App.vue, let’s create a method that switches the route and sends the image info as params.

gotoTracks(position, artistId) { 	this.$ router.push({ 		name: 'tracks', 		params: { 			id: artistId, 			position: position 		} 	}) }

Here’s the relevant code from the repo to reference, in case you’re interested.

Since we have received the position and ID of the image in Tracks, we have all the required data to show and animate it. We’ll first fetch artist information (specifically the name and image URL) using artist ID.

To animate the image, we need to calculate the transform value from the image’s starting position. To set the transform value, I’m using CSS custom properties, which can be done with CSS-in-JS techniques as well. Note that the image’s position that we received through props will be relative to window. Therefore we’ll have to subtract some fixed offset caused by the padding of the container <div> to even out our math.

const { x, y } = this.$ route.params.position // padding-left const offsetLeft = 100 // padding-top const offsetTop = 30  // Set CSS custom property value document.documentElement.style.setProperty( 	'--translate',  	`translate($ {x - offsetLeft}px, $ {y - offsetTop}px) scale(0.792)` )

We’ll use this value to create a keyframe animation to move the image:

@keyframes move-image { 	from { 		transform: var(--translate); 	} }

This gets assigned to the CSS animation:

.image { 	animation: move-image 0.6s; }

…and it will animate the image from this transform value to its original position on component load.

Transitioning from Artists to Tracks

We can use the same technique when going the opposite direction, Tracks to Artists. As we already have the clicked image’s position stored in the parent component, we can pass it to props for Artists as well.

Transitioning from Tracks to Artists

Step 5: Show the tracks!

It’s great that we can now move between our two views seamlessly, but the Tracks view is pretty sparse at the moment. So let’s add the track list for the selected artist.

We’ll create an empty white box and a new keyframe to slide it upwards on page load. Then we’ll add three subsections to it: Recent Tracks, Popular Tracks, and Playlist. Again, if you want to jump ahead, feel free to either reference or copy the final code from the repo.

The Tracks view with content

Recent Tracks is the row of thumbnails just below the artist image where each thumbnail includes the track name and track length below it. Since we’re covering animations here, we’ll create a scale-up animation, where the image starts invisible (opacity: 0) and a little smaller than it’s natural size (scale(0.7)), then is revealed (opacity: 1 )and scales up to its natural size (transform: none).

.track { 	opacity: 0; 	transform: scale(0.7); 	animation: scale-up 1s ease forwards; }  @keyframes scale-up { 	to { 		opacity: 1; 		transform: none; 	} }

The Popular Tracks list and Playlist sit side-by-side below the Recent Tracks, where Popular tracks takes up most of the space. We can slide them up a bit on initial view with another set of keyframes:

.track { 	... 	animation: slide-up 1.5s; }  @keyframes slide-up { 	from { 		transform: translateY(140px); 	} }

To make the animation feel more natural, we’ll create a stagger effect so the Recent Tracks lead a little ahead of the Popular Tracks and Playlist using an incremental animation delay to each item.

@for $ i from 1 to 5 { 	&:nth-child(#{$ i + 1}) { 		animation-delay: #{$ i * 0.05}s; 	} }

The code above is basically looking for each child element, then adding a 0.05 second delay to each element it finds. So, for example, the first child gets a 0.05 second delay, the second child gets a 0.10 second delay and so on.

Check out how nice and natural this all looks:

Bonus: micro-interactions!

One of the fun things about working with animations is thinking through the small details because they’re what tie things together and add delight to the user experience. We call these micro-interactions and they serve a good purpose by providing visual feedback when an action is performed.

Depending on the complexity of the animations, we might need a library like anime.js or GSAP. This example is pretty straightforward, so we can accomplish everything we need by writing some CSS.

First micro-interaction: The volume icon

Let’s first get a volume icon in SVG format (Noun Project and Material Design are good sources). On click, we’ll animate-in and out its path element to show the level of volume. For this, we’ll create a method which switches its CSS class according to the volume level.

<svg @click="changeVolume"> 	<g :class="`level-$ {volumeLevel}`"> 		<path d="..."/> <!-- volume level 1 --> 		<path d="..."/> <!-- volume level 2 --> 		<path d="..."/> <!-- volume level 3 --> 		<polygon points="..."/> 	</g> </svg>

Based on this class, we can show and hide certain path elements as:

path { 	opacity: 0; 	transform-origin: left; 	transform: translateX(-5px) scale(0.6); 	transition: transform 0.25s, opacity 0.2s; }  .level-1 path:first-child, .level-2 path:first-child, .level-2 path:nth-child(2), .level-3 path { 	opacity: 1; 	transform: none; }
The animated volume control

Second micro-interaction: The favorite icon

Do you like it when you click on Twitter’s heart button? That’s because it feels unique and special by the way it animates on click. We’ll make something similar but real quick. For this, we first get an SVG heart icon and add it to the the markup. Then we’ll add a bouncy animation to it that’s triggered on click.

@keyframes bounce { 	0%, 100% { 		transform: none; 	} 	30% { 		transform: scale(1.3); 	} 	60% { 		transform: scale(0.9); 	} }

Another fun thing we can do is add other small heart icons around it with random sizes and positions. Ideally, we’d add a few absolute-positioned HTML elements that a heart as the background. Let’s Arrange each of them as below by setting their left and bottom values.

We’ll also include a fade away effect so the icons appear to dissolve as they move upward by adding a keyframe animation on the same click event.

@keyframes float-upwards { 	0%, 100% { 		opacity: 0; 	} 	50% { 		opacity: 0.7; 	} 	50%, 100% { 		transform: translate(-1px, -5px); 	} }
The animated favorite button

Summing up

That’s all! I hope you find all this motivating to try animations on your own websites and projects.

While writing this, I also wanted to expand on the fundamental animation principles we glossed over earlier because I believe that they help choose animation durations, and avoid non-meaningful animations. That’s important to discuss because doing animations correctly is better than doing them at all. But this sounds like a whole another topic to be covered in a future article.

The post Writing Animations That Bring Your Site to Life appeared first on CSS-Tricks.

CSS-Tricks

, , , ,
[Top]