Tag: Together

How to Make GraphQL and DynamoDB Play Nicely Together

Serverless, GraphQL, and DynamoDB are a powerful combination for building websites. The first two are well-loved, but DynamoDB is often misunderstood or actively avoided. It’s often dismissed by folks who consider it only worth the effort “at scale.”

That was my assumption, too, and I tried to stick with a SQL database for my serverless apps. But after learning and using DynamoDB, I see the benefits of it for projects of any scale.

To show you what I mean, let’s build an API from start to finish — without any heavy Object Relational Mapper (ORM) or GraphQL framework to hide what is really going on. Maybe when we’re done you might consider giving DynamoDB a second look. I think it is worth the effort.

The main objections to DynamoDB and GraphQL

The main objection to DynamoDB is that it is hard to learn, but few people argue about its power. I agree the learning curve feels very steep. But SQL databases are not the best fit with serverless applications. Where do you stand up that SQL database? How do you manage connections to it? These things just don’t mesh with the serverless model very well. DynamoDB is serverless-friendly by design. You are trading the up-front pain of learning something hard to save yourself from future pain. Future pain that only grows if your application grows.

The case against using GraphQL with DynamoDB is a little more nuanced. GraphQL seems to fit well with relational databases partly because it is assumed by a lot of the documentation, tutorials, and examples. Alex Debrie is a DynamoDB expert who wrote The DynamoDB Book which is a great resource to deeply learn it. Even he recommends against using the two together, mostly because of the way that GraphQL resolvers are often written as sequential independent database calls that can result in excessive database reads.

Another potential problem is that DynamoDB works best when you know your access patterns beforehand. One of the strengths of GraphQL is that it can handle arbitrary queries more easily by design than REST. This is more of a problem with a public API where users can write arbitrary queries. In reality, GraphQL is often used for private APIs where you control both the client and the server. In this case, you know and can control the queries you run. With a GraphQL API it is possible to write queries that clobber any database without taking steps to avoid them.

A basic data model

For this example API, we will model an organization with teams, users, and certifications. The entity relational diagram is shown below. Each team has many users and each user can have many certifications.

Relational database model

Our end goal is to model this data in a DynamoDB table, but if we did model it in a SQL database, it would look like the following diagram:

To represent the many-to-many relationship of users to certifications, we add an intermediate table called “Credential.” The only unique attribute on this table is the expiration date. There would be other attributes for each of the tables, but we reduce it to just a name for each for simplicity.

Access patterns

The key to designing a data model for DynamoDB is to know your access patterns up front. In a relational database you start with normalized data and perform joins across the data to access it. DynamoDB does not have joins, so we build a data model that matches how we intend to access it. This is an iterative process. The goal is to identify the most frequent patterns to start. Most of these will directly map to a GraphQL query, but some may be only used internally to the back end to authenticate or check permissions, etc. An access pattern that is rarely used, like a check run once a week by an administrator, does not need to be designed. Something very inefficient (like a table scan) can handle these queries.

Most frequently accessed:

  • User by ID or name
  • Team by ID or name
  • Certification by ID or name

Frequently accessed:

  • All Users on a Team by Team ID
  • All Certifications for a given User
  • All Teams
  • All Certifications

Rarely accessed

  • All Certifications of users on a Team
  • All Users who have a Certification
  • All Users who have a Certification on a Team

DynamoDB single table design

DynamoDB does not have joins and you can only query based on the primary key or predefined indexes. There is no set schema for items imposed by the database, so many different types of items can be stored in a single table. In fact, the recommended best practice for your data schema is to store all items in a single table so that you can access related items together with a single query. Below is a single table model representing our data. To design this schema, you take the access patterns above and choose attributes for the keys and indexes that match.

The primary key here is a composite of the partition/hash key (pk) and the sort key (sk). To retrieve an item in DynamoDB, you must specify the partition key exactly and either a single value or a range of values for the sort key. This allows you to retrieve more than one item if they share a partition key. The indexes here are shown as gsi1pk, gsi1sk, etc. These generic attribute names are used for the indexes (i.e. gsi1pk) so that the same index can be used to access different types of items with different access pattern. With a composite key, the sort key cannot be empty, so we use “#” as a placeholder when the sort key is not needed.

Access pattern Query conditions
Team, User, or Certification by ID   Primary Key, pk=”T#”+ID, sk=”#”  
Team, User, or Certification by name Index GSI 1, gsi1pk=type, gsi1sk=name
All Teams, Users, or Certifications   Index GSI 1, gsi1pk=type    
All Users on a Team by ID Index GSI 2, gsi2pk=”T#”+teamID
All Certifications for a User by ID Primary Key, pk=”U#”+userID, sk=”C#”+certID
All Users with a Certification by ID Index GSI 1, gsi1pk=”C#”+certID, gsi1sk=”U#”+userID

Database schema

We enforce the “database schema” in the application. The DynamoDB API is powerful, but also verbose and complicated. Many people jump directly to using an ORM to simplify it. Here, we will directly access the database using the helper functions below to create the schema for the Team item.

const DB_MAP = {   TEAM: {     get: ({ teamId }) => ({       pk: 'T#'+teamId,       sk: '#',     }),     put: ({ teamId, teamName }) => ({       pk: 'T#'+teamId,       sk: '#',       gsi1pk: 'Team',       gsi1sk: teamName,       _tp: 'Team',       tn: teamName,     }),     parse: ({ pk, tn, _tp }) => {       if (_tp === 'Team') {         return {           id: pk.slice(2),           name: tn,           };         } else return null;         },     queryByName: ({ teamName }) => ({       IndexName: 'gsi1pk-gsi1sk-index',       ExpressionAttributeNames: { '#p': 'gsi1pk', '#s': 'gsi1sk' },       KeyConditionExpression: '#p = :p AND #s = :s',       ExpressionAttributeValues: { ':p': 'Team', ':s': teamName },       ScanIndexForward: true,     }),     queryAll: {       IndexName: 'gsi1pk-gsi1sk-index',       ExpressionAttributeNames: { '#p': 'gsi1pk' },       KeyConditionExpression: '#p = :p ',       ExpressionAttributeValues: { ':p': 'Team' },       ScanIndexForward: true,     },   },   parseList: (list, type) => {     if (Array.isArray(list)) {       return list.map(i => DB_MAP[type].parse(i));     }     if (Array.isArray(list.Items)) {       return list.Items.map(i => DB_MAP[type].parse(i));     }   }, };

To put a new team item in the database you call:

DB_MAP.TEAM.put({teamId:"t_01",teamName:"North Team"})

This forms the index and key values that are passed to the database API. The parse method takes an item from the database and translates it back to the application model.

GraphQL schema

type Team {   id: ID!   name: String   members: [User] } type User {   id: ID!   name: String   team: Team   credentials: [Credential] } type Certification {   id: ID!   name: String } type Credential {   id: ID!   user: User   certification: Certification   expiration: String } type Query {   team(id: ID!): Team   teamByName(name: String!): [Team]   user(id: ID!): User   userByName(name: String!): [User]   certification(id: ID!): Certification   certificationByName(name: String!): [Certification]   allTeams: [Team]   allCertifications: [Certification]   allUsers: [User] }

Bridging the gap between GraphQL and DynamoDB with resolvers

Resolvers are where a GraphQL query is executed. You can get a long way in GraphQL without ever writing a resolver. But to build our API, we’ll need to write some. For each query in the GraphQL schema above there is a root resolver below (only the team resolvers are shown here). This root resolver returns either a promise or an object with part of the query results.

If the query returns a Team type as the result, then execution is passed down to the Team type resolver. That resolver has a function for each of the values in a Team. If there is no resolver for a given value (i.e. id), it will look to see if the root resolver already passed it down.

A query takes four arguments. The first, called root or parent, is an object passed down from the resolver above with any partial results. The second, called args, contains the arguments passed to the query. The third, called context, can contain anything the application needs to resolve the query. In this case, we add a reference for the database to the context. The final argument, called info, is not used here. It contains more details about the query (like an abstract syntax tree).

In the resolvers below, ctx.db.singletable is the reference to the DynamoDB table that contains all the data. The get and query methods directly execute against the database and the DB_MAP.TEAM.... translates the schema to the database using the helper functions we wrote earlier. The parse method translates the data back to the from needed for the GraphQL schema.

const resolverMap = {   Query: {     team: (root, args, ctx, info) => {       return ctx.db.singletable.get(DB_MAP.TEAM.get({ teamId: args.id }))         .then(data => DB_MAP.TEAM.parse(data));     },     teamByName: (root, args, ctx, info) =>; {       return ctx.db.singletable         .query(DB_MAP.TEAM.queryByName({ teamName: args.name }))         .then(data => DB_MAP.parseList(data, 'TEAM'));     },     allTeams: (root, args, ctx, info) => {       return ctx.db.singletable.query(DB_MAP.TEAM.queryAll)         .then(data => DB_MAP.parseList(data, 'TEAM'));     },   },   Team: {     name: (root, _, ctx) => {       if (root.name) {         return root.name;       } else {         return ctx.db.singletable.get(DB_MAP.TEAM.get({ teamId: root.id }))           .then(data => DB_MAP.TEAM.parse(data).name);       }     },     members: (root, _, ctx) => {       return ctx.db.singletable         .query(DB_MAP.USER.queryByTeamId({ teamId: root.id }))         .then(data => DB_MAP.parseList(data, 'USER'));     },   },   User: {     name: (root, _, ctx) => {       if (root.name) {         return root.name;       } else {         return ctx.db.singletable.get(DB_MAP.USER.get({ userId: root.id }))           .then(data => DB_MAP.USER.parse(data).name);       }     },     credentials: (root, _, ctx) => {       return ctx.db.singletable         .query(DB_MAP.CREDENTIAL.queryByUserId({ userId: root.id }))         .then(data =>DB_MAP.parseList(data, 'CREDENTIAL'));     },   }, };

Now let’s follow the execution of the query below. First, the team root resolver reads the team by id and returns id and name. Then the Team type resolver reads all the members of that team. Then the User type resolver is called for each user to get all of their credentials and certifications. If there are five members on the team and each member has five credentials, that results in a total of seven reads for the database. You could argue that is too many. In a SQL database this might be reduced to four database calls. I would argue that the seven DynamoDB reads will be cheaper and faster than the four SQL reads in many cases. But this comes with a big dose of “it depends” on a lot of factors.

query { team( id:"t_01" ){   id   name   members{     id     name     credentials{       id       certification{         id         name       }     }   } }}

Over-fetching and the N+1 problem

Optimizing a GraphQL API involves balancing a whole lot of tradeoffs that we won’t get into here. But two that weigh heavily in the decision of DynamoDB versus SQL are over-fetching and the N+1 problem. In many ways, these are opposite sides of the same coin. Over-fetching is when a resolver requests more data from the database than it needs to respond to the query. This often happens when you try to make one call to the database in the root resolver or a type resolver (e.g., members in the Team type resolver above) to get as much of the data as you can. If the query did not request the name attribute, it can be seen as wasted effort.

The N+1 problem is almost the opposite. If all the reads are pushed down to the lowest level resolver, then the team root resolver and the members resolver (for Team type) would make only a minimal or no request to the database. They would just pass the IDs down to the Team type and User type resolver. In this case, instead of members making one call to get all five members, it would push down to User to make five separate reads. This would result in potentially 36 or more separate reads for the query above. In practice, this does not happen because an optimized server would use something like the DataLoader library that acts as a middleware to intercept those 36 calls and batch them into probably only four calls to the database. These smaller atomic read requests are needed so that the DataLoader (or similar tool) can efficiently batch them into fewer reads.

So, to optimize a GraphQL API with SQL, it is usually best to have small resolvers at the lowest levels and use something like DataLoader to optimize them. But for a DynamoDB API it is better to have “smarter” resolvers higher up that better match the access patterns your single table database it written for. The over-fetching that results in this case is usually the lesser of the two evils.

Deploy this example in 60 seconds

This is where you realize the full payoff of using DynamoDB together with serverless GraphQL. I built this example with Architect. It is an open-source tool to build serverless apps on AWS without most of the headaches of directly using AWS. Once you clone the repo and run npm install, you can launch the app for local development (including a built-in local version of the database) with a single command. Not only that, you can also deploy it straight to production infrastructure (including DynamoDB) on AWS with a single command when you are ready.


The post How to Make GraphQL and DynamoDB Play Nicely Together appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , ,

How to Get Sticky and Full-Bleed Elements to Play Well Together

I had a unique requirement the other day: to build a layout with full-bleed elements while one element stays stuck to the top. This ended up being rather tricky to pull off so I’m documenting it here in case anyone needs to re-create this same effect. Part of the trickiness was dealing with logical positioning on small screens as well.

It’s tough to describe the effect, so I recorded my screen to show what I mean. Pay special attention to the main call to action section, the one with the “Try Domino Today” header.

The idea is to display the main call to action on the right side while users scroll past other sections on larger viewports. On smaller viewports, the call to action element has to display after the main hero section with the “Start your trial” header.

There are a two main challenges here:

  • Make full-bleed elements that don’t interfere with the sticky element
  • Avoid duplicating the HTML

Before we dive into a couple of possible solutions (and their limitations), let’s first set up the semantic HTML structure.

The HTML

When building these kinds of layouts, one might be tempted to build duplicate call-to-action sections: one for the desktop version and the other for the mobile version and then toggle the visibility of them when appropriate. This avoids having to find the perfect place in the HTML and needing to apply CSS that handles both layout needs. I must admit, I am guilty of doing that from time to time. But this time, I wanted to avoid duplicating my HTML.

The other thing to consider is that we’re using the sticky positioning on the .box--sticky element, which means it needs to be the sibling of other elements, including full-bleed ones, for it to properly work.

Here’s the markup:

<div class="grid">    <div class="box box--hero">Hero Box</div>    <div class="box box--sticky">Sticky Box</div>    <div class="box box--bleed">Full-bleed Box</div>   <div class="box box--bleed">Full-bleed Box</div>   <!-- a bunch more of these -->  </div>

Let’s get sticky

Making sticky elements in a CSS grid layout is pretty straightforward. We add position: sticky to the .box--sticky element with a top: 0 offset, indicating where it starts to stick. Oh, and notice that we’re only making the element sticky on viewports larger that 768px.

@media screen and (min-width: 768px) {   .box--sticky {     position: sticky;     top: 0;   } }

Beware that there is a known issue with sticky positioning in Safari when it’s used with overflow: auto. It is documented over at caniuse in the known issues section:

A parent with overflow set to auto will prevent position: sticky from working in Safari.

Nice, that was easy. Let’s solve the challenge of full-bleed elements next.

Solution 1: Pseudo-elements

The first solution is something I use often: absolutely positioned pseudo-elements that stretch from one side to side. The trick here is to use a negative offset.

If we are talking about centered content, then the calculation is quite straightforward:

.full-bleed-element {   max-width: 600px;   margin-right: auto;   margin-left: auto;   padding: 20px;   position: relative;  }  .full-bleed-element::before {   content: "";   background-color: dodgerblue;    position: absolute;   top: 0;   bottom: 0;   right: calc((100vw - 100%) / -2);   left: calc((100vw - 100%) / -2); }

In short, the negative offset is the width of the viewport, 100vw, minus the width of the element, 100%, and then divided by -2, because we need two negative offsets.

Beware that there is a known bug when using 100vw, that is also documented over at caniuse:

Currently all browsers but Firefox incorrectly consider 100vw to be the entire page width, including vertical scroll bar, which can cause a horizontal scroll bar when overflow: auto is set.

Now let’s make full-bleed elements when the content is not centered. If you watch the video again, notice that there is no content below the sticky element. We don’t want our sticky element to overlap the content and that is the reason why be don’t have centered content in this particular layout.

First, we are going to create the grid:

.grid {   display: grid;   grid-gap: var(--gap);   grid-template-columns: var(--cols);   max-width: var(--max-width);   margin-left: auto;   margin-right: auto; }

We’re using custom properties which allows us to redefine the maximum width, the gap, and grid columns without redeclaring the properties. In other words, instead of redeclaring the grid-gap, grid-template-columns, and max-width properties, we are re-declaring variable values:

:root {   --gap: 20px;   --cols: 1fr;   --max-width: calc(100% - 2 * var(--gap)); }  @media screen and (min-width: 768px) {   :root {     --max-width: 600px;     --aside-width: 200px;     --cols: 1fr var(--aside-width);   } }  @media screen and (min-width: 980px) {   :root {     --max-width: 900px;     --aside-width: 300px;   } }

On viewports that are 768px wide and above, we have defined two columns: one with a fixed width, --aside-width, and one with that fills the remaining space, 1fr, as well as maximum width of the grid container, --max-width.

On viewports smaller than 768px, we have defined a single column and the gap. The maximum width of the grid container is 100% of the viewport, minus gaps on each side.

Now comes the fun part. The content isn’t centered on bigger viewports, so the calculation isn’t as straightforward as you might think. Here’s how it looks:

.box--alpha {   position: relative;   z-index: 0; }  .box--alpha::before {   content: "";   display: block;   position: absolute;   top: 0;   bottom: 0;   left: calc((100vw - (100% + var(--gap) + var(--aside-width))) / -2);   right: calc(((100vw - (100% - var(--gap) + var(--aside-width))) / -2) - (var(--aside-width)));   z-index: -1; }

Instead of using 100% of the parent’s width, we’re taking into account the widths of the gap and the sticky element. That means width of the content in full-bleed elements will not exceed the bounds of the hero element. That way, we ensure the sticky element won’t overlap any important piece of information.

The left offset is simpler because we only need to subtract the width of the element (100%), the gap (--gap), and the sticky element (--aside-width) from the viewport width (100vw).

left: (100vw - (100% + var(--gap) + var(--aside-width))) / -2);

The right offset is more complicated because we have to add the width of the sticky element to the previous calculation, --aside-width, as well as the gap, --gap:

right: ((100vw - (100% + var(--gap) + var(--aside-width))) / -2) - (var(--aside-width) + var(--gap));

Now we are sure the sticky element doesn’t overlap any content in full-bleed elements.

Here’s the solution with a horizontal bug:

And here’s the solution with a horizontal bugfix:

The fix is to hide overflow on the x-axis of the body, which might be a good idea in general anyway:

body {   max-width: 100%;   overflow-x: hidden; }

This is a perfectly viable solution and we could end here. But where’s the fun in that? There’s usually more than one way to accomplish something, so let’s look at another approach.

Solution 2: Padding calculations

Instead of using a centered grid container and pseudo elements, we could achieve the same effect by configuring our grid. Let’s start by defining the grid just as we did last time:

.grid {   display: grid;   grid-gap: var(--gap);   grid-template-columns: var(--cols); }

Again, we are using custom properties to define the gap and the template columns:

:root {   --gap: 20px;   --gutter: 1px;   --cols: var(--gutter) 1fr var(--gutter); }

We’re showing three columns on viewports smaller than 768px. The center column takes as much space as possible, while the other two are used only to force the horizontal gap.

@media screen and (max-width: 767px) {   .box {     grid-column: 2 / -2;   } }

Note that all grid elements are placed in the center column.

On viewports bigger than 768px, we are defining a --max-width variable that limits the width of the inner columns. We’re also defining --aside-width, the width of our sticky element. Again, this way we ensure the sticky element won’t be positioned over any content inside the full-bleed elements.

:root {   --gap: 20px; }  @media screen and (min-width: 768px) {   :root {     --max-width: 600px;     --aside-width: 200px;     --gutter: calc((100% - (var(--max-width))) / 2 - var(--gap));     --cols: var(--gutter) 1fr var(--aside-width) var(--gutter);   } }  @media screen and (min-width: 980px) {   :root {     --max-width: 900px;     --aside-width: 300px;   } }

Next, we are calculating the gutter width. The calculation is:

--gutter: calc((100% - (var(--max-width))) / 2 - var(--gap));

…where 100% is the viewport width. First, we are subtracting the maximum width of the inner columns from the width of the viewport. Then, we are dividing that result by 2 to create the gutters. Finally, we are subtracting the grid’s gap to get the correct width of the gutter columns.

Now let’s push the .box--hero element over so it starts at the first inner column of the grid:

@media screen and (min-width: 768px) {   .box--hero {     grid-column-start: 2;   } }

This automatically pushes the sticky box so it starts right after the hero element. We could also explicitly define the placement of the sticky box, like this:

.box--sticky {   grid-column: 3 / span 1; }

Finally, let’s make the full-bleed elements by setting grid-column to 1 / -1. That tells the elements to start the content at the first grid item and span through to the last one.

@media screen and (min-width: 768px) {     .box--alpha {     grid-column: 1 / -1;   } }

To center the content, we are going to calculate left and right padding. The left padding is equal to the size of the gutter column, plus the grid gap. The right padding is equal to the size of the left padding, plus another grid gap as well as the width of the sticky element.

@media screen and (min-width: 768px) {   .box--alpha {       padding-left: calc(var(--gutter) + var(--gap));     padding-right: calc(var(--gutter) + var(--gap) + var(--gap) + var(--aside-width));   } }

Here’s the final solution:

I prefer this solution to the first one because it isn’t using buggy viewport units.


I love CSS calculations. Using mathematical operations is not always straightforward, especially when combining different units, like 100%. Figuring out what 100% means is half of the effort.

I also love solving simple, yet complicated layouts, like this one, using only CSS. Modern CSS has native solutions — like grid, sticky positioning and calculations — that remove complicated and somewhat heavy JavaScript solutions. Let’s leave the dirty work for the browser!

Do you have a better solution or different approach for this? I would be happy to hear about it.


The post How to Get Sticky and Full-Bleed Elements to Play Well Together appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , , ,
[Top]

Using Flexbox and text ellipsis together

You can truncate a single line of text with an ellipsis (…) fairly easily with text-overflow and a few friends. But, as you might expect, that truncation happens at the end of the line of text. What if you want to truncate content in the middle?

Leonardo Faria details good use cases for this, like in an operating system window listing files. The line of text is a file name and a file extension. When that line truncates, it truncates just the name, always leaving the extension at the end. The trick is a flexbox parent so you can use overflow on just the file name part, but have to make sure to reset the min-width, as the natural value there is min-content, which prevents the truncation which is confusing.

Direct Link to ArticlePermalink


The post Using Flexbox and text ellipsis together appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , ,
[Top]

Online Together

(This is a sponsored post.)

An Event Apart: Online Together is a single-day online conference with an intense focus on digital design, UX, content, code, and more, giving you deep insights into where we are now and where things are going next.

AEA! With a brand new online version of their conference! That’s awesome. AEA is a best-in-class web conference, so if you’re looking for a conference to help get your mind going on big-picture web design and development stuff again and connect with some like-minded industry people, this is your opportunity.

Direct Link to ArticlePermalink

The post Online Together appeared first on CSS-Tricks.

CSS-Tricks

,
[Top]

How They Fit Together: Transform, Translate, Rotate, Scale, and Offset

Firefox 72 was first out of the gate with “independent transforms.” That is, instead of having to combine transforms together, like:

.el {   transform: rotate(10deg) scale(0.95) translate(10px, 10px); }

…we can do:

.el {   rotate: 10deg;   scale: 0.95;   translate: 10px 10px; }

That’s extremely useful, as having to repeat other transforms when you change a single one, lest remove them, is tedious and prone to error.

But there is some nuance to know about here, and Dan Wilson digs in.

Little things to know:

  • Independent transforms happen first. So, if you also use a transform, that can override them if the same transform types is used.
  • They all share the same transform-origin.
  • The offset-* properties also effectively moves/rotates elements. Those happen after independent transforms and before transform.

Direct Link to ArticlePermalink

The post How They Fit Together: Transform, Translate, Rotate, Scale, and Offset appeared first on CSS-Tricks.

CSS-Tricks

, , , , , ,
[Top]

Making width and flexible items play nice together

The short answer: flex-shrink and flex-basis are probably what you’re lookin’ for.

The long answer

Let’s say you want to align an image and some text next to each other with like this:

A small image of a yellow illustrated bird to the left of a block of text.

Now let’s say you reach for flexbox to make it happen. Setting the parent element to display: flex; is a good first start.

.container {    display: flex;  }

And this results in…

See the Pen
Flex-Shrink Example 1
by Robin Rendle (@robinrendle)
on CodePen.

Yikes! Well, that’s kinda okay, I guess. It makes sense that the image would bump right up against the text like that because we haven’t set a width on the image. Ideally, though, we’d like that image to have a fixed width and then the text should take up whatever space is left over.

Okay, so let’s go do that!

.container {    display: flex;  }  img {    width: 50px;   margin-right: 20px;  }

See the Pen
Flex-Shrink Example 2
by Robin Rendle (@robinrendle)
on CodePen.

This looks great in Chrome. But wait, what? If we inspect the image tag in Firefox DevTools, we’ll find that it’s not the width value that we set at all:

We could use min-width to force the image to the 50px width we want:

img {   min-width: 50px;   margin-right: 20px; }

Buuuuuuut, that only sets helps with the width so we’ve got to put a margin in as well.

img {   min-width: 50px;   margin-right: 20px; }

There we go. That’s better in Firefox and still works in Chrome.

The even longer answer

I realized the image is getting the squished treatment because we need to use the flex-shrink property to tell flex items not to decrease in size, regardless of whether or not they have a width.

All flex-items have a flex-shrink value of 1. We need to set the image element to 0:

.container {    display: flex;  }  img {   width: 50px;   margin-right: 20px;   flex-shrink: 0; }

See the Pen
Flex-Shrink Example 3
by Robin Rendle (@robinrendle)
on CodePen.

Getting better! But we can still do more to improve this.

The director’s cut answer

We can tidy things up further because flex-shrink is included in the flex shorthand property.

flex: none | [ <'flex-grow'> <'flex-shrink'>? || <'flex-basis'> ]

If we set the flex-shrink value to 0 and the flex-basis value to the default width we want the image to be, then we can get rid of the width property altogether.

.container {    display: flex;  }  img {   flex: 0 0 50px;   margin-right: 20px; }

Oh yeah:

See the Pen
Flex-Shrink Example 2
by Geoff Graham (@geoffgraham)
on CodePen.

Another example

That flex-shrink property solves a ton of other problems and is pretty dang important if you want to start using flexbox. Here’s another example why: I stumbled upon yet another problem like the one above and I mentioned it in a recent edition of the newsletter. I was building a navigation component that would let users scroll left and right through multiple items. I noticed the following problem when checking my work:

See the Pen
flex-shrink nav item 1
by Robin Rendle (@robinrendle)
on CodePen.

That longer navigation item shouldn’t break into multiple lines like that — but I finally understood why this was happening, thanks to the previous issue. If you set the flex-shrink property to 0 then it will tell each item in this navigation not to shrink and instead assume the width of the content instead, like this:

See the Pen
flex-shrink nav item
by Robin Rendle (@robinrendle)
on CodePen.

And, yes, we can go the extra step once again to use the flex property instead, this time using auto as the flex-basis since we want the maximum amount of space for all items to be considered when divvying up space in the navigation container.

See the Pen
Setting flex for flexible nav elements
by Geoff Graham (@geoffgraham)
on CodePen.

Huzzah! We figured it out. Even though the answer is a single line of code, it’s is pretty essential one to making truly flexible elements.

CSS-Tricks

, , , , , ,
[Top]

Using “box shadows” and clip-path together

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

You make a box

.tag {   background: #FB8C00;   color: #222;   font: bold 32px system-ui;   padding: 2rem 3rem 2rem 4rem; }

You fashion it into a nice tag shape

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

.tag {   /* ... */   clip-path: polygon(30px 0%, 100% 0%, 100% 100%, 30px 100%, 0 50%) }

You want a shadow on it, so you…

Try to use box-shadow.

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

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

You can drop-shadow a parent element instead

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

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

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

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

That’s all.

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

CSS-Tricks

, , , ,
[Top]

Piecing Together Approaches for a CSS Masonry Layout

Masonry layout, on the web, is when items of an uneven size are laid out such that there aren’t uneven gaps. I would guess the term was coined (or at least popularized) for the web by David DeSandro because of his popular Masonry JavaScript library, which has been around since 2010.

JavaScript library. Nothing against JavaScript, but it’s understandable we might not want to lean on it for doing layout. Is there anything we can do in CSS directly these days? Sorta.

Is vertical order with ragged bottoms OK?

If it is, then CSS columns will do just fine.

See the Pen Masonry with Columns by Chris Coyier (@chriscoyier) on CodePen.

Flexbox can do vertical columns with ragged endings too

But it’s not quite as clever, because you’ll need to set a height of some kind to get it to wrap the columns. You’ll also have to be explicit about widths rather than having it decide columns for you.

But it’s doable and it does auto space the gaps if there is room.

See the Pen Masonry with Flexbox by Chris Coyier (@chriscoyier) on CodePen.

Do you need a clean bottom edge? A Flexbox/JavaScript combo can help.

Jamie Perkins originally wrote this, then Janosh Riebesell re-wrote it and, now I’m porting it here.

It totally messes with the order and requires the children to be flexy about their height, but it does the trick:

See the Pen Masonry with Flexbox + JS by Chris Coyier (@chriscoyier) on CodePen.

Is horizontal line masonry OK?

If it’s just the uneven brick-like look you’re after, then horizontal masonry is way easier. You could probably even float stuff if you don’t care about the ragged edge. If you wanna keep it a block… flexbox with allowed flex-grow is the ticket.

See the Pen Masonry with Flexbox + JS by Chris Coyier (@chriscoyier) on CodePen.

You’d think CSS grid could help

CSS grid is very amazing and useful in a CSS developer’s everyday life, but it’s not really designed for masonry style layouts. CSS grid is about defining lines and placing things along those lines, where masonry is about letting elements end where they may, but still exerting some positional influence.

Balázs Sziklai has a nice example of auto-flowing grids that all stack together nicely, with pretty good horiziontal ordering:

See the Pen True Masonry with Grid Layout by Balázs Sziklai (@balazs_sziklai) on CodePen.

But you can see how strict the lines are. There is a way though!

Grid + JavaScript-maniplated row spans

Andy Barefoot wrote up a great guide. The trick is setting up repeating grid rows that are fairly short, letting the elements fall into the grid horizontally as they may, then adjusting their heights to match the grid with some fairly light math to calculate how many rows they should span.

See the Pen CSS Grid Masonry (Step 10) by Andy Barefoot (@andybarefoot) on CodePen.

DOM-shifted elements in a CSS columns layout

What people generally want is column-stacking (varied heights on elements), but with horizontal ordering. That last grid demo above handles it pretty well, but it’s not the only way.

Jesse Korzan tackled it with CSS columns. It needs JavaScript as well to get it done. In this case, it shifts the elements in the DOM to order them left-to-right while providing a horizontal stack using a CSS columns layout. This introduces a bit of an accessibility problem since the visual order (left-to-right) and source order (top-to-bottom) are super different & dash; though perhaps fixable with programmatic tabindex?

There’s also the original library

Float away, my pretties.

See the Pen Masonry with Masonry by Chris Coyier (@chriscoyier) on CodePen.

The post Piecing Together Approaches for a CSS Masonry Layout appeared first on CSS-Tricks.

CSS-Tricks

, , , ,
[Top]

Fighting FOIT and FOUT Together

Lots from Divya with the setup:

There are 2 kinds of problems that can arise when using webfonts; Flash of invisible text (FOIT) and Flash of Unstyled Text (FOUT) … If we were to compare them, FOUT is of course the lesser of the two evils

If you wanna fight FOIT, the easiest tool is the font-display CSS property. I like the optional value because I generally dislike the look of fonts swapping.

If you want to fight them both, one option is to preload the fonts:

<link rel="preload" href="/fonts/awesome-l.woff2" as="font" />

But…

Preload is your friend, but not like your best friend … preloading in excess can significantly worsen performance, since preloads block initial render.

Just like CSS does.

Even huge sites aren’t doing much about font loading perf. Roel Nieskens:

I expected major news sites to be really conscious about the fonts they use, and making sure everything is heavily optimised. Turns out a simple Sunday afternoon of hacking could save a lot of data: we could easily save roughly 200KB

Fonts are such big part of web perf, so let’s get better at it! Here’s Zach Leatherman at the Performance.now() conference:

Part of the story is that we might just have lean on JavaScript to do the things we need to do. Divya again:

Web fonts are primarily CSS oriented. It can therefore feel like bad practice to reach for JavaScript to solve a CSS issue, especially since JavaScript is a culprit for an ever increasing page weight.

But sometimes you just have to do it in order to get what you need done. Perhaps you’d like to handle page visibility yourself? Here’s Divya’s demonstration snippet:

const font = new fontFace("My Awesome Font", "url(/fonts/my-awesome-font.woff2)" format('woff2')")   font.load().then (() => {     document.fonts.add(font);     document.body.style.fontFamily = "My Awesome Font, serif";      // we can also control visibility of the page based on a font loading //     var content = document.getElementById("content");     content.style.visibility = "visible";     })

But there are a bunch of other reasons. Zach has documented them:

  • Because, as you load in fonts, each of them can cause repaints and you want to group them.
  • Because the user has indicated they want to use less data or reduce motion.
  • Because you’d like to be kind to slow networks.
  • Because you’re using a third-party that can’t handle font-display.

The post Fighting FOIT and FOUT Together appeared first on CSS-Tricks.

CSS-Tricks

, , ,
[Top]