Consistent Backends and UX: How Do New Algorithms Help?
Strategy

Consistent Backends and UX: How Do New Algorithms Help?


In previous articles, we explained what consistency is, the difference between “strong” and “eventual” consistency, and why this distinction is more important than ever to modern application developers. We also introduced the notion of ‘consistency tax’: the extra time and effort that a development team needs to invest if they choose a system with only eventual consistency or limited consistency guarantees. 

Several modern databases use state-of-the-art algorithms to eliminate the tradeoff between consistency and performance. Of course, we would not want you to take our word for it without a proper explanation. Therefore, in this final article, we dive into the technical details behind some of these databases. Typically, the only source of information for these technical details are research papers, so the point of this article is to explain these systems in simpler terms.  Because these systems are far more complex in reality, we’ll provide the links in the text in case you want to know more and love to read research papers.

Introduction

In parts 1 and 2 of this article series, we explained how distributed databases use different replicas to spread the load and/or serve users in different regions. To summarize here, for new readers, a replica is just a duplication of your data. And this duplication can live either in the same location for redundancy, or in another location to offer lower latencies to users in those locations. Having multiple replicas that can handle both reads and writes has a strong advantage, because the database becomes scalable and can offer lower latency to all your users, no matter where they are. However, you do not want each of the replicas to have their own interpretation of the data. Instead of small data differences between each replica, you want one unique interpretation of the data, which is often referred to as a single source of truth. In order to achieve that, you need to have some sort of agreement on data changes. We need a consensus. 

Waiting for consensus

Every distributed database that aims to be consistent has multiple replicas that have to agree on the outcome of transactions. If conflicting data updates happen these replicas have to agree which update goes through and which doesn’t. This is called “consensus.”

Let’s go back to our game to exemplify why we need consensus. Imagine that the player of our game only has 3 gold pieces left, but tries to simultaneously buy two different items from two different shops for a total budget larger than the remaining 3 gold pieces. This involves two transactions, one for each item/shop, which we denote as t1 and t2. And let’s pretend that the owners of the shops are across the globe from each other, so the transactions take place on two different replicas. If both of the transactions are accepted the user would be able to buy more than he can afford. How do we prevent the user from overspending?

 An example of two replicas that each receive a transaction (t1) and (t2). If we let both go through it would violate our business rule that users can’t spend more than they own. Clearly these replicas need decide which transaction is allowed and which should be blocked.

We know that these replicas need to communicate in order to agree on the final outcome of the two transactions. What we don’t know is how much communication they need. How many messages have to go back and forth between replica 1 and replica 2 in order to agree which transaction gets priority and which one gets cancelled?

As replicas in a distributed database are meant to serve users from different regions in the world with low latency, they are far apart by nature. By placing duplicates of the data closer to the end users, these users can read with lower latencies. However, when writes happen, the replicas need to send messages to each other to update all duplicated data uniformly–and these messages can take several 10s of milliseconds because they’re bridled by the speed of light as they travel across the globe. It’s clear that we need to keep the number of cross-data center messages as small as possible so that the end user isn’t left waiting around for these replicas across the globe to come to consensus. 

For a long time, it had been thought to be impossible or impractical to do this. But today, several technologies exist to keep the number of round-trips low and bring latency within normal bounds.

The distance between New York and Paris is 5,839 km. For light to travel from New York to Paris and then back again would take 40 milliseconds.

Theoretical vs real-world speed

If it takes a minimum of 40 milliseconds to travel between New York and Paris, a round-trip would take at least 80ms. The most important question that remains is: “How many round-trips do we need to execute transactions?” The answer to this question depends largely on the algorithms that are used.

How to reach agreement? 

It appears that in order to achieve consensus about something, you need at least four hops (or two rounds of communication): one round to let each replica know that you are about to do something, then a second round to actually execute the action once everyone agrees that this action can be executed. This is something called distributed two-phase commit which is used by almost any distributed database. Let’s look at an analogy. Imagine you have to agree with a group of people on a good date for a party. It might go like this:

First, Polly asks everyone if they can make it to a party on Monday; she now knows that everyone can actually come to the party. Next, she needs to let everyone know that the party will indeed be on Monday, and people acknowledge that they will be there.

These are very similar to the two phases in two-phase commit. Of course, databases don’t party so the phases have different functions. In the case of a distributed system, the phases are called: 

  • Prepare or request to commit: make sure that everyone knows about the transaction. In this phase, replicas in a distributed database store the query in some kind of todo list (a transaction log) on the disk to make sure they still know what to do if the server goes down. 
  • Commit: actually calculate the results and store them 

Of course, as always, it’s never that simple. There are many flavors of such algorithms. For example, there are improvements of two-phase commits called Paxos and Raft and even many variants of these (multi paxos/fast paxos/…). These alternatives aim to improve issues of availability or performance. To understand the availability issues, simply imagine that Polly falls sick or Amber’s phone dies. In the former case, she would be unable to continue her work as party coordinator and in the latter case, it would temporarily be impossible for Polly to know whether Amber agrees on the party date. Raft and Paxos improve on this by only requiring the majority to answer and/or selecting a new coordinator automatically when the leader or coordinator goes down. A good animation that shows how Raft works can be found here

Agree about what? 

Can we conclude that each distributed database then requires 2 round trips to write/read data? No, the reality is more complex than that. On one side, there are many possible optimizations and on the other side, there might be multiple things we need to agree on. 

  • Agree on the time of a transaction
  • Agree whether reads can be executed
  • Agree whether reads can be executed

The simplest example that has multiple two-phase commit rounds is probably Cassandra’s light-weight transactions. They first require consensus agreements on reads and then consensus on writes. If each message takes 40ms to travel, this means the entire transaction requires 320ms or longer–depending on the required “locks” as we’ll explain later.

This is fairly easy to understand, but there are some issues with the implementation since Cassandra was never designed to be strongly consistent. Does that mean that strongly consistent databases are even slower? Not at all! Modern distributed databases use a mix of interesting features to achieve better performance.

Waiting for locks

Not only do we need to wait for messages to come to an agreement, but almost every distributed database will also use “locks”. Locks guarantee that the data about to be altered by a transaction is not being simultaneously altered by another transaction. When data is locked, it can’t be altered by other transactions, which means that these transactions have to wait. The duration of such a lock, therefore, has a big impact on performance. Again, this performance impact depends on the algorithm and optimizations that were implemented by the database. Some databases hold locks longer than others and some databases do not use locks at all. 

Now that we know enough basics, let’s dive into the algorithms. 

Modern Algorithms for Consensus

We now know that consensus and locks are the main bottlenecks that we need to optimize. So let’s go back to the main question of this article: “How does new technology lower these latencies within acceptable bounds?” Let’s start off with the first of these modern algorithms, which sparked interesting ideas for the rest of the database world.  

2010 – Percolator

Percolator is an internal system built upon BigTable (one of the early NoSQL databases built by Google) that Google used to make incremental updates to their search index’s page crawling speed.  The first paper on Percolator was released in 2010, inspiring the first distributed database inspired by it: FoundationDB in 2013. FoundationDB then got acquired by Apple to finally release a stable version in 2019, together with the release of a FoundationDB paper.

Although Percolator allowed Google to speed up page crawling significantly, it  was not originally built as a general-purpose database. It was rather intended to be a fast and scalable incremental processing engine to support Google’s search index. Since the search index had to be scalable, many calculations had to happen on many machines concurrently, which required a distributed database. As we learned in the previous articles, programming against distributed systems that store data can be very complex, and traditionally required that developers pay a ‘consistency tax’ to program around unpredictable database behavior. To avoid paying so high a consistency tax, Google  adopted a strong consistency model when they built Percolator. 

The consistency model of Percolator could not exist without two key ingredients: versioning, and the Timestamp Oracle

Ingredient 1: Versioning

As we mentioned in previous articles, strong consistency requires us to agree on a global order for our transactions. Versioning is one of the elements that will be crucial to many of these algorithms since it can be used for failure recovery, to help replicate data, and to support a consistency model called ‘snapshot isolation’.

Versioning helps in failure recovery when a node fails or gets disconnected. When the node comes back online, thanks to the versions, it can easily restore its state by starting at the last snapshot that it was able to save, and then replaying the transactions based on the versions in another node. All it has to do is ask another node: “Hey, what has changed since I was gone?” Without versioning, it would have to copy over all the data, which would have put a huge strain on the system.

Failure recovery is great, but the strongest advantage lies in the fact that such a versioning system can be used to implement a strong consistency model. If the versioning system keeps versions for each data change, we can actually go back in time and do queries against an earlier version of our data.

Some bright minds found out that this historical querying capability could be used to provide a consistency model called ‘snapshot consistency’. The idea of snapshot consistency is to pick a version of the data at the beginning of the query, work with that version of the data during the rest of the query, then write a new version at the end of the query.

There is one possible pitfall here: during the execution of such a query, another query could be writing data that conflicts with the first query. For example, if two write queries start with the same snapshot of a bank account with $1000 on it, they could both spend the money since they do not see the writes of the other query. To prevent that, an additional transaction will take place to see if the snapshot’s values changed before either query writes a result. If something conflicting did happen to change the snapshot’s value, the transaction is rolled back and has to be restarted.

However, there is still one problem Percolator needs to solve. Clocks on different machines can easily drift apart a few 100s of milliseconds. If data for a query is split over multiple machines such as in our initial example, you can’t simply ask both machines to give you data at a certain timestamp since they have a slightly different idea of what the current time is. It’s a matter of milliseconds, but when many transactions have to be processed, a few milliseconds are all it takes to go from correct data to faulty data.

Time synchronization brings us to the second Percolator ingredient.

Ingredient 2: The Timestamp Oracle

Percolator’s solution to the time synchronization problem is something called the Timestamp Oracle. Instead of letting each node dictate its own time (which was not accurate enough), Percolator uses a central system that exposes an API providing you with a timestamp. The node on which this system lives is the Timestamp Oracle. When we keep multiple versions of our data, we need at least two timestamps for each query. First, we need a timestamp to query a snapshot, which we will use to read data. Then, at the end of the transaction when we are ready to write, we need a second timestamp to tag the new data version. As a result, Percolator has the disadvantage that it needs at least two calls to the Timestamp Oracle, which introduces even more latency if the Oracle is in another region from the nodes where the calls originated. When Google came up with their Distributed Database Spanner, they solved this problem.  

2012 – Spanner

Spanner was the first globally distributed database to offer strong consistency, which essentially means that you get low latency reads without having to worry about potential database errors anymore. Developers no longer need to invest extra work to circumvent potential bugs caused by eventual consistency. The paper was released in 2012 and it was released to the general public in 2017 as Spanner Cloud.

Ingredient 1: Versioning

Google built Spanner after their experience with Percolator. Since Percolator’s versioning system proved to work, they kept this in Spanner’s design.  This versioning system provided the ability to do very fast reads (snapshot reads) if you were willing to give up consistency. In that case, you could run queries and give Spanner a maximum age of the results. For example: “Please return my current inventory as fast as possible, but the data can only be 15 seconds old”. Basically, instead of abandoning consistency, you could now choose for each query which consistency level suited your use-case. 

Ingredient 2: TrueTime

To eliminate the extra overhead to synchronize time between machines, Spanner abandoned the Timestamp Oracle in favor of a new concept called TrueTime. Instead of having one central system that provides a unified view of time, TrueTime tries to reduce the clock drift between the machines themselves. Engineers at Google managed to limit local clock drift by implementing a time synchronization protocol based on GPS and atomic clocks. This synchronization algorithm allowed them to limit clock drift within a boundary of 7ms, but required specific hardware that consisted of a combination of GPS and Atomic clock technology. 

Of course, there is still a potential clock drift of 7ms, which means that two servers could still interpret a timestamp to be two different snapshots. This is solved by the third ingredient for Spanner: commit-wait. 

Ingredient 3: Commit-wait 

In fact, the TrueTime API does not return one timestamp but returns and interval n which it is sure that the current timestamp should lie. Once it is ready to commit, it will just wait a few milliseconds to cope with the potential drift which is called ‘Commit-wait’. This makes sure that the timestamp that will be assigned to the write is a timestamp that has passed on all nodes. It’s also the reason that running Spanner on commodity hardware can not deliver the same guarantee since the wait period would need to be a few 100s of milliseconds.

2012 – Calvin

The first paper on the Calvin algorithm was released in 2012, from research at Yale. Just like the previous approaches, Calvin consists of several ingredients. Although versioning is also part of it, the rest of the approach is radically different which requires a few extra ingredients to work: deterministic calculations, and the separation of ordering from locking. These are ingredients that are typically not found in databases with traditional architecture. By changing the architecture and accepting that queries have to be deterministic, Calvin can reduce the worst-case number of cross- datacenter messages to two. This pushes down the worst-case latency of global transactions significantly and brings it below 200ms or theoretically even below 100ms. Of course, in order to believe that this is possible, you might want to know how it works first, so let’s take a look at the algorithm.  

Ingredient 1: Versioning

Similar to Percolator and Spanner, Calvin relies on versioned data. These snapshots in Calvin are mainly used to ensure fault-tolerance. Each node stores different snapshots which can be considered as checkpoints. A disconnected node that comes back online only needs to grab the timestamp of the last checkpoint it has witnessed, and then ask another node to inform him of all the transactions that came after that checkpoint. 

Ingredient 2: Deterministic calculations

Many front-end developers will have heard of the Elm frontend framework which implements a React Redux-like workflow. Elm has a steeper learning curve than similar JavaScript-based frameworks because it requires you to learn a new language. However, because the language is functional (no side-effects), Elm allows some impressive optimizations. The key is that functions in Elm give up destructive manipulations to be deterministic. You can run the same function with the same input twice and it will always yield the same result. Because they are deterministic, Elm queries can now more efficiently decide how to update views. 

Similar to Elm, Calvin has given up something to speed up the calculations. In the case of Calvin, we can basically say that the result of a transaction will be the same, whether it’s executed on machine A or Machine B. This might seem evident, but typically databases do not guarantee this. Remember that SQL allows you to use the current time or allows something called interactive transactions where user input can be inserted in the middle of a transaction, both of which could violate the guarantees provided by Calvin. 

To achieve deterministic calculations, Calvin (1) needs to take out calculations such as current time and pre-calculate them, and (2) does not allow interactive transactions. Interactive transactions are transactions where a user starts a transaction, reads some data, provides some additional user input in the middle, and then finally does some extra calculations and possibly some writes. Since the user is not predictable, such a transaction is not deterministic. In essence, Calvin trades in a minor convenience (interactive transactions) for great performance.

Ingredient 3: Separate the problem of ordering.

Databases spend a lot of time negotiating locks in order to make it look like the system is executing in a specific order”. If an order is all you need, maybe we can separate the problem of locking from the problem of ordering. This means though that your transactions have to be pure.

— Kyle Kingsbury

Separating the concern of ordering transactions from the actual execution has been considered many times in the database world but without much success. However, when your transactions are deterministic, separating the ordering from the calculations actually becomes feasible. In fact, the combination of deterministic calculations and the separation of ordering from the rest of the algorithm is extremely powerful since it helps to reduce lock duration and greatly diminishes the slower communication between distant nodes (cross-datacenter communication). 

Shorter lock duration

Whenever locks are held on a piece of data, it means that other queries that use that data have to wait. Therefore, shorter locking results in better performance. Below is an image that shows an overview of the locking procedure in Calvin compared to how a traditional distributed database might do it. Most databases would keep a lock on data until there is at least a consensus on what to write while Calvin would only keep the lock until all nodes agree on the order. Because the calculations are deterministic and they all agreed on the order, each node will calculate separately and come to the same end result.

Less communication between distant nodes

Besides the advantages in lock duration, separating ordering from the rest of the algorithm also requires less communication. As explained before with the Cassandra example, a distributed database typically requires cross-datacenter communication in many phases of their algorithm. In the case of Calvin, the only moment we need to agree on something is at the moment we determine the order. With the Raft protocol, this could be done in two hops which makes it possible to achieve sub 100ms latencies for read-write queries. 

Together with the reduced lock time, this also brings superb throughput. The original Calvin paper has also done experiments that show that this approach significantly outperforms traditional distributed database designs under high contention workloads. Their results of half a million transactions per second on a cluster of commodity machines are competitive with the current world record results obtained on much higher-end hardware.

Run on any hardware

Besides that, Calvin has another advantage: it no longer requires specific hardware in order to obtain such results. Since Calvin can run on commodity machines, it can run on any cloud provider.

2014 – The FaunaDB flavor of Consensus

Ingredient 1: Versioning

FaunaDB has its own distributed transaction protocol with some similarities to Calvin. Just like the former approaches, FaunaDB’s data is also versioned. Since versioning is not only useful for the consistency model but can also have business value, FaunaDB has upgraded this mechanism to a first-class citizen that can be used by end-users. This feature essentially allows time-traveling queries. End-users can execute a query on historic data to answer questions such as: “What would the result of this query have been 20 days ago?”. This is useful to recover data that was accidentally overwritten, audit data changes, or simply incorporate time-travel in your application’s features. 

Ingredient 2 and 3: Deterministic calculations and Separation

Like Calvin, FaunaDB also has deterministic calculations and separates the problem of ordering from the rest of the algorithm. Although there are similarities, calculating transactions in FaunaDB happens in a different phase than Calvin. Where Calvin takes advantage of the deterministic nature to execute the same transaction multiple times once the order is set, FaunaDB will calculate only once prior to consensus on the order of the transactions. Which brings us to the fourth ingredient.

Ingredient 4: Optimistic calculation

FaunaDB adds a fourth ingredient which we have seen already when we talked about Snapshot Isolation: Optimistic calculations instead of locking. 

FaunaDB will not lock, but will instead optimistically calculate the result of the transaction once in the node where the transaction was received, and then add the result and the original input values to the log. Where Calvin would have saved the query that needs to be executed in the transaction log, FaunaDB will save both the result of the calculation and the original input values in the log. Once there is consensus on the order in which the results have to be applied, FaunaDB will verify whether the input data for that calculation has changed or not (thanks to versioning). If the input values have changed, the transaction is aborted and restarted, if they have remained the same, the results are applied on all nodes without any extra calculation.

FaunaDB’s algorithm has similar advantages as Calvin, but reduces the amount of required calculations in the cluster. 

Conclusion

In this series, we have explained how strong consistency can help you build error-free applications more efficiently. In this last article, we have further explained how revolutionary ideas can power a new generation of distributed databases that are both consistent and performant. The takeaway in the previous articles was: “Consistency matters”. In this final article, the takeaway is encompassed in the following:

In the near future, if you read a phrase such as: 

“Many NoSQL databases do not offer atomic writes for multiple documents, and in return give better performance. And while consistency is another great feature of SQL databases, it impedes the ability to scale out a database across multiple nodes, so many NoSQL databases give up consistency.” – the biggest challenges of moving to NoSQL

Realize that modern algorithms enable databases to deliver consistency without centralization. In this article, we have seen a few examples of algorithms and databases that do this. Databases that build upon these algorithms are a next generation of databases that no longer can be described by simple categories such as NoSQL, SQL, or even NewSQL.

With distributed cloud databases based on Percolator, Spanner, Calvin, and FaunaDB’s transaction protocol, you can have highly performant distributed databases that offer stronger consistency models. This means that you can build data-intensive applications that offer low-latency without having to worry about data errors, performance, or service provisioning. In such systems, consistency is transparent, and you do not have to think about it as a developer. The next time you choose a database, pick one that is consistent by default.



Source link

Post image
Strategy

Why won’t the grid columns expand to the entire width when I…


Hi everyone,

I’m trying to figure out why I can’t get this grid template to expand and/or even center the viewport. It works fine and as expected in desktop view…

Any ideas? Let me know if you require any more code snippets.

Post image

.grid-container-columns {

height: 100%;

display: grid;

grid-template-columns: repeat(12, 1fr);

grid-gap: 16px;

align-content: stretch;

}

header#home-section h1 {

grid-column: 3/span 8;

justify-self: center;

align-self: center;

text-align: center;

padding-top: 10rem;

z-index: 1;

}

The container div just provides a max-width of 1100px with a margin 0 auto.

Thanks for any help!



Source link

How to Repeat Text as a Background Image in CSS Using elemen...
Strategy

How to Repeat Text as a Background Image in CSS Using elemen…


There’s a design trend I’ve seen popping up all over the place. Maybe you’ve seen it too. It’s this sort of thing where text is repeated over and over. A good example is the price comparison website, GoCompare, who used it in a major multi-channel advertising campaign.

Nike has used it as well, like in this advertisement:

Diggin’ that orange! (Source)

I couldn’t help but wonder how I would implement this sort of design for the web. I mean, we could obviously just repeat the text in markup. We could also export the design as an image using something like Photoshop, but putting text in images is bad for both SEO and accessibility. Then there’s the fact that, even if we did use actual text, it’s not like we’d want a screen reader speak it out.

Versatility
Versatility
Versatility
Versatility

OK, stop already!

These considerations make it seem unrealistic to do something like this on the web. Then I found myself pining for the long-existing, yet badly supported, element() feature in CSS. It enables the use of any HTML element as a background image, whether it be a single button element, or an entire <div> full of content.

According to the spec:

The element() function only reproduces the appearance of the referenced element, not the actual content and its structure. Authors should only use this for decorative purposes.

For our purposes, we’d be referencing a text element to get that repeating effect.

Let’s define an ID we can apply to the text element we want to repeat. Let’s call it #thingy. Note that when we use #thingy, we’ve got to prefix the element() value with -moz-. While element() has been supported in Firefox since 2010, it sadly hasn’t landed in any other browser since.

.element {
  background-image: -moz-element(#thingy);
}

Here’s a somewhat loose recreation of the Nike advertisement we saw earlier. Again, Firefox is required to see the demo as intended.

See how that works conceptually? I placed an element (#versatility) on the page, hid it by giving it zero height, set it as the background-image on the body, then used the background-repeat property to duplicate it vertically down the page.

The element() background is live. That means the background-image appearance on the thing using it will change if the referenced HTML element changes. It’s the same sort of deal when working with custom properties: change the variable and it updates everywhere it’s used.

There are, of course, other use cases for this property. Check out how Preethi used it to make in-page scrolling navigation for an article. You could also use a HTML canvas element as a background if you want to get fancy. One way I’ve used it is to show screenshots of pages in a table of contents. Vincent De Oliveira, has documented some wildly creative examples. Here’s an image-reflection effect, if you’re into retro web design:


Pretty neat, right? Again, I wish I could say this is a production-ready approach to get that neat design effect, but things are what they are at the moment. Actually, that’s a good reminder to make your voice heard for features you’d like to see implemented in browsers. There are open tickets in WebKit and Chromium where you can do that. Hopefully we’ll eventually get this feature in Safari-world and Chrome-world browsers.



Source link

Value Bubbles for Range Inputs
Strategy

Value Bubbles for Range Inputs


Range inputs in HTML are like this:

<input type="range" name="quantity" min="1" max="10">

In browsers that support them, they look like this:

Now that’s great and all. You could use it for anything where you want to collect a number from a user that has an enforced minimum and maximum value.

But notice anything weird? All by itself, that range input doesn’t communicate to the user what number they will actually be submitting. Now if your input is something like “How are you feeling? Left for sad, right for happy.” – then fine, you probably don’t need to show the user a number. But I would wager it’s more common that you’ll need to show the number than not show it.

To be fair, the spec says:

The input element represents a control for setting the element’s value to a string representing a number, but with the caveat that the exact value is not important, letting UAs provide a simpler interface than they do for the Number state.

But c’mon, just because we want a cool slider doesn’t automatically mean we should prevent the user from knowing the submitted value. I wouldn’t necessarily say browsers should alter their UI control to show that number. I am saying we should build that ourselves!

This is the perfect use case for the <output> tag, which is specifically for values calculated by form elements. Here is a super simple implementation of how you might use it:

<input type="range" name="foo">
<output for="foo" onforminput="value = foo.valueAsNumber;"></output>

Update! New version with Vanilla JavaScript that also works better.

Our goal here is to display a “bubble” that shows the current value of a range input.

Setting the value of our “bubble” from the value of the input is a matter of pulling the range value and plopping it in the bubble:

range.addEventListener("input", () => {
  bubble.innerHTML = rangel.value;
});

The trick is positioning the bubble along the range input so it slides alongside the “thumb”. To do that, we’ll need to calculate what % the bubble needs to be scooted to the left. So let’s make a function to do that to keep things a smidge cleaner:

range.addEventListener("input", () => {
  setBubble(range, bubble);
});

function setBubble(range, bubble) {
  const val = range.value;
  const min = range.min ? range.min : 0;
  const max = range.max ? range.max : 100;
  const newVal = Number(((val - min) * 100) / (max - min));
  bubble.innerHTML = val;

  // Sorta magic numbers based on size of the native UI thumb
  bubble.style.left = newVal = "%";
}

Here we’re making sure we account for the range inputs min and max attributes and calculating a % position between 0-100 based on the current value in that range. Not all ranges are the default 0-100 numbers, so say a range was at value 50 in a range of 0 to 200, that would be 25% of the way. This accounts for that.

But it has one annoying flaw: the bubble is too far to the left at the start and too far to the right at the end. On range inputs, the thumb doesn’t hang off the left edge so it’s center is at the start, and same at the end. Like a scrollbar, the edges of the thumb stop within the track.

We can use some magic numbers there that seem to work decently well across browsers:

// Sorta magic numbers based on size of the native UI thumb
  bubble.style.left = `calc(${newVal}% + (${8 - newVal * 0.15}px))`;

Here’s that final demo:

I was inspired to poke around with this because reader Max Globa wrote in with their version which I’ll post here:

A cool aspect of Max’s version is that the range input is CSS-styled, so the exact size of the thumb is known. There are some numbers that feel rather magic in the JavaScript math, but at least they are based on real numbers set in the CSS about the size of the thumb.

Other Versions

Dave Olsen ported the (original) idea to not have a dependency on jQuery. Here’s that version:


Sean Stopnik:


simurai:


Vincent Durand:


Don’t forget range input can have datalists which put little notches on them which is kinda cool.


Ana Tudor has a massive collection, many of which indicate the current value through their design.

😬 Old Version from Original Version of this Post (jQuery, plus doesn’t work as well)

Just leaving this in here for historical reasons.

Let’s pull in our friend jQuery and get our CSS on. This goal is below. Any range input, any time, any min/max/step – we put a bubble above it showing the current value.

Let’s style the output element first. We’ll absolutely position it above the input. That gives us the ability to adjust the left value as well, once we figure out with JavaScript what it should be. We’ll fancy it up with gradients and border-radius, and even add a little pointer triangle with a pseudo-element.

output { 
  position: absolute;
  background-image: linear-gradient(top, #444444, #999999);
  width: 40px; 
  height: 30px; 
  text-align: center; 
  color: white; 
  border-radius: 10px; 
  display: inline-block; 
  font: bold 15px/30px Georgia;
  bottom: 175%;
  left: 0;
  margin-left: -1%;
}
output:after { 
  content: "";
  position: absolute;
  width: 0;
  height: 0;
  border-top: 10px solid #999999;
  border-left: 5px solid transparent;
  border-right: 5px solid transparent;
  top: 100%;
  left: 50%;
  margin-left: -5px;
  margin-top: -1px;
}

Now what we need to do is watch all range inputs for a change in their value. Our goal is to shift the left position of the bubble in pace with the slider. That’s not the simplest thing in the world, being that sliders can be of any width and any minimum or maximum value. We’re going to have to do a little math. Here’s all the jQuery JavaScript, commented up:

// DOM Ready
$(function() {
 var el, newPoint, newPlace, offset;
 
 // Select all range inputs, watch for change
 $("input[type='range']").change(function() {
 
   // Cache this for efficiency
   el = $(this);
   
   // Measure width of range input
   width = el.width();
   
   // Figure out placement percentage between left and right of input
   newPoint = (el.val() - el.attr("min")) / (el.attr("max") - el.attr("min"));
   
   // Janky value to get pointer to line up better
   offset = -1.3;
   
   // Prevent bubble from going beyond left or right (unsupported browsers)
   if (newPoint < 0) { newPlace = 0; }
   else if (newPoint > 1) { newPlace = width; }
   else { newPlace = width * newPoint + offset; offset -= newPoint; }
   
   // Move bubble
   el
     .next("output")
     .css({
       left: newPlace,
       marginLeft: offset + "%"
     })
     .text(el.val());
 })
 // Fake a change to position bubble at page load
 .trigger('change');
});

The one gross part in there is that 1.3 value. I was trying to line up the tip of the bubble’s triangle with the center of the slider. It’s not easy, because the slider’s center is never 100% left or right. That value isn’t perfect, nor perfectly implemented, but it’s better than not having it.

As a bonus, browsers that don’t support the range input still get the bubble action.

The above code depends on the range inputs having a specified min and max value. If they don’t it kinda breaks. I think it would be weird to use a range input without specifying these things, although if you don’t it seems they default to 0 and 100. To bulletproof this, you’d grab each attribute, test it, and if it didn’t look right fix it. Something like:

var minValue, maxValue;
if (!el.attr("min")) { minValue = 0; } else { minValue = el.attr("min"); }

…then use the minValue variable in the math. And do similar for max. Anyway, here’s the live demo:



Source link

CSS Viewport Units
Strategy

CSS Viewport Units


CSS Viewport units have been around for the last few years, and by time, I see them being used more and more by developers. Their benefit lies in providing us with a way to size things in a fluid and dynamic way, without the need to use JavaScript. Also, it’s easy to provide a fallback if they fail.

In this article, we will learn about CSS viewport units and how to use them, along with some use-cases and solutions for common issues. Let’s start and dig in!

Introduction

According to the CSS spec, viewport-percentage units are relative to the size of the initial containing block, which is the root element of a web page.

The viewport units are: vw, vh, vmin, and vmax.

Viewport Width

The vw unit represents a percentage of the root element width. One vw is equal to 1% of the viewport width.

We have an element with the following CSS:

.element {
    width: 50vw;
}

When the width of the viewport is 500px, the 50vw will be calculated as below:

width = 500*50% = 250px

Viewport Height

The vh unit represents a percentage of the root element height. One vh is equal to 1% of the viewport height.

We have an element with the following CSS:

.element {
    height: 50vh;
}

When the height of the viewport is 290px, the 70vh will be calculated as below:

height = 290*70% = 202px

That’s it. Let’s move to a different kind of viewport units!

vmin Unit

The vmin represents the value of the minimum of the width and height of the viewport. If the viewport width is greater than its height, then the value will be calculated based on the height.

Let’s take the following example.

We have a mobile in landscape mode, and an element has the vmin unit. In that case, the value will be calculated based on the viewport height, because it’s less than the width.

.element {
    padding-top: 10vmin;
    padding-bottom: 10vmin;
{

Here is how vmin will be calculated:

As you might guessed it, the result will be calculated as the below:

padding-top = (10% of height) = 10% * 164 = 32px
padding-bottom = (10% of height) = 10% * 164 = 32px

vmax unit

It’s the opposite of vmin. The value is calculated based on the maximum width and height.

Let’s take the following example.

.element {
    padding-top: 10vmax;
    padding-bottom: 10vmax;
{

The result will be calculated as the below:

padding-top = (10% of width) = 10% * 350 = 35px
padding-bottom = (10% of width) = 10% * 350 = px

How Viewport Units Are Different From Percentages?

Viewport units are based on the root element of the page, while the percentage is based on the container they are in. For that reason, they’re different from each other and each one has its use cases.

Use Cases For Viewport Units

In the following sections, I will explore some use cases for viewport units and how to implement them in your project.

Font Size

CSS viewport units are perfect for responsive typography. For example, we can use the following for an article title:

.title {
    font-size: 5vw;
}

The title’s font-size will increase or shrink based on the viewport width. It’s like giving a font size of 5% of the viewport width. However, it might be tempting to just use it without proper testing. Let’s see the video below:

Notice that the font size became very small in mobile sizing, this is bad for accessibility and user experience. As far as I know, the minimum font size on a mobile device shouldn’t go beyond 14px. In the GIF, the font size went below 10px.

To solve that issue, we need to give the title a minimum font size that it can’t go below it. CSS calc() to the rescue!

.title {
    font-size: calc(14px + 2vw);
}

The calc() CSS function will have a base 14px value, and it will add 2vw to it. With that in hand, the font-size value won’t become too small.

Another important thing to consider is how the font size will behave on large screens, for example, a 27” iMac. What will happen? Well, you guessed it. The font size went to around 95px, which a huge value. To prevent that, we should use media queries at certain breakpoints and change the font-size.

@media (min-width: 1800px) {
    .title {
        font-size: 40px;
    }
}

By resetting the font-size, we can be sure that the size won’t go too huge. You might also need to add multiple media queries, but it’s up to you and depends on the context of your project.

Demo

Full Screen Sections

Sometimes, we need a section to take 100% of the viewport height. This is known as ** full-screen sections**. To do so, we can use the viewport height unit.

.section {
    height: 100vh;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
}

By adding height: 100vh, we can ensure that the section height will take 100% of the viewport. Also, I added some flexbox to handle centering the content horizontally and vertically.

Demo

On a large screen, you might notice that website content is short, and the footer is not stuck to the bottom. That’s normal, and it’s not considered as a bad practice. However, there is room for enhancement. Consider the following figure that represents the issue:

To solve that, we need to give the main content a height that is equal to viewport height – (header + footer). It’s tricky to do that dynamically, but with CSS viewport units, it’s quite easy.

1. First Solution: Calc and Viewport Units

If the header and footer height is fixed, it’s possible to combine both CSS calc() function with viewport units. Here is how:

header,
footer {
    height: 70px;
}

main {
    height: calc(100vh - 70px - 70px);
}

This solution is not guaranteed to work all the time, especially for the footer. In my career, I haven’t used a footer with a fixed height, because it’s not doable, for example, on different screen sizes.

By adding the 100vh as a height for the body element, we can then use flexbox to make the main element take the remaining space.

body {
    min-height: 100vh;
    display: flex
    flex-direction: column;
{

main {
    /* This will make the main element take the remaining space dynamically */
    flex-grow: 1;
}

With that, the issue is fixed and we have a sticky footer regardless of the content length.

Demo

Responsive Elements

I stumbled upon this article while researching, and I really liked it, so I will re-take the use-case and explain it my way.

Suppose that we have a portfolio to showcase our responsive design work, and we have three devices (mobile, tablet, and laptop). Each device contains an image. The goal is to make this 100% responsive in CSS.

By using CSS grid and viewport units, we can make this fully responsive and dynamic.

<div class="wrapper">
  <div class="device laptop"></div>
  <div class="device mobile"></div>
  <div class="device tablet"></div>
</div>

Notice that viewport units are used in the grid-* properties. They’re also used for the border, border-radius, and other properties. All of this will result in a fluid design.

.wrapper {
  display: grid;
  grid-template-columns: repeat(20, 5vw); 
  grid-auto-rows: 6vw;
}

.mobile { 
  position: relative;
  z-index: 1;
  grid-column: 2 / span 3;
  grid-row: 3 / span 5;
}
 
.tablet {
  position: relative;
  z-index: 1;
  grid-column: 13 / span 7; 
  grid-row: 4 / span 4;
  border-bottom: 1vw solid #a9B9dd;
  border-right: solid 3vw #a9B9dd;
}
 
.laptop {
  position: relative;
  grid-column: 3/span 13;
  grid-row: 2 / span 8;
}

/* Viewport units are used for the bottom, left, right, height, and border-radius. Isn't that cool? */
.laptop:after {
    content:"";
    position:absolute;
    bottom: -3vw;
    left: -5.5vw;
    right: -5.5vw;
    height: 3vw;
    background-color: #a9B9dd;
    border-radius: 0 0 1vw 1vw;
}

Demo

Breaking Out Of The Container

I have noticed a use case which is most suitable for editorial layouts. A child element that takes 100% width of the viewport, even if its parent width is limited. Consider the below figure:

To achieve a similar effect, we can use viewport units and some positioning properties. Here is the CSS:

.break-out {
    width: 100vw;
    position: relative;
    left: 50%;
    right: 50%;
    margin-left: -50vw;
    margin-right: -50vw;
}

Let’s break it down so we understand how all of this works bit by bit.

1. Adding width: 100vw

The most important step, which will give the image a width equal to 100% of the viewport.

2. Adding margin-left: -50vw

To center the image, we will need to give it a negative margin with half of the viewport width.

3. Adding left: 50%

Finally, we will need to push the image to the right with a value of 50% of its parent width.

Demo

Vertical and Horizontal Spacing

Another interesting use-case that I thought about is using viewport units for spacing between elements. This can be used with values like margin, top, bottom, and grid-gap. When used, the spacing will be based on the viewport width or height, which might be useful to make a layout more dynamic.

For modals, we need to push them from the top of the viewport. Often, I made this with the top property and I use either percentage or pixel value. However, with viewport units, we can add a spacing that can change based on the viewport’s height.

.modal-body {
    top: 20vh;
}

See the video below for the final result.

Demo

A page header is a section that acts as an introduction for a page. It often has a title and a description, and among that there is either a fixed height or padding for the top and bottom edges. We’re interested in the padding thing.

For example, here is how the CSS looks:

.page-header {
    padding-top: 1rem;
    padding-bottom: 1rem;
}

@media (min-width: 800px) {
    .page-header {
        padding-top: 4rem;
        padding-bottom: 4rem;
    }
}

The vertical padding is small on mobile, and it gets larger on greater viewports. What about using viewport units? Let’s explore that.

.page-header {
    padding-top: 10vh;
    padding-bottom: 10vh;
}

.page-header h2 {
    margin-bottom: 1.5vh;
}

I used vh unit for the padding of the page header, and the margin below the title. Notice how the spacing changes!

Demo

Grid of Items

Another use case for the dynamic spacing is a grid of items. It can for anything, like a grid of articles, services section.. etc. For our case, we will take a look at how to use that for an article grid.

By using viewport units inside grid-gap, we can get the desired result. Notice that I used CSS calc() function as well. The goal of using calc() is to have a base vertical and horizontal gap.

.wrapper {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
    grid-gap: calc(14px + 1vh) calc(14px + 0.5vw);
}

Demo

Vmin and Vmax Use Cases

While researching about use-cases for these two values, I didn’t find anything but this one from CSS-Tricks.

The use-case is about the top and bottom padding for a page header element. The padding is often reduced when the viewport is small (mobile). By using vmin, we can have a fluid top and bottom padding based on the viewport smaller dimension (width or height).

.page-header {
    padding: 10vmin 1rem;
}

Demo

Four years ago, I wrote an article about vertical media queries. I want to shed light on this topic since it’s related to this article that you’re reading now

Full Height Sections in Landscape Mode

By using vertical media queries, we can check if the user phone or tablet is in landscape mode. If it is, then it won’t make sense to have a section taking the full height of the viewport by using height: 100vh.

To solve that issue, we can do the following:

@media (min-height: 400px) {
    .section {
        height: 100vh;
    }
}

Or we can use the orientation media query:

@media (orientation: landscape) {
    .section {
        height: 100vh;
    }
}

Aspect Ratio

We can use vw unit to create responsive elements that maintain its aspect ratio regardless of the viewport size. Let’s explore that.

We will need to decide on the aspect ratio we need. For this example, I will use 9/16.

section {
    /* 9/16 * 100 */
    height: 56.25vw;
}

Demo

Using Viewport Units For Graphical Elements

I’m not sure if my naming for this is correct, but I hope you will get my point from the following visual examples.

Do you know that top border which is most of the websites use these days? Mostly, its color is the same as the brand color – which will give some personality.

Let’s support that the initial value for the border is 3px. How to convert that fixed value to a viewport thing? Well, here is how to calculate the vw equivalent of it.

vw = (Pixel Value / Viewport width) * 100

The viewport width is used to estimate the ratio between the pixel value and the desired vw unit.

For our example, here is how the top border added:

.header {
    border-top: 4px solid #8f7ebc;  
}

In my case, the viewport width is 1440 (This is not a fixed number, replace it with your own)

vw = (4 / 1440) * 100 = 0.277

.header {
    border-top: 0.277vw solid #8f7ebc;  
}

Even better, we can use a base pixel value, and the viewport unit can be an addition.

.header {
    border-top: calc(2px + 0.138vw) solid $color-main;
}

Section Numbering

For section items with a counter or an icon, we can leverage the usage of viewport units. See the mockup below:

We have a circle containing a number, and there is text content. Both of them need to be sized fluidly with viewport units. Let’s explore how!

/**
1. Using a fluid value for the flex, width, and height properties.
2. The line-height is used to center the text vertically
3. Font size
4. The spacing between the circle and the text
**/
.point:before {
    flex: 0 0 calc(24px + 4vw); /* [1] */
    width: calc(24px + 4vw); /* [1] */
    height: calc(24px + 4vw); /* [1] */
    line-height: calc(24px + 4vw); /* [2] */
    font-size: calc(14px + 1vw); /* [3] */
    margin-right: calc(10px + 0.5vw); /* [4] */
}

Demo

Viewport Units Watcher

I made a tool to help me in checking the current computed value elements with viewport units. Consider the below:

I added the class name and property for each element, and on viewport resize, the values are updated dynamically. This is much better than inspecting DevTools and going to the computed for each specific element.

Try it yourself

There is a common issue in mobile which cases a scroll, even if 100vh is used. The reason is that the address bar height is in view. Louis Hoebregts wrote an article about that with a simple and easy fix.

.my-element {
  height: 100vh; /* Fallback for browsers that do not support Custom Properties */
  height: calc(var(--vh, 1vh) * 100);
}
// First we get the viewport height and we multiple it by 1% to get a value for a vh unit
let vh = window.innerHeight * 0.01;
// Then we set the value in the --vh custom property to the root of the document
document.documentElement.style.setProperty('--vh', `${vh}px`);

Demo

Accessibility Is Important

When using viewport units extensively in a project, like for building a whole layout with it, accessibility is important.
When a layout is zoomed in/out, the elements with viewport units won’t scale as you might expect. This will cause an issue to users who rely on zoom to browse the web.

As per this tweet by Sāra Soueidān, using viewport units alone for font sizing is bad for accessibility. It’s better to combine them with fixed values, like the below:

.title {
    font-size: calc(16px + .3vw);
}

..and it will be good to go. Please make sure that using viewport units is suitable for what you’re doing, and test well.

  • I found this little px to vw converter which might help you in your project.

Do you have a useful tool? Please let me know at @shadeed9

The End

That’s a wrap. Do you have a comment or a suggestion? Please feel free to ping me on @shadeed9.





Source link

How to Animate Text with SVG and CSS
Strategy

How to Animate Text with SVG and CSS


The other day I was helping my pal Jez work Dept. of Enthusiasm, the site for his newsletter, and I had a thought. What if we made the word “enthusiasm” in the title animate a little bit? Like, what if each of the letters in the word bopped up and down enthusiastically?

Like this:

Neat, huh? To build this thing I knew we could use SVG for the text and then animate things with CSS. Each letter is a path with its own class, which makes it possible to select each one. That said, there’s nothing really stopping us from doing this with HTML and CSS. Using SVG is just one approach that felt right to me at the time.

To get started we headed over to Figma and typed out the text in separate text boxes. We did this so that when we click on the “Outline stroke” menu item here…

…we have individual vectors of each letter. This will help us when we export the SVG so that we can add the correct CSS classes to each element. Once we’ve outlined the strokes of each letter we can then edit the points in the vector (but we don’t need to for what we’re about to do):

If we added all the text in one box and clicked “Outline Stroke” then it would’ve created a single vector with all these letters combined. That would then make a single path with the coordinates and that’s pretty difficult for me to style or even understand what the heck is going on in there.

Next up, I put all these letters in a Frame (Sketch calls this an Artboard) and placed each word into a Group. This way, when they’re exported as an SVG, each word will be in it’s own g tag which also helps us style the letters:

From there, I exported the SVG — but! — I had to make sure to include the id option when doing it.

If we don’t do this we’ll get a bunch of path elements for each letter but they won’t have an id attributes.

This is what we get after the export:

I’m not sure how much of this weirdness is me and how much is Figma’s SVG export, but I deleted that <rect> element because it’s unecessary. Then I gave the body element a background so I could see the text and remove those inline height and width attributes on the SVG itself:

Neato! Now we can get to the fun part: animating each letter in the word.

If you look at the HTML of that example above you’ll notice there’s a g element which with an id with the same name of the Frame in Figma.There are also g elements for each word and every path that makes up the word will have an individual id. (This is why naming our Frames and Groups properly, as well as keeping things organized in any design application, is important.)

One thing that surprised me was the order in which each path is exported though: it’s in the opposite order than the one I’d expect, with M being the first letter in the “ENTHUSIASM” group. So I cleaned that up a bit and made sure each letter is in the correct order.

To get the animation working we first bump down each letter by 2px:

g path {
  transform: translateY(2px);
}

That’s because I want each letter to make a 2px hop which we’ll get to in a bit. I also noticed with this change I’d need to update the SVG viewbox too. Otherwise, the bottom of each letter will be cut off:

<svg class="header" viewBox="0 0 146 13" fill="none" xmlns="http://www.w3.org/2000/svg">

I probably should’ve have just repositioned the text within the frame in Figma and exported it again, but this is fine for what I needed.

Now I can target the third group in the SVG (the word “enthusiasm”) and set the animation-count to infinite:

/* targets the word "enthusiasm" */
g:nth-child(3) path {
  animation-name: wiggleWiggle;
  animation-duration: 2.5s;
  animation-iteration-count: infinite;
}

The code above then calls the wiggleWiggle animation below:

@keyframes wiggleWiggle {
  20%,
  100% {
    transform: translate(0, 2px); /* stay on the baseline for most of the animation duration */
  }

  0% {
    transform: translate(0, 0px); /* hop up */
  }
  10% {
    transform: translate(0, 2px); /* return to baseline */
  }
}

See the beginning of that keyframe — the 20%, 100% bit? What that’s saying is “keep all the text on the baseline for the majority of the animation.” That’s what gives us a nice delay between each bounce:

I learnt this trick from this really good post about animation timing by Geoff and I would highly recommend you check it out if you’re about to start learning about animation in CSS.

Now for the fun bit: with the animation-delay property, we can make each letter hop just after the one before it. There’s definitely a smarter way I could be doing this, but I just used the id of each letter like so:

#E {
  animation-delay: 0s;
}

#N {
  animation-delay: 0.1s;
}

#T {
  animation-delay: 0.15s;
}

#H {
  animation-delay: 0.2s;
}

#U {
  animation-delay: 0.25s;
}

#S_2 {
  animation-delay: 0.3s;
}

#I {
  animation-delay: 0.35s;
}

#A {
  animation-delay: 0.4s;
}

#S {
  animation-delay: 0.45s;
}

#M {
  animation-delay: 0.5s;
}

It sure is messy, but writing the loop wouldn’t save me that much time and I won’t need to update it in the future, so I think it’s fine enough. And with that we’re pretty much done!

We now have a bouncy, enthusiastic title to say hello. Yay for wiggly text!



Source link