Tag: Features

Interaction Media Features and Their Potential (for Incorrect Assumptions)

This is an updated and greatly expanded version of the article originally published on dev.opera back in 2015. That article referenced the Editor’s Draft, 24 March 2015 of the specification Media Queries Level 4, and contained a fairly big misunderstanding about how any-hover:none would end up being evaluated by browsers in practice.

The spec has since been updated (including clarifications and examples that I submitted following the publication of the original article), so this updated version removes the incorrect information of the original and brings the explanations in line with the most recent working draft. It also covers additional aspects relating to JavaScript touch/input detection.

The Media Queries Level 4 Interaction Media Featurespointer, hover, any-pointer and any-hover — are meant to allow sites to implement different styles and functionality (either CSS-specific interactivity like :hover, or JavaScript behaviors, when queried using window.matchMedia), depending on the particular characteristics of a user’s input devices.

Although the specification is still in working draft, interaction media features are generally well supported, though, to date, there are still some issues and inconsistencies in the various browser implementations — see the recent pointer/hover/any-pointer/any-hover test results, with references to relevant browser bugs.

Common use cases cited for interaction media features are often “make controls bigger/smaller depending on whether the users has a touchscreen device or is using a mouse/stylus” and “only use a CSS dropdown menu if the user has an input that allows hover-based interactions.”

@media (pointer: fine) {   /* using a mouse or stylus - ok to use small buttons/controls */ } @media (pointer: coarse) {   /* using touch - make buttons and other "touch targets" bigger */ } @media (hover: hover) {   /* ok to use :hover-based menus */ } @media (hover: none) {   /* don't use :hover-based menus */ }

There are also examples of developers using these new interaction media features as a way of achieving standards-based “touch detection,” often just for listening to touch events when the device is identified as having a coarse pointer.

if (window.matchMedia && window.matchMedia("(pointer:coarse)").matches) {   /* if the pointer is coarse, listen to touch events */   target.addEventListener("touchstart", ...);   // ... } else {   /* otherwise, listen to mouse and keyboard events */   // ... }

However, these approaches are slightly naive, and stem from a misunderstanding of what these interaction media queries are designed to tell us.

What’s the primary input?

One of the limitations of pointer and hover is that, by design, they only expose the characteristics of what a browser deems to be the primary pointer input. What the browser thinks, and what a user is actually using as their primary input, may differ — particularly now that the lines between devices, and the types of inputs they support, is becoming more and more blurry.

Microsoft Surface with a keyboard, trackpad, external bluetooth mouse, touchscreen.
Which one’s the “primary” input? the answer may depend on the activity.

Right out of the gate, it’s worth noting that interaction media features only cover pointer inputs (mouse, stylus, touchscreen). They don’t provide any way of detecting if a user’s primary input is a keyboard or keyboard-like interface, such as a switch control. In theory, for a keyboard user, a browser could report pointer: none, signaling that the user’s primary input is not a pointer at all. However, in practice, no browser offers a way for users to specify that they are in fact keyboard users. So keep in mind that, regardless of what the interaction media feature queries may return, it’s worth making sure that your site or app also works for keyboard users.

Traditionally, we could say that a phone or tablet’s primary input is the touchscreen. However, even on these devices, a user may have an additional input, like a paired bluetooth mouse (a feature that has been available for years on Android, is now supported in iPadOS, and is sure land in iOS), that they are using as their primary input.

An Android phone with a paired bluetooth keyboard and mouse, with the screen showing an actual mouse pointer and right-click context menu in Chrome

An iPad with a paired bluetooth keyboard, mouse, and Apple Pencil, with the screen showing the mouse “dot” and right-click context menu in Safari

In this case, while the device nominally has pointer: coarse and hover: none, users may actually be using a fine pointer device that is capable of hovers. Similarly, if a user has a stylus (like the Apple Pencil), their primary input may still be reported as the touchscreen, but rather than pointer: coarse, they now have an input that can provide fine pointer accuracy.

In these particular scenarios, if all the site is doing is making buttons and controls bigger and avoiding hover-based interactions, that would not be a major problem for the user: despite using a fine and hover-capable mouse, or a fine but still not hover-capable stylus, they will get styling and functionality aimed at the coarse, non-hover-capable touchscreen.

If the site is using the cues from pointer: coarse for more drastic changes, such as then only listening to touch events, then that will be problematic for users — see the section about incorrect assumptions that can completely break the experience.

However, consider the opposite: a “regular” desktop or laptop with a touchscreen, like Microsoft’s Surface. In most cases, the primary input will be the trackpad/mouse — with pointer:fine and hover:hover — but the user may well be using the touchscreen, which has coarse pointer accuracy and does not have hover capability. If styling and functionality are then tailored specifically to rely on the characteristics of the trackpad/mouse, the user may find it problematic or impossible to use the coarse, non-hover-capable touchscreen.

Feature Touchscreen Touchscreen + Mouse Desktop/Laptop Desktop/Laptop + Touchscreen
pointer:coarse true true false false
pointer:fine false false true true
hover:none true true false false
hover:hover false false true true

For a similar take on this problem, see ”The Good & Bad of Level 4 Media Queries” by Stu Cox. While it refers to an even earlier iteration of the spec that only contained pointer and hover and a requirement for these features to report the least capable, rather than the primary, input device.

The problem with the original pointer and hover on their own is that they don’t account for multi-input scenarios, and they rely on the browser to be able to correctly pick a single primary input. That’s where any-pointer and any-hover come into play.

Testing the capabilities of all inputs

Instead of focusing purely on the primary pointer input, any-pointer and any-hover report the combined capabilities of all available pointer inputs.

In order to support multi-input scenarios, where different (pointer-based) inputs may have different characteristics, more than one of the values for any-pointer (and, theoretically, any-hover, but this aspect is useless as we’ll see later) can match, if different input devices have different characteristics< (compared to pointer and hover, which only ever refer to the capabilities of the primary pointer input). In current implementations, these media features generally evaluate as follows:

Feature Touchscreen Touchscreen + Mouse Desktop/Laptop Desktop/Laptop + Touchscreen
any-pointer:coarse true true false true
any-pointer:fine false true true true
any-hover:none false false false false
any-hover:hover false true true true
Comparison of Firefox on Android’s media query results with just the touchscreen, and when adding a bluetooth mouse. Note how pointer and hover remain the same, but any-pointer and any-hover change to cover the new hover-capable fine input.

Going back to the original use cases for the interaction media features, instead of basing our decision to provide larger or smaller inputs or to enable hover-based functionality only on the characteristics of the primary pointer input, we can make that decision based on the characteristics of any available pointer inputs. Roughly translated, instead of saying “make all controls bigger if the primary input has pointer: coarse” or “only offer a CSS menu if the primary input has hover: hover,” we can build media queries that equate to saying, “if any of the pointer inputs is coarse, make the controls bigger” and “only offer a hover-based menu if at least one of the pointer inputs available to the user is hover-capable.”

@media (any-pointer: coarse) {   /* at least one of the pointer inputs     is coarse, best to make buttons and      other "touch targets" bigger (using      the query "defensively" to target      the least capable input) */ } @media (any-hover: hover) {   /* at least one of the inputs is       hover-capable, so it's at least       possible for users to trigger      hover-based menus */ }

Due to the way that any-pointer and any-hover are currently defined (as “the union of capabilities of all pointing devices available to the user”), any-pointer: none will only ever evaluate to true if there are no pointer inputs available, and, more crucially, any-hover: none will only ever be true if none of the pointer inputs present are hover-capable. Particularly for the latter, it’s therefore not possible to use the any-hover: none query to determine if only one or more of the pointer inputs present is not hover-capable — we can only use this media feature query to determine whether or not all inputs are not hover-capable, which is something that can just as well be achieved by checking if any-hover: hover evaluates to false. This makes the any-hover: none query essentially redundant.

We could work around this by inferring that if any-pointer: coarse is true, it’s likely a touchscreen, and generally those inputs are not hover-capable, but conceptually, we’re making assumptions here, and the moment there’s a coarse pointer that is also hover-capable, that logic falls apart. (And for those doubting that we may ever see a touchscreen with hover, remember that some devices, like the Samsung Galaxy Note and Microsoft’s Surface, have a hover-capable stylus that is detected even when it’s not touching the digitizer/screen, so some form of “hovering touch” detection may not be out of the question in the future.)

Combining queries for more educated guesses

The information provided by any-pointer and any-hover can of course be combined with pointer and hover, as well as the browser’s determination of what the primary input is capable of, for some slightly more nuanced assessments.

@media (pointer: coarse) and (any-pointer: fine) {   /* the primary input is a touchscreen, but      there is also a fine input (a mouse or       perhaps stylus) present. Make the design      touch-first, mouse/stylus users can      still use this just fine (though it may       feel a big clunky for them?) */ } @media (pointer: fine) and (any-pointer: coarse) {   /* the primary input is a mouse/stylus,      but there is also a touchscreen       present. May be safest to make       controls big, just in case users do       actually use the touchscreen? */ } @media (hover: none) and (any-hover: hover) {   /* the primary input can't hover, but      the user has at least one other      input available that would let them      hover. Do you trust that the primary      input is in fact what the user is       more likely to use, and omit hover-      based interactions? Or treat hover       as as something optional — can be       used (e.g. to provide shortcuts) to       users that do use the mouse, but       don't rely on it? */ }

Dynamic changes

Per the specification, browsers should re-evaluate media queries in response to changes in the user environment. This means that pointer, hover, any-pointer, and any-hover interaction media features can change dynamically at any point. For instance, adding/removing a bluetooth mouse on a mobile/tablet device will trigger a change in any-pointer / any-hover. A more drastic example would be a Surface tablet, where adding/removing the device’s “type cover” (which includes a keyboard and trackpad) will result in changes to the primary input itself (going from pointer: fine / hover: hover when the cover is present, to pointer: coarse / hover: none when the Surface is in “tablet mode”).

Screenshots of Firefox on a Surface tablet. With the cover attached, pointer:finehover:hoverany-pointer:coarseany-pointer:fine, and any-hover:hover are true; once the cover is removed (and Windows asks if the user wants to switch to “tablet mode”), touch becomes the primary input with pointer:coarse and hover:none, and only any-pointer:coarse and any-hover:none are true.

If you’re modifying your site’s layout/functionality based on these media features, be aware that the site may suddenly change “under the user’s feet” whenever the inputs change — not just when the page/site is first loaded.

Media queries may not be enough — roll on scripting

The fundamental shortcoming of the interaction media features is that they won’t necessarily tell us anything about the input devices that are in use right now. For that, we may need to dig deeper into solutions, like What Input?, that keep track of the specific JavaScript events fired. But of course, those solutions can only give us information about the user’s input after they have already started interacting with the site — at which point it may be too late to make drastic changes to your layout or functionality.

Keep in mind that even these JavaScript-based approaches can just as easily lead to incorrect results. That’s especially true on mobile/tablet platforms, or in situations where assistive technologies are involved, where it is common to see “faked” events being generated. For instance, if we look over the series of events fired when activating a control on desktop using a keyboard and screen reader, we can see that fake mouse events are triggered. Assistive technologies do this because, historically, a lot of web content has been coded to work for mouse users, but not necessarily for keyboard users, making a simulation of those interactions necessary for some functionalities.

Similarly, when activating “Full Keyboard Support” in iOS’s Settings → Accessibility → Keyboard, it’s possible for users to navigate web content using an external bluetooth keyboard, just as they would on desktop. But if we look at the event sequence for mobile/tablet devices and paired keyboard/mouse, that situation produces pointer events, touch events, and fallback mouse events — the same sequence we’d get for a touchscreen interaction.

Showing iOS settings with Full Keyboard Access enabled on the left and an iPhone browser window open to the right with the What Input tool.
When enabled, iOS’s “Full Keyboard Access” setting results in pointer, touch, and mouse events. What Input? identifies this as a touch input

In all these situations, scripts like What Input? will — understandably, through no fault of its own — misidentify the current input type.

Incorrect assumptions that can completely break the experience

Having outlined the complexity of multi-input devices, it should be clear by now that approaches that only listen to specific types of events, like the form of “touch detection” we see commonly in use, quickly fall apart.

if (window.matchMedia && window.matchMedia("(pointer: coarse)").matches) {   /* if the pointer is coarse, listen to touch events */   target.addEventListener("touchstart", ...);   // ... } else {   /* otherwise, listen to mouse and keyboard events */   target.addEventListener("click", ...);   // ... }

In the case of a “touch” device with additional inputs — such as a mobile or tablet with an external mouse — this code will essentially prevent the user from being able to use anything other than their touchscreen. And on devices that are primarily mouse-driven but do have a secondary touchscreen interface — like a Microsoft Surface — the user will be unable to use their touchscreen.

Instead of thinking about this as “touch or mouse/keyboard,” realize that it’s often a case of “touch and mouse/keyboard.” If we only want to register touch events when there’s an actual touchscreen device for performance reasons, we can try detecting any-pointer: coarse. But we should also keep other regular event listeners for mouse and keyboard.

/* always, as a matter of course, listen to mouse and keyboard events */ target.addEventListener("click", ...);  // ...  if (window.matchMedia && window.matchMedia("(any-pointer: coarse)").matches) {   /* if there's a coarse pointer, *also* listen to touch events */   target.addEventListener("touchstart", ...);   // ... }

Alternatively, we could avoid this entire conundrum about different types of events by using pointer events, which cover all types of pointer inputs in a single, unified event model, and are fairly well supported.

Give users an explicit choice

One potential solution for neatly circumventing our inability to make absolute determinations about which type of input the users are using may be to use the information provided by media queries and tools like What Input?, not to immediately switch between different layouts/functionalities — or worse, to only listen to particular types of events, and potentially locking out any additional input types — but to use them only as signals for when to provide users with an explicit way to switch modes.

For instance, see the way Microsoft Office lets you change between “Touch” and “Mouse” mode. On touch devices, this option is shown by default in the application’s toolbar, while on non-touch devices, it’s initially hidden (though it can be enabled, regardless of whether or not a touchscreen is present).

Screenshot of Microsoft Office's 'Touch/Mouse mode' dropdown, and a comparison of (part of) the toolbar as it's presented in each mode

A site or web application could take the same approach, and even set the default based on what the primary input is — but still allow users to explicitly change modes. And, using an approach similar to What Input?, the site could detect the first appearance of a touch-based input, and alert/prompt the user if they want to switch to a touch-friendly mode.

Potential for incorrect assumptions — query responsibly

Using Media Queries Level 4 Interaction Media Features and adapting our sites based on the characteristics of the available primary or additional pointer input is a great idea — but beware false assumptions about what these media features actually say. As with similar feature detection methods, developers need to be aware of what exactly they’re trying to detect, the limitations of that particular detection, and most importantly, consider why they are doing it — in a similar way to the problem I outlined in my article on detecting touch.

pointer and hover tell us about the capabilities of whatever the browser determines to be the primary device input. any-pointer and any-hover tell you about the capabilities of all connected inputs, and combined with information about the primary pointer input, they allow us to make educated guesses about a user’s particular device/scenario. We can use these features to inform our layout, or the type of interaction/functionality we want to offer; but don’t discount the possibility that those assumptions may be incorrect. The media queries themselves are not necessarily flawed (though the fact that most browsers seem to still have quirks and bugs adds to the potential problems). It just depends on how they’re used.

With that, I want to conclude by offering suggestions to “defend” yourself from the pitfalls of input detections.

Don’t

Assume a single input type. It’s not “touch or mouse/keyboard” these days, but “touch and mouse/keyboard” — and the available input types may change at any moment, even after the initial page load.

Just go by pointer and hover. the “primary” pointer input is not necessarily the one that your users are using.

Rely on hover in general. Regardless of what hover or any-hover suggest, your users may have a pointer input that they’re currently using that is not hover-capable, and you can’t currently detect this unless it’s the primary input (since hover: none  is true if that particular input lacks hover, but any-hover: none will only ever be true if none of the inputs are hover-capable). And remember that hover-based interfaces generally don’t work for keyboard users.

Do

Make your interfaces “touch-friendly.” If you detect that there’s an any-pointer:coarse input (most likely a touchscreen), consider providing large touch targets and sufficient spacing between them. Even if the user is using another input, like a mouse, at that moment, no harm done.

Give users a choice. If all else fails, consider giving the user an option/toggle to switch between touch or mouse layouts. Feel free to use any information you can glean from the media queries (such as any-pointer: coarse being true) to make an educated guess about the toggle’s initial setting.

Remember about keyboard users. Regardless of any pointer inputs that the user may or may not be using, don’t forget about keyboard accessibility — it can’t be conclusively detected, so just make sure your stuff works for keyboard users as a matter of course.


The post Interaction Media Features and Their Potential (for Incorrect Assumptions) appeared first on CSS-Tricks.

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

CSS-Tricks

, , , , , ,

Beyond Media Queries: Using Newer HTML & CSS Features for Responsive Designs

Beyond using media queries and modern CSS layouts, like flexbox and grid, to create responsive websites, there are certain overlooked things we can do well to make responsive sites. In this article, we’ll dig into a number tools (revolving around HTML and CSS) we have at the ready, from responsive images to relatively new CSS functions that work naturally whether we use media queries or not.

In fact, media queries become more of a complement when used with these features rather than the full approach. Let’s see how that works.

Truly responsive images

Remember when we could just chuck width: 100% on images and call it a day? That still works, of course, and does make images squishy, but there are a number of downsides that come with it, the most notable of which include:

  • The image might squish to the extent that it loses its focal point.
  • Smaller devices still wind up downloading the full size image.

When using images on the web, we have to make sure they’re optimized in terms of their resolution and size. The reason is to ensure that we have the right image resolution to fit the right device, so we don’t end up downloading really large and heavy images for smaller screens which could end up reducing the performance of a site. 

In simple terms, we’re making sure that larger, high-resolution images are sent to larger screens, while smaller, low-resolution variations are sent to smaller screens, improving both performance and user experience.

HTML offers the <picture> element that allows us specify the exact image resource that will be rendered based on the media query we add. As described earlier, instead of having one image (usually a large, high-resolution version) sent to all screen sizes and scaling it to the viewport width, we specify a set of images to serve in specific situations.

<picture>   <source media="(max-width:1000px)" srcset="picture-lg.png">   <source media="(max-width:600px)" srcset="picture-mid.png">   <source media="(max-width:400px)" srcset="picture-sm.png">   <img src="picture.png" alt="picture""> </picture>

In this example, picture.png is the full-size image. From there, we define the next-largest version of the image, picture-lg.png, and the size reduces in descending order until the smallest version, picture-sm.png. Note that we’re still using media queries in this approach, but it’s the <picture> element itself that is driving the responsive behavior rather than defining breakpoints in the CSS.

The media queries are added appropriately to scale with the sizes of the picture:

  • Viewports that are 1000px and above get picture.png.
  • Viewports that are between 601px and 999px get picture-lg.png.
  • Viewports that are between 401px and 600px get picture-sm.png.
  • Any thing smaller than 400px gets picture-sm.png.

Interestingly, we can also label each image by image density —  1x, 2x, 3x and so forth — after the URL. This works if we have made the different images in proportion to each other (which we did). This allows the browser to determine which version to download based on the screen’s pixel density in addition to the viewport size. But note how many images we wind up defining:

<picture>   <source media="(max-width:1000px)" srcset="picture-lg_1x.png 1x, picture-lg_2x.png 2x, picture-lg_3x.png 3x">   <source media="(max-width:600px)" srcset="picture-mid_1x.png 1x, picture-mid_2x.png 2x, picture-mid_3x.png 3x">   <source media="(max-width:400px)" srcset="picture-small_1x.png 1x, picture-small_2x.png 2x, picture-small_1x.png 3x">   <img src="picture.png" alt="picture""> </picture>

Let’s look specifically at the two tags nested inside the <picture> element: <source> and <img>.

The browser will look for the first <source> element where the media query matches the current viewport width, and then it will display the proper image (specified in the srcset attribute). The <img> element is required as the last child of the <picture> element, as a fallback option if none of the initial source tags matches.

We can also use image density to handle responsive images with just the <img> element using the srcset attribute:

<img  srcset="   flower4x.png 4x,   flower3x.png 3x,   flower2x.png 2x,   flower1x.png 1x  "  src="flower-fallback.jpg" >

Another thing we can do is write media queries in the CSS based on the screen resolution (usually measured in dots per inch, or dpi) of the device itself and not just the device viewport. What this means is that instead of:

@media only screen and (max-width: 600px) {   /* Style stuff */ }

We now have:

@media only screen and (min-resolution: 192dpi) {   /* Style stuff */ }

This approach lets us dictate what image to render based the screen resolution of the device itself, which could be helpful when dealing with high resolution images. Basically, that means we can display high quality pictures for screens that support higher resolutions and smaller versions at lower resolutions. It’s worth noting that, although mobile devices have small screens, they’re usually high resolution. That means it’s probably not the best idea rely on resolution alone when determining which image to render. It could result in serving large, high-resolution images to really small screens, which may not be the version we really want to display at such a small screen size.

body {   background-image : picture-md.png; /* the default image */ } 
 @media only screen and (min-resolution: 192dpi) {   body {     background-image : picture-lg.png; /* higher resolution */   } }

What <picture> gives us is basically the ability to art direct images. And, in keeping with this idea, we can leverage CSS features, like the object-fit property which, when used with object-position, allows us to crop images for better focal points while maintaining the image’s aspect ratio.

So, to change the focal point of an image:

@media only screen and (min-resolution: 192dpi) {   body {     background-image : picture-lg.png;     object-fit: cover;     object-position: 100% 150%; /* moves focus toward the middle-right */   } }

Setting minimum and maximum values in CSS

The min() function specifies the absolute smallest size that an element can shrink to. This function proves really useful in terms of helping text sizes to properly scale across different screen sizes, like never letting fluid type to drop below a legible font size:

html {   font-size: min(1rem, 22px); /* Stays between 16px and 22px */ }

min() accepts two values, and they can be relative, percentage, or fixed units. In this example, we’re telling the browser to never let an element with class .box go below 45% width or 600px, whichever is smallest based on the viewport width:

.box {   width : min(45%, 600px) }

If 45% computes to a value smaller than 600px, the browser uses 45% as the width. Conversely, if  45% computes to a value greater than 600px, then 600px will be used for the element’s width.

The same sort of thing goes for the max() function. It also accepts two values, but rather than specifying the smallest size for an element, we’re defining the largest it can get.

.box {   width : max(60%, 600px) }

If 60% computes to a value smaller than 600px, the browser uses 60% as the width. On the flip side, if 60% computes to a value greater than 600px, then 600px will be used as the element’s width.

And, hey, we can even set a minimum and maximum range instead using the minmax() function:

.box {   width : minmax( 600px, 50% ); /* at least 600px, but never more than 50% */ }

Clamping values

Many of us have been clamoring for clamp() for some time now, and we actually have broad support across all modern browsers (sorry, Internet Explorer). clamp() is the combination of the min() and max() functions, accepting three parameters:

  1. the minimum value,
  2. the preferred value, and
  3. the maximum value

For example:

.box {   font-size : clamp(1rem, 40px, 4rem) }

The browser will set the font at 1rem until the computed value of 1rem is larger than 40px. And when the computed value is above 40px? Yep, the browser will stop increasing the size after it hits 4rem. You can see how clamp() can be used to make elements fluid without reaching for media queries.

Working with responsive units

Have you ever built a page with a large heading or sub-heading and admired how great it looked on a desktop screen, only to check it on a mobile device and find out that’s it’s too large? I have definitely been in this situation and in this section I’ll be explaining how to handle such problems.

In CSS, you can determine sizes or lengths of elements using various units of measurements, and the most used units of measurements includes: px, em, rem, %, vw, and vh. Although, there are several more units that aren’t used as frequently. What’s of interest to us is that px can be considered an absolute unit, while the rest are considered relative units.

Absolute units

A pixel (px) is considered an absolute unit mainly because it’s fixed and does not change based on the measurement of any other element. It can be considered as the base, or root, unit that some other relative units use. Trying to use pixels for responsive behavior can bump into issues because it’s fixed, but they’re great if you have elements that should not be resized at all.

Relative units

Relative units, like %, em, and rem, are better suited to responsive design mainly because of their ability to scale across different screen sizes.

vw: Relative to the viewport’s width
vh: Relative to the viewport’s height
rem: Relative to the root (<html>) element (default font-size is usually 16px )
em: Relative to the parent element
%: Relative to the parent element

Again, the default font size for most browsers is 16px and and that’s what rem units use to generate their computed values. So, if a user adjusts the font size on the browser, everything on the page scales properly depending on the root size. For example, when dealing a root set at 16px, the number you specify will multiply that number times the default size. For example:

.8rem = 12.8px (.8 * 16) 1rem = 16px (1 * 16) 2rem = 32px (2 * 16)

What if either you or the user changes the default size? As we said already, these are relative units and the final size values will be based off of the new base size. This proves useful within media queries, where you just change the font size and the entire page scales up or down accordingly.

For example, if you changed the font-size to 10px within the CSS, then the calculated sizes would end up being:

html {   font-size : 10px; }
1rem = 10px (1 * 10) 2rem = 20px (2 * 10) .5rem = 5px (.5 * 10)

Note: This also applies to percentage %. For instance:

100% = 16px; 200% = 32px;  50% = 8px;

And what’s the difference between rem and em units? It’s what the unit uses as its base element. rem calculates values using the font size of the root (<html>) element, whereas an element declaring em values references the font size of the parent element that contains it. If the size of specified parent element is different from the root element (e.g. the parent elements is 18px but the root element is 16px) then em and rem will resolve to different computed values. This gives us more fine-grained control of how our elements respond in different responsive contexts.

vh is an acronym for viewport height, or the viewable screen’s height. 100vh represent 100% of the viewport’s height (depending on the device). In the same vein, vw stands for viewport width, meaning the viewable screen’s width of the device, and 100vw literally represents 100% of the viewport’s width.

Moving beyond media queries

See that? We just looked at a number of really powerful and relatively new HTML and CSS features that give us additional (and possible more effective) ways to build for responsiveness. It’s not that these new-fangled techniques replace what we’ve been doing all along. They are merely more tools in our developer tool belt that give us greater control to determine how elements behave in different contexts. Whether it’s working with font sizes, resolutions, widths, focal points, or any number of things, we have more fine-grain control of the user experience than ever before.

So, next time you find yourself working on a project where you wish you had more control over the exact look and feel of the design on specific devices, check out what native HTML and CSS can do to help — it’s incredible how far things have come along.


The post Beyond Media Queries: Using Newer HTML & CSS Features for Responsive Designs appeared first on CSS-Tricks.

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

CSS-Tricks

, , , , , , , ,
[Top]

When Sass and New CSS Features Collide

Recently, CSS has added a lot of new cool features such as custom properties and new functions. While these things can make our lives a lot easier, they can also end up interacting with preprocessors, like Sass, in funny ways.

So this is going to be a post about the issues I’ve encountered, how I go around them, and why I still find Sass necessary these days.

The errors

If you’ve played with the new min() and max() functions, you may have ran into an error message like this when working with different units: “Incompatible units: vh and em.”

Screenshot. Shows the `Incompatible units: 'em' and 'vh'` error when trying to set `width: min(20em, 50vh)`.
An error when working with different types of units in the min()/ max() function

This is because Sass has its ownmin() function, and ignores the CSS min() function. Plus, Sass cannot perform any sort of computation using two values with units that don’t have a fixed relation between them.

For example, cm and in units have a fixed relation between them, so Sass can figure out what’s the result of min(20in, 50cm) and doesn’t throw an error when we try to use it in our code.

The same things goes for other units. Angular units, for example, all have a fixed relation between them: 1turn, 1rad or 1grad always compute to the same deg values. Same goes for 1s which is always 1000ms, 1kHz which is always 1000Hz, 1dppx which is always 96dpi, and 1in which is always 96px. This is why Sass can convert between them and mix them in computations and inside functions such as its own min() function.

But things break when these units don’t have a fixed relation between them (like the earlier case with em and vh units).

And it’s not just different units. Trying to use calc() inside min() also results in an error. If I try something like calc(20em + 7px), the error I get is, “calc(20em + 7px) is not a number for min.”

Screenshot. Shows the `'calc(20em + 7px)' is not a number for 'min'` error when trying to set `width: min(calc(20em + 7px), 50vh)`.
An error when using different unit values with calc() nested in the min()function

Another problem arises when we want to use a CSS variable or the result of a mathematical CSS function (such as calc(), min() or max()) in a CSS filter like invert().

In this case, we get told that “$ color: 'var(--p, 0.85) is not a color for invert.”

Screenshot. Shows the `$ color: 'var(--p, 0.85)' is not a color for 'invert'` error when trying to set `filter: invert(var(--p, .85))`.
var() in filter: invert() error

The same thing happens for grayscale(): “$ color: ‘calc(.2 + var(--d, .3))‘ is not a color for grayscale.”

Screenshot. Shows the `$ color: 'calc(.2 + var(--d, .3))' is not a color for 'grayscale'` error when trying to set `filter: grayscale(calc(.2 + var(--d, .3)))`.
calc() in filter: grayscale() error

opacity() causes the same issue: “$ color: ‘var(--p, 0.8)‘ is not a color for opacity.”

Screenshot. Shows the `$ color: 'var(--p, 0.8)' is not a color for 'opacity'` error when trying to set `filter: opacity(var(--p, 0.8))`.
var() in filter: opacity() error

However, other filter functions — including sepia(), blur(), drop-shadow(), brightness(), contrast() and hue-rotate()— all work just fine with CSS variables!

Turns out that what’s happening is similar to the min() and max() problem. Sass doesn’t have built-in sepia(), blur(), drop-shadow(), brightness(), contrast(), hue-rotate() functions, but it does have its own grayscale(), invert() and opacity() functions, and their first argument is a $ color value. Since it doesn’t find that argument, it throws an error.

For the same reason, we also run into trouble when trying to use a CSS variable that lists at least two hsl()or hsla() values.

Screenshot. Shows the `wrong number of arguments (2 for 3) for 'hsl'` error when trying to set `color: hsl(9, var(--sl, 95%, 65%))`.
var() in color: hsl() error.

On the flip side, color: hsl(9, var(--sl, 95%, 65%)) is perfectly valid CSS and works just fine without Sass.

The exact same thing happens with the rgb()and rgba() functions.

Screenshot. Shows the `$ color: 'var(--rgb, 128, 64, 64)' is not a color for 'rgba'` error when trying to set `color: rgba(var(--rgb, 128, 64, 64), .7)`.
var() in color: rgba() error.

Furthermore, if we import Compass and try to use a CSS variable inside a linear-gradient() or inside a radial-gradient(), we get another error, even though using variables inside conic-gradient() works just fine (that is, if the browser supports it).

Screenshot. Shows the At least two color stops are required for a linear-gradient error when trying to set background: linear-gradient(var(--c, pink), gold).
var() in background: linear-gradient() error.

This is because Compass comes with linear-gradient() and radial-gradient() functions, but has never added a conic-gradient() one.

The problems in all of these cases arise from Sass or Compass having identically-named functions and assuming those are what we intended to use in our code.

Drat!

The solution

The trick here is to remember that Sass is case-sensitive, but CSS isn’t.

That means we can write Min(20em, 50vh)and Sass won’t recognize it as its own min() function. No errors will be thrown and it’s still valid CSS that works as intended. Similarly, writing HSL()/ HSLA()/ RGB()/ RGBA() or Invert() allows us to avoid issues we looked at earlier.

As for gradients, I usually prefer linear-Gradient() and radial-Gradient() just because it’s closer to the SVG version, but using at least one capital letter in there works just fine.

But why?

Almost every time I tweet anything Sass-related, I get lectured on how it shouldn’t be used now that we have CSS variables. I thought I’d address that and explain why I disagree.

First, while I find CSS variables immensely useful and have used them for almost everything for the past three years, it’s good to keep in mind that they come with a performance cost and that tracing where something went wrong in a maze of calc() computations can be a pain with our current DevTools. I try not to overuse them to avoid getting into a territory where the downsides of using them outweigh the benefits.

Screenshot. Shows how `calc()` expressions are presented in DevTools.
Not exactly easy to figure out what’s the result of those calc() expressions.

In general, if it acts like a constant, doesn’t change element-to-element or state-to-state (in which case custom properties are definitely the way to go) or reduce the amount of compiled CSS (solving the repetition problem created by prefixes), then I’m going to use a Sass variable.

Secondly, variables have always been a pretty small portion of why I use Sass. When I started using Sass in late 2012, it was primarily for looping, a feature we still don’t have in CSS. While I’ve moved some of that looping to an HTML preprocessor (because it reduces the generated code and avoids having to modify both the HTML and the CSS later), I still use Sass loops in plenty of cases, like generating lists of values, stop lists inside gradient functions, lists of points inside a polygon function, lists of transforms, and so on.

Here’s an example. I used to generate n HTML items with a preprocessor. The choice of preprocessor matters less, but I’ll be using Pug here.

- let n = 12;  while n--   .item

Then I would set the $ n variable into the Sass (and it would have to be equal to that in the HTML) and loop up to it to generate the transforms that would position each item:

$ n: 12; $ ba: 360deg/$ n; $ d: 2em;  .item {   position: absolute;   top: 50%; left: 50%;   margin: -.5*$ d;   width: $ d; height: $ d;   /* prettifying styles */    @for $ i from 0 to $ n {     &:nth-child(#{$ i + 1}) {       transform: rotate($ i*$ ba) translate(2*$ d) rotate(-$ i*$ ba); 			       &::before { content: '#{$ i}' }     }   } }

However, this meant that I would have to change both the Pug and the Sass when changing the number of items, making the generated code very repetitive.

Screenshot. Shows the generated CSS, really verbose, almost completely identical transform declaration repeated for each item.
CSS generated by the above code

I have since moved to making Pug generate the indices as custom properties and then use those in the transform declaration.

- let n = 12;  body(style=`--n: $ {n}`)   - for(let i = 0; i < n; i++)     .item(style=`--i: $ {i}`)
$ d: 2em;  .item {   position: absolute;   top: 50%;   left: 50%;   margin: -.5*$ d;   width: $ d;   height: $ d;   /* prettifying styles */   --az: calc(var(--i)*1turn/var(--n));   transform: rotate(var(--az)) translate(2*$ d) rotate(calc(-1*var(--az)));   counter-reset: i var(--i); 	   &::before { content: counter(i) } }

This significantly reduces the generated code.

Screenshot. Shows the generated CSS, much more compact, no having almost the exact same declaration set on every element separately.
CSS generated by the above code

However, looping in Sass is still necessary if I want to generate something like a rainbow.

@function get-rainbow($ n: 12, $ sat: 90%, $ lum: 65%) {   $ unit: 360/$ n;   $ s-list: (); 	   @for $ i from 0 through $ n {     $ s-list: $ s-list, hsl($ i*$ unit, $ sat, $ lum)   } 	   @return $ s-list }  html { background: linear-gradient(90deg, get-rainbow()) }

Sure, I could generate it as a list variable from Pug, but doing so doesn’t take advantage of the dynamic nature of CSS variables and it doesn’t reduce the amount of code that gets served to the browser, so there’s no benefit coming out of it.

Another big part of my Sass (and Compass) use is tied to built-in mathematical functions (such as trigonometric functions), which are part of the CSS spec now, but not yet implemented in any browser. Sass doesn’t come with these functions either, but Compass does and this is why I often need to use Compass.

And, sure, I could write my own such functions in Sass. I did resort to this in the beginning, before Compass supported inverse trigonometric functions. I really needed them, so I wrote my own based on the Taylor series. But Compass provides these sorts of functions nowadays and they are better and more performant than mine.

Mathematical functions are extremely important for me as I’m a technician, not an artist. The values in my CSS usually result from mathematical computations. They’re not magic numbers or something used purely for aesthetics. A example is generating lists of clip paths points that create regular or quasi-regular polygons. Think about the case where we want to create things like non-rectangular avatars or stickers.

Let’s consider a regular polygon with vertices on a circle with a radius 50% of the square element we start from. Dragging the slider in the following demo allows us to see where the points are placed for different numbers of vertices:

Putting it into Sass code, we have:

@mixin reg-poly($ n: 3) {   $ ba: 360deg/$ n; // base angle   $ p: (); // point coords list, initially empty 	   @for $ i from 0 to $ n {     $ ca: $ i*$ ba; // current angle     $ x: 50%*(1 + cos($ ca)); // x coord of current point     $ y: 50%*(1 + sin($ ca)); // y coord of current point     $ p: $ p, $ x $ y // add current point coords to point coords list   } 	   clip-path: polygon($ p) // set clip-path to list of points }

Note that here we’re also making use of looping and of things such as conditionals and modulo that are a real pain when using CSS without Sass.

A slightly more evolved version of this might involve rotating the polygon by adding the same offset angle ($ oa) to the angle of each vertex. This can be seen in the following demo. This example tosses in a star mixin that works in a similar manner, except we always have an even number of vertices and every odd-indexed vertex is situated on a circle of a smaller radius ($ f*50%, where $ f is sub-unitary):

We can also have chubby stars like this:

Or stickers with interesting border patterns. In this particular demo, each sticker is created with a single HTML element and the border pattern is created with clip-path, looping and mathematics in Sass. Quite a bit of it, in fact.

Another example are these card backgrounds where looping, the modulo operation and exponential functions work together to generate the dithering pixel background layers:

This demo just happens to rely heavily on CSS variables as well.

Then there’s using mixins to avoid writing the exact same declarations over and over when styling things like range inputs. Different browsers use different pseudo-elements to style the components of such a control, so for every component, we have to set the styles that control its look on multiple pseudos.

Sadly, as tempting as it may be to put this in our CSS:

input::-webkit-slider-runnable-track,  input::-moz-range-track,  input::-ms-track { /* common styles */ }

…we cannot do it because it doesn’t work! The entire rule set is dropped if even one of the selectors isn’t recognized. And since no browser recognises all three of the above, the styles don’t get applied in any browser.

We need to have something like this if we want our styles to be applied:

input::-webkit-slider-runnable-track { /* common styles */ } input::-moz-range-track { /* common styles */ } input::-ms-track { /* common styles */ }

But that can mean a lot of identical styles repeated three times. And if we want to change, say, the background of the track, we need to change it in the ::-webkit-slider-runnable-track styles, in the ::-moz-range-track styles and in the ::-ms-track styles.

The only sane solution we have is to use a mixin. The styles get repeated in the compiled code because they have to be repeated there, but we don’t have to write the same thing three times anymore.

@mixin track() { /* common styles */ }  input {   &::-webkit-slider-runnable-track { @include track }   &::-moz-range-track { @include track }   &::-ms-track { @include track } }

The bottom line is: yes, Sass is still very much necessary in 2020.

The post When Sass and New CSS Features Collide appeared first on CSS-Tricks.

CSS-Tricks

, ,
[Top]

Custom Styling Form Inputs With Modern CSS Features

It’s entirely possible to build custom checkboxes, radio buttons, and toggle switches these days, while staying semantic and accessible. We don’t even need a single line of JavaScript or extra HTML elements! It’s actually gotten easier lately than it has been in the past. Let’s take a look.

Here’s where we’ll end up:

Things sure have gotten easier than they were!

The reason is that we can finally style the ::before and ::after pseudo-elements on the <input> tag itself. This means we can keep and style an <input> and won’t need any extra elements. Before, we had to rely on the likes of an extra <div> or <span>, to pull off a custom design.

Let’s look at the HTML

Nothing special here. We can style our inputs with just this HTML:

<!-- Checkbox --> <input type="checkbox">  <!-- Radio --> <input type="radio">  <!-- Switch --> <input type="checkbox" class="switch">

That’s it for the HTML part, but of course it’s recommended to have name and id attributes, plus a matching <label> element:

<!-- Checkbox --> <input type="checkbox" name="c1" id="c1"> <label for="c1">Checkbox</label>  <!-- Radio --> <input type="radio" name="r1" id="r1"> <label for="r1">Radio</label>  <!-- Switch --> <input type="checkbox" class="switch" name="s1" id="s1"> <label for="s1">Switch</label>

Getting into the styling 

First of all, we check for the support of appearance: none;, including it’s prefixed companions. The appearance property is key because it is designed to remove a browser’s default styling from an element. If the property isn’t supported, the styles won’t apply and default input styles will be shown. That’s perfectly fine and a good example of progressive enhancement at play.

@supports(-webkit-appearance: none) or (-moz-appearance: none) {   input[type='checkbox'],   input[type='radio'] {     -webkit-appearance: none;     -moz-appearance: none;   } }

As it stands today, appearance  is a working draft, but here’s what support looks like:

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

Desktop

Chrome Firefox IE Edge Safari
82* 74* No 79* TP*

Mobile / Tablet

Android Chrome Android Firefox Android iOS Safari
79* 68* 76* 13.3*

Like links, we’ve gotta consider different interactive states with form elements. We’ll consider these when styling our elements:

  • :checked
  • :hover
  • :focus
  • :disabled

For example, here’s how we can style our toggle input, create the knob, and account for the :checked state:

/* The toggle container */ .switch {   width: 38px;   border-radius: 11px; }  /* The toggle knob */ .switch::after {   left: 2px;   top: 2px;   border-radius: 50%;   width: 15px;   height: 15px;   background: var(--ab, var(--border));   transform: translateX(var(--x, 0)); }  /* Change color and position when checked */ .switch:checked {   --ab: var(--active-inner);   --x: 17px; }  /* Drop the opacity of the toggle knob when the input is disabled */ .switch:disabled:not(:checked)::after {   opacity: .6; }

We are using the <input> element like a container. The knob inside of the input is created with the ::after pseudo-element. Again, no more need for extra markup!

If you crack open the styles in the demo, you’ll see that we’re defining some CSS custom properties because that’s become such a nice way to manage reusable values in a stylesheet:

@supports(-webkit-appearance: none) or (-moz-appearance: none) {   input[type='checkbox'],   input[type='radio'] {     --active: #275EFE;     --active-inner: #fff;     --focus: 2px rgba(39, 94, 254, .25);     --border: #BBC1E1;     --border-hover: #275EFE;     --background: #fff;     --disabled: #F6F8FF;     --disabled-inner: #E1E6F9;   } }

But there’s another reason we’re using custom properties — they work well for updating values based on the state of the element! We won’t go into full detail here, but here’s an example how we can use custom properties for different states.

/* Default */ input[type='checkbox'], input[type='radio'] {   --active: #275EFE;   --border: #BBC1E1;   border: 1px solid var(--bc, var(--border)); }  /* Override defaults */ input[type='checkbox']:checked, input[type='radio']:checked {   --b: var(--active);   --bc: var(--active); }    /* Apply another border color on hover if not checked & not disabled */ input[type='checkbox']:not(:checked):not(:disabled):hover, input[type='radio']:not(:checked):not(:disabled):hover {   --bc: var(--border-hover); }

For accessibility, we ought to add a custom focus style. We are removing the default outline because it can’t be rounded like the rest of the things we’re styling. But a border-radius along with a box-shadow can make for a rounded style that works just like an outline.

input[type='checkbox'], input[type='radio'] {   --focus: 2px rgba(39, 94, 254, .25);   outline: none;   transition: box-shadow .2s; }  input[type='checkbox']:focus, input[type='radio']:focus {   box-shadow: 0 0 0 var(--focus); }

It’s also possible to align and style the <label> element which directly follows the <input> element in the HTML:

<input type="checkbox" name="c1" id="c1"> <label for="c1">Checkbox</label>
input[type='checkbox'] + label, input[type='radio'] + label {   display: inline-block;   vertical-align: top;   /* Additional styling */ }  input[type='checkbox']:disabled + label, input[type='radio']:disabled + label {     cursor: not-allowed; }

Here’s that demo again:

Hopefully, you’re seeing how nice it is to create custom form styles these days. It requires less markup, thanks to pseudo-elements that are directly on form inputs. It requires less fancy style switching, thanks to custom properties. And it has pretty darn good browser support, thanks to @supports.

All in all, this is a much more pleasant developer experience than we’ve had to deal with in the past!

The post Custom Styling Form Inputs With Modern CSS Features appeared first on CSS-Tricks.

CSS-Tricks

, , , , ,
[Top]

My Favorite Netlify Features

👋 Hey folks! Silvestar pitched this post to us because he is genuinely enthusiastic about JAMstack and all of the opportunities it opens up for front-end development. We wanted to call that out because, although some of the points in here might come across as sponsored content and Netlify is indeed a CSS-Tricks sponsor, it’s completely independent of Netlify.

Being a JAMstack developer in 2019 makes me feel like I am living in a wonderland. All these modern frameworks, tools, and services make our lives as JAMstack developers quite enjoyable. In fact, Chris would say they give us superpowers.

Yet, there is one particular platform that stands out with its formidable products and features — Netlify. You’re probably pretty well familiar with Netlify if you read CSS-Tricks regularly. There’s a slew of articles on it. There are even two CSS-Tricks microsites that use it.

This article is more of a love letter to Netlify and all of the great things it does. I decided to sit down and list my most favorite things about it. So that’s what I’d like to share with you here. Hopefully, this gives you a good idea not only what Netlify is capable of doing, but helps you get the most out of it as well.

You can customize your site’s Netlify subdomain.

When creating a new project on Netlify, you start by either:

  • choosing a repository from a Git provider, or
  • uploading a folder.

The project should be ready in a matter of minutes, and you could start configuring it for your needs right away. Start by choosing the site name.

The site name determines the default URL for your site. Only alphanumeric characters and hyphens are allowed.

Netlify randomly creates a default name for a new project. If you don’t like the name, choose your own and make it one that would be much easier for you to remember.

The “Site information” section of the Netlify dashboard.

For example, my site name is silvestarcodes, and I could access my site by visiting silvestarcodes.netlify.com.

You can manage all your DNS on Netlify.

If you are setting up an actual site, you would want to add a custom domain. From the domain management panel, go to the custom domains section, click on the “Add custom domain” button, enter your domain, and click the “Verify” button.

Now you have two options:

  1. Point your DNS records to Netlify load balancer IP address
  2. Let Netlify handle your DNS records

For the first option, you could read the full instructions in the official documentation for custom domains.

For the second option, you should add or update the nameservers on your domain registrar. If you didn’t buy the domain already, you could register it right from the dashboard.

Netlify has a service for provisioning DNS records called Netlify DNS.

Once you have configured the custom domain, you could handle your DNS records from the Netlify dashboard.

The “DNS” section of the Netlify dashboard.

If you want to set up a dev subdomain for your dev branch to preview development changes for your site, you could do it automatically. From the Domain Management section in the Settings section of your site, select the dev branch and Netlify would add a new subdomain dev for you automagically. Now you could see the previews by visiting dev subdomain.

The “Subdomains” section of the Netlify dashboard.

You could configure a subdomain for a different website. To achieve this, create a new Netlify site, enter a new subdomain as a custom domain, and Netlify would automatically add the records for you.

As an icing on the DNS management cake, Netlify lets you create Let’s Encrypt certificates for your domain automatically… for free.

You can inject snippets into pages, which is sort of like a Tag Manager.

Snippet injection is another excellent feature. I am using it mostly for inserting analytics, but you could use it for adding meta tags for responsive behavior, favicon tags, or Webmention.io tags.

The “Snippet injection” section of the Netlify dashboard.

When inserting snippets, you could choose to append the code fragment at the end of the <head> block, or at the end of the <body> block.

Every deploy has its own URL forever.

Netlify creates a unique preview link for every successful build. That means you could easily compare revisions made to your site. For example, here is the link to my website from January this year, and here is the link from January last year. Notice the style and content changes.

In his talk, Phil Hawksworth calls this feature immutable, atomic deploys.

They are immutable deployments that live on forever.
— Phil Hawksworth

I found this feature useful when completing tasks and sending the preview links to the clients. If there is a person in charge of handling Git-related tasks, like publishing to production, these preview links could be convenient to understand what to expect during the merge. You could even set up the preview builds for every pull request.

Netlify allows for the cleanest and most responsible A/B testing you can do.

If you ever wanted to run A/B tests on your site, you would find that Netlify makes running A/B tests quite straightforward. Split testing on Netlify allows you to display different versions of your website from different Git branches without any hackery.

The “Split testing” section of the Netlify dashboard.

Start by adding and publishing a separate branch with desired changes. From “Split testing” panel, select which branches to test, set a split percentage, and start the test. You could even set a variable in analytics code to track which branch is currently displayed. You might need to active branch deploys if you didn’t do this already.

Netlify’s Split Testing lets you divide traffic to your site between different deploys, straight from our CDN network, without losing any download performance, and without installing any third party JavaScript library.
Netlify documentation

I have been using A/B testing on my site for a few different features so far:

  • Testing different versions of contact forms
  • Displaying different versions of banners
  • Tracking user behavior, like heatmaps

If you want to track split testing information, you could set up the process environment variable for this purpose. You could learn more about it in the official documentation. The best part? Most A/B testing services use client-side JavaScript to do it, which is unreliable and not great for performance. Doing it at the load balancer level like this is so much better.

There are lots of options for notifications, like email and Slack.

If you want to receive a notification when something happens with your Netlify project, you could choose from a wide variety of notification options. I prefer getting an email for every successful or failed build.

The “Notifications” section of the Netlify dashboard.

If you are using Gmail, you could notice “See the changes live” link for every successful build when hovering your message in Gmail inbox. That means you could open a preview link without opening the email. There are other links like “See full deploy logs” when your build have any issues or “Check usage details” when your plan is near its limits. How awesome is that?

Netlify email notifications include a preview link.

If you want to set up a hook for third-party services, all you need is a URL (JWS secret token is optional). Slack hooks are built-in with Netlify and could be set up within seconds if you know your Slack incoming webhook URL.

Conclusion

All of the features mentioned above are part of the free Netlify plan. I cannot even imagine the effort invested in providing a seamless experience as it is now. But Netlify doesn’t stop there. They are introducing more and more new and shiny features, like Netlify Dev CLI for local development and deploy cancelations. Netlify has established as an undoubtedly game-changing platform in modern web development of static websites, and it is a big part of the growth and popularity of static sites.

The post My Favorite Netlify Features appeared first on CSS-Tricks.

CSS-Tricks

, ,
[Top]

New ES2018 Features Every JavaScript Developer Should Know

The ninth edition of the ECMAScript standard, officially known as ECMAScript 2018 (or ES2018 for short), was released in June 2018. Starting with ES2016, new versions of ECMAScript specifications are released yearly rather than every several years and add fewer features than major editions used to. The newest edition of the standard continues the yearly release cycle by adding four new RegExp features, rest/spread properties, asynchronous iteration, and Promise.prototype.finally. Additionally, ES2018 drops the syntax restriction of escape sequences from tagged templates.

These new changes are explained in the subsections that follow.

The Rest/Spread Properties

One of the most interesting features added to ES2015 was the spread operator. This operator makes copying and merging arrays a lot simpler. Rather than calling the concat() or slice() method, you could use the ... operator:

const arr1 = [10, 20, 30];  // make a copy of arr1 const copy = [...arr1];  console.log(copy);    // → [10, 20, 30]  const arr2 = [40, 50];  // merge arr2 with arr1 const merge = [...arr1, ...arr2];  console.log(merge);    // → [10, 20, 30, 40, 50]

The spread operator also comes in handy in situations where an array must be passed in as separate arguments to a function. For example:

const arr = [10, 20, 30]  // equivalent to // console.log(Math.max(10, 20, 30)); console.log(Math.max(...arr));    // → 30

ES2018 further expands this syntax by adding spread properties to object literals. With the spread properties you can copy own enumerable properties of an object onto a new object. Consider the following example:

const obj1 = {   a: 10,   b: 20 };  const obj2 = {   ...obj1,   c: 30 };  console.log(obj2);    // → {a: 10, b: 20, c: 30}

In this code, the ... operator is used to retrieve the properties of obj1 and assign them to obj2. Prior to ES2018, attempting to do so would throw an error. If there are multiple properties with the same name, the property that comes last will be used:

const obj1 = {   a: 10,   b: 20 };  const obj2 = {   ...obj1,   a: 30 };  console.log(obj2);    // → {a: 30, b: 20}

Spread properties also provide a new way to merge two or more objects, which can be used as an alternative to the Object.assign() method:

const obj1 = {a: 10}; const obj2 = {b: 20}; const obj3 = {c: 30};  // ES2018 console.log({...obj1, ...obj2, ...obj3});    // → {a: 10, b: 20, c: 30}  // ES2015 console.log(Object.assign({}, obj1, obj2, obj3));    // → {a: 10, b: 20, c: 30}

Note, however, that spread properties do not always produce the same result as Object.assign(). Consider the following code:

Object.defineProperty(Object.prototype, 'a', {   set(value) {     console.log('set called!');   } });  const obj = {a: 10};  console.log({...obj});     // → {a: 10}  console.log(Object.assign({}, obj));     // → set called! // → {}

In this code, the Object.assign() method executes the inherited setter property. Conversely, the spread properties simply ignore the setter.

It’s important to remember that spread properties only copy enumerable properties. In the following example, the type property won’t show up in the copied object because its enumerable attribute is set to false:

const car = {   color: 'blue' };  Object.defineProperty(car, 'type', {   value: 'coupe',   enumerable: false });  console.log({...car});    // → {color: "blue"}

Inherited properties are ignored even if they are enumerable:

const car = {   color: 'blue' };  const car2 = Object.create(car, {   type: {     value: 'coupe',     enumerable: true,   } });  console.log(car2.color);                      // → blue console.log(car2.hasOwnProperty('color'));    // → false  console.log(car2.type);                       // → coupe console.log(car2.hasOwnProperty('type'));     // → true  console.log({...car2});                       // → {type: "coupe"}

In this code, car2 inherits the color property from car. Because spread properties only copy the own properties of an object, color is not included in the return value.

Keep in mind that spread properties can only make a shallow copy of an object. If a property holds an object, only the reference to the object will be copied:

const obj = {x: {y: 10}}; const copy1 = {...obj};     const copy2 = {...obj};   console.log(copy1.x === copy2.x);    // → true

The x property in copy1 refers to the same object in memory that x in copy2 refers to, so the strict equality operator returns true.

Another useful feature added to ES2015 was rest parameters, which enabled JavaScript programmers to use ... to represent values as an array. For example:

const arr = [10, 20, 30]; const [x, ...rest] = arr;  console.log(x);       // → 10 console.log(rest);    // → [20, 30]

Here, the first item in arr is assigned to x, and remaining elements are assigned to the rest variable. This pattern, called array destructuring, became so popular that the Ecma Technical Committee decided to bring a similar functionality to objects:

const obj = {   a: 10,   b: 20,   c: 30 };  const {a, ...rest} = obj;  console.log(a);       // → 10 console.log(rest);    // → {b: 20, c: 30}

This code uses the rest properties in a destructuring assignment to copy the remaining own enumerable properties into a new object. Note that rest properties must always appear at the end of the object, otherwise an error is thrown:

const obj = {   a: 10,   b: 20,   c: 30 };  const {...rest, a} = obj;    // → SyntaxError: Rest element must be last element

Also keep in mind that using multiple rest syntaxes in an object causes an error, unless they are nested:

const obj = {   a: 10,   b: {     x: 20,     y: 30,     z: 40   } };  const {b: {x, ...rest1}, ...rest2} = obj;    // no error  const {...rest, ...rest2} = obj;    // → SyntaxError: Rest element must be last element

Support for Rest/Spread Properties

Chrome Firefox Safari Edge
60 55 11.1 No
Chrome Android Firefox Android iOS Safari Edge Mobile Samsung Internet Android Webview
60 55 11.3 No 8.2 60

Node.js:

  • 8.0.0 (requires the --harmony runtime flag)
  • 8.3.0 (full support)

Asynchronous Iteration

Iterating over a collection of data is an important part of programming. Prior to ES2015, JavaScript provided statements such as for, for...in, and while, and methods such as map(), filter(), and forEach() for this purpose. To enable programmers to process the elements in a collection one at a time, ES2015 introduced the iterator interface.

An object is iterable if it has a Symbol.iterator property. In ES2015, strings and collections objects such as Set, Map, and Array come with a Symbol.iterator property and thus are iterable. The following code gives an example of how to access the elements of an iterable one at a time:

const arr = [10, 20, 30]; const iterator = arr[Symbol.iterator]();    console.log(iterator.next());    // → {value: 10, done: false} console.log(iterator.next());    // → {value: 20, done: false} console.log(iterator.next());    // → {value: 30, done: false} console.log(iterator.next());    // → {value: undefined, done: true}

Symbol.iterator is a well-known symbol specifying a function that returns an iterator. The primary way to interact with an iterator is the next() method. This method returns an object with two properties: value and done. The value property contains the value of the next element in the collection. The done property contains either true or false denoting whether or not the end of the collection has reached.

By default, a plain object is not iterable, but it can become iterable if you define a Symbol.iterator property on it, as in this example:

const collection = {   a: 10,   b: 20,   c: 30,   [Symbol.iterator]() {     const values = Object.keys(this);     let i = 0;     return {       next: () => {         return {           value: this[values[i++]],           done: i > values.length         }       }     };   } };  const iterator = collection[Symbol.iterator]();    console.log(iterator.next());    // → {value: 10, done: false} console.log(iterator.next());    // → {value: 20, done: false} console.log(iterator.next());    // → {value: 30, done: false} console.log(iterator.next());    // → {value: undefined, done: true}

This object is iterable because it defines a Symbol.iterator property. The iterator uses the Object.keys() method to get an array of the object’s property names and then assigns it to the values constant. It also defines a counter variable and gives it an initial value of 0. When the iterator is executed it returns an object that contains a next() method. Each time the next() method is called, it returns a {value, done} pair, with value holding the next element in the collection and done holding a Boolean indicating if the iterator has reached the need of the collection.

While this code works perfectly, it’s unnecessarily complicated. Fortunately, using a generator function can considerably simplify the process:

const collection = {   a: 10,   b: 20,   c: 30,   [Symbol.iterator]: function * () {     for (let key in this) {       yield this[key];     }   } };  const iterator = collection[Symbol.iterator]();    console.log(iterator.next());    // → {value: 10, done: false} console.log(iterator.next());    // → {value: 20, done: false} console.log(iterator.next());    // → {value: 30, done: false} console.log(iterator.next());    // → {value: undefined, done: true}

Inside this generator, a for...in loop is used to enumerate over the collection and yield the value of each property. The result is exactly the same as the previous example, but it’s greatly shorter.

A downside of iterators is that they are not suitable for representing asynchronous data sources. ES2018’s solution to remedy that is asynchronous iterators and asynchronous iterables. An asynchronous iterator differs from a conventional iterator in that, instead of returning a plain object in the form of {value, done}, it returns a promise that fulfills to {value, done}. An asynchronous iterable defines a Symbol.asyncIterator method (instead of Symbol.iterator) that returns an asynchronous iterator.

An example should make this clearer:

const collection = {   a: 10,   b: 20,   c: 30,   [Symbol.asyncIterator]() {     const values = Object.keys(this);     let i = 0;     return {       next: () => {         return Promise.resolve({           value: this[values[i++]],            done: i > values.length         });       }     };   } };  const iterator = collection[Symbol.asyncIterator]();    console.log(iterator.next().then(result => {   console.log(result);    // → {value: 10, done: false} }));  console.log(iterator.next().then(result => {   console.log(result);    // → {value: 20, done: false}  }));  console.log(iterator.next().then(result => {   console.log(result);    // → {value: 30, done: false}  }));  console.log(iterator.next().then(result => {   console.log(result);    // → {value: undefined, done: true}  }));

Note that it’s not possible to use an iterator of promises to achieve the same result. Although a normal, synchronous iterator can asynchronously determine the values, it still needs to determine the state of “done” synchronously.

Again, you can simplify the process by using a generator function, as shown below:

const collection = {   a: 10,   b: 20,   c: 30,   [Symbol.asyncIterator]: async function * () {     for (let key in this) {       yield this[key];     }   } };  const iterator = collection[Symbol.asyncIterator]();    console.log(iterator.next().then(result => {   console.log(result);    // → {value: 10, done: false} }));  console.log(iterator.next().then(result => {   console.log(result);    // → {value: 20, done: false}  }));  console.log(iterator.next().then(result => {   console.log(result);    // → {value: 30, done: false}  }));  console.log(iterator.next().then(result => {   console.log(result);    // → {value: undefined, done: true}  }));

Normally, a generator function returns a generator object with a next() method. When next() is called it returns a {value, done} pair whose value property holds the yielded value. An async generator does the same thing except that it returns a promise that fulfills to {value, done}.

An easy way to iterate over an iterable object is to use the for...of statement, but for...of doesn’t work with async iterables as value and done are not determined synchronously. For this reason, ES2018 provides the for...await...of statement. Let’s look at an example:

const collection = {   a: 10,   b: 20,   c: 30,   [Symbol.asyncIterator]: async function * () {     for (let key in this) {       yield this[key];     }   } };  (async function () {   for await (const x of collection) {     console.log(x);   } })();  // logs: // → 10 // → 20 // → 30

In this code, the for...await...of statement implicitly calls the Symbol.asyncIterator method on the collection object to get an async iterator. Each time through the loop, the next() method of the iterator is called, which returns a promise. Once the promise is resolved, the value property of the resulting object is read to the x variable. The loop continues until the done property of the returned object has a value of true.

Keep in mind that the for...await...of statement is only valid within async generators and async functions. Violating this rule results in a SyntaxError.

The next() method may return a promise that rejects. To gracefully handle a rejected promise, you can wrap the for...await...of statement in a try...catch statement, like this:

const collection = {   [Symbol.asyncIterator]() {     return {       next: () => {         return Promise.reject(new Error('Something went wrong.'))       }     };   } };  (async function() {   try {     for await (const value of collection) {}   } catch (error) {     console.log('Caught: ' + error.message);   } })();  // logs: // → Caught: Something went wrong.

Support for Asynchronous Iterators

Chrome Firefox Safari Edge
63 57 12 No
Chrome Android Firefox Android iOS Safari Edge Mobile Samsung Internet Android Webview
63 57 12 No 8.2 63

Node.js:

  • 8.10.0 (requires the –harmony_async_iteration flag)
  • 10.0.0 (full support)

Promise.prototype.finally

Another exciting addition to ES2018 is the finally() method. Several JavaScript libraries had previously implemented a similar method, which proved useful in many situations. This encouraged the Ecma Technical Committee to officially add finally() to the specification. With this method, programmers will be able to execute a block of code regardless of the promise’s fate. Let’s look at a simple example:

fetch('https://www.google.com')   .then((response) => {     console.log(response.status);   })   .catch((error) => {      console.log(error);   })   .finally(() => {      document.querySelector('#spinner').style.display = 'none';   });

The finally() method comes in handy when you need to do some clean up after the operation has finished regardless of whether or not it succeeded. In this code, the finally() method simply hides the loading spinner after the data is fetched and processed. Instead of duplicating the final logic in the then() and catch() methods, the code registers a function to be executed once the promise is either fulfilled or rejected.

You could achieve the same result by using promise.then(func, func) rather than promise.finally(func), but you would have to repeat the same code in both fulfillment handler and rejection handler, or declare a variable for it:

fetch('https://www.google.com')   .then((response) => {     console.log(response.status);   })   .catch((error) => {      console.log(error);   })   .then(final, final);  function final() {   document.querySelector('#spinner').style.display = 'none'; }

As with then() and catch(), the finally() method always returns a promise, so you can chain more methods. Normally, you want to use finally() as the last chain, but in certain situations, such as when making a HTTP request, it’s a good practice to chain another catch() to deal with errors that may occur in finally().

Support for Promise.prototype.finally

Chrome Firefox Safari Edge
63 58 11.1 18
Chrome Android Firefox Android iOS Safari Edge Mobile Samsung Internet Android Webview
63 58 11.1 No 8.2 63

Node.js:

10.0.0 (full support)

New RegExp Features

ES2018 adds four new features to the RegExp object, which further improves JavaScript’s string processing capabilities. These features are as follows:

  • s (dotAll) flag
  • Named capture groups
  • Lookbehind assertions
  • Unicode property escapes

s (dotAll) Flag

The dot (.) is a special character in a regular expression pattern that matches any character except line break characters such as line feed (\n) or carriage return (\r). A workaround to match all characters including line breaks is to use a character class with two opposite shorthands such as [\d\D]. This character class tells the regular expression engine to find a character that’s either a digit (\d) or a non-digit (\D). As a result, it matches any character:

console.log(/one[\d\D]two/.test('one\ntwo'));    // → true

ES2018 introduces a mode in which the dot can be used to achieve the same result. This mode can be activated on per-regex basis by using the s flag:

console.log(/one.two/.test('one\ntwo'));     // → false console.log(/one.two/s.test('one\ntwo'));    // → true

The benefit of using a flag to opt in to the new behavior is backwards compatibility. So existing regular expression patterns that use the dot character are not affected.

Named Capture Groups

In some regular expression patterns, using a number to reference a capture group can be confusing. For example, take the regular expression /(\d{4})-(\d{2})-(\d{2})/ which matches a date. Because date notation in American English is different from British English, it’s hard to know which group refers to the day and which group refers to the month:

const re = /(\d{4})-(\d{2})-(\d{2})/; const match= re.exec('2019-01-10');  console.log(match[0]);    // → 2019-01-10 console.log(match[1]);    // → 2019 console.log(match[2]);    // → 01 console.log(match[3]);    // → 10

ES2018 introduces named capture groups which uses the (?<name>...) syntax. So, the pattern to match a date can be written in a less ambiguous manner:

const re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/; const match = re.exec('2019-01-10');  console.log(match.groups);          // → {year: "2019", month: "01", day: "10"} console.log(match.groups.year);     // → 2019 console.log(match.groups.month);    // → 01 console.log(match.groups.day);      // → 10

You can recall a named capture group later in the pattern by using the \k<name> syntax. For example, to find consecutive duplicate words in a sentence, you can use /\b(?<dup>\w+)\s+\k<dup>\b/:

const re = /\b(?<dup>\w+)\s+\k<dup>\b/; const match = re.exec('Get that that cat off the table!');          console.log(match.index);    // → 4 console.log(match[0]);       // → that that

To insert a named capture group into the replacement string of the replace() method, you will need to use the $ <name> construct. For example:

const str = 'red & blue';  console.log(str.replace(/(red) & (blue)/, '$  2 & $  1'));     // → blue & red  console.log(str.replace(/(?<red>red) & (?<blue>blue)/, '$  <blue> & $  <red>'));     // → blue & red

Lookbehind Assertions

ES2018 brings lookbehind assertions to JavaScript, which have been available in other regex implementations for years. Previously, JavaScript only supported lookahead assertions. A lookbehind assertion is denoted by (?<=...), and enables you to match a pattern based on the substring that precedes the pattern. For example, if you want to match the price of a product in dollar, pound, or euro without capturing the currency symbol, you can use /(?<=$ |£|€)\d+(\.\d*)?/:

const re = /(?<=$  |£|€)\d+(\.\d*)?/;  console.log(re.exec('199'));      // → null  console.log(re.exec('$  199'));     // → ["199", undefined, index: 1, input: "$  199", groups: undefined]  console.log(re.exec('€50'));      // → ["50", undefined, index: 1, input: "€50", groups: undefined]

There is also a negative version of lookbehind, which is denoted by (?<!...). A negative lookbehind allows you to match a pattern only if it is not preceded by the pattern within the lookbehind. For example, the pattern /(?<!un)available/ matches the word available if it does not have a “un” prefix:

const re = /(?<!un)available/;  console.log(re.exec('We regret this service is currently unavailable'));     // → null  console.log(re.exec('The service is available'));              // → ["available", index: 15, input: "The service is available", groups: undefined]

Unicode Property Escapes

ES2018 provides a new type of escape sequence known as Unicode property escape, which provides support for full Unicode in regular expressions. Suppose you want to match the Unicode character ㉛ in a string. Although ㉛ is considered a number, you can’t match it with the \d shorthand character class because it only supports ASCII [0-9] characters. Unicode property escapes, on the other hand, can be used to match any decimal number in Unicode:

const str = '㉛';  console.log(/\d/u.test(str));    // → false console.log(/\p{Number}/u.test(str));     // → true

Similarly, if you want to match any Unicode word character, you can use \p{Alphabetic}:

const str = 'ض';  console.log(/\p{Alphabetic}/u.test(str));     // → true  // the \w shorthand cannot match ض   console.log(/\w/u.test(str));    // → false

There is also a negated version of \p{...}, which is denoted by \P{...}:

console.log(/\P{Number}/u.test('㉛'));    // → false console.log(/\P{Number}/u.test('ض'));    // → true  console.log(/\P{Alphabetic}/u.test('㉛'));    // → true console.log(/\P{Alphabetic}/u.test('ض'));    // → false

In addition to Alphabetic and Number, there are several more properties that can be used in Unicode property escapes. You can find a list of supported Unicode properties in the current specification proposal.

Support for New RegExp Features

Chrome Firefox Safari Edge
s (dotAll) Flag 62 No 11.1 No
Named Capture Groups 64 No 11.1 No
Lookbehind Assertions 62 No No No
Unicode Property Escapes 64 No 11.1 No
Chrome (Android) Firefox (Android) iOS Safari Edge Mobile Samsung Internet Android Webview
s (dotAll) Flag 62 No 11.3 No 8.2 62
Named Capture Groups 64 No 11.3 No No 64
Lookbehind Assertions 62 No No No 8.2 62
Unicode Property Escapes 64 No 11.3 No No 64

Node.js:

  • 8.3.0 (requires the –harmony runtime flag)
  • 8.10.0 (support for s (dotAll) flag and lookbehind assertions)
  • 10.0.0 (full support)

Template Literal Revision

When a template literal is immediately preceded by an expression, it is called a tagged template literal. A tagged template comes in handy when you want to parse a template literal with a function. Consider the following example:

function fn(string, substitute) {   if(substitute === 'ES6') {     substitute = 'ES2015'   }   return substitute + string[1]; }  const version = 'ES6'; const result = fn`$  {version} was a major update`;  console.log(result);    // → ES2015 was a major update

In this code, a tag expression — which is a regular function — is invoked and passed the template literal. The function simply modifies the dynamic part of the string and returns it.

Prior to ES2018, tagged template literals had syntactic restrictions related to escape sequences. A backslash followed by certain sequence of characters were treated as special characters: a \x interpreted as a hex escape, a \u interpreted as a unicode escape, and a \ followed by a digit interpreted as an octal escape. As a result, strings such as "C:\xxx\uuu" or "\ubuntu" were considered invalid escape sequences by the interpreter and would throw a SyntaxError.

ES2018 removes these restrictions from tagged templates and instead of throwing an error, represents invalid escape sequences as undefined:

function fn(string, substitute) {   console.log(substitute);    // → escape sequences:   console.log(string[1]);     // → undefined }  const str = 'escape sequences:'; const result = fn`$  {str} \ubuntu C:\xxx\uuu`;

Keep in mind that using illegal escape sequences in a regular template literal still causes an error:

const result = `\ubuntu`; // → SyntaxError: Invalid Unicode escape sequence

Support for Template Literal Revision

Chrome Firefox Safari Edge
62 56 11 No
Chrome Android Firefox Android iOS Safari Edge Mobile Samsung Internet Android Webview
62 56 11 No 8.2 62

Node.js:

  • 8.3.0 (requires the –harmony runtime flag)
  • 8.10.0 (full support)

Wrapping up

We’ve taken a good look at several key features introduced in ES2018 including asynchronous iteration, rest/spread properties, Promise.prototype.finally(), and additions to the RegExp object. Although some of these features are not fully implemented by some browser vendors yet, they can still be used today thanks to JavaScript transpilers such as Babel.

ECMAScript is rapidly evolving and new features are being introduced every so often, so check out the list of finished proposals for the full scope of what’s new. Are there any new features you’re particularly excited about? Share them in the comments!

The post New ES2018 Features Every JavaScript Developer Should Know appeared first on CSS-Tricks.

CSS-Tricks

, , , , , ,
[Top]