Design News: 2nd of June Edition

Overlapping Header with CSS Grid

Snook shows off a classic design with an oversized header up top, and a content area that is “pulled up” into that header area. My mind goes to the same place:

Historically, I’ve done this with negative margins. The header has a height that adds a bunch of padding to the bottom and then the body gets a margin-top: -50px or whatever the design calls for.

If you match the margin and padding with a situation like this, it’s not exactly magic numbers, but it still doesn’t feel great to me beaus they’re still numbers you need to keep in sync across totally different elements.

His idea? Build it with CSS grid instead. Definitely feels much more robust.

Random coinsidence, I was reading Chen Hui Jing’s “The one in black and orange” post and the pattern showed up there as well.

Direct Link to ArticlePermalink

The post Overlapping Header with CSS Grid appeared first on CSS-Tricks.

CSS-Tricks

, ,
[Top]

Rotated Table Column Headers… Now With Fewer Magic Numbers!

Rotated <table> column headers is something that’s been covered before right here on CSS-Tricks, so shout-out to that for getting me started and helping me achieve this effect. As the article points out, if you aren’t using trigonometry to calculate your table styles, you’ll have to rely on magic numbers and your table will be brittle and any dreams of responsiveness crushed. 

Fortunately, in this case, we can take the trigonometry out and replace it with some careful geometry and our magic numbers all turn into 0 (a truly magical number).

For those in a hurry, here is the CSS (it’s very similar to the styles in the other article). Below is a thorough walk-through.

<th class="rotate"><div><span>Column Header 1</span></div></th>
table {  border-collapse: collapse;  --table-border-width: 1px; } th.rotate {   white-space: nowrap;   position: relative; 
} th.rotate > div {   /* place div at bottom left of the th parent */   position: absolute;   bottom: 0;   left: 0;   /* Make sure short labels still meet the corner of the parent otherwise you'll get a gap */   text-align: left;   /* Move the top left corner of the span's bottom-border to line up with the top left corner of the td's border-right border so that the border corners are matched    * Rotate 315 (-45) degrees about matched border corners */   transform:      translate(calc(100% - var(--table-border-width) / 2), var(--table-border-width))     rotate(315deg);   transform-origin: 0% calc(100% - var(--table-border-width));   width: 100%; 
} th.rotate > div > span {   /* make sure the bottom of the span is matched up with the bottom of the parent div */   position: absolute;   bottom: 0;   left: 0;   border-bottom: var(--table-border-width) solid gray; } td {   border-right: var(--table-border-width) solid gray;   /* make sure this is at least as wide as sqrt(2) * height of the tallest letter in your font or the headers will overlap each other*/   min-width: 30px;   padding-top: 2px;   padding-left: 5px;   text-align: right; }

Let’s unpack this table and see what’s going on. The magic starts with that funny chain of HTML tags. We’re putting a <span> inside of a <div> inside of our <th>. Is this all really necessary? Between how borders behave, the positioning flexibility we need, and what determines the width of a table column… yes, they each have a purpose and are necessary.

Let’s see what happens if we rotate the <th> directly:

<th class="rotate">Column header 1</th>
table {   border-collapse: collapse; } th.rotate {   border-bottom: 1px solid gray;   transform: rotate(315deg);   white-space: nowrap; } td {   border-right: 1px solid gray;   min-width: 30px;   padding-top: 2px;   padding-left: 5px;   text-align: right; }

Ignoring the fact that we haven’t corrected position, there are two big issues here: 

  1. The column width is still calculated from the header length which is what we were trying to avoid.
  2. Our border didn’t come with us in the rotation, because it is actually part of the table.

These problems aren’t so difficult to fix. We know that if the <th> has a child element with a border, the browser won’t treat that border as part of the table. Further, we know that absolutely-positioned elements are taken out of the document flow and won’t affect the parent’s width. Enter <div> tag, stage left…and right, I guess.

<th class="rotate"><div>Column header 1</div></th>
table {   border-collapse: collapse; } th.rotate {   white-space: nowrap;   position: relative; } th.rotate > div {   position: absolute;   transform: rotate(315deg);   border-bottom: 1px solid gray; } td {   border-right: 1px solid gray;   min-width: 30px;   text-align: right;   padding-top: 2px;   padding-left: 5px; }
Now our headers don’t influence the column width and the borders are rotated. We just need to line things up.

It’s easier to tell in the image with the rotated <th> elements, but that rotation is happening around the center of the element (that’s the default behavior of transform-origin). It is only another transform in x and y to get it to the right spot, but this is where we’d need trigonometry to figure out just how much x and y to line it up with the column borders. If we instead carefully choose the point to rotate the header about, and use transform-origin to select it, then we can end up with distances that are more straightforward than magic numbers.

The animation below helps illustrate what we’re going to do to avoid complicated math. The black dot in the top left of the blue border needs to match the red dot on the right border of the table column and rotate about it. Then there won’t be any gaps between the two borders.

It’s not helpful to start going somewhere if you don’t know where you are. The absolute positioning is going to help us out with this. By specifying bottom: 0; left: 0; on the <div>, it ends up at the bottom left of the parent <th>. This means the <div> border’s bottom-left corner is sitting on top of the left column border and halfway through it. From here, it’s apparent we need to move down one border width and over one cell width, but how are we going to get that responsively? It’s at this very moment you may recall that we haven’t added the <span> yet — we’re going to need it!

We’ll use the <div> to “figure out” how big the table cells are and the <span> to actually hold the text and position it absolutely as well to overflow the parent.

<th class="rotate"><div><span>Column header 1</span></div></th>
th.rotate{   white-space: nowrap;   position: relative; } th.rotate > div {   position: absolute;   bottom: 0;   left: 0;   width: 100%;  /* <- now the div parent is as wide as the columns */ } th.rotate > div > span {   position: absolute;   bottom: 0;   left: 0;   border-bottom: 1px solid gray; }

Great! When we set the width of the <div> to 100%, it holds the information for how big the column is regardless of what the content is in the table cells. With this in place, we can easily translate things over by the width of the <div> — but don’t forget that we need to shave off a half border width. Our translation becomes:

transform: translate( calc( 100% - var(--table-border-width)/2), var(--table-border-width));

The <div> is now in the right spot to rotate, but we have to make sure to pick the correct transform-origin. We want it to be on the top-left corner of the border, which will be on the left and up one border’s width from the bottom of our <div> element:

transform-origin: 0%, calc(100% - var(--table-border-width));

This brings us to our final style for the table header.

table {   border-collapse: collapse;   --table-border-width: 1px; } th.rotate{   white-space: nowrap;   position: relative; } th.rotate > div {   position: absolute;   bottom: 0;   left: 0;   width: 100%;   transform:     translate( calc( 100% - var(--table-border-width)/2), var(--table-border-width));     rotate(315deg);   transform-origin: 0%, calc(100% - var(--table-border-width)); } th.rotate > div > span {   position: absolute;   bottom: 0;   left: 0;   border-bottom: var(--table-border-width) solid gray; }

Note that transformations happen after everything is placed. That means the rotated headers will overflow onto everything as best they can. You will need to wrap the whole table in something to compensate for the unexpected height. I put the title and table together in a flexbox <div> and set the flex-basis of the title to a value large enough to compensate for the tall headers.

#div-with-table {   display: flex;   flex-direction: column;   justify-content: space-around; } #title {   flex-basis: 140px; }

The post Rotated Table Column Headers… Now With Fewer Magic Numbers! appeared first on CSS-Tricks.

CSS-Tricks

, , , , , ,
[Top]

Increment Issue 13: Frontend

Increment is a beautiful quarterly magazine (print and web) published by Stripe “about how teams build and operate software systems at scale”. While there is always stuff about making websites in general, this issue is the first focused on front-end¹ development.

I’ve got an article in there: When frontend means full stack. I’ll probably someday port it over here and perhaps add some more context (there were some constraints for print) but I love how it turned out on their site! A taste:

We handle this growing responsibility in different ways. Even though we all technically fall within the same big-tent title, many frontend developers wind up specializing. Often, we don’t have a choice. The term “unicorn” once described the extremely rare person who was good at both frontend and backend development, but these days it’s just as rare to find people skilled at the full spectrum of frontend development. In fact, the term “full stack” has largely come to mean “a frontend developer who does a good amount of the stuff a backend developer used to do.”

The whole issue is chock full of wonderful authors:

And the article that is the most right up my alley, Why is CSS . . . the way it is? by Chris Lilley. It’s somehow astonishing, gutwrenching, understandable, and comfortable to know that CSS evolves like any other software project. Sometimes thoughtfully and carefully, and sometimes with a meh, we’ll fix it later.

Once a feature is in place, it’s easier to slightly improve it than to add a new, better, but completely different feature that does the same thing.

This explains, for example, why list markers were initially specified in CSS by expanding the role of float. (The list marker was floated left so the list item text wrapped around it to the right.) That effort was abandoned and replaced by the list-style-position property, whose definition currently has the following, not very confidence-inspiring inline issue: “This is handwavey nonsense from CSS2, and needs a real definition.”

That’s a damn fine collection of writings on front end if you ask me.

A big thank you to Sid Orlando and Molly McArdle who helped me through the process and seem to do a great job running the ship over there.

  1. The issue uses “frontend” throughout, and I appreciate them having a styleguide and being consistent about it. But I can’t bring myself to use it. 🔗 The term “front-end” is correct when used as a compound adjective, and the term “front end” is correct when used as a noun.

The post Increment Issue 13: Frontend appeared first on CSS-Tricks.

CSS-Tricks

, ,
[Top]

Global CSS options with custom properties

With a preprocessor, like Sass, building a logical “do this or don’t” setting is fairly straightforward:

$  option: false;  @mixin doThing {   @if $  option {     do-thing: yep;   } }  .el {   @include doThing; }

Can we do that in native CSS with custom properties? Mark Otto shows that we can. It’s just a smidge different.

html {   --component-shadow: 0 .5rem 1rem rgba(0,0,0,.1); }  .component {   box-shadow: var(--component-shadow); }  <!-- override the global anywhere more specific! like      <div class="component remove-shadow">      or      <body class="remove-shadow"> --> .remove-shadow {   --component-shadow: none; }

Direct Link to ArticlePermalink

The post Global CSS options with custom properties appeared first on CSS-Tricks.

CSS-Tricks

, , ,
[Top]

5 Good Reasons to Switch for the Brave Browser

[Top]

Jamstack News!

I totally forgot that the Jamstack Conf was this week but thankfully they’ve already published the talks on the Jamstack YouTube channel. I’m really looking forward to sitting down with these over a coffee while I also check out Netlify’s other big release today: Build Plugins.

These are plugins that run whenever your site is building. One example is the A11y plugin that will fail a build if accessibility failures are detected. Another minifies HTML and there’s even one that inlines critical CSS. What’s exciting is that these build plugins are kinda making complex Gulp/Grunt environments the stuff of legend. Instead of going through the hassle of config stuff, build plugins let Netlify figure it all out for you. And that’s pretty neat.

Also, our very own Sarah Drasner wrote just about how to create your first Netlify Build Plugin. So, if you have an idea for something that you could share with the community, then that may be the best place to start.

Direct Link to ArticlePermalink

The post Jamstack News! appeared first on CSS-Tricks.

CSS-Tricks

,
[Top]

Building Your First Serverless Service With AWS Lambda Functions

Many developers are at least marginally familiar with AWS Lambda functions. They’re reasonably straightforward to set up, but the vast AWS landscape can make it hard to see the big picture. With so many different pieces it can be daunting, and frustratingly hard to see how they fit seamlessly into a normal web application.

The Serverless framework is a huge help here. It streamlines the creation, deployment, and most significantly, the integration of Lambda functions into a web app. To be clear, it does much, much more than that, but these are the pieces I’ll be focusing on. Hopefully, this post strikes your interest and encourages you to check out the many other things Serverless supports. If you’re completely new to Lambda you might first want to check out this AWS intro.

There’s no way I can cover the initial installation and setup better than the quick start guide, so start there to get up and running. Assuming you already have an AWS account, you might be up and running in 5–10 minutes; and if you don’t, the guide covers that as well.

Your first Serverless service

Before we get to cool things like file uploads and S3 buckets, let’s create a basic Lambda function, connect it to an HTTP endpoint, and call it from an existing web app. The Lambda won’t do anything useful or interesting, but this will give us a nice opportunity to see how pleasant it is to work with Serverless.

First, let’s create our service. Open any new, or existing web app you might have (create-react-app is a great way to quickly spin up a new one) and find a place to create our services. For me, it’s my lambda folder. Whatever directory you choose, cd into it from terminal and run the following command:

sls create -t aws-nodejs --path hello-world

That creates a new directory called hello-world. Let’s crack it open and see what’s in there.

If you look in handler.js, you should see an async function that returns a message. We could hit sls deploy in our terminal right now, and deploy that Lambda function, which could then be invoked. But before we do that, let’s make it callable over the web.

Working with AWS manually, we’d normally need to go into the AWS API Gateway, create an endpoint, then create a stage, and tell it to proxy to our Lambda. With serverless, all we need is a little bit of config.

Still in the hello-world directory? Open the serverless.yaml file that was created in there.

The config file actually comes with boilerplate for the most common setups. Let’s uncomment the http entries, and add a more sensible path. Something like this:

functions:   hello:     handler: handler.hello #   The following are a few example events you can configure #   NOTE: Please make sure to change your handler code to work with those events #   Check the event documentation for details     events:       - http:         path: msg         method: get

That’s it. Serverless does all the grunt work described above.

CORS configuration 

Ideally, we want to call this from front-end JavaScript code with the Fetch API, but that unfortunately means we need CORS to be configured. This section will walk you through that.

Below the configuration above, add cors: true, like this

functions:   hello:     handler: handler.hello     events:       - http:         path: msg         method: get         cors: true

That’s the section! CORS is now configured on our API endpoint, allowing cross-origin communication.

CORS Lambda tweak

While our HTTP endpoint is configured for CORS, it’s up to our Lambda to return the right headers. That’s just how CORS works. Let’s automate that by heading back into handler.js, and adding this function:

const CorsResponse = obj => ({   statusCode: 200,   headers: {     "Access-Control-Allow-Origin": "*",     "Access-Control-Allow-Headers": "*",     "Access-Control-Allow-Methods": "*"   },   body: JSON.stringify(obj) });

Before returning from the Lambda, we’ll send the return value through that function. Here’s the entirety of handler.js with everything we’ve done up to this point:

'use strict'; const CorsResponse = obj => ({   statusCode: 200,   headers: {     "Access-Control-Allow-Origin": "*",     "Access-Control-Allow-Headers": "*",     "Access-Control-Allow-Methods": "*"   },   body: JSON.stringify(obj) }); 
 module.exports.hello = async event => {   return CorsResponse("HELLO, WORLD!"); };

Let’s run it. Type sls deploy into your terminal from the hello-world folder.

When that runs, we’ll have deployed our Lambda function to an HTTP endpoint that we can call via Fetch. But… where is it? We could crack open our AWS console, find the gateway API that serverless created for us, then find the Invoke URL. It would look something like this.

The AWS console showing the Settings tab which includes Cache Settings. Above that is a blue notice that contains the invoke URL.

Fortunately, there is an easier way, which is to type sls info into our terminal:

Just like that, we can see that our Lambda function is available at the following path:

https://6xpmc3g0ch.execute-api.us-east-1.amazonaws.com/dev/ms

Woot, now let’s call It!

Now let’s open up a web app and try fetching it. Here’s what our Fetch will look like:

fetch("https://6xpmc3g0ch.execute-api.us-east-1.amazonaws.com/dev/msg")   .then(resp => resp.json())   .then(resp => {     console.log(resp);   });

We should see our message in the dev console.

Console output showing Hello World.

Now that we’ve gotten our feet wet, let’s repeat this process. This time, though, let’s make a more interesting, useful service. Specifically, let’s make the canonical “resize an image” Lambda, but instead of being triggered by a new S3 bucket upload, let’s let the user upload an image directly to our Lambda. That’ll remove the need to bundle any kind of aws-sdk resources in our client-side bundle.

Building a useful Lambda

OK, from the start! This particular Lambda will take an image, resize it, then upload it to an S3 bucket. First, let’s create a new service. I’m calling it cover-art but it could certainly be anything else.

sls create -t aws-nodejs --path cover-art

As before, we’ll add a path to our HTTP endpoint (which in this case will be a POST, instead of GET, since we’re sending the file instead of receiving it) and enable CORS:

// Same as before   events:     - http:       path: upload       method: post       cors: true

Next, let’s grant our Lambda access to whatever S3 buckets we’re going to use for the upload. Look in your YAML file — there should be a iamRoleStatements section that contains boilerplate code that’s been commented out. We can leverage some of that by uncommenting it. Here’s the config we’ll use to enable the S3 buckets we want:

iamRoleStatements:  - Effect: "Allow"    Action:      - "s3:*"    Resource: ["arn:aws:s3:::your-bucket-name/*"]

Note the /* on the end. We don’t list specific bucket names in isolation, but rather paths to resources; in this case, that’s any resources that happen to exist inside your-bucket-name.

Since we want to upload files directly to our Lambda, we need to make one more tweak. Specifically, we need to configure the API endpoint to accept multipart/form-data as a binary media type. Locate the provider section in the YAML file:

provider:   name: aws   runtime: nodejs12.x

…and modify if it to:

provider:   name: aws   runtime: nodejs12.x   apiGateway:     binaryMediaTypes:       - 'multipart/form-data'

For good measure, let’s give our function an intelligent name. Replace handler: handler.hello with handler: handler.upload, then change module.exports.hello to module.exports.upload in handler.js.

Now we get to write some code

First, let’s grab some helpers.

npm i jimp uuid lambda-multipart-parser

Wait, what’s Jimp? It’s the library I’m using to resize uploaded images. uuid will be for creating new, unique file names of the sized resources, before uploading to S3. Oh, and lambda-multipart-parser? That’s for parsing the file info inside our Lambda.

Next, let’s make a convenience helper for S3 uploading:

const uploadToS3 = (fileName, body) => {   const s3 = new S3({});   const  params = { Bucket: "your-bucket-name", Key: `/$ {fileName}`, Body: body }; 
   return new Promise(res => {     s3.upload(params, function(err, data) {       if (err) {         return res(CorsResponse({ error: true, message: err }));       }       res(CorsResponse({          success: true,          url: `https://$ {params.Bucket}.s3.amazonaws.com/$ {params.Key}`        }));     });   }); };

Lastly, we’ll plug in some code that reads the upload files, resizes them with Jimp (if needed) and uploads the result to S3. The final result is below.

'use strict'; const AWS = require("aws-sdk"); const { S3 } = AWS; const path = require("path"); const Jimp = require("jimp"); const uuid = require("uuid/v4"); const awsMultiPartParser = require("lambda-multipart-parser"); 
 const CorsResponse = obj => ({   statusCode: 200,   headers: {     "Access-Control-Allow-Origin": "*",     "Access-Control-Allow-Headers": "*",     "Access-Control-Allow-Methods": "*"   },   body: JSON.stringify(obj) }); 
 const uploadToS3 = (fileName, body) => {   const s3 = new S3({});   var params = { Bucket: "your-bucket-name", Key: `/$ {fileName}`, Body: body };   return new Promise(res => {     s3.upload(params, function(err, data) {       if (err) {         return res(CorsResponse({ error: true, message: err }));       }       res(CorsResponse({          success: true,          url: `https://$ {params.Bucket}.s3.amazonaws.com/$ {params.Key}`        }));     });   }); }; 
 module.exports.upload = async event => {   const formPayload = await awsMultiPartParser.parse(event);   const MAX_WIDTH = 50;   return new Promise(res => {     Jimp.read(formPayload.files[0].content, function(err, image) {       if (err || !image) {         return res(CorsResponse({ error: true, message: err }));       }       const newName = `$ {uuid()}$ {path.extname(formPayload.files[0].filename)}`;       if (image.bitmap.width > MAX_WIDTH) {         image.resize(MAX_WIDTH, Jimp.AUTO);         image.getBuffer(image.getMIME(), (err, body) => {           if (err) {             return res(CorsResponse({ error: true, message: err }));           }           return res(uploadToS3(newName, body));         });       } else {         image.getBuffer(image.getMIME(), (err, body) => {           if (err) {             return res(CorsResponse({ error: true, message: err }));           }           return res(uploadToS3(newName, body));         });       }     });   }); };

I’m sorry to dump so much code on you but — this being a post about Amazon Lambda and serverless — I’d rather not belabor the grunt work within the serverless function. Of course, yours might look completely different if you’re using an image library other than Jimp.

Let’s run it by uploading a file from our client. I’m using the react-dropzone library, so my JSX looks like this:

<Dropzone   onDrop={files => onDrop(files)}   multiple={false} >   <div>Click or drag to upload a new cover</div> </Dropzone>

The onDrop function looks like this:

const onDrop = files => {   let request = new FormData();   request.append("fileUploaded", files[0]); 
   fetch("https://yb1ihnzpy8.execute-api.us-east-1.amazonaws.com/dev/upload", {     method: "POST",     mode: "cors",     body: request     })   .then(resp => resp.json())   .then(res => {     if (res.error) {       // handle errors     } else {       // success - woo hoo - update state as needed     }   }); };

And just like that, we can upload a file and see it appear in our S3 bucket! 

Screenshot of the AWS interface for buckets showing an uploaded file in a bucket that came from the Lambda function.

An optional detour: bundling

There’s one optional enhancement we could make to our setup. Right now, when we deploy our service, Serverless is zipping up the entire services folder and sending all of it to our Lambda. The content currently weighs in at 10MB, since all of our node_modules are getting dragged along for the ride. We can use a bundler to drastically reduce that size. Not only that, but a bundler will cut deploy time, data usage, cold start performance, etc. In other words, it’s a nice thing to have.

Fortunately for us, there’s a plugin that easily integrates webpack into the serverless build process. Let’s install it with:

npm i serverless-webpack --save-dev

…and add it via our YAML config file. We can drop this in at the very end:

// Same as before plugins:   - serverless-webpack

Naturally, we need a webpack.config.js file, so let’s add that to the mix:

const path = require("path"); module.exports = {   entry: "./handler.js",   output: {     libraryTarget: 'commonjs2',     path: path.join(__dirname, '.webpack'),     filename: 'handler.js',   },   target: "node",   mode: "production",   externals: ["aws-sdk"],   resolve: {     mainFields: ["main"]   } };

Notice that we’re setting target: node so Node-specific assets are treated properly. Also note that you may need to set the output filename to  handler.js. I’m also adding aws-sdk to the externals array so webpack doesn’t bundle it at all; instead, it’ll leave the call to const AWS = require("aws-sdk"); alone, allowing it to be handled by our Lamdba, at runtime. This is OK since Lambdas already have the aws-sdk available implicitly, meaning there’s no need for us to send it over the wire. Finally, the mainFields: ["main"] is to tell webpack to ignore any ESM module fields. This is necessary to fix some issues with the Jimp library.

Now let’s re-deploy, and hopefully we’ll see webpack running.

Now our code is bundled nicely into a single file that’s 935K, which zips down further to a mere 337K. That’s a lot of savings!

Odds and ends

If you’re wondering how you’d send other data to the Lambda, you’d add what you want to the request object, of type FormData, from before. For example:

request.append("xyz", "Hi there");

…and then read formPayload.xyz in the Lambda. This can be useful if you need to send a security token, or other file info.

If you’re wondering how you might configure env variables for your Lambda, you might have guessed by now that it’s as simple as adding some fields to your serverless.yaml file. It even supports reading the values from an external file (presumably not committed to git). This blog post by Philipp Müns covers it well.

Wrapping up

Serverless is an incredible framework. I promise, we’ve barely scratched the surface. Hopefully this post has shown you its potential, and motivated you to check it out even further.

If you’re interested in learning more, I’d recommend the learning materials from David Wells, an engineer at Netlify, and former member of the serverless team, as well as the Serverless Handbook by Swizec Teller

The post Building Your First Serverless Service With AWS Lambda Functions appeared first on CSS-Tricks.

CSS-Tricks

, , , , ,
[Top]

Why we at $FAMOUS_COMPANY Switched to $HYPED_TECHNOLOGY

Too funny:

After careful consideration, we settled on rearchitecting our platform to use $ FLASHY_LANGUAGE and $ HYPED_TECHNOLOGY. Not only is $ FLASHY_LANGUAGE popular according to the Stack Overflow developer survey, it’s also cross platform; we’re using it to reimplement our mobile apps as well. Rewriting our core infrastructure was fairly straightforward: as we have more engineers than we could possibly ever need or even know what to do with, we simply put a freeze on handling bug reports and shifted our effort to $ HYPED_TECHNOLOGY instead. We originally had some trouble with adapting to some of $ FLASHY_LANGUAGE’s quirks, and ran into a couple of bugs with $ HYPED_TECHNOLOGY, but overall their powerful new features let us remove some of the complexity that our previous solution had to handle.

There is absolutely no way Saagar Jha is poking at this or this.

Direct Link to ArticlePermalink

The post Why we at $ FAMOUS_COMPANY Switched to $ HYPED_TECHNOLOGY appeared first on CSS-Tricks.

CSS-Tricks

, ,
[Top]

A First Look at `aspect-ratio`

Oh hey! A brand new property that affects how a box is sized! That’s a big deal. There are lots of ways already to make an aspect-ratio sized box (and I’d say this custom properties based solution is the best), but none of them are particularly intuitive and certainly not as straightforward as declaring a single property.

So, with the impending arrival of aspect-ratio (MDN, and not to be confused with the media query version), I thought I’d take a look at how it works and try to wrap my mind around it.

Shout out to Una where I first saw this. Boy howdy did it strike interest in folks:

https://twitter.com/Una/status/1260980901934137345

Just dropping aspect-ratio on an element alone will calculate a height based on the auto width.

Without setting a width, an element will still have a natural auto width. So the height can be calculated from the aspect ratio and the rendered width.

.el {   aspect-ratio: 16 / 9; }

If the content breaks out of the aspect ratio, the element will still expand.

The aspect ratio becomes ignored in that situation, which is actually nice. Better to avoid potential data loss. If you prefer it doesn’t do this, you can always use the padding hack style.

If the element has either a height or width, the other is calculated from the aspect ratio.

So aspect-ratio is basically a way of seeing the other direction when you only have one (demo).

If the element has both a height and width, aspect-ratio is ignored.

The combination of an explicit height and width is “stronger” than the aspect ratio.

Factoring in min-* and max-*

There is always a little tension between width, min-width, and max-width (or the height versions). One of them always “wins.” It’s generally pretty intuitive.

If you set width: 100px; and min-width: 200px; then min-width will win. So, min-width is either ignored because you’re already over it, or wins. Same deal with max-width: if you set width: 100px; and max-width: 50px; then max-width will win. So, max-width is either ignored because you’re already under it, or wins.

It looks like that general intuitiveness carries on here: the min-* and max-* properties will either win or are irrelevant. And if they win, they break the aspect-ratio.

.el {   aspect-ratio: 1 / 4;   height: 500px;    /* Ignored, because width is calculated to be 125px */   /* min-width: 100px; */    /* Wins, making the aspect ratio 1 / 2 */   /* min-width: 250px; */ } 

With value functions

Aspect ratios are always most useful in fluid situations, or anytime you essentially don’t know one of the dimensions ahead of time. But even when you don’t know, you’re often putting constraints on things. Say 50% wide is cool, but you only want it to shrink as far as 200px. You might do width: max(50%, 200px);. Or constrain on both sides with clamp(200px, 50%, 400px);.

This seems to work inutitively:

.el {   aspect-ratio: 4 / 3;   width: clamp(200px, 50%, 400px); }

But say you run into that minimum 200px, and then apply a min-width of 300px? The min-width wins. It’s still intuitive, but it gets brain-bending because of how many properties, functions, and values can be involved.

Maybe it’s helpful to think of aspect-ratio as the weakest way to size an element?

It will never beat any other sizing information out, but it will always do its sizing if there is no other information available for that dimension.

The post A First Look at `aspect-ratio` appeared first on CSS-Tricks.

CSS-Tricks

, ,
[Top]