Customise the caret color with the CSS `caret-color` property

Thanks to this tweet by Álvaro Trigo I found out that you can change the color of the caret β€” that little blinking | in text inputs β€” using the caret-color CSS property:

Example:

input {
  caret-color: red;
}

πŸ’β€β™‚οΈ Do note that the caret-color defaults to currentColor, so you don’t need to manually match the color set on an element.

~

Browser support is really great:

Data on support for the css-caret-color feature across the major browsers from caniuse.com

πŸ’‘ Shown above is a live CanIUse.com table, showing an always up-to-date support table. By the time you are reading this browser support might have become better.

~

Wondering if there were more caret-* properties this turned out to be not the case. A pity though, as I can think of cases where I would like to set the caret-height and caret-thickness, such as emulating the cursor of a terminal:

~

As a fun little experiment Tim Carambat created a Pen that changes the caret color from green to yellow to red as you near the maxlength of the <textarea> you’re typing in.

See the Pen
Color Changing Cursor in TextArea
by Timothy Carambat (@rambat1010)
on CodePen.

Usability-wise this isn’t that great, but it’s fun nonetheless πŸ™‚

Native Aspect Ratio Boxes in CSS thanks to aspect-ratio


Old vs. New. Image by @una.

Back in May 2020 I was very delighted to read that the first Working Draft of the CSS Box Sizing Module Level 4 got published, as it featured an addition to CSS that I’ve been wanting for a long time now: native support for aspect ratio boxes through the new aspect-ratio CSS property.

With Chromium 89 (current Canary) and Firefox 85 (current Nightly) already supporting aspect-ratio unflagged, it’s time to start playing with this new addition and start thinking about dropping all those nasty hacks to mimic aspect ratios in CSS. Let’s take a look …

πŸ€” Working Draft (WD)?

The Working Draft (WD) phase is the first phase of the W3C Recommendation Track, and is considered the exploring phase of a W3C spec.

From thereon a spec can become a Candidate Recommendation (CR) to finally land on being a Recommendation (REC). In between those three stages there are two transition stages: Last Call Working Draft (LCWD) and Proposed Recommendation (PR)

In visual form, the Recommendation Track looks something like this:

See An Inside View of the CSS Working Group at W3C for more details on all phases.

⚠️ Note that the current implementations in both Firefox Nightly and Chrome Canary are not finalised yet. You can track these bugs to stay up-to-date:

~

~

# Welcome aspect-ratio

In short, the aspect-ratio property allows you to define a preferred aspect ratio on elements:

.box {
  width: 20vw;
  aspect-ratio: 16 / 9;
}

[CodePen Demo]

In the example above the .box will have a preferred aspect ratio of 16:9. Since its width is set to 20vw, the resulting height will be 20vw / 16 * 9 = 11.25vw. Easy, right?

🀞 Psst, further down this you can find a demo that includes a fallback for browsers that don’t support aspect-ratio πŸ˜‰

~

# Allowed values for aspect-ratio

The value as set in the example above for aspect-ratio is of the <ratio> type:

  • It typically consists of two numbers separated by a /. The first parameter targets the width and the second one the height.
  • It’s also allowed to pass in just a single number. In that case the second number will be we considered to be 1. E.g. a <ratio> of 2 will translate to 2 / 1.
  • Passing in a 0 for either of the numbers is not allowed.
  • The spaces around the / are not required, so 2/1 is also a valid <ratio> value.

Another allowed value for the aspect-ratio property β€” which also is the default β€” is auto. This indicates that the box has no preferred aspect ratio and should size itself as it normally would.

πŸ™‹β€β™‚οΈ Hold up! How come images already behave correctly, without needing to define an aspect-ratio?

Images may be commonly used, but they are a quite uncommon type of HTML element:

  1. Images are replaced elements:

    A replaced element is an element whose content is outside the scope of the CSS formatting model, such as an image or embedded document. For example, the content of the HTML <img> element is often replaced by the image that its src attribute designates.

    Just check your DevTools: the browser will make an extra HTTP request for any image and fetch its contents separately. Once loaded, the browser will replace the original img tag with the actual image contents.

  2. Images have an intrinsic aspect ratio:

    The intrinsic dimensions represent a preferred or natural size of the object itself; that is, they are not a function of the context in which the object is used.

    Each photo that you take with your phone results in an image that has a certain width and height, which is referred to as the intrinsic or natural width/height. The intrinsic aspect ratio is the ratio between the intrinsic width and intrinsic height.

    When the browser has fetched the image and needs to draw it on screen it will take its intrinsic aspect ratio into account to know how big it should be drawn.

  3. When you define width and height attributes on an img, the browser will take those into account when drawing the image on screen. Nowadays browsers even internally map those properties to CSS sizing properties.

☝️ Do note that you can still set an aspect-ratio on an element that has an intrinsic aspect ratio. In that case your defined aspect-ratio will override the intrinsic aspect ratio.

~

# The fine print

# Aspect of what?

Depending upon which of width or height you set, the box dimensions will be calculated against that.

.box {
  width: 20vw;
  aspect-ratio: 16 / 9; /* Dimensions will be calculated against the width,
                           yielding a height of 11.25vw (20vw / 16 * 9) */
}
.box {
  height: 20vw;
  aspect-ratio: 16 / 9; /* Dimensions will be calculated against the height,
                           yielding a width of 35.55vw (20vw / 9 * 16) */
}

# aspect-ratio+width+height = 🚫

Setting an aspect-ratio won’t have effect on elements that have both a CSS width and CSS height set to a value other than auto. Only one of width or height can be explicitly set, and the other should remain set to auto.

.box {
  width: 20vw;
  height: 20vw;
  aspect-ratio: 16 / 9; /* won't have any effect! */
}

# aspect-ratio + percentage based width/height

In case one of the width and height should be set to a percentage based value such as 100%, the targeted box will take a look at the direct parent element’s dimensions to define its value upon that.

.parent {
  height: 100px;
}
.parent .box {
  height: 100%;
  aspect-ratio: 1 / 1; /* .box will be 100px by 100px */
}

There’s some more edge cases here too, but let’s not get too deep into the spec πŸ˜‰

# aspect-ratio sets a preferred aspect ratio

Setting an aspect-ratio will tell the browser that this is a preferred aspect ratio. Should the content of the box be larger, then the box will simply grow.

div {
  aspect-ratio: 1/1;
  /* 'width' and 'height' both default to 'auto' */
}
+----------+  +----------+  +----------+
| ~~~~~~~~ |  | ~~~~~~~~ |  | ~~~~~~~~ |
| ~~~~~~~~ |  | ~~~~~~~~ |  | ~~~~~~~~ |
| ~~~~~~~  |  | ~~~~~~~~ |  | ~~~~~~~~ |
|          |  | ~~~      |  | ~~~~~~~~ |
+----------+  +----------+  | ~~~~~~~~ |
                            | ~~~~~~   |
                            +----------+

To maintain the aspect-ratio, you can set overflow to auto so that a scrollbar will be shown should the contents be larger:

div {
  overflow: auto;
  aspect-ratio: 1/1;
}
+----------+  +----------+  +----------+
| ~~~~~~~~ |  | ~~~~~~~~ |  | ~~~~~~~~^|
| ~~~~~~~~ |  | ~~~~~~~~ |  | ~~~~~~~~ |
| ~~~~~~~  |  | ~~~~~~~~ |  | ~~~~~~~~ |
|          |  | ~~~      |  | ~~~~~~~~v|
+----------+  +----------+  +----------+

What also works, is setting min-height

Overriding the min-height property also maintains the 1:1 aspect ratio, but will result in content overflowing the box if it is not otherwise handled.

div {
  aspect-ratio: 1/1;
  min-height: 0;
}
+----------+  +----------+  +----------+
| ~~~~~~~~ |  | ~~~~~~~~ |  | ~~~~~~~~ |
| ~~~~~~~~ |  | ~~~~~~~~ |  | ~~~~~~~~ |
| ~~~~~~~  |  | ~~~~~~~~ |  | ~~~~~~~~ |
|          |  | ~~~      |  | ~~~~~~~~ |
+----------+  +----------+  +-~~~~~~~~-+
                              ~~~~~~    

~

# Demos

# Using aspect-ratio with a fallback for older browsers

Thanks to the powerful @supports it’s possible to add a fallback for browsers that don’t support aspect-ratio. In the demo below (based upon this demo by Una) a fallback using the padding-top hack is applied:

[CodePen Demo]

# Using aspect-ratio with CSS Variables

By introducing a CSS Variable CSS Custom Property it’s possible to make your code more generic and extract away a .aspect-ratio class.

[CodePen Demo]

To use it, add apply the .aspect-ratio on the element you want, and pass in a --aspect-ratio CSS Custom Property:

<div
  class="aspect-ratio"
  style="--aspect-ratio: 16/9;"
>I am an aspect ratio box</div>

The code is written so that it will use the value for --aspect-ratio in both the fallback and the modern version.

# Automatically setting aspect-ratio on iframes and the like

When you embed an iframe you most likely set its width and height HTML attribute.

<iframe
  src="https://www.youtube.com/embed/e7BkmF8CJpQ"
  width="560"
  height="315"></iframe>

It’s possible to use the values of these attributes to automatically set the aspect-ratio.

iframe[width][height] {
  aspect-ratio: attr(width) / attr(height);
}

Heck, you could even target [width][height] if you’d want!

πŸ’β€β™‚οΈ FYI: This is also what browsers nowadays do for images: they map the values from the width and height HTML attributes from images to a aspect-ratio in order to prevent Cumulative Layout Shift.

Firefox’s internal stylesheet for example looks like this:

img, input[type="image"], video, embed, iframe, marquee, object, table {
  aspect-ratio: attr(width) / attr(height);
}

marquee, lol πŸ˜†

[CodePen Demo]

πŸ› I’ve noticed that reading the width/height attribute values using attr() to pass them into aspect-ratio doesn’t seem to work in current Chromium. To cater for that I’m also passing their values by means of a CSS Custom Property …

<iframe
  src="https://www.youtube.com/embed/e7BkmF8CJpQ"
  width="560"
  height="315"
  style="--aspect-ratio: 560 / 315"
></iframe>
πŸ™‹β€β™‚οΈ Why doesn’t this iframe demo have a padding-top fallback injected using :after?

Just like images, iframes also are replaced elements. It’s not possible to inject contents using :before/:after on replaced elements.

If you really need to have a fallback, you need to wrap the iframe in a container and apply the aspect-ratio on the container. See Embed Responsively and adjust were needed.

~

# In Closing

After 8 years of wanting this feature to land in CSS (ref) I’m very happy to see this addition make it into the spec. It’s still a Working Draft right now, but that doesn’t stop me from being excited about it already. I hope you are too πŸ™‚

πŸ”₯ Like what you see? Want to stay in the loop? Here's how:

Speed up build times with this little Git trick

When building applications on build pipelines like GitHub Actions, Google Cloud Build, CircleCI, etc. every second counts. Here’s this small trick I use to speed up build times: when cloning the repo from its Git source, I instruct Git to do a shallow clone of the single branch it is building.

πŸ’‘ If you’re running a prebuilt “git clone” step on your Build Pipeline it most likely already uses this trick. Be sure to double check though.

~

Shallow Cloning

When doing a git clone you, by default, get the entire history of the project along with that. Take this clone of Laravel for example:

$ git clone [email protected]:laravel/laravel.git
Cloning into 'laravel'...
remote: Enumerating objects: 19, done.
remote: Counting objects: 100% (19/19), done.
remote: Compressing objects: 100% (15/15), done.
remote: Total 32004 (delta 5), reused 11 (delta 3), pack-reused 31985
Receiving objects: 100% (32004/32004), 9.94 MiB | 6.98 MiB/s, done.
Resolving deltas: 100% (18934/18934), done.

That’s a whopping 32004 objects totalling Β±10MiB that have been downloaded, even though the default Laravel branch only counts 66 files spread across 36 directories.

The objects contained in this Β±10MiB make up the entire history of every file and folder the project. To build the project we don’t really need all that, as we’re only interested in the latest version of each file and folder. By leveraging the --depth argument of our git clone command, we can enforce just that. This is what we call Shallow Cloning.

$ git clone --depth 1 [email protected]:laravel/laravel.git
Cloning into 'laravel'...
remote: Enumerating objects: 108, done.
remote: Counting objects: 100% (108/108), done.
remote: Compressing objects: 100% (88/88), done.
remote: Total 108 (delta 6), reused 49 (delta 1), pack-reused 0
Receiving objects: 100% (108/108), 41.80 KiB | 535.00 KiB/s, done.
Resolving deltas: 100% (6/6), done.

That’s a much speedier clone with only 108 objects, totalling a mere Β±40KiB!

πŸ€” You could argue that 10MiB worth of objects is still OK to clone, but think of scenarios where you have a big β€œmonorepo” with plenty of branches … then you’ll be talking about hundreds of wasted MiBs, if not GiBs.

~

Single Branch Cloning

When building a project you’re building only one certain branch. Information about the other branches is irrelevant at that time. To directly clone one specific branch we can use the --branch option to target said branch. With that alone we won’t get there though, we we still need to discard information about other branches. This is where the --single-branch option comes into play:

$ git clone --branch 3.0 --single-branch [email protected]:laravel/laravel.git
Cloning into 'laravel'...
remote: Enumerating objects: 20392, done.
remote: Total 20392 (delta 0), reused 0 (delta 0), pack-reused 20392
Receiving objects: 100% (20392/20392), 5.79 MiB | 853.00 KiB/s, done.
Resolving deltas: 100% (12731/12731), done.

Here we’ve cloned only the 3.0 branch of Laravel, resulting in roughly 10000 fewer objects to be downloaded.

By checking the contents of git branch -a we can also verify that other branch info has not been fetched:

$ cd laravel
$ git branch -a
* 3.0
  remotes/origin/3.0

~

Shallow Cloning + Single Branch Cloning

By combining both we can download only the latest files of a specific branch. Since the use of --single-branch is implied when using --depth, we can drop the former and our command will look like this:

$ git clone --depth 1 --branch <branchname> <repo>

Here’s an example downloading the Laravel 3.0 branch:

$ git clone --depth 1 --branch 3.0 [email protected]:laravel/laravel.git
Cloning into 'laravel'...
remote: Enumerating objects: 545, done.
remote: Counting objects: 100% (545/545), done.
remote: Compressing objects: 100% (465/465), done.
remote: Total 545 (delta 78), reused 293 (delta 45), pack-reused 0
Receiving objects: 100% (545/545), 1.34 MiB | 832.00 KiB/s, done.
Resolving deltas: 100% (78/78), done.

~

With this in place you’ll see your build times drop by minutes, especially when working on a monorepo with many branches.

Did this help you out? Like what you see?
Thank me with a coffee.

I don't do this for profit but a small one-time donation would surely put a smile on my face. Thanks!

β˜•οΈ Buy me a Coffee (€3)

To stay in the loop you can follow @bramus or follow @bramusblog on Twitter.

🐳 Building Docker Images on Google Cloud Build? Check out this trick to enable caching.

Container Queries are coming to Chromium!

Just announced on the Chromium mailing list is an β€œIntent to Prototype” Container Queries, which is quite exciting news πŸŽ‰

πŸ€” Container Queries?

Container Queries allow authors to style elements according to the size of a container. This is similar to a @media query, except that it evaluates against a container instead of the viewport.

The experimental implementation will follow Miriam Suzanne’s proposal, which looks like this:

/* (1) Create an implicit "container root" */
main,
aside {
  contain: size;
}

.media-object {
  display: grid;
  gap: 1em;
}

/* (2) Container Query targeting the nearest 
   "container root" ancestor. The rules nested
   inside will only be applied if the "container
   root" has a max-width of 45em */
@container (max-width: 45em) {
  .media-object {
    grid-template: 'img content' auto / auto 1fr;
  }
}

Applying contain: size; (1) onto an element will make it an implicit “container root” or “containment context”. Elements contained inside it can then have container queries applied onto them, by use of a new at-rule @container (<container-media-query>) (2). The target selector and CSS rules to apply in that case are β€” similar to what we do with β€œregular” media queries β€” nested within the @container at-rule.

In the example above extra rules will be applied to .media-object whenever its nearest “container root” ancestor β€” such as <main> or <aside> β€” has a max-width of 45em.

~

A previous version of this proposal by L. David Baron required a context selector to be set, but that has been dropped here. The @container rule from Miriam’s version will work in any containment context (read: the nearest parent element that has contain: size set). The syntax might still change, but that’s irrelevant to the prototype which is to be implemented:

This is not at all finalized, but the underlying problems we need to solve in Blink are (mostly) the same regardless of how the feature is accessed, so we’ll for now use this proposal as the temporary syntax.

~

Intent to Prototype: Container Queries →
Chrome Tracking Bug →

Did this help you out? Like what you see?
Thank me with a coffee.

I don't do this for profit but a small one-time donation would surely put a smile on my face. Thanks!

β˜•οΈ Buy me a Coffee (€3)

To stay in the loop you can follow @bramus or follow @bramusblog on Twitter.

HTML Forms: How (and Why) to Prevent Double Form Submissions

When double clicking a submit button your form will be sent twice. Using JavaScript you can prevent this from happening, but wouldn’t it be nice if this behavior could be tweaked by use of an attribute on the <form>? If you think so, feel free to give this issue a thumbs up.

Today Sebastian wondered:

I quickly chimed in saying that I do tend to lock up forms when submitting them. Let me explain why …

~

I started locking up submitted forms as users of the apps I’m building reported that sometimes the actions they had taken β€” such as adding an entry β€” were performed twice. I took me some time to figure out what exactly was going on, but eventually I found out this was because they were double clicking the submit button of the forms. As they double clicked the button, the form got sent over twice. By locking up forms after their first submission, all successive submissions β€” such as those triggered by that second click of a double click β€” are ignored.

~

To prevent these extra form submissions from being made I don’t hijack the form with Ajax nor do I perform any other complicated things. I let the inner workings of the web and forms in the browser be: when pressing the submit button the browser will still collect all form data, build a new HTTP request, and execute that request.

What I simply do is extend the form’s capabilities by adding a flag β€” by means of a CSS class β€” onto the form to indicate whether it’s being submitted or not. I can then use this flag’s presence to deny any successive submissions, and also hook some CSS styles on. β€” Progressive Enhancement, Yay! πŸŽ‰

The code looks as follows:

 // Prevent Double Submits
document.querySelectorAll('form').forEach(form => {
	form.addEventListener('submit', (e) => {
		// Prevent if already submitting
		if (form.classList.contains('is-submitting')) {
			e.preventDefault();
		}
		
		// Add class to hook our visual indicator on
		form.classList.add('is-submitting');
	});
});

πŸ’‘ Although the problem initially was a double click problem, we don’t listen for any clicks on the submit button but listen for the form’s submit event. This way our code not only works when clicking any of the submit buttons, but also when pressing enter to submit.

With that .is-submitting class in place, we can then attach some extra CSS onto the form to give the user visual feedback. Here’s a few examples:

See the Pen
Prevent Form Double Submits
by Bramus (@bramus)
on CodePen.

See the Pen
Prevent Form Double Submits (Alternative version)
by Bramus (@bramus)
on CodePen.

Note that this solution might not cover 100% of all possible scenarios, as it doesn’t take failing networks and other things that might go wrong into account. However, as I’m relying on the basic mechanisms of the web I think I can also rely on the browser to show that typical “Confirm Form Resubmission” interstitial should a timeout occur. Additionally, if need be, one could always unlock the form after a fixed amount of time. That way the user will be able to re-submit it again.

~

Dealing with double form submissions isn’t a new issue at all. You’ll find quite some results when running a few queries through Google β€” something I only found out after stumbling over the issue myself.

Back in 2015 (!) Mattias Geniar also pondered about this, after being pulled into the subject from a sysadmin view. Now, when even sysadmins are talking about an HTML/UX issue you know there’s something going on. This made me wonder why browsers behaved like that and how we could solve it:

As a result I decided to open an issue at the WHATWG HTML Standard repo, suggesting for a way to fix this at spec level:

An attribute on <form> to tweak this behavior – instead of having to rely on JavaScript – would come in handy and form a nice addition to the spec.

I see two options to go forward:

  1. Browsers/the standard keeps the current behavior and allow multiple submits. Developers must opt-in to prevent multiple submissions using a preventmultiplesubmits attribute.
  2. Browsers/the standard adjust the current behavior to only submit forms once. Developers must opt-in to allow multiple submissions using a allowmultiplesubmits attribute.

Initial response on the issue was very low, and it looks like this isn’t that big of a priority.

Back then I was more in favor of the second solution, but now I’m quite undecided as changing the default behavior β€” which has been around for ages β€” is quite tricky.

~

Another way that this issue could be fixed is at the browser level: if they were to treat double clicks on form submit buttons as single clicks, then the double click problem β€” but not the double submit problem β€” could also be taken care of.

To then attach styles to forms being submitted a CSS Pseudo Class :submitting would come in handy. Come to think of it, this Pseudo Class would be a quite nice addition to CSS in itself, no matter whether this double click issue gets solved or not.

~

Winging back to the addition to the HTML spec I suggested: If you do think it could be something the HTML spec could benefit from, feel free to add a thumbs up to the issue to indicate that you want this, or add an extra comment to it if you have more input on the subject.

Did this help you out? Like what you see?
Thank me with a coffee.

I don't do this for profit but a small one-time donation would surely put a smile on my face. Thanks!

β˜•οΈ Buy me a Coffee (€3)

To stay in the loop you can follow @bramus or follow @bramusblog on Twitter.

Show the routing tables on Mac / Linux

In a project we at vBridge are working on, we rely on a Virtual Private Network to link our connected devices, certain servers, and our webapp together. I had an issue where a specific server in the 10.55/24 range was nog being reachable.

While debugging the issue β€” going deeper into the rabbit hole called the 5 Whys β€” I eventually needed to verify if the proper routing tables from the VPN connection had been set up or not. To do so, I used netstat:

$ netstat -nr
Routing tables

Internet:
Destination        Gateway            Flags        Netif Expire
default            192.168.83.1       UGSc           en0       
default            link#19            UCSI        utun10    
10.77/16           10.77.0.5          UGSc        utun10       
10.77.0.5          10.77.0.5          UH          utun10       
127                127.0.0.1          UCS            lo0       
127.0.0.1          127.0.0.1          UH             lo0       
169.254            link#5             UCS            en0      !
192.168.83         link#5             UCS            en0      !
192.168.83.1/32    link#5             UCS            en0      !
…
255.255.255.255    ff:ff:ff:ff:ff:ff  UHLWbI         en0      !
255.255.255.255/32 link#19            UCSI        utun10      

…

As you can see, the 10.55/24 route indeed wasn’t registered indeed, explaining why the host wasn’t reachable.

Did this help you out? Like what you see?
Thank me with a coffee.

I don't do this for profit but a small one-time donation would surely put a smile on my face. Thanks!

β˜•οΈ Buy me a Coffee (€3)

To stay in the loop you can follow @bramus or follow @bramusblog on Twitter.

Going Serverless with Google Cloud Run (JSConf.be)

Back in June I was invited to speak at JSConf.be. This year’s edition focused on DevSecOps and Security. My talk β€œGoing Serverless with Google Cloud Run” β€” which I have brought forward before at Full Stack Ghent and PHP-WVL β€” was a perfect match for it.

Cloud Run is a fully managed compute platform by Google that automatically scales stateless containers. By abstracting away all infrastructure management, us developers can focus on what matters most: building great applications.

In this talk I’ll show you not only how to deploy PHP/Node applications onto Cloud Run, but also how to create a build pipeline using either Google Cloud Build or Github Actions.

In mid August the video got released, which I’ve embedded below:

The slides are up on slidr.io, and also embedded below:

Thanks to the organisers for having me, and thanks to the attendees for coming to see me. I hope you all had fun attending this talk. I know I had making it (and whilst bringing it forward) πŸ™‚

πŸ’β€β™‚οΈ If you are a conference or meetup organiser, don’t hesitate to contact me to come speak at your event.

ESNext: Declarations in Conditionals (Stage-1)

An ECMAScript Language Feature that I’m looking forward to is Declarations in Conditionals. It adds the capability to declare variables inside conditional statements. Let’s take a look …

~

Setting the Scene

Say you want to iterate over an array which might be tucked away inside an object. To do so, you’d most likely first check for that variable’s existence using a simple if

if (user.meta.interests) {
    for (let interest of user.meta.interests) {
        // …
    }
}

As we’re now typing user.meta.interests twice in our code you might declare an intermediary variable, and use that further down in your code:

let interests = user.meta.interests;
if (interests) {
    for (let interest of interests) {
        // …
    }
}

But now you have a nasty side-effect: you’re loitering the scope with this extra interests variable. This is quite useless, as you’re only using it inside the if statement.

πŸ’β€β™‚οΈ To make it totally failsafe, you might also want to add some Optional Chaining in there and check if interests is iterable, but that’s beyond the scope of this post.

~

Hello “Declarations in Conditionals”

With the “Declarations in Conditionals” proposal we can keep this extra interests variable, yet without loitering the outer scope. We do this by declaring the variable directly inside the conditional (e.g. if statement), like so:

if (let interests = user.meta.interests) {
    for (let interest of interests) {
        // …
    }
}

⚠️ Note that this is an assignment right there, not an equality check!

With that one line the interests variable:

  1. will be declared
  2. will have a value assigned to it
  3. will be checked for being falsy or not

Scope-wise the interests variable will have its scope limited local to the conditional (e.g. only visible inside the if).

The proposal limits declarations to use either const or let; declarations in conditionals with var are currently not allowed.

πŸ’‘ You might not 100% realize it, but you’re already doing this kind of thing when using a for loop. Inside its initialization part (e.g. its first part) you are also declaring variables.

for (let i = 0; i < 5; i++) {
  console.log(i);
}

☝️ Did you know that you can declare more than one variable there, separated by a comma? In the example below I declare an extra variable len right after declaring i:

const cars = ['Ford', 'Mercedes', 'BMW'];

for (let i = 0, len = cars.length; i < len; i++) {
  console.log(cars[i]);
}

πŸ€” What about the scope of len you might ask? Variables declared using let are scoped to the statement (e.g. local to the loop). Variables declared using var get hoisted. And oh, it’s not allowed to declare variables using const here.

~

So, when can we use this?

The proposal is currently Stage-1, so it still has a long way to go before it can become an official part of the standard β€” if it ever will be.

πŸ’β€β™‚οΈ Stage-1?

The Technical Committee which is concerned with the standardization of ECMAScript (e.g. TC39) has a 5 stage process in place, ranging from stage-0 to stage-4, by which it develops a new language feature.

Stage-1 is the Proposal stage. It signals that the committee is showing interest in the proposal and that it is seeking further investigation on how to tackle it. At this stage the proposal is subject to heavy changes. It is only when a proposal reaches Stage 4 that it is ready to become part of the ECMAScript Specification.

As it’s still Stage-1, it can change quite a lot before it to be finished. And I’m quite sure it will change, as right now:

  • It only allows declaring one variable.
  • It only checks for values being falsy or not.

These two feature are currently listed under “Future Work” of the proposal, and it looks like this:

if (let x = 1, y = 2; x || y) {
    // …
}

Must say I’m pretty curious how this one will evolve, as I’m already quite excited about it: it’s one of those small additions that will have a pretty big impact on how I write my code.

If you have an addition to this proposal, feel free to join the discussion in the Declarations in Conditionals GitHub repository.

~

Did this help you out? Like what you see?
Thank me with a coffee.

I don't do this for profit but a small one-time donation would surely put a smile on my face. Thanks!

β˜•οΈ Buy me a Coffee (€3)

To stay in the loop you can follow @bramus or follow @bramusblog on Twitter.

Simple Image Gallery with display: grid; and object-fit: cover;

On the Full Stack Belgium Slack channel, user @Brammm recently asked how to create a simple image gallery.

Anyone have a favorite way of making an image grid with CSS? I’d like one of those β€œfancy” ones where no matter the aspect ratio of the image, they all have the same gap between them.

While some suggested existing solutions such as React Photo Gallery or photo-stream, I focussed on only the layout-question and suggested a DIY solution using display: grid; and object-fit: cover;.

Using only those those two props it’s really easy to create a grid based gallery.

  • Using display: grid; you create a grid layout. I decided to create a grid of square cells/tiles.
  • As I stretch out each image inside a tile, the images can get deformed. Using object-fit: cover; this can be prevented. Note that you will visually lose some data, as the edges will get clipped, but that was not a concern to @Brammm.

~

Proof Of Concept

Using photos from the birth of my son Noah I knocked up a small small proof of concept in about 10 minutes. Tweaking it a bit more – by adding some CSS Variables into the mix – I eventually landed on this:

See the Pen
Simple Gallery (display: grid; + object-fit: cover;) V1
by Bramus (@bramus)
on CodePen.

πŸ’β€β™‚οΈ To create a consistent gap between all items I used the CSS gap property, which replaces grid-gap.

~

Making it feel more dynamic

To make things visually more interesting, and also since some photos are portrait and some landscape, the size of the tiles must differ a bit. With CSS Grid in place it’s really easy to stretch out cells so that they span more than one column or row:

ul.gallery > li.wide {
    grid-column: span 2;
}

ul.gallery > li.wider {
    grid-column: span 3;
}

ul.gallery > li.high {
    grid-row: span 2;
    height: auto; /* to undo the height */
}

With these in place, my gallery started to look like this:

There, looks good, right? πŸ™‚

πŸ˜‹ I know, I cheated a bit as I added the .wide/.high classes onto carefully selected tiles, leaving no gaps in my grid. To workaround potential gaps, one can always use grid-auto-flow: dense;, or use the (still under development) Grid Masonry from the Grid Level 2 spec. Note that in those cases the order of the images will differ from their source order.

~

Going further: Zoom / Lightbox functionality

Where I had originally stopped working on the gallery after that, today I wondered if I could adjust it a bit further and give it the ability to show the images at full screen, in some sort of Lightbox. Still using only CSS it’s possible to show an overlay while pressing+holding a tile.

  • Using the :active pseudo-selector you can know which element is currently being pressed
  • Using position: fixed; you can put content on top of the entire viewport

Combining the two as follows …

ul.gallery > li:active > img {
    position: fixed; /* Position the image on top of the viewport contents */
    z-index: 11;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    object-fit: contain; /* Make sure the image isn't distorted */
    padding: 1vw; /* Add some padding to make it breathe a bit */
    background: rgba(0, 0, 0, 0.8); /* Dim the background */
}

… will get you this:

See the Pen
Simple Gallery (display: grid; + object-fit: cover;) V3 (WIP)
by Bramus (@bramus)
on CodePen.

While the version above does work (on Desktop), there’s a huge problem with it: it’s not (keyboard) accessible at all. In order to give the browser – and therefore user – hints about stuff being clickable we could add a truckload of ARIA attributes or simply use … links. An extra benefit of this approach is that we then start using CSS :target, and eventually create Pure CSS Lightbox.

See the Pen
Simple Gallery (display: grid; + object-fit: cover;) V4b
by Bramus (@bramus)
on CodePen.

It’s not entirely perfect though, when using only the keyboard to navigate, you have to do some trickery to close the Lightbox: after zooming in on an image you’ll have to hit TAB to focus the close link and then hit ENTER to activate it β€” If only autofocus where available on non-inputs …

As an extra I also added some JS to make the ←, β†’, and ESC keys work, so that it behaves like a β€œreal” Lightbox. There’s plenty more things I could add to it, but that’s something way beyond the original goal of this post so I left it at that.

~

Did this help you out? Like what you see?
Thank me with a coffee.

I don't do this for profit but a small one-time donation would surely put a smile on my face. Thanks!

β˜•οΈ Buy me a Coffee (€3)

To stay in the loop you can follow @bramus or follow @bramusblog on Twitter.

InfluxDB Flux: type conflict: int != float

In a Flux query I was writing I wanted to negate the fetched values using a map function to multiply each value by -1:

map(fn: (r) => ({ _value: r._value * -1 }))

To my surprise this yielded an error:

type conflict: int != float

Took me a few Google Search Coupons to realize the error got trigger not by the type of the _value, but by the -1 in there. The solution therefore is really simple: don’t multiply by an integer (e.g. -1) but multiply by a float (e.g. -1.0); like so:

map(fn: (r) => ({ _value: r._value * -1.0 }))

πŸ˜…

Did this help you out? Like what you see?
Thank me with a coffee.

I don't do this for profit but a small one-time donation would surely put a smile on my face. Thanks!

β˜•οΈ Buy me a Coffee (€3)

To stay in the loop you can follow @bramus or follow @bramusblog on Twitter.