Archive

Archive for September, 2019

Frankenstein Migration: Framework-Agnostic Approach (Part 1)

September 26th, 2019 No comments
Frankenstein monster should not necessarily be terrible. He can be cute. Sometimes.

Frankenstein Migration: Framework-Agnostic Approach (Part 1)

Frankenstein Migration: Framework-Agnostic Approach (Part 1)

Denys Mishunov

2019-09-26T12:30:59+02:002019-09-26T21:07:23+00:00

Migration, according to Oxford Learner’s Dictionary, is “the slow or gradual movement of something from one place to another.” This term describes many things and phenomena in our world — both with positive and negative tint. In software development, the word “migration,” when we need to upgrade or change technology in a project, usually falls under the latter case, unfortunately.

“Good,” “Fast,” “Cheap”. We used to pick only two in many situations when we need to make a choice either in development, in business, or in life in general. Typically, front-end migration, which is the main subject of this article, does not allow even that: “cheap” is out of reach for any migration, and you have to pick either “good” or “fast.” However, you cannot have both. Typically.

Frankenstein monster should not necessarily be terrible. He can be cute. Sometimes.

Frankenstein monster should not necessarily be terrible. He can be cute. Sometimes. (Large preview)

In an attempt of breaking the stereotypes, this article suggests a not-so-typical approach to framework-independent migration of front-end applications: the “Frankenstein Migration.” This approach allows us to combine “good” and “fast” while keeping the costs of migration at bay.

It’s not a silver bullet, nonetheless. Instead, I like to think about it as a small migration revolution. And like any other revolution, this approach might have side-effects, issues, and people full of energy to claim that this is not going to work even before they try.

We will certainly get to the potential issues of this approach further in the article, but bear with me and, maybe, you will still find one or two ideas useful for your next migration.

Furthermore, the same approach that we are going to discuss can be used for a broader range of tasks that are not directly related to migration:

  • Combining different bits of your application, written in different frameworks. It could be useful for rapid prototyping, bootstrapping, and even production-ready experiments.
  • Decoupling different features of your application in order to be able to deploy without re-building the whole application. Maybe even set your core features on the more frequent release cycle. It can be useful in large projects. In particular, those running through CI/CD every time you push things into master (that might take very long) and helps to save time on feature releases.
  • This approach might even allow you to have flexible hiring policy: you could hire smart developers even if they do not work with the framework of your project just yet. Developers can keep using tools and frameworks they are comfortable with and bring value to your company from day 1 (especially valuable in startups) while learning how your project works and picking the framework of your choice.

Nevertheless, this article is all about migration and before we dive deep into the dark waters of Frankenstein Migration, let’s see where we are with those “good” and “fast” migration alternatives to be aware of their strong as well as weak sides.

“Good” Migration: Complete Re-Write

Usually, complete re-write is considered to be a better way of migrating your applications in terms of quality. It makes sense: you’re writing your application from scratch, and hence, you can bring all of your experience and wisdom from current implementation into the new one right from the beginning, not as an afterthought. It is a big plus for this type of migration. However, there is a not-so-obvious problem with complete re-write.

To achieve this quality, you need time. Sometimes, a lot of time. Alternatively, many developers dedicated exclusively to re-write. Not every company can afford these options. Because of this, the most suitable scenario for this type of migration is either a small/personal project without a need for developing new features all the time or the project that is not mission-critical for your business.

To give you a perspective of time: once, I’ve been to a complete re-write of an application that took two years. Still, during all this time, the old project with all of its bugs was up and running. Nobody wanted to touch it and, instead, concentrated on the “new and shiny” one. Typically.

As a summary for this type of migration:

PROS:

  • Resulting quality.

CONS:

  • The time required to get that quality to the end-user;
  • The amount of work to be done during complete re-write is overwhelming, making it hard to estimate the time and resources required for this type of migration upfront.

Those who plan to migrate but cannot afford complete re-write due to time or resource constraints might want to look at the next migration type.

“Fast” Migration: Gradual Migration

Contrary to complete re-write, gradual migration does not require you to wait for the complete migration. Instead, you migrate application bit-by-bit and make those new bits available to your users as soon as they are ready. Calling this type of migration “fast” is a bit of a stretch, of course, if we talk about the whole application, but separate features clearly can be delivered to the users much faster. Though, let’s give gradual migration unbiased pros and cons as well:

PROS:

  • When it comes to delivering separate application portions to the end-user, gradual migration is indeed faster than complete re-write since we don’t need to wait for the whole application to be re-written.
  • By delivering new, migrated bits gradually, we get feedback on them (from the end-users) as we go. It allows us to catch bugs and issues faster and in a more isolated manner, comparing to complete re-write, where we deploy the migrated application as a whole and might overlook some smaller issues or bugs.

To better understand problems of gradual migration, try installing React in parallel with Vue in the same project as in Vue-to-React migration. I believe you have to truly enjoy digging configurations and solving console errors to enjoy this process. However, we don’t even need to get that deep. Let’s consider the following legacy example:

Components, no matter CSS Modules, are very vulnerable to global styles. In this simple example, we have at least four ways to break the Vue component visually.

Components, no matter CSS Modules, are very vulnerable to global styles. In this simple example, we have at least four ways to break the Vue component visually. (Source) (Large preview)

Here, we are integrating a Vue component into a Vanilla JS application as in potential Vanilla-to-Vue migration scenario. CSS Modules are responsible for the styling of the Vue component and provide proper scope for your components. As you can see, however, even though styling for the Vue component tells the subheader to be green, it is entirely off, and the example presents as many as four (but there are really many more) trivial ways of breaking component’s styling.

Also, other global styles getting into this Vue component can extend the look of our component entirely and, even though it might be seen as a feature in some projects, makes things hard to predict, maintain and is not necessarily what we want. This example reveals the most common and hard-to-tackle problem of gradual migration: the “Cascade” part of CSS can easily break components.

This artificially simplified example also reveals several other big problems related to gradual migration:

  • Because we’re combining two different systems, the result might turn out very cluttered: we have to support two different systems with their dependencies, requirements, and opinions simultaneously in the same project. Different frameworks might require the same dependencies, but in different versions that result in version conflicts.
  • Since the integrated application (Vue in our case) is rendered in the main DOM tree, global scope in JavaScript is conflicts-prone: both systems might want to manipulate DOM nodes that do not belong to them.
  • Furthermore, let me repeat this, as we are going to get to this point several times in this article: Because of the global nature of this integration, CSS overflows from one system to another without much control, polluting the global scope the same way JavaScript does.

To fix these issues (or at least to keep them at bay) we need to implement workarounds, hacks and implement development style for the whole team to follow. It all leads to lower, compromised-driven, result quality after gradual migration. It’s also harder to maintain such a project than that after complete re-write.

Both of the existing options have limitations and constraints, but we still have to pick one if migration is required. However, should this choice be as painful? Wouldn’t it be great to combine the best parts of both somehow, while minimizing the negative side-effects? Is it possible at all?

Let me introduce Frankenstein Migration to you.

Frankenstein Migration. Part1: Theory

This part of the series answers what Frankenstein Migration is. We are going to find out how it is different from other migration types. Also, most importantly, we are going to dive into the theory of technologies and approaches that make this type of migration even possible.

Why “Frankenstein”?

The name comes from the way the approach works. In essence, it provides a roadmap for two or more applications, written in entirely different frameworks, to work as one solid well-orchestrated body. Just like Victor Frankenstein built his monster in Mary Shelley’s book “Frankenstein; or, The Modern Prometheus”.

People tend to be afraid of monsters. Typically.

People tend to be afraid of monsters. Typically. (Large preview)

Keep in mind that recently different people and organizations independently have explored the problem of combining different frameworks in the same project: Micro Frontends, Allegro Tech, etc. Frankenstein Migration, however, is an independent, structured approach to migration in the first place.

There are two fundamental technologies/approaches in the heart of Frankenstein Migration:

Microservices Architecture

The main idea behind microservices (contrary to monolithic architecture) is that you architect your application with the help of isolated and independent services dedicated to one particular small job.

I’ll repeat the things you need to keep in mind:

  • “independent”
  • “one job”

Microservices architecture is a set of independent services that are all connected into a network.

Microservices architecture is a set of independent services that are all connected into a network. (Large preview)

In an application, such services get connected into a communicational network that can get new services added/removed/replaced easily at any time, and that’s what we call “microservices.” This flexible approach is well-established and widely-adopted by back-end and server architects. However, can we have real microservices on the frontend?

Let’s take a look at the main features of service in such architecture:

  • Small in size,
  • Bounded by contexts,
  • Built and released with automated processes,
  • Autonomously developed, and
  • Independently deployable.

The first three points are not a problem for front-end technologies. Pretty much all of the modern frameworks and libraries provide one or another type of abstraction to satisfy these three requirements. However, independence of services for both development and deployment has always been a problem for front-end technologies. Even in the landscape of modern frameworks that provide a paradigm of a component (like React or Vue), those components are usually still very dependent on the system and cannot be autonomous or independent from the framework that initialized them. You can always fall back to iframe, of course, and get this level of independence. However, let’s find a better — not so radical — alternative.

There is one type of component that gets close to this level of independence, and that is Web Components. So this is the second building block of Frankenstein Migration.

Web Components

People say that it’s enough to mention “Web Components” to start a fight nowadays. People like Rich Harris even write blog posts about why they don’t use Web Components. However, the purpose of this article is not to convince you that Web Components are useful or to initiate a hot debate on the topic. Web Components is not a make-everything-OK tool. As with any other tool, there might be limitations and possible side effects.

Serhii Kulykov provides a series of better-grounded articles on the subject and also curates a “Web Components the Right Way” repository in which you can find much more information for general Web Components discussion. However, when it comes to Frankenstein Migration, Web Components prove to be a very suitable instrument.

Let’s take a quick look at the main elements of Web Components that make them suitable candidates for closing gaps in microservices adoption by the frontend:

In particular, Shadow DOM is the tool capable of fixing the issues we typically meet in gradual migration and provides an actual encapsulation mechanism for component’s CSS. Previously, we mentioned that maintaining a cascade of CSS is problematic when we try to use components written with different frameworks or libraries side-by-side in the global scope.

Now, let’s see how Shadow DOM solves this problem.

CSS Scoping vs. Encapsulation. The Shadow DOM style

The encapsulation mechanism of Shadow DOM is essential for understanding as it’s different from how popular tools like CSS Modules or scoped attribute in Vue work. These tools provide scoping for styles, defined in a component, without breaking global styles and other components. However, they don’t protect components from global styles leaking into the component (the very problem of cascade discussed above) and hence, potentially breaking your components.

At the same time, styles defined within Shadow DOM are not only scoped to the current component but are also protected from global styles that don’t have explicit access to the internals of Shadow DOM no matter the specificity. To see it in action, take a look at the updated example:

Here, we moved styles out of the Vue component, straight into the Shadow DOM and that’s what’s happening (automatic though) when you set up your Vue components to work within Shadow DOM. This example shows that Shadow DOM provides a mechanism for genuinely independent components that can be used in any context (library, framework) while preserving the look and functionality of these components.

Now let’s talk through the main concepts and steps of Frankenstein Migration to see how exactly microservices and Web Components help us in the migration of front-end applications.

Let’s assume you have a project that you want to migrate to another framework.

Our demo project on a not-so-hot-anymore framework that we want to migrate.

Our demo project on a not-so-hot-anymore framework that we want to migrate. (Large preview)

It doesn’t matter what framework/library we migrate away from and what framework/library we want to get to; the principle and steps are the same for more or less any tool you pick (some generic exceptions are mentioned further in the article). That’s why Frankenstein Migration is called the “framework-agnostic” approach.

Now, where do we start?

  1. Identify Microservices
  2. Allow Host-to-Alien Access
  3. Write An Alien Component
  4. Write Web Component Wrapper Around Alien Service
  5. Replace Host Service With Web Component
  6. Rinse And Repeat
  7. Switch To Alien

1. Identify Microservices

It is the core step, essential for the whole process’ success or failure. So we should dive more in-depth here.

Technically, we have to split our existing application into microservices virtually. It is an entirely subjective process though and doesn’t have a “correct” answer. However, what does it mean in practice then?

By “virtually” I mean that in general, you don’t need to change your existing application physically: it’s enough to have structure settled in any form even if only on paper.

We have to have a clear split in our current application into services that are:

  • Independent;
  • Dedicated to one small job.

An input field for adding new items to a database could be an example of a service: it’s dedicated to one particular job (adding new items) and does the job without dependency on any other service. Alternatively, the whole listing of items already added to the database: it’s trivial in functionality and, again, doesn’t depend on other components for listing items. It doesn’t sound too complicated, I believe, but that might be a deceptive feeling.

Let’s start with the easy parts: If a framework in your current project is based on a concept of “component” (React, Vue), you probably already have a reasonable basis for this type of migration. You can treat every component of your application as a separate service in a microservices architecture.

If your project currently is on a legacy basis (e.g. such as jQuery), you should turn on your imagination and think through how you would like to structure your application, following microservices’ principles of independence and one-job per service.

We can structure our application any way we want to just as long as we follow the principles of microservices.

We can structure our application any way we want to just as long as we follow the principles of microservices. (Large preview)

Refactor If Needed

I hate my ability to repeat things multiple times, but in this case, it makes much sense: be sure that your services (or components, or containers, or whatever you prefer to call your building blocks) do not depend on other services. Otherwise, both of the services should be treated as one — for the sake of independence and isolation.

A simple test to make sure your service is appropriately independent: Remove HTML for your component/service from the Host and reload the application. If there are no JS errors in the console and the remaining part of the application works as expected, the service in question is most probably independent enough from the rest of the application.

To give you a better explanation, let’s consider the following, artificially simplified, legacy example:

index.html

<form id="form">
  <input id="inputTodo" type="text" placeholder="New Todo"/>
  <button type="submit">Add Todo</button>
</form>

<ul id="listing" class="d-none"></ul>

index.js

const form = document.getElementById("form");
form.addEventListener("submit", ev => {
  ev.preventDefault();
  const listing = document.getElementById("listing");
  const input = document.getElementById("inputTodo");
  const newEntry = document.createElement("li");
  newEntry.innerHTML = input.value;
  input.value = "";
  listing.prepend(newEntry);
  listing.classList.remove("d-none");
});

Here, #form expects #listing to be present in the markup as its submit handler updates the listing directly. Hence these two depend on each other, and we cannot split them into separate services: they are parts of the same job and help each other to serve the same purpose.

However, as a possibly better alternative, we could refactor this code to make the two components independent from each other and satisfy the requirement of independence:

index.js

function notifyAboutNewItem(ev) {
  ev.preventDefault();
  const input = document.getElementById("inputTodo");
  const event = new CustomEvent("new-todo", { detail: { val: input.value } });
  document.dispatchEvent(event);
  input.value = "";
}
function updateList(ev) {
  const listing = document.getElementById("listing");
  const newEntry = document.createElement("li");
  newEntry.innerHTML = ev.detail.val;
  listing.prepend(newEntry);
  listing.classList.remove("d-none");
}

document.getElementById("form").addEventListener("submit", notifyAboutNewItem);
document.addEventListener("new-todo", updateList);

Now, our #form and #listing components do not communicate with each other directly, but through the DOM event (it can be a state management or any other storing mechanism with notification instead): when a new item is added, notifyAboutNewItem() dispatches an event, while we subscribe #listing to listen to this event. Now any component can dispatch this event. Moreover, any component can listen to it: our components became independent from each other, and hence we can treat them separately in our migration.

Too Small For A Service?

Another thing to keep in mind: when splitting your application with already-existing components (like React or Vue) into services, some of your components might be too small for a proper service. It’s not to say they cannot be small, because nothing stops you from structuring your application as atomic as you wish, but most of the simple re-usable UI components (like the form button or input field in the previous example) are better included in broader services for the sake of minimizing work for you.

On a larger scale, you can approach Step #1 as chaotic as you wish. You don’t need to start Frankenstein Migration with the global plan: you can start with just one element of your application. For example, split some complex

into services. Alternatively, you can structure your app one whole route or page at a time and then, maybe, your

becomes one single service. It doesn’t matter much; any structure is better than heavy, hard-to-maintain monolithic application. However, I would suggest being careful with the too granular approach — it’s boring and doesn’t give you many benefits in this case.

My rule of thumb: you get the best flow of process with services that can be migrated and pushed into production in one week. If it takes less, then your services are a tad too small. If it takes longer, you might be trying to chew too many large pieces, so it’s better to split those. However, it all depends on your capacity and your project’s needs.

After virtually splitting your current application into services, we’re ready to move on to the next step.

2. Allow Host-to-Alien Access

This should come as absolutely unclear title, of course. Neither have we discussed what is Host nor have we mentioned Alien yet. So let’s clear these out first.

We have mentioned that services in our current application should be independent. However, this is not the only place where we strive for independence. Contrary to the typical gradual migration approach, where we put everything in the same pot and develop new components alongside the old ones, Frankenstein Migration requires us to develop new components outside of the current application.

Bear with me.

Further, in the article, we are going to use word Host to refer to the current application, written with the framework we’re about to migrate away from. At the same time, the new application, written with the framework we are migrating to will be called Alien, as it injects its services into Host at some point.

Host — is our current application; Alien — our migrated application on the new framework

‘Host’ is our current application while ‘Alien’ is our migrated application on the new framework. (Large preview)

Yes, we do not treat Alien as just a set of components, but as a proper application that we build over time. Technically, both Host and Alien should be two completely different applications written with any framework you want, with own dependencies, bundling tools, and so on. It is essential to avoid typical problems of gradual migration, however, there is a significant additional benefit to this approach. By keeping Host and Alien independent, we get both systems deployable anytime — should we need this at some point of migration.

There are several ways you can organize Host and Alien:

  • Different domains or IP addresses;
  • Different folders on your server;
  • git submodules;
  • And so on.

The primary condition for any scenario you pick, though, is that the Host should have access to Alien’s assets. So, if you choose to work with different domains, you have to take a look at setting up CORS for your Alien domain. If you decide to organize it as simple as different folders on your server, make sure resources from Host’s folder have access to Alien’s folder. If you go with git submodule, before adding Alien as a submodule of your Host, make sure you read the documentation and know how it works: it’s not as hard as it may sound.

Host should have access to Alien

Host should have access to Alien. (Large preview)

After you have set up your applications and provided access from Host to Alien, things go quite straightforward.

3. Write An Alien Component

The title should be self-explanatory, I believe. At this point, we have:

  • A clear overview of the services in our Host application,
  • Set up application basis for Alien, and
  • Allowed access to Alien’s assets from Host.

Now it’s time to pick a Host service we want to migrate first and re-write this service in Alien application, using the new framework. Keep in mind: we do not wait for the whole application to be re-written as in “complete re-write.” Instead, we migrate bit-by-bit as in gradual migration.

The next, practical part of the article will contain more details of actual tips on how to write your Alien component for easier integration. However, for now, you might have a question:

If Alien and Host are entirely different systems, how on Earth are we supposed to integrate our newly-written Alien service into Host?

Here is where we get to the second building block of the approach: the Web Components.

4. Write Web Component Wrapper Around Alien Service

The Web Component wrapper is the core of our integration part. Before I cover more on this, there are a couple of things to keep in mind:

  1. First of all, you are free to pick any abstraction layer you want for your Web Component. You can pick lit-element, Stencil, or really anything that gives you Web Components at the end. However, the Web Components that we need for Frankenstein Migration are so pure (they are just the wrappers and nothing more) that I think using an abstraction layer for this is overkill.
  2. Secondly, your Web Component wrapper lives on the Host’s side. So, based on the needs and requirements of your Host, you have to decide for yourself whether or not you need to polyfill Web Components. Just check the support for two technologies that we are going to rely upon:
    1. Shadow DOM, and
    2. Custom Elements.

      The support for both is quite similar, and with Edge switching to Chromium in version 75, native support for Web Components in browsers is very impressive. Nevertheless, should you need the polyfills to run your Web Components in IE11, for example, take a look at the stable polyfill.

Web Component is a pure wrapper around Alien service

Web Component is a pure wrapper around Alien service. (Large preview)

The main functions of our Web Component wrapper:

  • Setting up a boilerplate for a new Custom Element with Shadow DOM;
  • Importing our Alien component;
  • Rendering Alien component within Shadow DOM of the wrapper;
  • Importing relevant styles and putting them in the Shadow DOM together with the Alien component itself (only if required by the Alien component).

As a sneak-preview of how such component can feel like, take a look at the very basic example of importing a React component (HeaderApp) into Web Component wrapper (frankenstein-header-wrapper):

import React from "../../react/node_modules/react";
import ReactDOM from "../../react/node_modules/react-dom";
import HeaderApp from "../../react/src/components/Header";

class FrankensteinWrapper extends HTMLElement {
  connectedCallback() {
    const mountPoint = document.createElement("div");
    this.attachShadow({ mode: "open" }).appendChild(mountPoint);
    ReactDOM.render(, mountPoint);
  }
}
customElements.define("frankenstein-header-wrapper", FrankensteinWrapper);

Note: Take a closer look at the imports. We do not install React in our Host but instead import everything from Alien’s location with all of its dependencies. In this case, Alien has been added to Host as a git submodule and hence is visible to Host as a sub-folder that makes accessing its contents from Host a trivial task. Here, Alien is still a separate entity that is independent from Host though. It should explain the importance of Step #2 where we allowed access from Host to Alien.

That’s pretty much it for the functions of the wrapper. After you wrote your Web Component, imported your Alien service and rendered it within the Web Component, we need to replace our Host service with our Web Component (that brings Alien service with itself).

5. Replace Host Service With Web Component

This step is very trivial, I believe. What you need to do effectively is replace markup of your Host service with your Web Component. The next chapter will cover different ways of setting up communication between your Host and Alien (that sits within Web Component) components, but in essence, there is no rocket science here:

  1. We have to connect both services to the same storage;
  2. We have to dispatch and listen (on both sides) to events when storage gets updated.

After we've wrapped Alien's service with the Web Component wrapper, it's time to replace the corresponding Host service with the wrapper

After we’ve wrapped Alien’s service with the Web Component wrapper, it’s time to replace the corresponding Host service with the wrapper. (Large preview)

This schema should be the same no matter whether you have a state management system(s), route your communication through localStorage, or communicate with simple DOM events. By replacing your Host service with the Web Component wrapper, you finish the migration of the service and can enjoy this cute Frankenstein in your project.

However, it doesn’t smell like a real migration just yet. There has to be something else to it.

6. Rinse And Repeat

After you’ve migrated your first service, you need to go through Steps 3 to 5 for all of your services/components. All the principles and recommendations remain valid. Just continue evolving your Alien as if you do a complete re-write: you’re working on a new application in parallel with your Host. You have to be able to start and build your Alien at any time and any way you want. The only difference now is that you can push your Alien services into production on Host whenever they are ready.

At some point, you get all your services migrated, but you won’t have Host services anymore because all of them are replaced with Web Component wrappers that containing Alien services. Technically speaking, you get Alien application with remaining glue from Host. You could leave your application like this, but it’s not performant (we discuss performance tips and tricks in one of the next parts of the article) and looks quite messy, to be honest. There is a better way.

When all of the Host services got replaced with Web Component wrappers, our Host resembles Alien and it's time for a simple trick

When all of the Host services got replaced with Web Component wrappers, our Host resembles Alien and it’s time for a simple trick. (Large preview)

I have to repeat the core idea: “At this point, you have Alien application with remaining glue from Host.” It means that instead of serving our users this not-so-cute-anymore Frankenstein, we can serve real Alien instead of Host. At this moment, Alien should represent precisely the same picture as we have in Host, but orchestrated by Alien’s natural means and without any Web Components. The only question is: “How do we do that?”

7. Switch To Alien

Remember when we said that an independence of Host and Alien is essential for this type of migration, and so we split them into two separate applications? Well, now it’s time to enjoy the benefits of that decision.

If you kept your Host and Alien independent after all Host services got replaced, you should be able to switch your server configuration to serve requests from Alien and forget about Frankenstein

If you kept your Host and Alien independent after all Host services got replaced, you should be able to switch your server configuration to serve requests from Alien and forget about Frankenstein. (Large preview)

I assume you serve your Host with a configurable web server. By “configurable”, I mean that you have control over the configuration file of your server. It allows you to control routing to your site.

If this assumption is correct, you should be able to switch your server to serve requests from your Alien’s folder instead of Host for all incoming HTTP requests. For example, in your Apache’s httpd.conf, if you used git submodule for adding a React application to your Host, you should be able to update DocumentRoot.

For example, the default setting:

DocumentRoot "/var/www/html"

becomes something like:

DocumentRoot "/var/www/html/react/dist"

That’s it! From now on, we’re directing HTTP traffic to our React subfolder.

When this configuration is confirmed to be working and your users are served your fully migrated Alien application instead of your Host, your Alien becomes your new Host. Now, the old Host and all of its Frankenstein parts (including the Web Component wrappers) are not needed anymore and can be safely thrown away! Your migration is over.

Conclusion

All in all, Frankenstein Migration — is an attempt to combine “good” and “fast” migration types in which we get high-quality results such as the complete re-write that is combined with the delivery speed of gradual migration. This way, we’re able to deliver migrated services to the end-users as soon as the services are ready.

I realize that the ideas in this article may feel provoking for some readers. Others may feel like we’re overdoing things. Keep in mind that this type of migration still needs testing with as many possible frameworks, libraries, and their combinations. The next part of this article is going to show practical examples of this approach along with code examples and git repositories for you to play with at your own pace. We wouldn’t want people to form a false opinion by claiming that it’s not going to work without even trying, would we?

Frankenstein, even if cute, has to go for now. See you in the next part, Frank.

Frankenstein, even if cute, has to go for now. See you in the next part, Frank. (Large preview)
(dm, yk, il)
Categories: Others Tags:

A Dark Mode Toggle with React and ThemeProvider

September 25th, 2019 No comments

I like when websites have a dark mode option. Dark mode makes web pages easier for me to read and helps my eyes feel more relaxed. Many websites, including YouTube and Twitter, have implemented it already, and we’re starting to see it trickle onto many other sites as well.

In this tutorial, we’re going to build a toggle that allows users to switch between light and dark modes, using a <ThemeProvider wrapper from the styled-components library. We’ll create a useDarkMode custom hook, which supports the prefers-color-scheme media query to set the mode according to the user’s OS color scheme settings.

If that sounds hard, I promise it’s not! Let’s dig in and make it happen.

See the Pen
Day/night mode switch toggle with React and ThemeProvider
by Maks Akymenko (@maximakymenko)
on CodePen.

Let’s set things up

We’ll use create-react-app to initiate a new project:

npx create-react-app my-app
cd my-app
yarn start

Next, open a separate terminal window and install styled-components:

yarn add styled-components

Next thing to do is create two files. The first is global.js, which will contain our base styling, and the second is theme.js, which will include variables for our dark and light themes:

// theme.js
export const lightTheme = {
  body: '#E2E2E2',
  text: '#363537',
  toggleBorder: '#FFF',
  gradient: 'linear-gradient(#39598A, #79D7ED)',
}

export const darkTheme = {
  body: '#363537',
  text: '#FAFAFA',
  toggleBorder: '#6B8096',
  gradient: 'linear-gradient(#091236, #1E215D)',
}

Feel free to customize variables any way you want, because this code is used just for demonstration purposes.

// global.js
// Source: https://github.com/maximakymenko/react-day-night-toggle-app/blob/master/src/global.js#L23-L41

import { createGlobalStyle } from 'styled-components';

export const GlobalStyles = createGlobalStyle`
  *,
  *::after,
  *::before {
    box-sizing: border-box;
  }

  body {
    align-items: center;
    background: ${({ theme }) => theme.body};
    color: ${({ theme }) => theme.text};
    display: flex;
    flex-direction: column;
    justify-content: center;
    height: 100vh;
    margin: 0;
    padding: 0;
    font-family: BlinkMacSystemFont, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
    transition: all 0.25s linear;
  }

Go to the App.js file. We’re going to delete everything in there and add the layout for our app. Here’s what I did:

import React from 'react';
import { ThemeProvider } from 'styled-components';
import { lightTheme, darkTheme } from './theme';
import { GlobalStyles } from './global';

function App() {
  return (
    <ThemeProvider theme={lightTheme}>
      <>
        <GlobalStyles />
        <button>Toggle theme</button>
        <h1>It's a light theme!</h1>
        <footer>
        </footer>
      </>
    </ThemeProvider>
  );
}

export default App;

This imports our light and dark themes. The ThemeProvider component also gets imported and is passed the light theme (lightTheme) styles inside. We also import GlobalStyles to tighten everything up in one place.

Here’s roughly what we have so far:

Now, the toggling functionality

There is no magic switching between themes yet, so let’s implement toggling functionality. We are only going to need a couple lines of code to make it work.

First, import the useState hook from react:

// App.js
import React, { useState } from 'react';

Next, use the hook to create a local state which will keep track of the current theme and add a function to switch between themes on click:

// App.js
const [theme, setTheme] = useState('light');

// The function that toggles between themes
const toggleTheme = () => {
  // if the theme is not light, then set it to dark
  if (theme === 'light') {
    setTheme('dark');
  // otherwise, it should be light
  } else {
    setTheme('light');
  }
}

After that, all that’s left is to pass this function to our button element and conditionally change the theme. Take a look:

// App.js
import React, { useState } from 'react';
import { ThemeProvider } from 'styled-components';
import { lightTheme, darkTheme } from './theme';
import { GlobalStyles } from './global';

// The function that toggles between themes
function App() {
  const [theme, setTheme] = useState('light');
  const toggleTheme = () => {
    if (theme === 'light') {
      setTheme('dark');
    } else {
      setTheme('light');
    }
  }
  
  // Return the layout based on the current theme
  return (
    <ThemeProvider theme={theme === 'light' ? lightTheme : darkTheme}>
      <>
        <GlobalStyles />
        // Pass the toggle functionality to the button
        <button onClick={toggleTheme}>Toggle theme</button>
        <h1>It's a light theme!</h1>
        <footer>
        </footer>
      </>
    </ThemeProvider>
  );
}

export default App;

How does it work?

// global.js
background: ${({ theme }) => theme.body};
color: ${({ theme }) => theme.text};
transition: all 0.25s linear;

Earlier in our GlobalStyles, we assigned background and color properties to values from the theme object, so now, every time we switch the toggle, values change depending on the darkTheme and lightTheme objects that we are passing to ThemeProvider. The transition property allows us to make this change a little more smoothly than working with keyframe animations.

Now we need the toggle component

We’re generally done here because you now know how to create toggling functionality. However, we can always do better, so let’s improve the app by creating a custom Toggle component and make our switch functionality reusable. That’s one of the key benefits to making this in React, right?

We’ll keep everything inside one file for simplicity’s sake,, so let’s create a new one called Toggle.js and add the following:

// Toggle.js
import React from 'react'
import { func, string } from 'prop-types';
import styled from 'styled-components';
// Import a couple of SVG files we'll use in the design: https://www.flaticon.com
import { ReactComponent as MoonIcon } from 'icons/moon.svg';
import { ReactComponent as SunIcon } from 'icons/sun.svg';

const Toggle = ({ theme, toggleTheme }) => {
  const isLight = theme === 'light';
  return (
    <button onClick={toggleTheme} >
      <SunIcon />
      <MoonIcon />
    </button>
  );
};

Toggle.propTypes = {
  theme: string.isRequired,
  toggleTheme: func.isRequired,
}

export default Toggle;

You can download icons from here and here. Also, if we want to use icons as components, remember about importing them as React components.

We passed two props inside: the theme will provide the current theme (light or dark) and toggleTheme function will be used to switch between them. Below we created an isLight variable, which will return a boolean value depending on our current theme. We’ll pass it later to our styled component.

We’ve also imported a styled function from styled-components, so let’s use it. Feel free to add this on top your file after the imports or create a dedicated file for that (e.g. Toggle.styled.js) like I have below. Again, this is purely for presentation purposes, so you can style your component as you see fit.

// Toggle.styled.js
const ToggleContainer = styled.button`
  background: ${({ theme }) => theme.gradient};
  border: 2px solid ${({ theme }) => theme.toggleBorder};
  border-radius: 30px;
  cursor: pointer;
  display: flex;
  font-size: 0.5rem;
  justify-content: space-between;
  margin: 0 auto;
  overflow: hidden;
  padding: 0.5rem;
  position: relative;
  width: 8rem;
  height: 4rem;

  svg {
    height: auto;
    width: 2.5rem;
    transition: all 0.3s linear;
    
    // sun icon
    &:first-child {
      transform: ${({ lightTheme }) => lightTheme ? 'translateY(0)' : 'translateY(100px)'};
    }
    
    // moon icon
    &:nth-child(2) {
      transform: ${({ lightTheme }) => lightTheme ? 'translateY(-100px)' : 'translateY(0)'};
    }
  }
`;

Importing icons as components allows us to directly change the styles of the SVG icons. We’re checking if the lightTheme is an active one, and if so, we move the appropriate icon out of the visible area — sort of like the moon going away when it’s daytime and vice versa.

Don’t forget to replace the button with the ToggleContainer component in Toggle.js, regardless of whether you’re styling in separate file or directly in Toggle.js. Be sure to pass the isLight variable to it to specify the current theme. I called the prop lightTheme so it would clearly reflect its purpose.

The last thing to do is import our component inside App.js and pass required props to it. Also, to add a bit more interactivity, I’ve passed condition to toggle between “light” and “dark” in the heading when the theme changes:

// App.js
<Toggle theme={theme} toggleTheme={toggleTheme} />
<h1>It's a {theme === 'light' ? 'light theme' : 'dark theme'}!</h1>

Don’t forget to credit the flaticon.com authors for the providing the icons.

// App.js
<span>Credits:</span>
<small><b>Sun</b> icon made by <a href="https://www.flaticon.com/authors/smalllikeart">smalllikeart</a> from <a href="https://www.flaticon.com">www.flaticon.com</a></small>
<small><b>Moon</b> icon made by <a href="https://www.freepik.com/home">Freepik</a> from <a href="https://www.flaticon.com">www.flaticon.com</a></small>

Now that’s better:

The useDarkMode hook

While building an application, we should keep in mind that the app must be scalable, meaning, reusable, so we can use in it many places, or even different projects.

That is why it would be great if we move our toggle functionality to a separate place — so, why not to create a dedicated account hook for that?

Let’s create a new file called useDarkMode.js in the project src directory and move our logic into this file with some tweaks:

// useDarkMode.js
import { useEffect, useState } from 'react';

export const useDarkMode = () => {
  const [theme, setTheme] = useState('light');
  const toggleTheme = () => {
    if (theme === 'light') {
      window.localStorage.setItem('theme', 'dark')
      setTheme('dark')
    } else {
      window.localStorage.setItem('theme', 'light')
      setTheme('light')
    }
  };

  useEffect(() => {
    const localTheme = window.localStorage.getItem('theme');
    localTheme && setTheme(localTheme);
  }, []);

  return [theme, toggleTheme]
};

We’ve added a couple of things here. We want our theme to persist between sessions in the browser, so if someone has chosen a dark theme, that’s what they’ll get on the next visit to the app. That’s a huge UX improvement. For this reasons we use localStorage.

We’ve also implemented the useEffect hook to check on component mounting. If the user has previously selected a theme, we will pass it to our setTheme function. In the end, we will return our theme, which contains the chosen theme and toggleTheme function to switch between modes.

Now, let’s implement the useDarkMode hook. Go into App.js, import the newly created hook, destructure our theme and toggleTheme properties from the hook, and, put them where they belong:

// App.js
import React from 'react';
import { ThemeProvider } from 'styled-components';
import { useDarkMode } from './useDarkMode';
import { lightTheme, darkTheme } from './theme';
import { GlobalStyles } from './global';
import Toggle from './components/Toggle';

function App() {
  const [theme, toggleTheme] = useDarkMode();
  const themeMode = theme === 'light' ? lightTheme : darkTheme;

  return (
    <ThemeProvider theme={themeMode}>
      <>
        <GlobalStyles />
        <Toggle theme={theme} toggleTheme={toggleTheme} />
        <h1>It's a {theme === 'light' ? 'light theme' : 'dark theme'}!</h1>
        <footer>
          Credits:
          <small>Sun icon made by smalllikeart from www.flaticon.com</small>
          <small>Moon icon made by Freepik from www.flaticon.com</small>
        </footer>
      </>
    </ThemeProvider>
  );
}

export default App;

This almost works almost perfectly, but there is one small thing we can do to make our experience better. Switch to dark theme and reload the page. Do you see that the sun icon loads before the moon icon for a brief moment?

That happens because our useState hook initiates the light theme initially. After that, useEffect runs, checks localStorage and only then sets the theme to dark.

So far, I found two solutions. The first is to check if there is a value in localStorage in our useState:

// useDarkMode.js
const [theme, setTheme] = useState(window.localStorage.getItem('theme') || 'light');

However, I am not sure if it’s a good practice to do checks like that inside useState, so let me show you a second solution, that I’m using.

This one will be a bit more complicated. We will create another state and call it componentMounted. Then, inside the useEffect hook, where we check our localTheme, we’ll add an else statement, and if there is no theme in localStorage, we’ll add it. After that, we’ll set setComponentMounted to true. In the end, we add componentMounted to our return statement.

// useDarkMode.js
import { useEffect, useState } from 'react';

export const useDarkMode = () => {
  const [theme, setTheme] = useState('light');
  const [componentMounted, setComponentMounted] = useState(false);
  const toggleTheme = () => {
    if (theme === 'light') {
      window.localStorage.setItem('theme', 'dark');
      setTheme('dark');
    } else {
      window.localStorage.setItem('theme', 'light');
      setTheme('light');
    }
  };

  useEffect(() => {
    const localTheme = window.localStorage.getItem('theme');
    if (localTheme) {
      setTheme(localTheme);
    } else {
      setTheme('light')
      window.localStorage.setItem('theme', 'light')
    }
    setComponentMounted(true);
  }, []);
  
  return [theme, toggleTheme, componentMounted]
};

You might have noticed that we’ve got some pieces of code that are repeated. We always try to follow the DRY principle while writing the code, and right here we’ve got a chance to use it. We can create a separate function that will set our state and pass theme to the localStorage. I believe, that the best name for it will be setTheme, but we’ve already used it, so let’s call it setMode:

// useDarkMode.js
const setMode = mode => {
  window.localStorage.setItem('theme', mode)
  setTheme(mode)
};

With this function in place, we can refactor our useDarkMode.js a little:

// useDarkMode.js
import { useEffect, useState } from 'react';
export const useDarkMode = () => {
  const [theme, setTheme] = useState('light');
  const [componentMounted, setComponentMounted] = useState(false);

  const setMode = mode => {
    window.localStorage.setItem('theme', mode)
    setTheme(mode)
  };

  const toggleTheme = () => {
    if (theme === 'light') {
      setMode('dark');
    } else {
      setMode('light');
    }
  };

  useEffect(() => {
    const localTheme = window.localStorage.getItem('theme');
    if (localTheme) {
      setTheme(localTheme);
    } else {
      setMode('light');
    }
    setComponentMounted(true);
  }, []);

  return [theme, toggleTheme, componentMounted]
};

We’ve only changed code a little, but it looks so much better and is easier to read and understand!

Did the component mount?

Getting back to componentMounted property. We will use it to check if our component has mounted because this is what happens in useEffect hook.

If it hasn’t happened yet, we will render an empty div:

// App.js
if (!componentMounted) {
  return <div />
};

Here is how complete code for the App.js:

// App.js
import React from 'react';
import { ThemeProvider } from 'styled-components';
import { useDarkMode } from './useDarkMode';
import { lightTheme, darkTheme } from './theme';
import { GlobalStyles } from './global';
import Toggle from './components/Toggle';

function App() {
  const [theme, toggleTheme, componentMounted] = useDarkMode();

  const themeMode = theme === 'light' ? lightTheme : darkTheme;

  if (!componentMounted) {
    return <div />
  };

  return (
    <ThemeProvider theme={themeMode}>
      <>
        <GlobalStyles />
        <Toggle theme={theme} toggleTheme={toggleTheme} />
        <h1>It's a {theme === 'light' ? 'light theme' : 'dark theme'}!</h1>
        <footer>
          <span>Credits:</span>
          <small><b>Sun</b> icon made by <a href="https://www.flaticon.com/authors/smalllikeart">smalllikeart</a> from <a href="https://www.flaticon.com">www.flaticon.com</a></small>
          <small><b>Moon</b> icon made by <a href="https://www.freepik.com/home">Freepik</a> from <a href="https://www.flaticon.com">www.flaticon.com</a></small>
        </footer>
      </>
    </ThemeProvider>
  );
}

export default App;

Using the user’s preferred color scheme

This part is not required, but it will let you achieve even better user experience. This media feature is used to detect if the user has requested the page to use a light or dark color theme based on the settings in their OS. For example, if a user’s default color scheme on a phone or laptop is set to dark, your website will change its color scheme accordingly to it. It’s worth noting that this media query is still a work in progress and is included in the Media Queries Level 5 specification, which is in Editor’s Draft.

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

Desktop

Chrome Opera Firefox IE Edge Safari
76 62 67 No 76 12.1

Mobile / Tablet

iOS Safari Opera Mobile Opera Mini Android Android Chrome Android Firefox
13 No No 76 No 68

The implementation is pretty straightforward. Because we’re working with a media query, we need to check if the browser supports it in the useEffect hook and set appropriate theme. To do that, we’ll use window.matchMedia to check if it exists and whether dark mode is supported. We also need to remember about the localTheme because, if it’s available, we don’t want to overwrite it with the dark value unless, of course, the value is set to light.

If all checks are passed, we will set the dark theme.

// useDarkMode.js
useEffect(() => {
if (
  window.matchMedia &&
  window.matchMedia('(prefers-color-scheme: dark)').matches && 
  !localTheme
) {
  setTheme('dark')
  }
})

As mentioned before, we need to remember about the existence of localTheme — that’s why we need to implement our previous logic where we’ve checked for it.

Here’s what we had from before:

// useDarkMode.js
useEffect(() => {
const localTheme = window.localStorage.getItem('theme');
  if (localTheme) {
    setTheme(localTheme);
  } else {
    setMode('light');
  }
})

Let’s mix it up. I’ve replaced the if and else statements with ternary operators to make things a little more readable as well:

// useDarkMode.js
useEffect(() => {
const localTheme = window.localStorage.getItem('theme');
window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches && !localTheme ?
  setMode('dark') :
  localTheme ?
    setTheme(localTheme) :
    setMode('light');})
})

Here’s the userDarkMode.js file with the complete code:

// useDarkMode.js
import { useEffect, useState } from 'react';

export const useDarkMode = () => {
  const [theme, setTheme] = useState('light');
  const [componentMounted, setComponentMounted] = useState(false);
  const setMode = mode => {
    window.localStorage.setItem('theme', mode)
    setTheme(mode)
  };

  const toggleTheme = () => {
    if (theme === 'light') {
      setMode('dark')
    } else {
      setMode('light')
    }
  };

  useEffect(() => {
    const localTheme = window.localStorage.getItem('theme');
    window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches && !localTheme ?
      setMode('dark') :
      localTheme ?
        setTheme(localTheme) :
        setMode('light');
    setComponentMounted(true);
  }, []);

  return [theme, toggleTheme, componentMounted]
};

Give it a try! It changes the mode, persists the theme in localStorage, and also sets the default theme accordingly to the OS color scheme if it’s available.


Congratulations, my friend! Great job! If you have any questions about implementation, feel free to send me a message!

The post A Dark Mode Toggle with React and ThemeProvider appeared first on CSS-Tricks.

Categories: Designing, Others Tags:

Thinking in React Hooks

September 25th, 2019 No comments

Amelia Wattenberger has written this wonderful and interactive piece about React Hooks and details how they can clean up code and remove all those troubling lifecycle events:

React introduced hooks one year ago, and they’ve been a game-changer for a lot of developers. There are tons of how-to introduction resources out there, but I want to talk about the fundamental mindset change when switching from React class components to function components + hooks.

Make sure you check out that lovely animation when you select the code, too. It’s a pretty smart effect to show the old way of doing things versus the new and fancy way. Also make sure to check out our very own Intro to React Hooks once you’re finished to find more examples of this pattern in use.

Direct Link to ArticlePermalink

The post Thinking in React Hooks appeared first on CSS-Tricks.

Categories: Designing, Others Tags:

Creating Tables In Figma

September 25th, 2019 No comments
The Background Component

Creating Tables In Figma

Creating Tables In Figma

Sasha Belichenko

2019-09-25T12:30:59+02:002019-09-25T12:09:04+00:00

In this tutorial, we will talk about how tables can be created in Figma by using components and Atomic Design methodology. We will also take a look at the basic elements of the table layout and how components can be included in the component library so that they can become part of the design system you are using.

To make it easy for you, I’ve prepared a mockup example that uses all of the components we need for this tutorial.

To follow along, you will need to have at least some understanding of the basic Figma concepts, its interface, and how to work with Figma components. However, if you’re new to Figma and working with table data, I recommend watching the “Getting Started” video to help you better understand Figma end-to-end, as well as the article “How To Architect A Complex Web Table” that was published not too long ago here on Smashing Magazine.

To simplify the scope of this tutorial, let’s assume that the colors, fonts, and effects already exist as styles in the Figma project you’re about to begin. In terms of Atomic Design, they are atoms. (To learn more, the folks at littleBits wrote a great article on the topic.)

The target audience for this tutorial are designers (UX, UI) who have either already adopted Figma into their workflows or are planning to try Figma in their next design projects but aren’t sure how to get started.

So, without further ado, let’s dig in!

Quick Note: While writing this article, Figma introduced plugins. At the time of publishing, there weren’t any good ones for working with tables, but things might change fast. Who knows, maybe this article will actually help an aspiring Figma plugin developer to create a really neat Figma Tables plugin, or at least, I hope it will. ?

Introduction

Imagine the table as an organism. The table cell is then a molecule which is comprised of individual atoms. In design terms, they’re cell properties.

So, let’s start with the cell. It has three properties:

  1. Background
  2. Border
  3. Content

Now we’ll take a closer look at each one of them.

Background

The background will be a separate component in Figma. The size doesn’t really matter since we can stretch the component as we need, but let’s begin with setting the size to 100×36 pixels.

In this component, add a rectangle of the same size as the component itself. It will be the only object inside the component. We need to attach the rectangle’s borders to the component’s borders by using constraints (set constraints to “Left & Right” and “Top & Bottom” at the right panel in the Constraints section), so that the rectangle stretches automatically to the size of the component.

If you’d like to see this in action, watch this tutorial on how the constraints work in Figma.

The Background Component

The Background Component (the ‘atom’) (Large preview)

The fill color of the rectangle will determine the background color of the cell. Let’s pick the white color for it. I recommend choosing that color from the color styles that are configured at the beginning of the project.

Changing the background color (Large preview)

Border

This one is a bit trickier than the background. You can’t just create one rectangle with a stroke. We may need different kinds of borders: one for the separate cells (with borders around), one for the whole row of cells with only top and bottom borders, or one for the table header that we might want to separate from the rest with a wider line. There are many options.

Border properties:

  • Border line (left, right, top, bottom, or absence of any of them)
  • Line width
  • Line color
  • Line style

Each line within the cell border might havea different width, color, and style. For example, the left one could be a continuous red line, and the top one a dotted grey line.

Let’s create a component with a size of 100×36 pixels (the same as we did before). Inside the component, we need to add 4 lines for each border. Now pay attention to how we are going to do this.

  1. Add a line for the bottom border with the length of the component width;
  2. Set its position to the bottom border and constraints to stretch horizontally and stick to the bottom border;
  3. For the top border, duplicate the line for the bottom border, rotate it by 180 degrees and stick to the top of the component. (Don’t forget to change its constraints to stick to the top and stretch horizontally.);
  4. Next, for the left border, simply rotate by -90 degrees and set its position and constraints to be at the left side sticking to the left border and stretching vertically;
  5. Last but not least, you can create the right border by rotating it by 90 degrees and setting its position and constraints. Set stroke color and stroke width for each line to gray (select from the color styles) and 1 pixel respectively.

Note: You may be asking yourself why we rotated the line for the bottom border. Well, when you change the stroke width for a line in Figma, it will rise. So we had to set this “rise” direction to the center of the component. Changing the line’s stroke width (in our case it is the border size) won’t expand outside the component (cell).

Now we can hide or customize the styles separately for every border in the cell.

The Border Component

A border component with 1px stroke (Large preview)

If your project has several styles for table borders (a few border examples shown below), you should create a separate component for each style. Simply create a new master component as we did before and customize it the way you need.

Border Styles

A few extra examples of border styles. Note that the white background is not included in the component. (Large preview)

The separate stroke component will save up lots of your time and add scalability. If you change the stroke color inside the master component, the whole table will adjust. Same as with the background color above, each individual cell can have its own stroke parameters.

Changing border’s width and color (Large preview)

Content

This is the most complex component of all.

We need to create all possible variations of the table content in the project: plain text, a text with an icon (left or right, different alignment), checkboxes, switches, and any other content that a cell may possibly contain. To simplify this tutorial, please check the components in the mockup file. How to create and organize components in Figma is a topic for another article.

However, there are a few requirements for content components:

  • Components should stretch easily both vertically and horizontally to fit inside a cell;
  • The minimum size of the component should be less than the default cell size (especially height, keep in mind possible cell paddings);
  • Avoid any margins, so the components can align properly inside a cell;
  • Avoid unnecessary backgrounds because a cell itself has it already.

Content components examples

Examples of cell content in components. This is not a complete list; you can use most of the components of your design system inside a table. (Large preview)

Content components can be created gradually: start with the basic ones like text components and add new ones as the project grows in size.

The reason we want the content to be in components is the same as with other elements — it saves uptime. To change the cell’s content, we just need to switch it in the component.

Changing the component inside the cell
Editing the table using cells components (Large preview)

Creating A Cell Component

We created all the atoms we need: background, border, content. It’s time to create a cell component, i.e. the molecule made from atoms. Let’s gather all the components in a cell.

The cell component

The cell component (the ‘molecule’) (Large preview)

Set the background component as the bottom layer and stretch it to the whole cell size (set constraints to “Left & Right” and “Top & Bottom”).

Add the border component with the same constraints as the background component.

Now to the most complicated part — the content content.

The cell has paddings, so you need to make a frame with the component’s content. That frame should be stretched to the whole cell size except for the paddings. The content component should also be stretched to the whole frame size. The content itself needs to be deprived of any margins, so all paddings will be set by the cell.

At the end of the day, cell paddings are the only property in a component that we will set only once without an opportunity to change it later. In the example above, I made it 4px for all sides.

Note: As a fix, you can create columns with empty cells (with no content and width of 16px for example) left and right to the column where extra margin is needed. Or if your table’s design allows, you can add horizontal paddings inside the cell component. For example, cells in Google Material Design have 16px paddings by default.

Don’t forget to remove the “Clip content” option for the cell and frame (this can be done at the right-hand panel in the Properties section). The cell’s content can go out of its borders; for example, when a dropdown is inside your cell and you want to show its state with a popup.

Note: We’ll be using this cell style as the main one. Don’t worry if your table has additional styles — we’ll cover that in the Table States and Components, Not Overrides sections.

Cell Options For A Standard Table

This step could be optional but if your table needs states then you can’t go without it. And even more so if there is more than one border style in the table.

So let’s create additional cell components from which it’d be easier to build up a table. When working with a table, we will select the appropriate component depending on its position in the table (e.g. depending on the type of borders).

In order to do that, let’s take our cell component and create eight more masters from it. We also need to disable the appropriate layers responsible for borders. The result should look like the image below.

Cell options

The cell options we need to build a table. Note that there could be a few extra depending on your table borders styles. (Large preview)

The top row is for the cells on top and in the middle of the table. The bottom row is only for the cells at the bottom. This way we’ll be able to put the cells one after another with no gaps and keep the same stroke width.

A few examples:

The First example

If each cell in the table has a border, we’d only need cells 1, 4, 5 and 8. (Large preview)

The Second example

If there are merged cells or border absence, we must apply the rest 2 and 3 cells as well as 6 and 7 to the bottom row. (Large preview)

The Third example

If the table design considers the absence of vertical borders, cells 2 and 6 would be enough. (Large preview)

Note: For each border style created above, it’d be good to add master components like the ones described earlier.

So we have excluded the necessity of overriding cell’s instances (disabling the appropriate layers, to be precise). Instead of that, we use various components. Now if, for example, a column uses a different style from the default (the fill color or border), you can choose this column and simply change the relative component. And everything will be alright. On the opposite side, changing a border of each cell manually (disabling the appropriate borders) is a pain you don’t want to bother with.

Now we are ready to create tables (in terms of Atomic Design — organisms) from the various cell components (molecules) we made.

Customizing The Table

Changing the row’s height in the whole table is relatively easy: highlight the table, change the element height (in this case, the cell’s height, H in the right-hand panel in the Properties section), and then change the vertical margin from the element to 0. That’s it: changing the line height took two clicks!

Changing the row height
Changing the row height for the whole table (Large preview)

Changing the column width: highlight the column and change the width size. After moving the rest of the table close up, select the whole table by using the Tide Up option in the Alignment panel as well as the first item in the dropdown list under the rightmost icon.

Changing the column width
Changing the column width. (Large preview)

Note: I wouldn’t recommend grouping rows and columns. If you change the column size extending the elements, you’ll get fractional values for width and height. If you don’t group them and snap to the pixel grid, the cell size will remain an integer number.

The background color, stroke type, and content data can be changed in the appropriate component or in one of the eight cells master components (cells that had different stroke styles). The only parameter that can’t be changed right away is the cell margins, e.g. content paddings. The rest are easily customizable.

Components, Not Overrides

Looking at what we got in the end, it might seem like overkill. And it is if there is only one table in your project. In this case, you can simply create one cell component and leave the background and stroke components off. Simply include them in the cell component, create the table and do the necessary customization for each separate cell.

But if components are included in a library that is used by a number of other files, here comes the most interesting stuff.

Note: *I do not recommend changing the background color and stroke in components’ instances. Change them only in the master. By doing so, those instances with overrides won’t get updated. This means you would have to do that manually and that’s what we’re trying to avoid. So let’s stick to the master components.*

If we need to create an additional type of table cells (e.g. the table header), we add the necessary set of master components for cells with the appropriate styles (just like we did above with the eight cells that had different stroke styles), and use it. Yes, it takes longer than overriding components’ instances but this way you will avoid the case when changing the masters will apply those changes to all layouts.

Table States

Let’s talk about the states of the table’s elements. A cell can have three states: default, hover, and selected. Same for columns and rows.

If your project is relatively small, all states can be set by overrides inside instances of your table components. But if it’s a big one, and you’d want to be able to change the look of the states in the future, you’ll have to create separate components for everything.

You’ll need to add all eight cells with different stroke variants for each of the states (maybe less, depends on the stroke style). And yes, we’ll need separate components for the background color and the stroke for the states as well.

In the end, it’ll look similar to this:

Hover and Selected

The cells’ states (hover and selected) (Large preview)

Here’s where a bit of trouble comes in. Unfortunately, if we do everything as described above (when changing the component’s state from one to another), there is a risk of losing the cell’s content. We’ll have to update it apart from the case when the content type is the same as in the master cell. At this point, we can’t do anything about it.

Table with rows' states

Table with various rows’ states. (Large preview)

I added tables in the mockup file that were made in a few different ways:

  • Using this tutorial (separate components for cells’ styles);
  • Using the cell component (components for borders, background, and content);
  • Using the cell component that unites everything (with only content components in addition).

Try to play around and change the cell’s styles.

Changing the state
Changing the state of the row. (Large preview)

Conclusion

If you’re using the same components library in several projects and you’ve got a reasonable number of tables in each of them, you can create a local copy of components (cells components with stroke styles and, if needed, cells components with different states), customize them, and use them in the project. The cell content can be set based on local components.

Also, if you’re using the table for one large project with different kinds of tables, all the above-mentioned components are easily scaled. The table components can be improved to infinity and beyond, like creating the cell states when hovering and other kinds of interactions.

Questions, feedback, thoughts? Leave a comment below, and I’ll do my best to help you!

Figma Table Mockup Download

As promised, I created a complete version of the Figma table mockup that you’re welcome to use for learning purposes or anything else you like. Enjoy!

Tables in Figma mockup design

Here’s a Figma table mockup that you can use for learning purposes — let the creativity begin!

Related Reading

Useful Resources

  • Figma YouTube Channel
    The official Figma channel on YouTube — it’s the first thing to watch if you are new to Figma.
  • Google Sheets Sync
    A Figma plugin that helps you get data from Google Sheets into your Figma file. This should work fine with the techniques from this tutoria, but you’ll need to invest some time into renaming all the text layers for this to work properly.
Smashing Editorial(mb, yk, il)
Categories: Others Tags:

Filtering Data Client-Side: Comparing CSS, jQuery, and React

September 24th, 2019 No comments

Say you have a list of 100 names:

<ul>
  <li>Randy Hilpert</li>
  <li>Peggie Jacobi</li>
  <li>Ethelyn Nolan Sr.</li> 
  <!-- and then some -->
</ul>

…or file names, or phone numbers, or whatever. And you want to filter them client-side, meaning you aren’t making a server-side request to search through data and return results. You just want to type “rand” and have it filter the list to include “Randy Hilpert” and “Danika Randall” because they both have that string of characters in them. Everything else isn’t included in the results.

Let’s look at how we might do that with different technologies.

CSS can sorta do it, with a little help.

CSS can’t select things based on the content they contain, but it can select on attributes and the values of those attributes. So let’s move the names into attributes as well.

<ul>
  <li data-name="Randy Hilpert">Randy Hilpert</li>
  <li data-name="Peggie Jacobi">Peggie Jacobi</li>
  <li data-name="Ethelyn Nolan Sr.">Ethelyn Nolan Sr.</li> 
  ...
</ul>

Now to filter that list for names that contain “rand”, it’s very easy:

li {
  display: none;
}
li[data-name*="rand" i] {
  display: list-item;
}

Note the i on Line 4. That means “case insensitive” which is very useful here.

To make this work dynamically with a filter , we’ll need to get JavaScript involved to not only react to the filter being typed in, but generate CSS that matches what is being searched.

Say we have a block sitting on the page:

<style id="cssFilter">
  /* dynamically generated CSS will be put in here */
</style>

We can watch for changes on our filter input and generate that CSS:

filterElement.addEventListener("input", e => {
  let filter = e.target.value;
  let css = filter ? `
    li {
      display: none;
    }
    li[data-name*="${filter}" i] {
      display: list-item;
    }
  ` : ``;
  window.cssFilter.innerHTML = css;
});

Note that we’re emptying out the style block when the filter is empty, so all results show.

See the Pen
Filtering Technique: CSS
by Chris Coyier (@chriscoyier)
on CodePen.

I’ll admit it’s a smidge weird to leverage CSS for this, but Tim Carry once took it way further if you’re interested in the concept.

jQuery makes it even easier.

Since we need JavaScript anyway, perhaps jQuery is an acceptable tool. There are two notable changes here:

  • jQuery can select items based on the content they contain. It has a selector API just for this. We don’t need the extra attribute anymore.
  • This keeps all the filtering to a single technology.

We still watch the input for typing, then if we have a filter term, we hide all the list items and reveal the ones that contain our filter term. Otherwise, we reveal them all again:

const listItems = $("li");

$("#filter").on("input", function() {
  let filter = $(this).val();
  if (filter) {
    listItems.hide();
    $(`li:contains('${filter}')`).show();
  } else {
    listItems.show();
  }
});

It’s takes more fiddling to make the filter case-insensitive than CSS does, but we can do it by overriding the default method:

jQuery.expr[':'].contains = function(a, i, m) {
  return jQuery(a).text().toUpperCase()
      .indexOf(m[3].toUpperCase()) >= 0;
};

See the Pen
Filtering Technique: jQuery
by Chris Coyier (@chriscoyier)
on CodePen.

React can do it with state and rendering only what it needs.

There is no one-true-way to do this in React, but I would think it’s React-y to keep the list of names as data (like an Array), map over them, and only render what you need. Changes in the input filter the data itself and React re-renders as necessary.

If we have an names = [array, of, names], we can filter it pretty easily:

filteredNames = names.filter(name => {
  return name.includes(filter);
});

This time, case sensitivity can be done like this:

filteredNames = names.filter(name => {
  return name.toUpperCase().includes(filter.toUpperCase());
});

Then we’d do the typical .map() thing in JSX to loop over our array and output the names.

See the Pen
Filtering Technique: React
by Chris Coyier (@chriscoyier)
on CodePen.

I don’t have any particular preference

This isn’t the kind of thing you choose a technology for. You do it in whatever technology you already have. I also don’t think any one approach is particularly heavier than the rest in terms of technical debt.

The post Filtering Data Client-Side: Comparing CSS, jQuery, and React appeared first on CSS-Tricks.

Categories: Designing, Others Tags:

An Explanation of How the Intersection Observer Watches

September 24th, 2019 No comments

There have been several excellent articles exploring how to use this API, including choices from authors such as Phil Hawksworth, Preethi, and Mateusz Rybczonek, just to name a few. But I’m aiming to do something a bit different here. I had an opportunity earlier in the year to present the VueJS transition component to the Dallas VueJS Meetup of which my first article on CSS-Tricks was based on. During the question-and-answer session of that presentation I was asked about triggering the transitions based on scroll events — which of course you can, but it was suggested by a member of the audience to look into the Intersection Observer.

This got me thinking. I knew the basics of Intersection Observer and how to make a simple example of using it. Did I know how to explain not only how to use it but how it works? What exactly does it provide to us as developers? As a “senior” dev, how would I explain it to someone right out of a bootcamp who possibly doesn’t even know it exists?

I decided I needed to know. After spending some time researching, testing, and experimenting, I’ve decided to share a good bit of what I’ve learned. Hence, here we are.

A brief explanation of the Intersection Observer

The abstract of the W3C public working draft (first draft dated September 14, 2017) describes the Intersection Observer API as:

This specification describes an API that can be used to understand the visibility and position of DOM elements (“targets”) relative to a containing element or to the top-level viewport (“root”). The position is delivered asynchronously and is useful for understanding the visibility of elements and implementing pre-loading and deferred loading of DOM content.

The general idea being a way to watch a child element and be informed when it enters the bounding box of one of its parents. This is most commonly going to be used in relation to the target element scrolling into view in the root element. Up until the introduction of the Intersection Observer, this type of functionality was accomplished by listening for scroll events.

Although the Intersection Observer is a more performant solution for this type of functionality, I do not suggest we necessarily look at it as a replacement to scroll events. Instead, I suggest we look at this API as an additional tool that has a functional overlap with scroll events. In some cases, the two can work together to solve specific problems.

A basic example

I know I risk repeating what’s already been explained in other articles, but let’s see a basic example of an Intersection Observer and what it gives us.

The Observer is made up of four parts:

  1. the “root,” which is the parent element the observer is tied to, which can be the viewport
  2. the “target,” which is a child element being observed and there can be more than one
  3. the options object, which defines certain aspects of the observer’s behavior
  4. the callback function, which is invoked each time an intersection change is observed

The code of a basic example could look something like this:

const options = [
  root: document.body,
  rootMargin: '0px',
  threshold: 0
}

function callback (entries, observer) {
  console.log(observer);
  
  entries.forEach(entry => {
    console.log(entry);
  });
}

let observer = new IntersectionObserver(callback, options);
observer.observe(targetElement);

The first section in the code is the options object which has root, rootMargin, and threshold properties.

The root is the parent element, often a scrolling element, that contains the observed elements. This can be just about any single element on the page as needed. If the property isn’t provided at all or the value is set to null, the viewport is set to be the root element.

The rootMargin is a string of values describing what can be called the margin of the root element, which affects the resulting bounding box that the target element scrolls into. It behaves much like the CSS margin property. You can have values like 10px 15px 20px which gives us a top margin of 10px, left and right margins of 15px, and a bottom margin of 20px. Only the bounding box is affected and not the element itself. Keep in mind that the only lengths allowed are pixels and percentage values, which can be negative or positive. Also note that the rootMargin does not work if the root element is not an actual element on the page, such as the viewport.

The threshold is the value used to determine when an intersection change should be observed. More than one value can be included in an array so that the same target can trigger the intersection multiple times. The different values are a percentage using zero to one, much like opacity in CSS, so a value of 0.5 would be considered 50% and so on. These values relate to the target’s intersection ratio, which will be explained in just a moment. A threshold of zero triggers the intersection when the first pixel of the target element intersects the root element. A threshold of one triggers when the entire target element is inside the root element.

The second section in the code is the callback function that is called whenever a intersection change is observed. Two parameters are passed; the entries are stored in an array and represent each target element that triggers the intersection change. This provides a good bit of information that can be used for the bulk of any functionality that a developer might create. The second parameter is information about the observer itself, which is essentially the data from the provided options object. This provides a way to identify which observer is in play in case a target is tied to multiple observers.

The third section in the code is the creation of the observer itself and where it is observing the target. When creating the observer, the callback function and options object can be external to the observer, as shown. A developer could write the code inline, but the observer is very flexible. For example, the callback and options can be used across multiple observers, if needed. The observe() method is then passed the target element that needs to be observed. It can only accept one target but the method can be repeated on the same observer for multiple targets. Again, very flexible.

Notice the console logs in the code. Here is what those output.

The observer object

Logging the observer data passed into the callback gets us something like this:

IntersectionObserver
  root: null
  rootMargin: "0px 0px 0px 0px"
  thresholds: Array [ 0 ]
  <prototype>: IntersectionObserverPrototype { }

…which is essentially the options object passed into the observer when it was created. This can be used to determine the root element that the intersection is tied to. Notice that even though the original options object had 0px as the rootMargin, this object reports it as 0px 0px 0px 0px, which is what would be expected when considering the rules of CSS margins. Then there’s the array of thresholds the observer is operating under.

The entry object

Logging the entry data passed into the callback gets us something like this:

IntersectionObserverEntry
  boundingClientRect: DOMRect
    bottom: 923.3999938964844, top: 771
    height: 152.39999389648438, width: 411
    left: 9, right: 420
    x: 9, y: 771
    <prototype>: DOMRectPrototype { }
  intersectionRatio: 0
  intersectionRect: DOMRect
    bottom: 0, top: 0
    height: 0, width: 0
    left: 0, right: 0
    x: 0, y: 0
    <prototype>: DOMRectPrototype { }
  isIntersecting: false
  rootBounds: null
  target: <div class="item">
  time: 522
  <prototype>: IntersectionObserverEntryPrototype { }

Yep, lots of things going on here.

For most devs, the two properties that are most likely to be useful are intersectionRatio and isIntersecting. The isIntersecting property is a boolean that is exactly what one might think it is — the target element is intersecting the root element at the time of the intersection change. The intersectionRatio is the percentage of the target element that is currently intersecting the root element. This is represented by a percentage of zero to one, much like the threshold provided in the observer’s option object.

Three properties — boundingClientRect, intersectionRect, and rootBounds — represent specific data about three aspects of the intersection. The boundingClientRect property provides the bounding box of the target element with bottom, left, right, and top values from the top-left of the viewport, just like with Element.getBoundingClientRect(). Then the height and width of the target element is provided as the X and Y coordinates. The rootBounds property provides the same form of data for the root element. The intersectionRect provides similar data but its describing the box formed by the intersection area of the target element inside the root element, which corresponds to the intersectionRatio value. Traditional scroll events would require this math to be done manually.

One thing to keep in mind is that all these shapes that represent the different elements are always rectangles. No matter the actual shape of the elements involved, they are always reduced down to the smallest rectangle containing the element.

The target property refers to the target element that is being observed. In cases where an observer contains multiple targets, this is the easy way to determine which target element triggered this intersection change.

The time property provides the time (in milliseconds) from when the observer is first created to the time this intersection change is triggered. This is how you can track the time it takes for a viewer to come across a particular target. Even if the target is scrolled into view again at a later time, this property will have the new time provided. This can be used to track the time of a target entering and leaving the root element.

While all this information is provided to us whenever an intersection change is observed, it’s also provided to us when the observer is first started. For example, on page load the observers on the page will immediately invoke the callback function and provide the current state of every target element it is observing.

This is a wealth of data about the relationships of elements on the page provided in a very performant way.

Intersection Observer methods

Intersection Observer has three methods of note: observe(), unobserve(), and disconnect().

  • observe(): The observe method takes in a DOM reference to a target element to be added to the list of elements to be watched by the observer. An observer can have more than one target element, but this method can only accept one target at a time.
  • unobserve(): The unobserve method takes in a DOM reference to a target element to be removed from the list of elements watched by the observer.
  • disconnect(): The disconnect method causes the observer to stop watching all of its target elements. The observer itself is still active, but has no targets. After disconnect(), target elements can still be passed to the observer with observe().

These methods provide the ability to watch and unwatch target elements, but there’s no way to change the options passed to the observer when once it is created. You’ll have to manually recreate the observer if different options are required.

Performance: Intersection Observer versus scroll events

In my exploration of the Intersection Observer and how it compares to using scroll events, I knew that I needed to do some performance testing. A totally unscientific effort was thus created using Puppeteer. For the sake of time, I only wanted a general idea of what the performance difference is between the two. Therefore, three simple tests were created.

First, I created a baseline HTML file that included one hundred divs with a bit of height to create a long scrolling page. With a basic http-server active, I loaded the HTML file with Puppeteer, started a trace, forced the page to scroll downward in preset increments to the bottom, stopped the trace once the bottom is reached, and finally saved the results of the trace. I also made it so the test can be repeated multiple times and output data each time. Then I duplicated the baseline HTML and wrote my JavaScript in a script tag for each type of test I wanted to run. Each test has two files: one for the Intersection Observer and the other for scroll events.

The purpose of all the tests is to detect when a target element scrolls upward through the viewport at 25% increments. At each increment, a CSS class is applied that changes the background color of the element. In other words, each element has DOM changes applied to it that would cause repaints. Each test was run five times on two different machines: my development Mac that’s rather up-to-date hardware-wise and my personal Windows 7 machine that’s probably average these days. The results of the trace summary of scripting, rendering, painting, and system were recorded and then averaged. Again, nothing too scientific about all this — just a general idea.

The first test has one observer or one scroll event with one callback each. This is a fairly standard setup for both the observer and scroll event. Although, in this case, the scroll event has a bit more work to do because it attempts to mimic the data that the observer provides by default. Once all those calculations are done, the data is stored in an entry array just like the observer does. Then the functionality for removing and applying classes between the two is exactly the same. I do throttle the scroll event a bit with requestAnimationFrame.

The second test has 100 observers or 100 scroll events with one callback for each type. Each element is assigned its own observer and event but the callback function is the same. This is actually inefficient because each observer and event behaves exactly the same, but I wanted a simple stress test without having to create 100 unique observers and events — though I have seen many examples of using the observer this way.

The third test has 100 observers or 100 scroll events with 100 callbacks for each type. This means each element has its own observer, event, and callback function. This, of course, is horribly inefficient since this is all duplicated functionality stored in huge arrays. But this inefficiency is the point of this test.

Intersection Observer versus Scroll Events stress tests

In the charts above, you’ll see the first column represents our baseline where no JavaScript was run at all. The next two columns represent the first type of test. The Mac ran both quite well as I would expect for a top-end machine for development. The Windows machine gave us a different story. For me, the main point of interest is the scripting results in red. On the Mac, the difference was around 88ms for the observer while around 300ms for the scroll event. The overall result on the Mac is fairly close for each but that scripting took a beating with the scroll event. For the Windows machine its far, far worse. The observer was around 150ms versus around 1400ms for the first and easiest test of the three.

For the second test, we start to see the inefficiency of the scroll test made clearer. Both the Mac and Windows machines ran the observer test with much the same results as before. For the scroll event test, the scripting gets more bogged down to complete the tasks given. The Mac jumped to almost a full second of scripting while the Windows machine jumped approximately to a staggering 3200ms.

For the third test, things thankfully did not get worse. The results are roughly the same as the second test. One thing to note is that across all three tests the results for the observer were consistent for both computers. Despite no efforts in efficiency for the observer tests, the Intersection Observer outperformed the scroll events by a strong margin.

So, after my non-scientific testing on my own two machines, I felt I had a decent idea of the differences in performance between scroll events and the Intersection Observer. I’m sure with some effort I could make the scroll events more efficient but is it worth doing? There are cases where the precision of the scroll events is necessary, but in most cases, the Intersection Observer will suffice nicely — especially since it appears to be far more efficient with no effort at all.

Understanding the intersectionRatio property

The intersectionRatio property, given to us by IntersectionObserverEntry, represents the percentage of the target element that is within the boundaries of the root element on an intersection change. I found I didn’t quite understand what this value actually represented at first. For some reason I was thinking it was a straightforward zero to 100 percent representation of the appearance of the target element, which it sort of is. It is tied to the thresholds passed to the observer when it is created. It could be used to determine which threshold was the cause of the intersection change just triggered, as an example. However, the values it provides are not always straightforward.

Take this demo for instance:

See the Pen
Intersection Observer: intersectionRatio
by Travis Almand (@talmand)
on CodePen.

In this demo, the observer has been assigned the parent container as the root element. The child element with the target background has been assigned as the target element. The threshold array has been created with 100 entries with the sequence 0, 0.01, 0.02, 0.03, and so on, until 1. The observer triggers every one percent of the target element appearing or disappearing inside the root element so that, whenever the ratio changes by at least one percent, the output text below the box is updated. In case you’re curious, this threshold was accomplished with this code:

[...Array(100).keys()].map(x => x / 100) }

I don’t recommend you set your thresholds in this way for typical use in projects.

At first, the target element is completely contained within the root element and the output above the buttons will show a ratio of one. It should be one on first load but we’ll soon see that the ratio is not always precise; it’s possible the number will be somewhere between 0.99 and 1. That does seem odd, but it can happen, so keep that in mind if you create any checks against the ratio equaling a particular value.

Clicking the “left” button will cause the target element to be transformed to the left so that half of it is in the root element and the other half is out. The intersectionRatio should then change to 0.5, or something close to that. We now know that half of the target element is intersecting the root element, but we have no idea where it is. More on that later.

Clicking the “top” button does much the same. It transforms the target element to the top of the root element with half of it in and half of it out again. And again, the intersectionRatio should be somewhere around 0.5. Even though the target element is in a completely different location than before, the resulting ratio is the same.

Clicking the “corner” button again transforms the target element to the upper-right corner of the root element. At this point only a quarter of the target element is within the root element. The intersectionRatio should reflect this with a value of around 0.25. Clicking “center” will transform the target element back to the center and fully contained within the root element.

If we click the “large” button, that changes the height of the target element to be taller than the root element. The intersectionRatio should be somewhere around 0.8, give or take a few ten-thousandths of a percent. This is the tricky part of relying on intersectionRatio. Creating code based on the thresholds given to the observer makes it possible to have thresholds that will never trigger. In this “large” example, any code based on a threshold of 1 will fail to execute. Also consider situations where the root element can be resized, such as the viewport being rotated from portrait to landscape.

Finding the position

So then, how do we know where the target element is in relation to the root element? Thankfully, the data for this calculation is provided by IntersectionObserverEntry, so we only have to do simple comparisons.

Consider this demo:

See the Pen
Intersection Observer: Finding the Position
by Travis Almand (@talmand)
on CodePen.

The setup for this demo is much the same as the one before. The parent container is the root element and the child inside with the target background is the target element. The threshold is an array of 0, 0.5, and 1. As you scroll inside the root element, the target will appear and its position will be reported in the output above the buttons.

Here’s the code that performs these checks:

const output = document.querySelector('#output pre');

function io_callback (entries) {
  const ratio = entries[0].intersectionRatio;
  const boundingRect = entries[0].boundingClientRect;
  const intersectionRect = entries[0].intersectionRect;

  if (ratio === 0) {
    output.innerText = 'outside';
  } else if (ratio < 1) {
    if (boundingRect.top < intersectionRect.top) {
      output.innerText = 'on the top';
    } else {
      output.innerText = 'on the bottom';
    }
  } else {
    output.innerText = 'inside';
  }
}

I should point out that I’m not looping over the entries array as I know there will always only be one entry because there’s only one target. I’m taking a shortcut by making use of entries[0] instead.

You’ll see that a ratio of zero puts the target on the “outside.” A ratio of less than one puts it either at the top or bottom. That lets us see if the target’s “top” is less than the intersectionRect‘s top, which actually means it’s higher on the page and is seen as “on the top.” In fact, checking against the root element’s “top” would work for this as well. Logically, if the target isn’t at the top, then it must be at the bottom. If the ratio happens to equal one, then it is “inside” the root element. Checking the horizontal position is the same except it’s done with the left or right property.

This is part of the efficiency of using the Intersection Observer. Developers don’t need to request this information from various places on a throttled scroll event (which fires quite a lot regardless) and then calculate the related math to figure all this out. It’s provided by the observer and all that’s needed is a simple if check.

At first, the target element is taller than the root element, so it is never reported as being “inside.” Click the “toggle target size” button to make it smaller than the root. Now, the target element can be inside the root element when scrolling up and down.

Restore the target element to its original size by clicking on “toggle target size” again, then click on the “toggle root size” button. This resizes the root element so that it is taller than the target element. Once again, while scrolling up and down, it is possible for the target element to be “inside” the root element.

This demo demonstrates two things about the Intersection Observer: how to determine the position of the target element in relation to the root element and what happens when resizing the two elements. This reaction to resizing is another advantage over scroll events — no need for code to adjust to a resize event.

Creating a position sticky event

The “sticky” value for the CSS position property can be a useful feature, yet it’s a bit limiting in terms of CSS and JavaScript. The styling of the sticky element can only be of one design, whether in its normal state or within its sticky state. There’s no easy way to know the state for JavaScript to react to these changes. So far, there’s no pseudo-class or JavaScript event that makes us aware of the changing state of the element.

I’ve seen examples of having an event of sorts for sticky positioning using both scroll events and the Intersection Observer. The solutions using scroll events always have issues similar to using scroll events for other purposes. The usual solution with an observer is with a “dummy” element that serves little purpose other than being a target for the observer. I like to avoid using single purpose elements like that, so I decided to tinker with this particular idea.

In this demo, scroll up and down to see the section titles reacting to being “sticky” to their respective sections.

See the Pen
Intersection Observer: Position Sticky Event
by Travis Almand (@talmand)
on CodePen.

This is an example of detecting when a sticky element is at the top of the scrolling container so a class name can be applied to the element. This is accomplished by making use of an interesting quirk of the DOM when giving a specific rootMargin to the observer. The values given are:

rootMargin: '0px 0px -100% 0px'

This pushes the bottom margin of the root’s boundary to the top of the root element, which leaves a small sliver of area available for intersection detection that’s zero pixels. A target element touching this zero pixel area triggers the intersection change, even though it doesn’t exist by the numbers, so to speak. Consider that we can have elements in the DOM that exist with a collapsed height of zero.

This solution takes advantage of this by recognizing the sticky element is always in its “sticky” position at the top of the root element. As scrolling continues, the sticky element eventually moves out of view and the intersection stops. Therefore, we add and remove the class based on the isIntersecting property of the entry object.

Here’s the HTML:

<section>
  <div class="sticky-container">
    <div class="sticky-content">
      <span>&sect;</span>
      <h2>Section 1</h2>
    </div>
  </div>

  {{ content here }}
  
</section>

The outer div with the class sticky-container is the target for our observer. This div will be set as the sticky element and acts as the container. The element used to style and change the element based on the sticky state is the sticky-content div and its children. This insures that the actual sticky element is always in contact with the shrunken rootMargin at the top of the root element.

Here’s the CSS:

.sticky-content {
  position: relative;
  transition: 0.25s;
}

.sticky-content span {
  display: inline-block;
  font-size: 20px;
  opacity: 0;
  overflow: hidden;
  transition: 0.25s;
  width: 0;
}

.sticky-content h2 {
  display: inline-block;
}
  
.sticky-container {
  position: sticky;
  top: 0;
}

.sticky-container.active .sticky-content {
  background-color: rgba(0, 0, 0, 0.8);
  color: #fff;
  padding: 10px;
}

.sticky-container.active .sticky-content span {
  opacity: 1;
  transition: 0.25s 0.5s;
  width: 20px;
}

You’ll see that .sticky-container creates our sticky element at the top of zero. The rest is a mixture of styles for the regular state in .sticky-content and the sticky state with .active .sticky-content. Again, you can pretty much do anything you want inside the sticky content div. In this demo, there’s a hidden section symbol that appears from a delayed transition when the sticky state is active. This effect would be difficult without something to assist, like the Intersection Observer.

The JavaScript:

const stickyContainers = document.querySelectorAll('.sticky-container');
const io_options = {
  root: document.body,
  rootMargin: '0px 0px -100% 0px',
  threshold: 0
};
const io_observer = new IntersectionObserver(io_callback, io_options);

stickyContainers.forEach(element => {
  io_observer.observe(element);
});

function io_callback (entries, observer) {
  entries.forEach(entry => {
    entry.target.classList.toggle('active', entry.isIntersecting);
  });
}

This is actually a very straightforward example of using the Intersection Observer for this task. The only oddity is the -100% value in the rootMargin. Take note that this can be repeated for the other three sides as well; it just requires a new observer with its own unique rootMargin with -100% for the appropriate side. There will have to be more unique sticky containers with their own classes such as sticky-container-top and sticky-container-bottom.

The limitation of this is that the top, right, bottom, or left property for the sticky element must always be zero. Technically, you could use a different value but then you’d have to do the math to figure out the proper value for the rootMargin. This is can be done easily, but if things get resized, not only does the math need to be done again, the observer has to be stopped and restarted with the new value. It’s easier to set the position property to zero and use the interior elements to style things how you want them.

Combining with Scrolling Events

As we’ve seen in some of the demos so far, the intersectionRatio can be imprecise and somewhat limiting. Using scroll events can be more precise but at the cost of inefficiency in performance. What if we combined the two?

See the Pen
Intersection Observer: Scroll Events
by Travis Almand (@talmand)
on CodePen.

In this demo, we’ve created an Intersection Observer and the callback function serves the sole purpose of adding and removing an event listener that listens for the scroll event on the root element. When the target first enters the root element, the scroll event listener is created and then is removed when the target leaves the root. As the scrolling happens, the output simply shows each event’s timestamp to show it changing in real time — far more precise than the observer alone.

The setup for the HTML and CSS is fairly standard at this point, so here’s the JavaScript.

const root = document.querySelector('#root');
const target = document.querySelector('#target');
const output = document.querySelector('#output pre');
const io_options = {
  root: root,
  rootMargin: '0px',
  threshold: 0
};
let io_observer;

function scrollingEvents (e) {
  output.innerText = e.timeStamp;
}

function io_callback (entries) {
  if (entries[0].isIntersecting) {
    root.addEventListener('scroll', scrollingEvents);
  } else {
    root.removeEventListener('scroll', scrollingEvents);
    output.innerText = 0;
  }
}

io_observer = new IntersectionObserver(io_callback, io_options);
io_observer.observe(target);

This is a fairly standard example. Take note that we’ll want the threshold to be zero because we’ll get multiple event listeners at the same time if there’s more than one threshold. The callback function is what we’re interested in and even that is a simple setup: add and remove the event listener in an if-else block. The event’s callback function simply updates the div in the output. Whenever the target triggers an intersection change and is not intersecting with the root, we set the output back to zero.

This gives the benefits of both Intersection Observer and scroll events. Consider having a scrolling animation library in place that works only when the section of the page that requires it is actually visible. The library and scroll events are not inefficiently active throughout the entire page.

Interesting differences with browsers

You’re probably wondering how much browser support there is for Intersection Observer. Quite a bit, actually!

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

Desktop

Chrome Opera Firefox IE Edge Safari
58 45 55 No 16 12.1

Mobile / Tablet

iOS Safari Opera Mobile Opera Mini Android Android Chrome Android Firefox
12.2-12.3 46 No 76 76 68

All the major browsers have supported it for some time now. As you might expect, Internet Explorer doesn’t support it at any level, but there’s a polyfill available from the W3C that takes care of that.

As I was experimenting with different ideas using the Intersection Observer, I did come across a couple of examples that behave differently between Firefox and Chrome. I wouldn’t use these example on a production site, but the behaviors are interesting.

Here’s the first example:

See the Pen
Intersection Observer: Animated Transform
by Travis Almand (@talmand)
on CodePen.

The target element is moving within the root element by way of the CSS transform property. The demo has a CSS animation that transforms the target element in and out of the root element on the horizontal axis. When the target element enters or leaves the root element, the intersectionRatio is updated.

If you view this demo in Firefox, you should see the intersectionRatio update properly as the target elements slides back and forth. Chrome behaves differently. The default behavior there does not update the intersectionRatio display at all. It seems that Chrome doesn’t keep tabs of a target element that is transformed with CSS. However, if we were to move the mouse around in the browser as the target element moves in and out of the root element, the intersectionRatio display does indeed update. My guess is that Chrome only “activates” the observer when there is some form of user interaction.

Here’s the second example:

See the Pen
Intersection Observer: Animated Clip-Path
by Travis Almand (@talmand)
on CodePen.

This time we’re animating a clip-path that morphs a square into a circle in a repeating loop. The square is the same size as the root element so the intersectionRatio we get will always be less than one. As the clip-path animates, Firefox does not update the intersectionRatio display at all. And this time moving the mouse around does not work. Firefox simply ignores the changing size of the element. Chrome, on the other hand, actually updates the intersectionRatio display in real time. This happens even without user interaction.

This seems to happen because of a section of the spec that says the boundaries of the intersection area (the intersectionRect) should include clipping the target element.

If container has overflow clipping or a css clip-path property, update intersectionRect by applying container’s clip.

So, when a target is clipped, the boundaries of the intersection area are recalculated. Firefox apparently hasn’t implemented this yet.

Intersection Observer, version 2

So what does the future hold for this API?

There are proposals from Google that will add an interesting feature to the observer. Even though the Intersection Observer tells us when a target element crosses into the boundaries of a root element, it doesn’t necessarily mean that element is actually visible to the user. It could have a zero opacity or it could be covered by another element on the page. What if the observer could be used to determine these things?

Please keep in mind that we’re still in the early days for such a feature and that it shouldn’t be used in production code. Here’s the updated proposal with the differences from the spec’s first version highlighted.

If you you’ve been viewing the the demos in this article with Chrome, you might have noticed a couple of things in the console — such as entries object properties that don’t appear in Firefox. Here’s an example of what Firefox logs in the console:

IntersectionObserver
  root: null
  rootMargin: "0px 0px 0px 0px"
  thresholds: Array [ 0 ]
  <prototype>: IntersectionObserverPrototype { }

IntersectionObserverEntry
  boundingClientRect: DOMRect { x: 9, y: 779, width: 707, ... }
  intersectionRatio: 0
  intersectionRect: DOMRect { x: 0, y: 0, width: 0, ... }
  isIntersecting: false
  rootBounds: null
  target: <div class="item">
  time: 261
  <prototype>: IntersectionObserverEntryPrototype { }

Now, here’s the same output from the same console code in Chrome:

IntersectionObserver
  delay: 500
  root: null
  rootMargin: "0px 0px 0px 0px"
  thresholds: [0]
  trackVisibility: true
  __proto__: IntersectionObserver

IntersectionObserverEntry
  boundingClientRect: DOMRectReadOnly {x: 9, y: 740, width: 914, height: 146, top: 740, ...}
  intersectionRatio: 0
  intersectionRect: DOMRectReadOnly {x: 0, y: 0, width: 0, height: 0, top: 0, ...}
  isIntersecting: false
  isVisible: false
  rootBounds: null
  target: div.item
  time: 355.6550000066636
  __proto__: IntersectionObserverEntry

There are some differences in how a few of the properties are displayed, such as target and prototype, but they operate the same in both browsers. What’s different is that Chrome has a few extra properties that don’t appear in Firefox. The observer object has a boolean called trackVisibility, a number called delay, and the entry object has a boolean called isVisible. These are the newly proposed properties that attempt to determine whether the target element is actually visible to the user.

I’ll give a brief explanation of these properties but please read this article if you want more details.

The trackVisibility property is the boolean given to the observer in the options object. This informs the browser to take on the more expensive task of determining the true visibility of the target element.

The delay property what you might guess: it delays the intersection change callback by the specified amount of time in milliseconds. This is sort of the same as if your callback function’s code were wrapped in setTimeout. For the trackVisibility to work, this value is required and must be at least 100. If a proper amount is not given, the console will display this error and the observer will not be created.

Uncaught DOMException: Failed to construct 'IntersectionObserver': To enable the 
'trackVisibility' option, you must also use a 'delay' option with a value of at
least 100. Visibility is more expensive to compute than the basic intersection;
enabling this option may negatively affect your page's performance.
Please make sure you really need visibility tracking before enabling the
'trackVisibility' option.

The isVisible property in the target’s entry object is the boolean that reports the output of the visibility tracking. This can be used as part of any code much the same way isIntersecting can be used.

In all my experiments with these features, seeing it actually working seems to be hit or miss. For example, delay works consistently but isVisible doesn’t always report true — for me at least — when the element is clearly visible. Sometimes this is by design as the spec does allow for false negatives. That would help explain the inconsistent results.

I, for one, can’t wait for this feature to be more feature complete and working in all browsers that support Intersection Observer.

Now thy watch is over

So comes an end to my research on Intersection Observer, an API that I definitely look forward to using in future projects. I spent a good number of nights researching, experimenting, and building examples to understand how it works. But the result was this article, plus some new ideas for how to leverage different features of the observer. Plus, at this point, I feel I can effectively explain how the observer works when asked. Hopefully, this article helps you do the same.

The post An Explanation of How the Intersection Observer Watches appeared first on CSS-Tricks.

Categories: Designing, Others Tags:

Browser Engine Diversity

September 24th, 2019 No comments

We lost Opera when they went Chrome in 2013. Same deal with Edge when it also went Chrome earlier this year. Mike Taylor called these changes a “Decreasingly Diverse Browser Engine World” in a talk I’d like to see.

So all we’ve got left is Chrome-stuff, Firefox-stuff, and Safari-stuff. Chrome and Safari share the same lineage but have diverged enough, evolve separately enough, and are walled away from each other enough that it makes sense to think of them as different from one another.

I know there are fancier words to articulate this. For example, browser engines themselves have names that are distinct and separate from the names of the browsers.

Take Chrome, which is based on the open-source project Chromium, which uses the rendering engine Blink and the JavaScript engine V8.

Firefox uses Gecko as its browser engine, which is turning into Quantum, which has sub-parts like Servo for CSS and rendering.

Safari uses WebKit as a browser engine ,which has parts like WebCore and JavaScriptCore.

It’s all kinda complicated and I’m not even sure I quite understand it all. My brain just thinks of it as everything under the umbrella of the main browser name.

The two extremes of looking at this from the perspective of decreasing diversity:

  • This is bad. Decreased diversity may hinder ecosystems from competing and innovating.
  • This is good. Cross-engine problems are a major productivity loss for the world. Getting down to one ecosystem would be even better.

Whichever it is, the ship has sailed. All we can do is look forward.

Random thoughts:

  • Perhaps diversity has just moved scope. Rather than the browser engines themselves representing diversity, maybe forks of the engnies we have left can compete against each other. Maybe starting from a strong foundation is a good place to start innovating?
  • If, god forbid, we got down to one browser engine, what happens to the web standards process? The fear would be that the last-engine-standing doesn’t have to worry about interop anymore and they run wild with implementations. But does running wild mean the playing field can never be competitive again?
  • It’s awesome when browsers compete on features that are great for users but don’t affect web standards. Great password managers, user protection features, clever bookmarking ideas, reader modes, clean integrations with payment APIs, free VPNs, etc. That was Opera’s play, and now we see many more in the same vein. Vivaldi is all about customization, Brave doubles down on privacy and security, and Puma is about monetization.

Brian Kardell wrote about some of this stuff recently in his “Beyond Browser Vendors” post. An interesting point is that the remaining browser engines are all open source. That means they can and do take outside contributions, which is exactly how CSS Grid came to exist.

Most of the work on CSS Grid in both WebKit and Chromium (Blink) was done, not by Google or Apple, but by teams at Igalia.

Think about that for a minute: The prioritization of its work was determined in 2 browsers not by a vendor, but by an investment from Bloomberg who had the foresight to fund this largely uncontroversial work.

And now, that idea continues:

This isn’t a unique story, it’s just a really important and highly visible one that’s fun to hold up. In fact, just in the last 6 months engineers as Igalia have worked on CSS Containment, ResizeObserver, BigInt, private fields and methods, responsive image preloading, CSS Text Level 3, bringing MathML to Chromium, normalizing SVG and MathML DOMs and a lot more.

What we may have lost in browser engine diversity we may gain back in the openness of browser engines and outside players stepping up.

The post Browser Engine Diversity appeared first on CSS-Tricks.

Categories: Designing, Others Tags:

Get Geographic Information from an IP Address for Free

September 24th, 2019 No comments

Say you need to know what country someone visiting your website is from, because you have an internationalized site and display different things based on that country. You could ask the user. You might want to have that functionality anyway to make sure your visitors have control, but surely they will appreciate it just being correct out of the gate, to the best of your ability.

There are no native web technologies that have this information. JavaScript has geolocation, but users would have to approve that, and even then you’d have to use some library to convert the coordinates into a more usable country. Even back-end languages, which have access to the IP address in a way that JavaScript doesn’t, don’t just automatically know the country of origin.

CANADA. I JUST WANNA KNOW IF THEY ARE FROM CANADA OR NOT.

You have to ask some kind of service that knows this. The IP Geolocation API is that service, and it’s free.

You perform a GET against the API. You can do it right in the browser if you want to test it:

https://api.ipgeolocationapi.com/geolocate/184.149.48.32

But you don’t just get the country. You get a whole pile of information you might need to use. I happen to be sitting in Canada and this is what I get for my IP:

{ 
   "continent":"North America",
   "address_format":"{{recipient}}n{{street}}n{{city}} {{region_short}} {{postalcode}}n{{country}}",
   "alpha2":"CA",
   "alpha3":"CAN",
   "country_code":"1",
   "international_prefix":"011",
   "ioc":"CAN",
   "gec":"CA",
   "name":"Canada",
   "national_destination_code_lengths":[ 
      3
   ],
   "national_number_lengths":[ 
      10
   ],
   "national_prefix":"1",
   "number":"124",
   "region":"Americas",
   "subregion":"Northern America",
   "world_region":"AMER",
   "un_locode":"CA",
   "nationality":"Canadian",
   "postal_code":true,
   "unofficial_names":[ 
      "Canada",
      "Kanada",
      "Canadá",
      "カナダ"
   ],
   "languages_official":[ 
      "en",
      "fr"
   ],
   "languages_spoken":[ 
      "en",
      "fr"
   ],
   "geo":{ 
      "latitude":56.130366,
      "latitude_dec":"62.832908630371094",
      "longitude":-106.346771,
      "longitude_dec":"-95.91332244873047",
      "max_latitude":83.6381,
      "max_longitude":-50.9766,
      "min_latitude":41.6765559,
      "min_longitude":-141.00187,
      "bounds":{ 
         "northeast":{ 
            "lat":83.6381,
            "lng":-50.9766
         },
         "southwest":{ 
            "lat":41.6765559,
            "lng":-141.00187
         }
      }
   },
   "currency_code":"CAD",
   "start_of_week":"sunday"
}

With that information, I could easily decide to redirect to the Canadian version of my website, if I have one, or show prices in CAD, or offer a French translation, or whatever else I can think of.

Say you were in PHP. You could get the IP like…

function getUserIpAddr() {
  if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
    $ip = $_SERVER['HTTP_CLIENT_IP'];
  } elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
    $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
  } else {
    $ip = $_SERVER['REMOTE_ADDR'];
  }
  return $ip;
}

The cURL to get the information:

$url = "https://api.ipgeolocationapi.com/geolocate/184.149.48.32"; 
$ch = curl_init();  
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 
curl_setopt($ch, CURLOPT_URL, $url); 
$result = curl_exec($ch); 

It comes back as JSON, so:

$json = json_decode($result);

And then $json->{'name'}; will be “Canada” if I’m in Canada.

So I can do like:

if ($json->{'name'} == "Canada") {
  // serve index-ca.php
} else {
  // server index.php
}

The post Get Geographic Information from an IP Address for Free appeared first on CSS-Tricks.

Categories: Designing, Others Tags:

Designing Complex Responsive Tables In WordPress

September 24th, 2019 No comments
e-commerce product tables

Designing Complex Responsive Tables In WordPress

Designing Complex Responsive Tables In WordPress

Suzanne Scacca

2019-09-24T13:00:59+02:002019-09-24T16:46:28+00:00

(This is a sponsored article.) Mobile devices can be problematic for displaying complex tables and charts that would otherwise stretch the entire width of a laptop or desktop screen. This may leave some of you wondering whether it’s even worth showing tables to mobile and tablet visitors of your website.

But that doesn’t make sense. In many cases, a table isn’t some stylistic choice for displaying content on a website. Tables are critical elements for gathering, organizing and sharing large quantities of complex and valuable data. Without them, your mobile visitors’ experience will be compromised.

You can’t afford to leave out the data. So, what do you do about it?

This requires a more strategic solution. This means understanding what purpose the data serves and then designing the complex web table in a way that makes sense for mobile consumption.

A WordPress table plugin called wpDataTables has made light work of designing both desktop and mobile compatible tables, so I’ve included examples of these complex tables throughout this post. Keep reading to explore the possibilities.

The Most Common Use Cases For Tables On The Web

There’s a lot of value in presenting data in a table format on a website.

Your writers could probably find a way to tackle each data point one-by-one or to provide a high-level summary of the data as a whole. However, when data is handled this way, your visitors are left with too much work to do, which will only hinder the decision-making process.

On the other hand, tables are great for organizing large quantities of data while also giving visitors an easier way to sift through the data on their own.

As such, your visitors would greatly benefit from having complex data sets presented as tables — across a wide variety of use cases, too.

Feature Lists

There are a couple of ways to use tables to show off product features.

For e-commerce sites, the product inventory is broken up by its most pertinent features, allowing visitors to filter their results based on what’s most important to them:

e-commerce product tables

e-Commerce sites can use product tables to quickly list out all products and their key features. (Image source: wpDataTables) (Large preview)

This would be great for any large vendor that has dozens or hundreds of similar-looking products they want customers to be able to filter and sort through.

You could also use a table to compare your product’s features directly against the competition’s. This would be better for a third-party marketplace where vendors sell their goods.

Amazon includes these kinds of tables:

Side-by-side competitor tables

Marketplace sites use side-by-side competitor tables to simplify decision-making. (Image source: Amazon) (Large preview)

By displaying the data in this format, customers can quickly do a side-by-side comparison of similar products to find the one that checks off all their requirements.

Pricing Tables

If you’re designing a website where services or memberships are sold instead of products, you can still use tables to display the information.

You’ll find a good example of this on the BuzzSumo website:

Service-based companies list prices in tables

Companies that sell services, like BuzzSumo, use tables to display pricing and features. (Image source: BuzzSumo) (Large preview)

Even though there’s less data to compile, you can see how the structure of the table and the stacking of the services side-by-side really help visitors make a more well-informed and easier buying decision.

Catalogs

A catalog is useful for providing visitors with an alphabetized or numerically ordered list. You might use one to organize a physical or digital inventory as this example demonstrates:

Catalog tables

Catalog tables make it easier for users to find what they’re looking for. (Image source: wpDataTables) (Large preview)

This would be good for bookstores, libraries and websites that have their own repository of reference material or content.

You might also use a catalog to help customers improve the accuracy of their orders:

Catalogs for order accuracy

Catalog tables can be used to aid shoppers with order accuracy. (Image source: wpDataTables) (Large preview)

This type of table provides customers with key specifications of available products to ensure they’re ordering the right kinds of parts or equipment.

Best Of Lists

There are tons of resources online that provide rundowns of the “Top” winners or “Best Of” lists. Tables are a useful way to summarize the findings of the article or report before readers scroll down to learn more.

This is something that websites like PC Mag (and, really, any tech or product review site) do really well:

Best-of reviews table

PC Mag organizes a summary of best-of reviews in a table format. (Image source: PC Mag) (Large preview)

This helps readers get a sense for what’s to come. It also allows those who are short on time to make a faster decision.

Directory Tables

Directory websites have ever-growing and regularly updated lists of data. These are your real estate listing sites, travel sites, professional directories and other sites containing high volumes of complex data that really shouldn’t be consumed without a filterable table.

Case in point: this list of available apartments:

Directory website table

Directory websites that change often need tables to keep listings organized. (Image source: wpDataTables) (Large preview)

This makes it much easier for visitors to see all options in a single glance, rather than have to go one-by-one through individual entries that matched a search query.

General Data

There are other data lists that are just too complex to handle as loose text. Sports data, for instance, should always be presented in this format:

Sports statistics table

Basic statistics, like for sports teams, should never be presented as loose data. (Image source: wpDataTables) (Large preview)

You can see how this keeps all data in one place and in a searchable list. Whether visitors are looking for their home team’s stats, or want to compare the performance of different teams from their fantasy sports league, it’s all right there.

How To Design Complex Responsive Tables

Regardless of what type of data you’re tasked with presenting on a website, the goal is to do so in a clear fashion so visitors can take quicker action.

Now, it’s time to figure out how to best format this data for mobile visitors.

Delete, Delete, Delete

If your client has pulled their data from an automated report, they may not have taken time to clean up the results. So, before you start any design work on the table, I would suggest reviewing the data they’ve given you.

First, ask yourself: Is there enough data that it warrants a table?

If it’s a simple and small enough list, it might make more sense to ditch the table.

Then, go over each column: Is each of these useful?

You may find that some of the columns included aren’t necessary and can be stripped out altogether.

You may also find that some columns, while an essential part of each item’s individual specifications list, won’t help visitors make a decision within the table. This would be the case if the column contains an identical data point for every item.

Finally, talk to your writer or data manager: Is there any way to shorten the columns?

The table’s labels and data may have been written in full, but your writer may have a way to simplify the responses without compromising on comprehension.

When possible, have them work their magic to shrink up the text so that columns don’t take up as much space and more can be revealed on mobile. Don’t just do this for mobile users either. Even on desktop and tablet screens where more screen real estate is available, the shortening of labels can help conserve space.

It may be as simple as changing the word “Rank” to the number symbol (#) and abbreviating “Points” as “Pts”.

Make data smaller

Designers and writers need to work together to create smaller tables. (Image source: wpDataTables) (Large preview)

While it might not seem like one word will make much of a difference, it adds up the more complex and lengthier your tables are.

Start With Two Columns

By default, mobile tables should always start with two columns. It’s about all the screen’s width will allow for without compromising the readability of the data within, so it’s best to start with the basics.

When you contrast a full-screen table on desktop against its counterpart on mobile, you can see how easy it is to identify the two columns to include. For example, a mobile statistics table includes a column for item type and one for the profits earned from each:

Mobile table with two columns

It’s a good idea to design responsive tables with two columns to start. (Image source: wpDataTables) (Large preview)

This doesn’t mean that all other data is lost on mobile. You just need to let visitors know how they can expand the table’s view.

In this example, when visitors select the eyeball icon above the table, they have the option to add more columns to the table:

Column view options

If visitors want to scroll right, give them column view options. (Image source: wpDataTables) (Large preview)

In allowing for this option on mobile, your visitors can control how they consume data while also selecting only the data points that are most important to them.

The result will then look like this:

Mobile table with more than two columns

An example of a mobile table with additional columns. (Image source: wpDataTables) (Large preview)

While users will have to scroll right to see the rest of the table, the control they wield over column views helps keep this a reasonable task. With just one scroll right, they’ll see the rest of the table:

Horizontal scrolling on mobile

Even with more columns on mobile, horizontal scrolling is kept to a minimum. (Image source: wpDataTables) (Large preview)

This is a good option to have for lists of products where the side-by-side comparison is useful in expediting the decision-making process.

Use An Accordion For Standalone Entries

There’s another option you can include which will give visitors more control over how they view table content.

For this example, we’ll look at a list of available cryptocurrencies:

Expandable accordions for mobile tables

Data lists (as opposed to product comparison) lists can use expandable accordions. (Image source: wpDataTables) (Large preview)

As you can see, the default here is still to only show two columns. In this case, though, a click of the plus-sign (+) will reveal a new way to view the table:

Expanded row on mobile

An example of what an expanded row looks like on mobile tables. (Image source: wpDataTables) (Large preview)

When open, all of the data that would otherwise force visitors to scroll right is now visible within a single screenful.

While you can certainly include an expandable accordion in any responsive table you create, it would be best suited to ones where a direct side-by-side comparison between products or services isn’t necessary.

Keep Vertical Scrolling To A Minimum

Just as you want to prevent your visitors from having to scroll past the horizontal boundaries of the mobile website’s pages, you should limit how much vertical scrolling they have to do as well.

Data consumption, in general, isn’t always an easy task, so the more you can minimize the work they have to do to get to it, the better.

One way to limit how much vertical scrolling your visitors do is by breaking a table with dozens or hundreds of rows into pages.

A table of annual temperatures on desktop and mobile

An example of how to shrink an extra-large table down to a couple columns and multiple pages. (Image source: wpDataTables) (Large preview)

Just remember to make it easy for visitors to scroll through the pages. A well-designed set of pagination controls either at the top or bottom of the table would be useful:

Responsive table pagination

Use pagination at the bottom of tables to decrease vertical scrolling. (Image source: wpDataTables) (Large preview)

This would be especially useful for a handful of pages. Anything more than that and the pagination process may become tedious.

You can also include a table search function directly above it:

Mobile table search function

Above-the-table search helps reduce the work of scrolling on mobile. (Image source: wpDataTables) (Large preview)

This allows for a quick shortcut when your users have a good idea of what they’re looking for and want to jump straight to it.

Include Both Filtering And Sorting For Larger Data Sets

So, let’s say that you have a very extensive list of data. You don’t want to force users to scroll through dozens of table pages, but you also can’t afford to remove any of the data sets. It’s all pertinent.

In that case, you’re going to hand some of the control back to your visitors. This way, their choices will determine how much of the table they end up seeing.

Let’s use this list of mutual funds as an example:

Complex table example

An example of a complex table on mobile. (Image source: wpDataTables) (Large preview)

The image above is the default view visitors would see if they scrolled immediately to the table. However, they might find it to be intimidating and decide that filtering out bad results will improve the view:

Table filters

Filtering allows users to greatly narrow down how many rows are displayed on mobile. (Image source: wpDataTables) (Large preview)

What’s nice about including filters on mobile tables is that they function the same way your mobile contact forms do. So, visitors should have an easy time filling in and moving between fields, which will get them quicker to the results they want to see.

Another way to improve how their results are displayed is by using the sorting feature. When they click on the top label of any column, it will automatically sort the column in descending order. Another click will reverse it.

Table sorting

Sorting allows users to see results in descending/ascending order. (Image source: wpDataTables) (Large preview)

These two features are a must-have for any table you build, though they’re especially important for mobile visitors that don’t have as much time or attention to give to your tables.

Wrapping Up

You’re here because you want a better way to present complex tables to your mobile visitors.

The key to doing this right is by first familiarizing yourself with the kinds of tables you can create. Even if mobile devices limit how much can be seen at first glance, that doesn’t make it impossible to share that kind of data with them.

Next, you need to build user control into your tables, so that visitors can decide what they see and how they see it.

And, finally, you’d do well to find a tool built specifically for this complex task. For those of you building websites with WordPress, wpDataTables is a WordPress table plugin that’s able to create responsive tables and charts. It doesn’t matter how large your data set, or what use case it’s for, it will enable you to quickly and effectively organize and display responsive tables on your WordPress website.

(ms, yk, il)
Categories: Others Tags:

Tips On Designing Creative Websites That Will Wow Your Clients

September 24th, 2019 No comments

The global market keeps getting bigger and bigger, which should be good news for web designers – but there’s a catch.

The clients you can expect to serve are more sophisticated and want creative websites, not just the regular HTML and CSS.

You can’t deliver just anything and expect them to be happy. You’ll also have some serious competition to contend with. As a consequence, your website must adequately address an ever-growing need for improvements. Like in flexibility, responsiveness, and conversion optimization.

Fortunately, the cloud has a silver lining; and a bright one at that. Tools to meet these challenges and address them head on are readily available. With the most notable case in point being Be Theme, the largest and most versatile WordPress theme of them all.

With a tool like Be Theme at your fingertips, you’ll be more than able to successfully handle any challenge that comes your way.

Then, it’s simply a matter of following these 5 simple steps to design creative websites. They are eye-catching, impressive in their ability to convert visitors to users and guaranteed to put smiles on your clients’ faces.

5 Steps to Building Astoundingly Creative Websites

Step 1: Choose a Mesmerizing Color Palette

The color palette you use can make a difference between a so-so website and an attention-getting one. Choosing one should not be difficult if you follow a few simple rules:

  • its colors need to attract instant attention;
  • they need to be on-brand, and;
  • the color palette you select needs to visually support the message your app or website is designed to convey

Artist features BOLD color touches that will instantly attract attention.

This Be Theme pre-built website is another great example of what can be accomplished with an eye-grabbing color palette.

Carbon8 is an example of how to align the color palette with the website’s brand. It is done with its clever use of various shades of green. Note how the deep green element draws visitor’s eyes to the center of the page.

BeInsurance features a subtle, crisp color palette. It perfectly reinforces a strategy of using crisp, clear images to gain the clients you need.

BeFestival is a great example of using a color palette designed to appeal to a larger audience.

Step 2: Display Crystal-Clear Photos and Images

This step should be obvious. A crystal clear presentation suggests the business behind it goes the extra mile. That is when it comes to clearly present their products and services. Using the best images you can get your hands on can give you an extra edge over the competition.

BeStylist is an example of the advantage of displaying images with flair. The crisp image definitely supports the intended message.

Or, try something like RansomLTD; minimalist, yet crisp and powerful.

A pre-built website like Zajno illustrates how crystal-clear pics can be used to display your creativity.

The Design Shop employs flair and creativity to entice visitors with crisp and compelling images of your products.

Step 3: Show Visitors How Your Creativity Benefits Them

Creativity isn’t about you. It’s about serving your visitors. It can be extremely effective when it helps them imagine themselves actually using your product or service.

BeMarketing‘s homepage video illustrates another way to show people how they can benefit from using your products.

Lane illustrates a fabulous approach you can use to express your structural design perspectives.

BeSimple takes a minimalist approach. It’s the typography and way the text is presented rather than the text itself that’s effective.

BeTravelBlogger provides the basis for a travel blogger’s dream site. You can use it to present your travel adventures and escapades with a super-cool layout of graphics and snippets.

Step 4: (Over)use “White” Space

Yes, it’s true. White space is a design element you can sometimes overuse without causing harm. In fact, in many cases “more is better”, as is obvious in the following examples.

Makespace has a clean design that allows the eye to focus on key elements, imagine, and create.

BeSketch & The Drive New York are two examples where plenty of white space is used to great effect.

When white space is part of the brand it can really help to drive the message home. BeIcecream is an extreme example of how plenty of white space can be used to great effect.

Step 5: Make Your CTAs Grab Them By The Eyeballs

If you don’t have CTA buttons that can’t be ignored, your website isn’t going to convert as many visitors into users as you hoped or planned. You want those buttons to be big, bright, and bold enough to make people feel they absolutely have to be clicked on.

BeDrawing‘s CTA button is above the fold as it should be, and it clearly stands out. You can’t help but notice it the instant you’ve finished reading the headline. Its centering on the page makes it serve as a gate that invites visitors to enter.

Stuart makes effective placement and use of 3 clearly defined CTA buttons.

Your CTA can match other elements on the page and still easily attract attention. A great example of this is BeKids, where the color of the button matches the color of other hero section elements.

Building Creative Websites – a Summary

Follow these surefire steps (with the help of a surefire website building tool) and you’re well on your way to satisfying the most demanding of clients. Crisp, stunning visuals, a cleverly chosen color palette and the clever use of white space, images that enable visitors to see themselves using your products, and the all-important bold and beautiful CTA buttons give you the combination for success.

Success typically breeds additional work. Sometimes web design is not as much fun as it used to be. If you suddenly find yourself juggling projects and deadlines to the point you’d be better off using pre-built websites.

You’ll find the most generous gallery of more than 450 creative websites on Be Theme that you can customize to your liking. Start using them now and you’ll never have to worry about juggling projects and deadlines!

[– This is a sponsored post on behalf of Be Theme –]

Source

Categories: Designing, Others Tags: