About

Saturday, February 26, 2022

Trailing Slashes on URLs: Contentious or Settled?

A fun deep dive from Zach. Do you have an opinion on which you should use?

1) https://website.com/foo/
2) https://websites.com/foo

The first option has a “trailing slash.” The second does not.

I’ve always preferred this thinking: you use a trailing slash if that page has child pages (as in, it is something of a directory page, even if has unique content of its own). If it’s the end-of-the-line (of content), no trailing slash.

I say that, but this very site doesn’t practice it. Blog posts on this site are like css-tricks.com/blog-post/ with a trailing slash and if you leave off the trailing slash, WordPress will redirect to include it. That’s part of the reason Zach is interested here. Redirects come with a performance penalty, so it’s ideal to have it happen as infrequently possible.

Performance is one thing, but SEO is another one. If you render the same content, both with and without a trailing slash, that’s theoretically a duplicate content penalty and a no-no. (Although that seems weird to me, I would think Google would smart enough not to be terribly concerned by this.)

Where resources resolve to seems like the biggest deal to me. Here’s Zach:

If you’re using relative resource URLs, the assets may be missing on Vercel, Render, and Azure Static Web Apps (depending on which duplicated endpoint you’ve visited).

<img src="image.avif"> on /resource/ resolves to /resource/image.avif

<img src="image.avif"> on /resource resolves to /image.avif

That’s a non-trivial difference and, to me, a reason the redirect is worth it. Can’t be having a page with broken resources for something this silly.

What complicates this is that the site-building framework might have opinions about this and a hosting provider might have opinions about this. As Zach notes, there are some disagreements among hosts, so it’s something to watch for.

Me, I’d go with the grain as much as I possibly could. As long as redirects are in place and I don’t have to override any config, I’m cool.

To Shared LinkPermalink on CSS-Tricks


Trailing Slashes on URLs: Contentious or Settled? originally published on CSS-Tricks. You should get the newsletter.



source https://css-tricks.com/trailing-slashes-on-urls-contentious-or-settled/

When to Avoid the text-decoration Shorthand Property

In my recent article about CSS underline bugs in Chrome, I discussed text-decoration-thickness and text-underline-offset, two relatively new and widely-supported CSS properties that give us more control over the styling of underlines.

Let me demonstrate the usefulness of text-decoration-thickness on a simple example. The Ubuntu web font has a fairly thick default underline. We can make this underline thinner like so:

:any-link {
  text-decoration-thickness: 0.08em;
}
Showing two links, a default and one that decreases the text-decoration-thickness.

/explanation Throughout this article, I will use the :any-link selector instead of the a element to match hyperlinks. The problem with the a tag as a selector is that it matches all <a> elements, even the ones that don’t have a href attribute and thus aren’t hyperlinks. The :any-link selector only matches <a> elements that are hyperlinks. Web browsers also use :any-link instead of a in their user agent stylesheets.

Hover underlines

Many websites, including Google Search and Wikipedia, remove underlines from links and only show them when the user hovers a link. Removing underlines from links in body text is not a good idea, but it can make sense in places where links are more spaced apart (navigation, footer, etc.). With that being said, here’s a simple implementation of hover underlines for links in the website’s header:

header :any-link {
  text-decoration: none;
}

header :any-link:hover {
  text-decoration: underline;
}

But there’s a problem. If we tested this code in a browser, we’d notice that the underlines in the header have the default thickness, not the thinner style that we declared earlier. Why did text-decoration-thickness stop working after we added hover underlines?

Let’s look at the full CSS code again. Can you think of a reason why the custom thickness doesn’t apply to the hover underline?

:any-link {
  text-decoration-thickness: 0.08em;
}

header :any-link {
  text-decoration: none;
}

header :any-link:hover {
  text-decoration: underline;
}

The reason for this behavior is that text-decoration is a shorthand property and text-decoration-thickness its associated longhand property. Setting text-decoration to none or underline has the side effect of re-initializing the other three text decoration components (thickness, style, and color). This is defined in the CSS Text Decoration module:

The text-decoration property is a shorthand for setting text-decoration-line, text-decoration-thickness, text-decoration-style, and text-decoration-color in one declaration. Omitted values are set to their initial values.

You can confirm this in the browser’s DevTools by selecting one of the hyperlinks in the DOM inspector and then expanding the text-decoration property in the CSS pane.

DevTools screenshot showing text-decoration styles on the :any-link pseudo-selector.

In order to get text-decoration-thickness to work on hover underlines, we’ll have to make a small change to the above CSS code. There are actually multiple ways to achieve this. We could:

  • set text-decoration-thickness after text-decoration,
  • declare the thickness in the text-decoration shorthand, or
  • use text-decoration-line instead of text-decoration.

Choosing the best text-decoration option

Our first thought might be to simply repeat the text-decoration-thickness declaration in the :hover state. It’s a quick and simple fix that indeed works.

/* OPTION A */

header :any-link {
  text-decoration: none;
}

header :any-link:hover {
  text-decoration: underline;
  text-decoration-thickness: 0.08em; /* set thickness again */
}

However, since text-decoration is a shorthand and text-decoration-thickness is its associated longhand, there really should be no need to use both at the same time. As a shorthand, text-decoration allows setting both the underline itself and the underline’s thickness, all in one declaration.

/* OPTION B */

header :any-link {
  text-decoration: none;
}

header :any-link:hover {
  text-decoration: underline 0.08em; /* set both line and thickness */
}

If this code looks unfamiliar to you, that could be because the idea of using text-decoration as a shorthand is relatively new. This property was only subsequently turned into a shorthand in the CSS Text Decoration module. In the days of CSS 2, text-decoration was a simple property.

Unfortunately, Safari still hasn’t fully caught up with these changes. In the WebKit browser engine, the shorthand variant of text-decoration remains prefixed (-webkit-text-decoration), and it doesn’t support thickness values yet. See WebKit bug 230083 for more information.

This rules out the text-decoration shorthand syntax. The above code won’t work in Safari, even if we added the -webkit- prefix. Luckily, there’s another way to avoid repeating the text-decoration-thickness declaration.

When text-decoration was turned into a shorthand, a new text-decoration-line longhand was introduced to take over its old job. We can use this property to hide and show the underline without affecting the other three text decoration components.

/* OPTION C */

header :any-link {
  text-decoration-line: none;
}

header :any-link:hover {
  text-decoration-line: underline;
}

Since we’re only updating the line component of the text-decoration value, the previously declared thickness remains intact. I think that this is the best way to implement hover underlines.

Be aware of shorthands

Keep in mind that when you set a shorthand property, e.g., text-decoration: underline, any missing parts in the value are re-initialized. This is also why styles such as background-repeat: no-repeat are undone if you set background: url(flower.jpg) afterwards. See the article “Accidental CSS Resets” for more examples of this behavior.


When to Avoid the text-decoration Shorthand Property originally published on CSS-Tricks. You should get the newsletter.



source https://css-tricks.com/when-to-avoid-css-text-decoration-shorthand/

Manuel Matuzovic’s CSS Specificity Demo

If you’re looking for a primer on CSS specificity, we’ve got that. And if you’re trying to get ahead of the game, you should be aware of CSS Cascade Layers as well.

Screenshot of the CSS Specificity Demo.

One of the ways to help get a grasp of CSS specificity is thinking terms of “what beats what” or how strong the specificity is. Manuel Matuzovic has a helpful interactive step-by-step demo. You keep clicking the “Add selector” button, and the CSS shown (and applied to the page) changes with ever-increasingly-strong selectors applied to the body that change the background-color. At the end, it veers into not-really-selectors trickery, like using @keyframes to override things.

More specificity practice

If you enjoyed the trickery at the end, check out Francisco Dias’ A Specificity Battle!, an article we published a few years back that does a back-and-forth styling battle with nineteen steps “selecting” the same element to re-style it. CSS is cray sometimes.

To Shared LinkPermalink on CSS-Tricks


Manuel Matuzovic’s CSS Specificity Demo originally published on CSS-Tricks. You should get the newsletter.



source https://css-tricks.com/manuel-matuzovics-css-specificity-demo/

Friday, February 25, 2022

My white whale: A use case for will-change

 Nic Chan:

[…] the will-change property landed in major browsers in August 2015, and I’ve been on the lookout for when to use it ever since. It might seem self-evident to apply it to commonly animated properties such as transform or opacity, but the browser already classifies them as composite properties, thus, they are known as the few properties that you can already expect decent animation performance from. So, heeding the advice of the great developers who came before me, I was cautious and waited for the right opportunity to come along.

I was thinking-out-loud about this as well on ShopTalk not too long ago. I get the spirit behind will-change. It’s like responsive images or DNS prefetching: you give the browser extra information about what you’re about to do, and it can optimize it when it happens. But with will-changewhen? Why isn’t there a simple reduced test case demo to showcase something with bad performance, then will-change being applied, and it becomes good performance?

Well Nic found one little directly useful case where a hover-transformed pseudo-element leaves a little dingus of color behind in Safari, and that goes away if you use will-change. I tested it in the latest versions of Safari and found it to be true. Alrighty then, one use case!

I’d love to see a more obvious direct use case. I imagine the sweet spot is on lower-power devices (that still have GPUs) but are new enough to know what will-change is.

To Shared LinkPermalink on CSS-Tricks


My white whale: A use case for will-change originally published on CSS-Tricks. You should get the newsletter.



source https://css-tricks.com/my-white-whale-a-use-case-for-will-change/

Explain the First 10 Lines of Twitter’s Source Code to Me

For the past few weeks, I’ve been hiring for a senior full-stack JavaScript engineer at my rental furniture company, Pabio. Since we’re a remote team, we conduct our interviews on Zoom, and I’ve observed that some developers are not great at live-coding or whiteboard interviews, even if they’re good at the job. So, instead, we have an hour-long technical discussion where I ask them questions about web vitals, accessibility, the browser wars, and other similar topics about the web. One of the questions I always like to ask is: “Explain the first ten or so lines of the Twitter source code to me.”

I think it’s a simple test that tells me a lot about the depth of fundamental front-end knowledge they have, and this article lists the best answers.

For context, I share my screen, open Twitter.com and click View source. Then I ask them to go line-by-line to help me understand the HTML, and they can say as much or as little as they like. I also zoom in to make the text more legible, so you don’t see the full line but you get an idea. Here’s what it looks like:

Screenshot of source code from Twitter.

Note that since our technical discussion is a conversation. I don’t expect a perfect answer from anyone. If I hear some right keywords, I know that the candidate knows the concept, and I try to push them in the right direction.

Line 1: <!DOCTYPE html>

The first line of every document’s source code is perfect for this interview because how much a candidate knows about the DOCTYPE declaration closely resembles how many years of experience they have. I still remember my Dreamweaver days with the long XHTML DOCTYPE line, like Chris listed in his article “The Common DOCTYPES” from 2009.

Perfect answer: This is the document type (doc-type) declaration that we always put as the first line in HTML files. You might think that this information is redundant because the browser already knows that the MIME type of the response is text/html; but in the Netscape/Internet Explorer days, browsers had the difficult task of figuring out which HTML standard to use to render the page from multiple competing versions.

This was especially annoying because each standard generated a different layout so this tag was adopted to make it easy for browsers. Previously, DOCTYPE tags were long and even included the specification link (kinda like SVGs have today), but luckily the simple <!doctype html> was standardized in HTML5 and still lives on.

Also accepted: This is the DOCTYPE tag to let the browser know that this is an HTML5 page and should be rendered as such.

Line 2: <html dir="ltr" lang="en">

This line in the source code tells me if the candidate knows about accessibility and localization. Surprisingly, only a few people knew about the dir attribute in my interviews, but it’s a great segue into a discussion about screen readers. Almost everyone was able to figure out the lang="en" attribute, even if they hadn’t used it before.

Perfect answer: This is the root element of an HTML document and all other elements are inside this one. Here, it has two attributes, direction and language. The direction attribute has the value left-to-right to tell user agents which direction the content is in; other values are right-to-left for languages like Arabic, or just auto which leaves it to the browser to figure out.

The language attribute tells us that all content inside this tag is in English; you can set this value to any language tag, even to differentiate en-us and en-gb, for example. This is also useful for screen readers to know which language to announce in.

Line 3: <meta charset="utf-8">

Perfect answer: The meta tag in the source code is for supplying metadata about this document. The character set (char-set) attribute tells the browser which character encoding to use, and Twitter uses the standard UTF-8 encoding. UTF-8 is great because it has many character points so you can use all sorts of symbols and emoji in your source code. It’s important to put this tag near the beginning of your code so the browser hasn’t already started parsing too much text when it comes across this line; I think the rule is to put it in the first kilobyte of the document, but I’d say the best practice is to put it right at the top of <head>.

As a side note, it looks like Twitter omits the <head> tag for performance reasons (less code to load), but I still like to make it explicit as it’s a clear home for all metadata, styles, etc.

Line 4: <meta name="viewport" content="width=device-...

Perfect answer: This meta tag in the source code is for properly sizing the webpage on small screens, like smartphones. If you remember the original iPhone keynote, Steve Jobs showed the entire New York Times website on that tiny 4.5-inch screen; back then it was an amazing feature that you had to pinch to zoom to actually be able to read.

Now that websites are responsive by design, width=device-width tells the browser to use 100% of the device’s width as the viewport so there’s no horizontal scrolling, but you can even specify specific pixel values for width. The standard best practice is to set the initial scale to 1 and the width to device-width so people can still zoom around if they wish.

The screenshot of the source code doesn’t show these values but it’s good to know: Twitter also applies user-scalable=0 which, as the name suggests, disables the ability to zoom. This is not good for accessibility but makes the webpage feel more like a native app. It also sets maximum-scale=1 for the same reason (you can use minimum and maximum scale to clamp the zoom-ablity between these values). In general, setting the full width and initial scale is enough.

Line 5: <meta property="og:site_name" content="Twitt...

About 50% of all candidates knew about Open Graph tags, and a good answer to this question shows that they know about SEO.

Perfect answer: This tag is an Open Graph (OG) meta tag for the site name, Twitter. The Open Graph protocol was made by Facebook to make it easier to unfurl links and show their previews in a nice card layout; developers can add all sorts of authorship details and cover images for fancy sharing. In fact, these days it’s even common to auto-generate the open graph image using something like Puppeteer. (CSS-Tricks uses a WordPress plugin that does it.)

Another interesting side note is that meta tags usually have the name attribute, but OG uses the non-standard property attribute. I guess that’s just Facebook being Facebook? The title, URL, and description Open Graph tags are kinda redundant because we already have regular meta tags for these, but people add them just to be safe. Most sites these days use a combination of Open Graph and other metatags and the content on a page to generate rich previews.

Line 6: <meta name="apple-mobile-web-app-title" cont...

Most candidates didn’t know about this one, but experienced developers can talk about how to optimize a website for Apple devices, like apple-touch-icons and Safari pinned tab SVGs.

Perfect answer: You can pin a website on an iPhone’s homescreen to make it feel like a native app. Safari doesn’t support progressive web apps and you can’t really use other browser engines on iOS, so you don’t really have other options if you want that native-like experience, which Twitter, of course, likes. So they add this to tell Safari that the title of this app is Twitter. The next line is similar and controls how the status bar should look like when the app has launched.

Line 8: <meta name="theme-color" content="#ffffff"...

Perfect answer: This is the proper web standards-esque equivalent of the Apple status bar color meta tag. It tells the browser to theme the surrounding UI. Chrome on Android and Brave on desktop both do a pretty good job with that. You can put any CSS color in the content, and can even use the media attribute to only show this color for a specific media query like, for example, to support a dark theme. You can also define this and additional properties in the web app manifest.

Line 9: <meta http-equiv="origin-trial" content="...

Nobody I interviewed knew about this one. I would assume that you’d know this only if you have in-depth knowledge about all the new things that are happening on the standards track.

Perfect answer: Origin trials let us use new and experimental features on our site and the feedback is tracked by the user agent and reported to the web standards community without users having to opt-in to a feature flag. For example, Edge has an origin trial for dual-screen and foldable device primitives, which is pretty cool as you can make interesting layouts based on whether a foldable phone is opened or closed.

Also accepted: I don’t know about this one.

Line 10: html{-ms-text-size-adjust:100%;-webkit-text...

Almost nobody knew about this one too; only if you know about CSS edge cases and optimizations, you’d be able to figure this line out.

Perfect answer: Imagine that you don’t have a mobile responsive site and you open it on a small screen, so the browser might resize the text to make it bigger so it’s easier to read. The CSS text-size-adjust property can either disable this feature with the value none or specify a percentage up to which the browser is allowed to make the text bigger.

In this case, Twitter says the maximum is 100%, so the text should be no bigger than the actual size; they just do that because their site is already responsive and they don’t want to risk a browser breaking the layout with a larger font size. This is applied to the root HTML tag so it applies to everything inside it. Since this is an experimental CSS property, vendor prefixes are required. Also, there’s a missing <style> before this CSS, but I’m guessing that’s minified in the previous line and we don’t see it.

Also accepted: I don’t know about this property in specific but the -ms and -webkit- are vendor prefixes needed by Internet Explorer and WebKit-based browsers, respectively, for non-standard properties. We used to require these prefixes when CSS3 came out, but as properties go from experimental to stable or are adopted to a standards track, these prefixes go away in favor of a standardized property.

Bonus — Line 11: body{margin:0;}

This line from Twitter’s source code is particularly fun because you can follow-up with a question about the difference between resetting and normalizing a webpage. Almost everyone knew a version of the right answer.

Perfect answer: Because different browsers have different default styles (user agent stylesheet), you want to overwrite them by resetting properties so your site looks the same across devices. In this case, Twitter is telling the browser to remove the body tag’s default margin. This is just to reduce browser inconsistencies, but I prefer normalizing the styles instead of resetting them, i.e., applying the same defaults across browsers rather than removing them altogether. People even used to use * { margin: 0 } which is totally overkill and not great for performance, but now it’s common to import something like normalize.css or reset.css (or even something newer) and start from there.

More lines!

I always enjoy playing with the browser Inspector tool to see how sites are made, which is how I came up with this idea. Even though I consider myself sort of an expert on semantic HTML, I learn something new every time I do this exercise.

Since Twitter is mostly a client-side React app, there’s only a few dozen lines in the source code. Even with that, there’s so much to learn! There are a few more interesting lines in the Twitter source code that I leave as an exercise for you, the reader. How many of them could you explain in an interview?

<link rel="search" type="application/opensearchdescription+xml" href="/opensearch.xml" title="Twitter">

…tells browsers that users can add Twitter as a search engine.

<link rel="preload" as="script" crossorigin="anonymous" href="https://abs.twimg.com/responsive-web/client-web/polyfills.cad508b5.js" nonce="MGUyZTIyN2ItMDM1ZC00MzE5LWE2YmMtYTU5NTg2MDU0OTM1" />

…has many interesting attributes that can be discussed, especially nonce.

<link rel="alternate" hreflang="x-default" href="https://twitter.com/" />

…for international landing pages.

:focus:not([data-focusvisible-polyfill]){outline: none;}

…for removing the focus outline when not using keyboard navigation (the CSS :focus-visible selector is polyfilled here).


Explain the First 10 Lines of Twitter’s Source Code to Me originally published on CSS-Tricks. You should get the newsletter.



source https://css-tricks.com/explain-the-first-10-lines-of-twitter-source-code/

Superior Image Optimization: An Ideal Solution Using Gatsby & ImageEngine

(This is a sponsored post.)

In recent years, the Jamstack methodology for building websites has become increasingly popular. Performance, scalable, and secure, it’s easy to see why it’s becoming an attractive way to build websites for developers.

GatsbyJS is a static site generator platform. It’s powered by React, a front-end JavaScript library, for building user interfaces. And uses GraphQL, an open-source data query and manipulation language, to pull structured data from other sources, typically a headless CMS like Contentful.

While GatsbyJS and similar platforms have revolutionized much about the web development process, one stubborn challenge remains: image optimization. Even using a modern front-end development framework like GatsbyJS, it tends to be a time-intensive and frustrating exercise.

For most modern websites, it doesn’t help much if you run on a performant technology but your images aren’t optimized. Today, images are the largest contributor to page weight, and growing, and have been singled out by Google as presenting the most significant opportunity for improving performance.

With that in mind, I want to discuss how using an image CDN as part of your technology stack can bring improvements both in terms of website performance and the entire development process.

A Quick Introduction to Gatsby

GatsbyJS is so much more than the conventional static site generators of old. Yes, you still have the ability to integrate with a software version control platform, like Git, as well as to build, deploy, and preview Gatsby projects. However, its services consist of a unified cloud platform that includes high-speed, scalable, and secure hosting as well as expert technical support and powerful third-party integrations.

What’s more, all of it comes wrapped in a user-friendly development platform that shares many similarities with the most popular CMSs of the day. For example, you can leverage pre-designed site templates or pre-configured functions (effectively website elements and modules) to speed up the production process.

It also offers many benefits for developers by allowing you to work with leading frameworks and languages, like JavaScript, React, WebPack, and GraphQL as well as baked-in capabilities to deal with performance, development iterations, etc.

For example, Gatsby does a lot to optimize your performance without any intervention. It comes with built-in code-splitting, prefetching resources, and lazy-loading. Static sites are generally known for being inherently performant, but Gatsby kicks it up a notch.

Does Gatsby Provide Built-in Image Optimization?

Gatsby does, in fact, offer built-in image optimization capabilities.

In fact, it recently upgraded in this regard, replacing the now deprecated gatsby-image package with the brand-new Gatsby image plugin. This plugin consists of two components for static and dynamic images, respectively. Typically, you would use the dynamic component if you’re handling images from a CMS, like Contentful.

Installing this plugin allows you to programmatically pass commands to the underlying framework in the form of properties, shown below:

Option Default Description
layout constrained / CONSTRAINED Determines the size of the image and its resizing behavior.
width/height Source image size Change the size of the image.
aspectRatio Source image aspect ratio Force a specific ratio between the image’s width and height.
placeholder "dominantColor" / DOMINANT_COLOR Choose the style of temporary image shown while the full image loads.
formats ["auto", "webp"] / [AUTO,WEBP] File formats of the images generated.
transformOptions [fit: "cover", cropFocus: "attention"] Options to pass to sharp to control cropping and other image manipulations.
sizes Generated automatically The <img> sizes attribute, passed to the img tag. This describes the display size of the image, and does not affect generated images. You are only likely to change this if you are using full width images that do not span the full width of the screen.
quality 50 The default image quality generated. This is override by any format-specific option.
outputPixelDensities For fixed images: [1, 2]

For constrained: [0.25, 0.5, 1, 2]
A list of image pixel densities to generate. It will never generate images larger than the source, and will always include a 1✕ image. The image is multiple by the image width, to give the generated sizes. For example, a 400px wide constrained image would generate 100, 200, 400 and 800px wide images by default. Ignored for full width layout images, which use breakpoints instead.
breakpoints [750, 1000, 1366, 1920] Output widths to generate for full width images. Default is to generate widths for common device resolutions. It will never generate an image larger than the source image. The browser will automatically choose the most appropriate.
blurredOptions None Options for the low-resolution placeholder image. Ignored unless placeholder is blurred.
tracedSVGOptions None Options for traced placeholder SVGs. See potrace options. Ignored unless placeholder is traced SVG.
jpgOptions None Options to pass to sharp when generating JPG images.

As you can see, that’s quite the toolkit to help developers process images in a variety of ways. The various options can be used to transform, style, and optimize images for performance as well as make images behave dynamically in a number of ways.

In terms of performance optimization, there are a few options that are particularly interesting:

  • Lazy-loading: Defers loading of off-screen images until they are scrolled into view.
  • Width/height: Resize image dimensions according to how they will be used.
  • Placeholder: When lazy-loading or while an image is loading in the background, use a placeholder. This can help to avoid performance penalties for core web vitals, like Cumulative Layout Shift (CLS).
  • Format: Different formats have inherently more efficient encoding. GatsbyJS supports WebP and AVIF, two of the most performant next-gen image formats.
  • Quality: Apply a specified level of quality compression to the image between 0 and 100.
  • Pixel density: A lower pixel density will save image data and can be optimized according to the screen size and PPI (pixels per inch).
  • Breakpoints: Breakpoints are important for ensuring that you serve a version of an image that’s sized appropriately for a certain threshold of screen sizes, especially that you serve smaller images for smaller screen sizes, like tablets or mobile phones. This is called responsive syntax.

So, all in all, Gatsby provides developers with a mature and sophisticated framework to process and optimize image content. The only important missing feature that’s missing is some type of built-in support for client hints.

However, there is one big catch: All of this has to be implemented manually. While GatsbyJS does use default settings for some image properties, it doesn’t offer built-in intelligence to automatically and dynamically process and serve optimized images tailored to the accessing device.

If you want to create an ideal image optimization solution, your developers will firstly have to implement device detection capabilities. They will then need to develop the logic to dynamically select optimization operations based on the specific device accessing your web app.

Finally, this code will continually need to be changed and updated. New devices come out all the time with differing properties. What’s more, standards regarding performance as well as image optimization are continually evolving. Even significant changes, additions, or updates to your own image assets may trigger the need to rework your implementation. Not to mention the time it takes to simply stay abreast of the latest information and trends and to make sure development is carried out accordingly.

Another problem is that you will need to continually test and refine your implementation. Without the help of an intelligent optimization engine, you will need to “feel out” how your settings will affect the visual quality of your images and continually fine-tune your approach to get the right results.

This will add a considerable amount of overhead to your development workload in the immediate and long term.

Gatsby also admits that these techniques are quite CPU intensive. In that case, you might want to preoptimize images. However, this also needs to be manually implemented in-code on top of being even less dynamic and flexible.

But, what if there was a better way to optimize your image assets while still enjoying all the benefits of using a platform like Gatsby? The solution I’m about to propose will help solve a number of key issues that arise from using Gatsby (and any development framework, for that matter) for the majority of your image optimization:

  • Reduce the impact optimizing images have on the development and design process in the immediate and long term.
  • Remove an additional burden and responsibility from your developers’ shoulders, freeing up time and resources to work on the primary aspects of your web app.
  • Improve your web app’s ability to dynamically and intelligently optimize image assets for each unique visitor.
  • All of this, while still integrating seamlessly with GatsbyJS as well as your CMS (in most cases).

Introducing a Better Way to Optimize Image Assets: ImageEngine

In short, ImageEngine is an intelligent, device-aware image CDN.

ImageEngine works just like any other CDN (content delivery network), such as Fastly, Akamai, or Cloudflare. However, it specializes in optimizing and serving image content specifically. 

Like its counterparts, you provide ImageEngine with the location where your image files are stored, it pulls them to its own image optimization servers, and then generates and serves optimized variants of images to your site visitors.

In doing this, ImageEngine is designed to decrease image payload, deliver optimized images tailored to each unique device, and serve images from edge nodes across its global CDN

Basically, image CDNs gather information on the accessing device by analyzing the ACCEPT header. A typical ACCEPT header looks like this (for Chrome):

image/avif,image/webp,image/apng,image/*,*/*;q=0.8

As you can see, this only provides the CDN with the accepted image formats and the recommended quality compression.

More advanced CDNs, ImageEngine, included, can also leverage client hints for more in-depth data points, such as the DPR (device pixel ratio) and Viewport-Width. This allows a larger degree of intelligent decision-making to more effectively optimize image assets while preserving visual quality.

However, ImageEngine takes things another step further by being the only mainstream image CDN that has built-in WURFL device detection. This gives ImageEngine the ability to read more information on the device, such as the operating system, resolution, and PPI (pixels per inch).

Using AI and machine-learning algorithms, this extra data means ImageEngine has virtually unparalleled decision-making power. Without any manual intervention, ImageEngine can perform all of the following image optimization operations automatically:

  • Resize your images according to the device screen size without the need for responsive syntax.
  • Intelligently compress the quality of the image to reduce the payload while preserving visual quality, using metrics like the Structural Similarity Index Method (SSIM).
  • Convert images to the most optimal, next-gen encoding formats. On top of WebP and AVIF, ImagEngine also supports JPEG 2000 (Safari), JPEG XR (Internet Explorer & Edge), and MP4 (for aGIFs).

These settings also play well with GatsbyJS’ built-in capabilities. So, you can natively implement breakpoints, lazy-loading, and image placeholders that don’t require any expertise or intelligent decision-making using Gatsby. Then, you can let ImageEngine handle the more advanced and intelligence-driven operations, like quality compression, image formatting, and resizing.

The best part is that ImageEngine does all of this automatically, making it a completely hands-off image optimization solution. ImageEngine will automatically adjust its approach with time as the digital image landscape and standards change, freeing you from this concern.

In fact, ImageEngine recommends using default settings for getting the best results in most situations.

What’s more, this logic is built into the ImageEngine edge servers. Firstly, with over 20 global PoPs, it means that the images are processed and served as close to the end-user as possible. It also means that the majority of processing happens server-side. With the exception of installing the ImageEngine Gatsby plugin, there is virtually no processing overhead at build or runtime.

This type of dynamic, intelligent decision-making will only become more important in the near and medium-term. Thanks to the number and variety of devices growing by the year, it’s becoming harder and harder to implement image optimization in a way that’s sensitive to every device.

That’s why ImageEngine can give you the edge in a mobile-first future that’s continually evolving. Simply put, ImageEngine will help futureproof your Gatsby web app.

How to Integrate ImageEngine with Gatsby: A Quick Guide

Integrating ImageEngine with GatsbyJS is trivial if you have experience installing any other third-party plugins. However, the steps will differ somewhat based on which backend CMS you use with GatsbyJS and where you store your image assets.

For example, you could use it alongside WordPress, Drupal, Contentful, and a range of other popular CMSs.

Usually, your stack would look something like this:

  • A CMS, like Contentful, to host your “space” where you’ll manage your assets and create structured data. Your images will be uploaded and stored in your space.
  • A versioning platform, like Github, to host your code and manage your versions and branches.
  • GatsbyJS to host your workspace, where you’ll build, deploy, and host the front end of your website.

So, the first thing you need to do is set up a site, or project, using GatsbyJS and link it to your CMS.

Next, you’ll install the ImageEngine plugin for GatsbyJS:

npm install @imageengine/gatsby-plugin-imageengine

You’ll also need to create a delivery address for your images via ImageEngine. You can get one by signing up for the 30-day trial here. The only thing you need to do is supply ImageEngine with the host origin URL. For Contentful, it’s images.ctfassets.net and for Sanity.io, it’s cdn.sanity.io.

ImageEngine will then provide you with a delivery address, usually in the format of {random_string}.cdn.imgeng.in.

You’ll use this delivery address to configure the ImageEngine plugin in your gatsby-config.js file. As part of this, you’ll indicate the source (Contentful, e.g.) as well as provide the ImageEngine delivery address. You can find examples of how that’s done in the documentation here.

Note that the ImageEngine plugin features built-in support for Contentful and Sanity.io as asset sources. You can also configure the plugin to pull locally stored images or from another custom source.

Once that’s done, development can begin!

Basically, Gatsby will create Graphql Nodes for the elements created in your CMS (e.g., ContentfulAsset, allSanityImageAsset, etc.). ImageEngine will then create a child node of childImageEngineAsset for each applicable element node.

You’ll then use GraphQL queries in the code for your Gatsby pages to specify the properties of the image variants you want to serve. For example, you can display an image that’s 500 ✕ 300px in the WebP format using the following query:

gatsbyImageData(width: 500, height: 300, format: jpg)

Once again, you should refer to the documentation for a more thorough treatment. You can find guides for integrating ImageEngine with Contentful, Sanity.io, and any other Gatsby project.

For a competent Gatsby user, integrating ImageEngine will only take a few minutes. And, ongoing maintenance will be minimal. If you know how to use GraphQL, then the familiar syntax to send directives and create specific image variants will be nearly effortless and should take about the same time as manually optimizing images using standard Gatsby React.

Conclusion

For most web projects, ImageEngine can reduce image payloads by up to 80%. That number can go up if you have especially high-res images.

However, you can really get the most out of your image optimization by combining the best parts of a static front-end development framework like Gatsby and an image CDN like ImageEngine. Specifically, you can use both to target Google’s core web vitals:

  • ImageEngine’s dynamic, intelligent, run-time optimization will optimize payloads to improve LCP, SI, FCP, and other data size-related metrics.
  • Using Gatsby, you can optimize for CLS and FID using best practices and by natively implementing lazy loading and image placeholders.

ImageEngine provides an Image Speed Test tool where you can quickly evaluate your current performance and see the impact of ImageEngine on key metrics. Even for a simple GatsbyJS project, the results in the Demo tool can be impressive. If you extrapolate those percentages for a larger, image-heavy site, combining Gatsby with ImageEngine could have a dramatic impact on the performance and user experience of your web app. What’s more, your developers will thank you for sparing them from the challenging and time-consuming chore of manual image optimization.


Superior Image Optimization: An Ideal Solution Using Gatsby & ImageEngine originally published on CSS-Tricks. You should get the newsletter.



source https://css-tricks.com/superior-image-optimization-an-ideal-solution-using-gatsby-imageengine/

Thursday, February 24, 2022

Various Meanings of SQL’s PARTITION BY Syntax

For SQL beginners, there’s a bit of an esoteric syntax named PARTITION BY, which appears all over the place in SQL. It always has a similar meaning, though in quite different contexts. The meaning is similar to that of GROUP BY, namely to group/partition data sets by some grouping/partitioning criteria.

For example, when querying the Sakila database:

SELECT actor_id, film_id
FROM film_actor

Something like the below might appear:

|actor_id|film_id|
|--------|-------|
|1       |1      |
|2       |3      |
|10      |1      |
|20      |1      |
|1       |23     |
|1       |25     |
|30      |1      |
|19      |2      |
|40      |1      |
|3       |17     |
|53      |1      |
|19      |3      |
|2       |31     |

And we could partition the data like this for the ACTOR_ID = 1 partition:

                      |actor_id|film_id|
                      |--------|-------|
                 +--> |1       |1      |
All ACTOR_ID = 1 |    |2       |3      |
                 |    |10      |1      |
                 |    |20      |1      |
                 +--> |1       |23     |
                 +--> |1       |25     |
                      |30      |1      |
                      |19      |2      |
                      |40      |1      |
                      |3       |17     |
                      |53      |1      |
                      |19      |3      |
                      |2       |31     |

For the ACTOR_ID = 2 partition:

                      |actor_id|film_id|
                      |--------|-------|
                      |1       |1      |
All ACTOR_ID = 2 +--> |2       |3      |
                 |    |10      |1      |
                 |    |20      |1      |
                 |    |1       |23     |
                 |    |1       |25     |
                 |    |30      |1      |
                 |    |19      |2      |
                 |    |40      |1      |
                 |    |3       |17     |
                 |    |53      |1      |
                 |    |19      |3      |
                 +--> |2       |31     |

How can we use these partitions in SQL, specifically? What do they mean? In short:

A partition separates a data set into subsets, which don’t overlap.

Window partitions

The first thing we can do is the window PARTITION clause, which we use when calculating window functions. For example, we might calculate:

SELECT 
  actor_id, 
  film_id,
  COUNT(*) OVER (PARTITION BY actor_id)
FROM film_actor

If we’re assuming that we’re seeing the entire data set (the actual table has more rows), then the following result would be displayed:

|actor_id|film_id|count|
|--------|-------|-----|
|1       |1      |3    |
|2       |3      |2    |
|10      |1      |1    |
|20      |1      |1    |
|1       |23     |3    |
|1       |25     |3    |
|30      |1      |1    |
|19      |2      |2    |
|40      |1      |1    |
|3       |17     |1    |
|53      |1      |1    |
|19      |3      |2    |
|2       |31     |2    |

In other words, we’re “counting rows over the partition“. It works almost like GROUP BY, where we count rows from the group, though the GROUP BY clause transforms the result set and the projectable columns, making non-grouped columns unavailable:

SELECT actor_id, COUNT(*)
FROM film_actor
GROUP BY actor_id

Resulting in:

|actor_id|count|
|--------|-----|
|1       |3    |
|2       |2    |
|10      |1    |
|20      |1    |
|30      |1    |
|19      |2    |
|40      |1    |
|3       |1    |
|53      |1    |

If you will, the partition contents are now collapsed such that each partition key / group key only appears once in the result set. This difference makes window functions vastly more powerful than ordinary aggregate functions and grouping.

See also our article about GROUP BY for more details.

MATCH_RECOGNIZE partitions

MATCH_RECOGNIZE is part of the SQL standard, invented by Oracle, and the envy of all other RDBMS (though some have started adopting it). It combines the power of regular expressions, pattern matching, data generation and SQL. It might be sentient, who knows.

For example, let’s look at customers who make small payments within a small amount of time. Look at this beauty. Just look!

SELECT 
  customer_id,
  payment_date,
  payment_id, 
  amount
FROM payment
MATCH_RECOGNIZE (

  -- Partition the data set by customer_id
  PARTITION BY customer_id

  -- Order each partition by payment_date
  ORDER BY payment_date

  -- Return all the matched rows
  ALL ROWS PER MATCH

  -- Match rows with 3 occurrences of event "A" in a row
  PATTERN (A {3})

  -- Define the event "A" as...
  DEFINE A AS 

      -- Being a payment whose amount is less than 1
      A.amount < 1 

      -- And whose payment date is less than 1 day after 
      -- the previous payment
      AND A.payment_date - prev(A.payment_date) < 1
)
ORDER BY customer_id, payment_date

Whew! This uses so many fancy keywords this cheap blog’s syntax highlighter here can’t even remotely keep up!

The result is:

|CUSTOMER_ID|PAYMENT_DATE           |PAYMENT_ID|AMOUNT|
|-----------|-----------------------|----------|------|
|72         |2005-08-18 10:59:04.000|1961      |0.99  |
|72         |2005-08-18 16:17:54.000|1962      |0.99  |
|72         |2005-08-19 12:53:53.000|1963      |0.99  |
|152        |2005-08-20 01:16:52.000|4152      |0.99  |
|152        |2005-08-20 19:13:23.000|4153      |0.99  |
|152        |2005-08-21 03:01:01.000|4154      |0.99  |
|207        |2005-07-08 17:14:14.000|5607      |0.99  |
|207        |2005-07-09 01:26:22.000|5608      |0.99  |
|207        |2005-07-09 13:56:56.000|5609      |0.99  |
|244        |2005-08-20 11:54:01.000|6615      |0.99  |
|244        |2005-08-20 17:12:28.000|6616      |0.99  |
|244        |2005-08-21 09:31:44.000|6617      |0.99  |

So, we can confirm that for each of those groups of 3 payments, there are:

  • Amounts less than 1.
  • Consecutive dates less than 1 day apart.
  • Groups are per customer, which is again the partition.

Want to learn more about MATCH_RECOGNIZE? I think this article explains it much better than anything else on the web. You can play around with it for free using the Oracle XE 21c, e.g. available on Docker by Gerald Venzl.

MODEL partitions

Even more arcane than MATCH_RECOGNIZE is the Oracle-specific MODEL or SPREADSHEET clause. Every complex application should have at least one MODEL query just to keep your coworkers wondering. An example can be found in our previous article. In short, you can do anything you could otherwise do in a spreadsheet software, such as MS Excel. I’ll give another example here, without a deep dive into how it works:

SELECT 
  customer_id,
  payment_date,
  payment_id, 
  amount
FROM (
  SELECT *
  FROM (
    SELECT p.*, 0 AS s, 0 AS n
    FROM payment p
  )
  MODEL

    -- We again partition our data set by customer_id
    PARTITION BY (customer_id)

    -- The "spreadsheet dimension" is the row number ordered
    -- by payment date, within a partition
    DIMENSION BY (
      row_number () OVER (
        PARTITION BY customer_id 
        ORDER BY payment_date
      ) AS rn
    )

    -- Measures is what we want to project, including
    -- o Table columns
    -- o Additional calculated values
    MEASURES (payment_date, payment_id, amount, s, n)

    -- These rules are the spreadsheet formulae
    RULES (

      -- S is the sum of previous amounts that are smaller than 1
      -- and whose payment dates are less than 1 day apart
      s[any] = CASE 
          WHEN amount[cv(rn)] < 1 
          AND payment_date[cv(rn)] - payment_date[cv(rn) - 1] < 1 
          THEN coalesce(s[cv(rn) - 1], 0) + amount[cv(rn)] 
          ELSE 0 
      END,

      -- N is the number of consecutive amounts with these properties
      n[any] = CASE 
          WHEN amount[cv(rn)] < 1 
          AND payment_date[cv(rn)] - payment_date[cv(rn) - 1] < 1 
          THEN coalesce(n[cv(rn) - 1], 0) + 1 
          ELSE 0 
      END
    )
) t

-- Filter out only those rows where we had more than 3 
-- consecutive events
WHERE n >= 3
ORDER BY customer_id, rn

Drop one of these into your production code base on Friday before deployment, and you’ll be everyone’s darling, guaranteed.

Anyway, MATCH_RECOGNIZE was a bit nicer, I think. The result is:

|CUSTOMER_ID|PAYMENT_DATE           |PAYMENT_ID|AMOUNT|
|-----------|-----------------------|----------|------|
|72         |2005-08-19 12:53:53.000|1963      |0.99  |
|152        |2005-08-21 03:01:01.000|4154      |0.99  |
|207        |2005-07-09 13:56:56.000|5609      |0.99  |
|244        |2005-08-21 09:31:44.000|6617      |0.99  |
|244        |2005-08-21 19:39:43.000|6618      |0.99  |
|252        |2005-07-28 02:44:25.000|6800      |0.99  |
|377        |2005-07-07 12:24:37.000|10211     |0.99  |
|425        |2005-08-01 12:37:46.000|11499     |0.99  |
|511        |2005-07-11 18:50:55.000|13769     |0.99  |

If you’re in for a thrill, try modifying my query to return the usual triple rows that form a group, just like in the MATCH_RECOGNIZE example, and leave your solution in the comments. It’s definitely doable!

Partitioned tables

At least Oracle and PostgreSQL support table partitioning on a storage level, probably others, too. The feature helps tame your storage trouble by separating data into separate physical tables, while transparently pretending you have a single logical table in your application, and by introducing other kinds of trouble.

The typical example is partitioning data sets by date ranges, e.g. that’s what’s documented in PostgreSQL.

CREATE TABLE payment (
  customer_id int not null,
  amount numeric not null,
  payment_date date not null
)
PARTITION BY RANGE (payment_date);

Now, we cannot use this table yet, because it only exists logically. It doesn’t know yet how to store data physically:

INSERT INTO payment (customer_id, amount, payment_date)
VALUES (1, 10, DATE '2000-01-01');

This produces:

SQL Error [23514]: ERROR: no partition of relation “payment” found for row
Detail: Partition key of the failing row contains (payment_date) = (2000-01-01).

So, let’s create some physical storage for a certain date range, e.g.:

CREATE TABLE payment_2000
PARTITION OF payment
FOR VALUES FROM (DATE '2000-01-01') TO (DATE '2000-12-31');

Now, the insert works. This interpretation of PARTITION again matches the window function one, where we partition our data set into subsets, which are clearly separated without overlaps.

Weird one: Outer Join Partitions

The next partitioning feature is part of the SQL standard, but I’ve only seen it being implemented in Oracle, so far, which has had it forever: partitioned outer joins. They’re not trivial to explain, and regrettably, their partitions have nothing to do with the window partitions. They’re more like CROSS JOIN syntax sugar (or vinegar, depending on your tastes).

Think about them this way, you can use partitioned outer joins to fill gaps in otherwise sparse data. Let’s look at an example:

SELECT
  f.film_id,
  f.title,
  c.category_id,
  c.name,
  count(*) OVER ()
FROM film f
  LEFT OUTER JOIN film_category fc
    ON f.film_id = fc.film_id
  LEFT OUTER JOIN category c
    ON fc.category_id = c.category_id
ORDER BY f.film_id, c.category_id

This query produces the category per film. If a category does not appear with a film, there’s no record in the result:

|FILM_ID|TITLE           |CATEGORY_ID|NAME       |COUNT(*)OVER()|
|-------|----------------|-----------|-----------|--------------|
|1      |ACADEMY DINOSAUR|6          |Documentary|1000          |
|2      |ACE GOLDFINGER  |11         |Horror     |1000          |
|3      |ADAPTATION HOLES|6          |Documentary|1000          |
|4      |AFFAIR PREJUDICE|11         |Horror     |1000          |
|5      |AFRICAN EGG     |8          |Family     |1000          |
|6      |AGENT TRUMAN    |9          |Foreign    |1000          |
|7      |AIRPLANE SIERRA |5          |Comedy     |1000          |
|8      |AIRPORT POLLOCK |11         |Horror     |1000          |
|9      |ALABAMA DEVIL   |11         |Horror     |1000          |
|10     |ALADDIN CALENDAR|15         |Sports     |1000          |

As you can see, we have 1000 films, and because the Sakila database is so boring, every film only has 1 category, even if the many-to-many relationship would allow for more than one assignment.

What happens if we add a PARTITION BY clause to one of the outer joins?

SELECT
  f.film_id,
  f.title,
  c.category_id,
  c.name,
  count(*) OVER ()
FROM film f
  LEFT OUTER JOIN film_category fc
    ON f.film_id = fc.film_id
  LEFT OUTER JOIN category c
  PARTITION BY (c.category_id) -- Magic here
    ON fc.category_id = c.category_id
ORDER BY f.film_id, c.category_id

I won’t show the entire result, but as you can see with the window function result, we now have 16000 rows in total, not 1000. That’s because we have 1000 films x 16 categories, so a cross product with blank category names (but not blank category IDs) in case there’s no match, if you will:

|FILM_ID|TITLE           |CATEGORY_ID|NAME       |COUNT(*)OVER()|
|-------|----------------|-----------|-----------|--------------|
|1      |ACADEMY DINOSAUR|1          |           |16000         |
|1      |ACADEMY DINOSAUR|2          |           |16000         |
|1      |ACADEMY DINOSAUR|3          |           |16000         |
|1      |ACADEMY DINOSAUR|4          |           |16000         |
|1      |ACADEMY DINOSAUR|5          |           |16000         |
|1      |ACADEMY DINOSAUR|6          |Documentary|16000         |
|1      |ACADEMY DINOSAUR|7          |           |16000         |
|1      |ACADEMY DINOSAUR|8          |           |16000         |
|1      |ACADEMY DINOSAUR|9          |           |16000         |
|1      |ACADEMY DINOSAUR|10         |           |16000         |
|1      |ACADEMY DINOSAUR|11         |           |16000         |
|1      |ACADEMY DINOSAUR|12         |           |16000         |
|1      |ACADEMY DINOSAUR|13         |           |16000         |
|1      |ACADEMY DINOSAUR|14         |           |16000         |
|1      |ACADEMY DINOSAUR|15         |           |16000         |
|1      |ACADEMY DINOSAUR|16         |           |16000         |
|2      |ACE GOLDFINGER  |1          |           |16000         |
|2      |ACE GOLDFINGER  |2          |           |16000         |
|2      |ACE GOLDFINGER  |3          |           |16000         |
|2      |ACE GOLDFINGER  |4          |           |16000         |
|2      |ACE GOLDFINGER  |5          |           |16000         |
|2      |ACE GOLDFINGER  |6          |           |16000         |
|2      |ACE GOLDFINGER  |7          |           |16000         |
|2      |ACE GOLDFINGER  |8          |           |16000         |
|2      |ACE GOLDFINGER  |9          |           |16000         |
|2      |ACE GOLDFINGER  |10         |           |16000         |
|2      |ACE GOLDFINGER  |11         |Horror     |16000         |
|2      |ACE GOLDFINGER  |12         |           |16000         |
|2      |ACE GOLDFINGER  |13         |           |16000         |
|2      |ACE GOLDFINGER  |14         |           |16000         |
|2      |ACE GOLDFINGER  |15         |           |16000         |
|2      |ACE GOLDFINGER  |16         |           |16000         |

In a way, this is useful whenever you want to create a report based on sparse data, and generate records for those gaps. A similar query without PARTITION BY would be on using CROSS JOIN

SELECT
  f.film_id,
  f.title,
  c.category_id,
  NVL2(fc.category_id, c.name, NULL) AS name,
  count(*) OVER ()
FROM film f
  CROSS JOIN category c
  LEFT JOIN film_category fc
    ON fc.film_id = f.film_id
    AND fc.category_id = c.category_id
ORDER BY f.film_id, c.category_id;

I must say, I haven’t found these partitioned outer join very useful or intelligible in the past, and I’m not convinced that other RDBMS are really lacking some important feature here, despite this being standard SQL.

So far, jOOQ doesn’t emulate the feature in other RDBMS yet.



from Java, SQL and jOOQ. https://ift.tt/mRMnUEa
via IFTTT

CSS Database Queries? Sure We Can!

Kinda silly sounding, isn’t it? CSS database queries. But, hey, CSS is capable of talking to other languages in the sense that it can set the values of things that they can read. Plus, CSS can request other files, and I suppose a server could respond to that request with something it requested from a database.

But I’m getting ahead of myself. The idea of CSS database queries was a joke tweet going around the other day about recruiters looking for a developer who can connect to a database with CSS. Lee Mei Chin (total guess on the name there based on the domain name) wrote “Yes, I can connect to a DB in CSS” as an equally funny retort.

Screenshot of a tweet sarcastically looking for someone who can do CSS database queries.

What’s the trick behind CSS database queries?

It’s nicely elaborate:

  1. Use a hand-modified-to-ESM version of SQL.js, which is SQLite in JavaScript.
  2. Get a database ready that SQL.js can query.
  3. Build a Houdini PaintWorklet that executes queries in JavaScript and paints the results back to the screen in that <canvas>-y way that PaintWorklets do.
  4. Pass the query you want to run into the worklet by way of a CSS custom property.

So, the usage is like this in the end:

<script>
  CSS.paintWorklet.addModule('./cssdb.js')
</script>
<style>
  main {
    --sql-query: SELECT name FROM test;
    background: paint(sql-db);
  }
</style>

Which, you gotta admit, is connecting and querying a database in CSS.

This reminds me that Simon Willison did this last year with a totally different approach. His concept was that you have RESTful endpoints, like /api/roadside_attractions, that return JSON data. But then as an alternative endpoint, you could make that /api/roadside_attractions.css which would return a valid CSS file with all the data as CSS custom properties.

So, instead it looks like this:

<link rel="stylesheet" href="/api/roadside_attractions.css">

<style>
  .attraction-name:after { content: var(--name); }
  .attraction-address:after { content: var(--address); }
</style>

<p class="attraction-name">Attraction name: </p>
<p class="attraction-address">Address: </p>

Which, again, is essentially connecting to a database in CSS (with HTML required, though). You can literally see it work.


CSS Database Queries? Sure We Can! originally published on CSS-Tricks. You should get the newsletter.



source https://css-tricks.com/css-database-queries/

5 Accessibility Quick Wins You Can Implement Today

Let’s face it: building an AA or AAA-accessible product can be quite daunting. Luckily, having an accessible product isn’t all-or-nothing. Even seemingly small improvements can have nice quality of life benefits for many people.

In that spirit, here are five accessibility quick wins you can implement today.

Quick Win 1: Indicate the Current Page

It’s probably safe to assume that a different style is the most common way to communicate the current page of a site or app. However, even if those styles are clear and with great contrast ratios, they’re still only a visual cue.

So what happens if a person with limited vision cannot see that separation? How will they know what page they’re on?

Creating an accessible product is to ensure its markup communicates as clearly as its design.

Adding aria-current="page" to the active navigation element is one way to ensure markup and design communicate the same information with or without assistive technologies.

<a aria-current="page" href="/">Home</a>

🎉 Bonus

Use CSS attribute selectors to style the aria-current="page" element to keep the visual and markup cues linked.

[aria-current="page"] { 
  /* Active element styles */
}

Quick Win 2: Document Language

While some people can visit a website and determine the language or locale of its content, not all people have that luxury. Again, markup must communicate the same information as the visual design — even if that information may seem implied.

Add the lang attribute to the <html> tag to communicate not only the document’s language, but its locale. This will help assistive technologies like screen readers understand and communicate the content. Even if the app only supports one language, this can be a nice quality of life improvement for many people.

<html lang="en-US">

For apps which support multiple languages, the <html> element is likely not the only one to need its lang value defined. Use the lang attribute on specific elements whose language differs from the rest of the document, like links within a language toggle menu. In this case, pair the use of lang with the hreflang attribute to not only communicate the language of the link itself, but also of its destination.

<a lang="fi" hreflang="fi" href="/" title="Suomeksi">
  <bdi>Suomeksi</bdi>
</a>

Quick Win 3: Use prefers-reduced-motion

Whether drawing attention to actions or updates, or creating a sense of life and charm, adding motion to an app can really elevate its experience. However, some people may find that experience disorienting.

Windows and MacOS both offer a setting at the OS level for people to greatly reduce the amount of motion when using their systems. The prefers-reduced-motion setting can greatly improve the experience on a computer, but it does not extends beyond the UI of the operating system. So wouldn’t it be nice if our apps could respect that same system setting and provide a more static experience for those who prefer it?

Well, with CSS media queries, they can.

The prefers-reduced-motion media query can be used to greatly reduce or remove all motion from an app whenever the system setting is enabled.

@media (prefers-reduced-motion: reduce) {
  * {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

The blanket approach shown here prevents all motion, but it can leave little room for nuance. It’d be best to review the needs of those using the product, but consider these other options as well.

One approach could be to only animate one property at a time in prefers-reduced-motion settings. So consider a <Modal /> that fades and scales into view with opacity and transform. In reduced motion environments, only the opacity would transition. The scaling effect would be removed as they are more commonly problematic than fading.

Another option could be to look at the prefers-reduced-motion environment a bit more literally and remove all motion. This would do away with our scaling modals, sliding drawers, and bouncing notifications, but would still leave room for color transitions on links and buttons.

Quick Win 4: Indicate Data Sorting State

A common theme across all of these tips is to ensure that an app’s visual design and markup communicate the same things. So, when the design uses an arrow to indicate the sort direction of a table column, how can that also be communicated in the markup?

Setting the aria-sort attribute to ascending /descending on the header of the actively-sorted column allows the markup to communicate the same content structure as a visual indicator in the UI.

This will help ensure that people using assistive technologies and those who aren’t can understand the content in the same way.

<thead>
  <tr>
    <th>First Name</th>
    <th aria-sort="ascending">Last Name</th>
  </tr>
</thead>

Quick Win 5: Lazy Loading Lists

Whether scrolling through an endless stream of tweets or through an impossible-to-decide list of products, the web has fully embraced lazy loading long lists of data (and alliteration, apparently).

This is when the aria-setsize and aria-posinset attributes become very valuable. While a person’s progression through the list can be communicated visually in many different ways, these attributes are used to communicate that same progression to many assistive technologies.

As developers, we likely have access to the length of an entire list as well as the index of the current items being displayed. With that, the aria-setsize attribute would define the total length of the list, while the aria-posinset attribute would define an item’s specific position (or index) within that list.

If the total length of the list is not known, then aria-setsize should be set to -1.

With these attributes, assistive technologies can better interpret a list and a person can better understand their position within it.

<h2 id="top-artists-title">Top Artists of 2021</h2>
<ul role="listbox" aria-labelledby="top-artists-title">
  <li role="option" aria-setsize="20" aria-posinset="5">Bloodbound</li>
  <li role="option" aria-setsize="20" aria-posinset="6">Manimal</li>
  <li role="option" aria-setsize="20" aria-posinset="7">Powerwolf</li>
</ul>

Take a listen to how these attributes are announced using MacOS VoiceOver.

🎉 Bonus Win: Axe-DevTools Extension

Implementing those five accessibility quick wins is a great start, but that’s exactly what it is —a start. There’s a sprawling landscape of assistive technologies and sets of abilities a person can posses, and navigating it all alone can feel overwhelming.

Fortunately, there are plenty of tools to help with auditing a product’s accessibility that make the journey much more manageable. My personal favorite — my trusty accessibility compass — is the Axe-DevTools browser extension.

Running the Axe-DevTools accessibility scanner can return tons of valuable information. Not only will it display all issues and warnings found on the page, but it groups them by approximate severity. It can also highlight the element on the page or in the Elements tab and provide links to learn more about the specific issue.

However, most importantly, it will offer clear and concise approaches to fix the specific issue.

A screenshot of an Axe-DevTools accessibility report. Using the extension can lead to accessibility quick wins.

Wrapping Up

A product isn’t made accessible overnight; nor is a product’s accessibility work ever really complete. Like anything else on the web, accessibility evolves and requires maintenance. However, even seemingly small additions can have an impact on a product’s accessibility and a person’s overall experience.

After stepping into a new codebase, these are often some of the first few things I look into — some “low-hanging fruit” of accessibility, if you will.

Reaching AAA or even AA conformance can feel like scaling an 8,000 meter peak. These steps won’t carry you to the summit, but an expedition is never completed in a single stride.

Resources


5 Accessibility Quick Wins You Can Implement Today originally published on CSS-Tricks. You should get the newsletter.



source https://css-tricks.com/5-accessibility-quick-wins-you-can-implement-today/