Archiwum kategorii: CSS

Building a Blog with Next.js

Post pobrano z: Building a Blog with Next.js

In this article, we will use Next.js to build a static blog framework with the design and structure inspired by Jekyll. I’ve always been a big fan of how Jekyll makes it easier for beginners to setup a blog and at the same time also provides a great degree of control over every aspect of the blog for the advanced users.

With the introduction of Next.js in recent years, combined with the popularity of React, there is a new avenue to explore for static blogs. Next.js makes it super easy to build static websites based on the file system itself with little to no configuration required.

The directory structure of a typical bare-bones Jekyll blog looks like this:

.
├─── _posts/          ...blog posts in markdown
├─── _layouts/        ...layouts for different pages
├─── _includes/       ...re-usable components
├─── index.md         ...homepage
└─── config.yml       ...blog config

The idea is to design our framework around this directory structure as much as possible so that it becomes easier to  migrate a blog from Jekyll by simply reusing the posts and configs defined in the blog.

For those unfamiliar with Jekyll, it is a static site generator that can transform your plain text into static websites and blogs. Refer the quick start guide to get up and running with Jekyll.

This article also assumes that you have a basic knowledge of React. If not, React’s getting started page is a good place to start.

Installation

Next.js is powered by React and written in Node.js. So we need to install npm first, before adding next, react and react-dom to the project.

mkdir nextjs-blog && cd $_
npm init -y
npm install next react react-dom --save

To run Next.js scripts on the command line, we have to add the next command to the scripts section of our package.json.

"scripts": {
  "dev": "next"
}

We can now run npm run dev on the command line for the first time. Let’s see what happens.

$ npm run dev
> nextjs-blog@1.0.0 dev /~user/nextjs-blog
> next

ready - started server on http://localhost:3000
Error: > Couldn't find a `pages` directory. Please create one under the project root

The compiler is complaining about a missing pages directory in the root of the project. We’ll learn about the concept of pages in the next section.

Concept of pages

Next.js is built around the concept of pages. Each page is a React component that can be of type .js or .jsx which is mapped to a route based on the filename. For example:

File                            Route
----                            -----
/pages/about.js                 /about
/pages/projects/work1.js        /projects/work1
/pages/index.js                 /

Let’s create the pages directory in the root of the project and populate our first page, index.js, with a basic React component.

// pages/index.js
export default function Blog() {
  return <div>Welcome to the Next.js blog</div>
}

Run npm run dev once again to start the server and navigate to http://localhost:3000 in the browser to view your blog for the first time.

Screenshot of the homepage in the browser. The content says welcome to the next.js blog.

Out of the box, we get:

  • Hot reloading so we don’t have to refresh the browser for every code change.
  • Static generation of all pages inside the /pages/** directory.
  • Static file serving for assets living in the/public/** directory.
  • 404 error page.

Navigate to a random path on localhost to see the 404 page in action. If you need a custom 404 page, the Next.js docs have great information.

Screenshot of the 404 page. It says 404 This page could not be found.

Dynamic pages

Pages with static routes are useful to build the homepage, about page, etc. However, to dynamically build all our posts, we will use the dynamic route capability of Next.js. For example:

File                        Route
----                        -----
/pages/posts/[slug].js      /posts/1
                            /posts/abc
                            /posts/hello-world

Any route, like /posts/1, /posts/abc, etc., will be matched by /posts/[slug].js and the slug parameter will be sent as a query parameter to the page. This is especially useful for our blog posts because we don’t want to create one file per post; instead we could dynamically pass the slug to render the corresponding post.

Anatomy of a blog

Now, since we understand the basic building blocks of Next.js, let’s define the anatomy of our blog.

.
├─ api
│  └─ index.js             # fetch posts, load configs, parse .md files etc
├─ _includes
│  ├─ footer.js            # footer component
│  └─ header.js            # header component
├─ _layouts
│  ├─ default.js           # default layout for static pages like index, about
│  └─ post.js              # post layout inherts from the default layout
├─ pages
│  ├─ index.js             # homepage
|  └─ posts                # posts will be available on the route /posts/
|     └─ [slug].js       # dynamic page to build posts
└─ _posts
   ├─ welcome-to-nextjs.md
   └─ style-guide-101.md

Blog API

A basic blog framework needs two API functions: 

  • A function to fetch the metadata of all the posts in _posts directory
  • A function to fetch a single post for a given slug with the complete HTML and metadata

Optionally, we would also like all the site’s configuration defined in config.yml to be available across all the components. So we need a function that will parse the YAML config into a native object.

Since, we would be dealing with a lot of non-JavaScript files, like Markdown (.md), YAML (.yml), etc, we’ll use the raw-loader library to load such files as strings to make it easier to process them. 

npm install raw-loader --save-dev

Next we need to tell Next.js to use raw-loader when we import .md and .yml file formats by creating a next.config.js file in the root of the project (more info on that).

module.exports = {
  target: 'serverless',
  webpack: function (config) {
    config.module.rules.push({test:  /\.md$/, use: 'raw-loader'})
    config.module.rules.push({test: /\.yml$/, use: 'raw-loader'})
    return config
  }
}

Next.js 9.4 introduced aliases for relative imports which helps clean up the import statement spaghetti caused by relative paths. To use aliases, create a jsconfig.json file in the project’s root directory specifying the base path and all the module aliases needed for the project.

{
  "compilerOptions": {
    "baseUrl": "./",
    "paths": {
      "@includes/*": ["_includes/*"],
      "@layouts/*": ["_layouts/*"],
      "@posts/*": ["_posts/*"],
      "@api": ["api/index"],
    }
  }
}

For example, this allows us to import our layouts by just using:

import DefaultLayout from '@layouts/default'

Fetch all the posts

This function will read all the Markdown files in the _posts directory, parse the front matter defined at the beginning of the post using gray-matter and return the array of metadata for all the posts.

// api/index.js
import matter from 'gray-matter'


export async function getAllPosts() {
  const context = require.context('../_posts', false, /\.md$/)
  const posts = []
  for(const key of context.keys()){
    const post = key.slice(2);
    const content = await import(`../_posts/${post}`);
    const meta = matter(content.default)
    posts.push({
      slug: post.replace('.md',''),
      title: meta.data.title
    })
  }
  return posts;
}

A typical Markdown post looks like this:

---
title:  "Welcome to Next.js blog!"
---
**Hello world**, this is my first Next.js blog post and it is written in Markdown.
I hope you like it!

The section outlined by --- is called the front matter which holds the metadata of the post like, title, permalink, tags, etc. Here’s the output:

[
  { slug: 'style-guide-101', title: 'Style Guide 101' },
  { slug: 'welcome-to-nextjs', title: 'Welcome to Next.js blog!' }
]

Make sure you install the gray-matter library from npm first using the command npm install gray-matter --save-dev.

Fetch a single post

For a given slug, this function will locate the file in the _posts directory, parse the Markdown with the marked library and return the output HTML with metadata.

// api/index.js
import matter from 'gray-matter'
import marked from 'marked'


export async function getPostBySlug(slug) {
  const fileContent = await import(`../_posts/${slug}.md`)
  const meta = matter(fileContent.default)
  const content = marked(meta.content)    
  return {
    title: meta.data.title, 
    content: content
  }
}

Sample output:

{
  title: 'Style Guide 101',
  content: '<p>Incididunt cupidatat eiusmod ...</p>'
}

Make sure you install the marked library from npm first using the command npm install marked --save-dev.

Config

In order to re-use the Jekyll config for our Next.js blog, we’ll parse the YAML file using the js-yaml library and export this config so that it can be used across components.

// config.yml
title: "Next.js blog"
description: "This blog is powered by Next.js"


// api/index.js
import yaml from 'js-yaml'
export async function getConfig() {
  const config = await import(`../config.yml`)
  return yaml.safeLoad(config.default)
}

Make sure you install js-yaml from npm first using the command npm install js-yaml --save-dev.

Includes

Our _includes directory contains two basic React components, <Header> and <Footer>, which will be used in the different layout components defined in the _layouts directory.

// _includes/header.js
export default function Header() {
  return <header><p>Blog | Powered by Next.js</p></header>
}


// _includes/footer.js
export default function Footer() {
  return <footer><p>©2020 | Footer</p></footer>
}

Layouts

We have two layout components in the _layouts directory. One is the <DefaultLayout> which is the base layout on top of which every other layout component will be built.

// _layouts/default.js
import Head from 'next/head'
import Header from '@includes/header'
import Footer from '@includes/footer'


export default function DefaultLayout(props) {
  return (
    <main>
      <Head>
        <title>{props.title}</title>
        <meta name='description' content={props.description}/>
      </Head>
      <Header/>
      {props.children}
      <Footer/>
    </main>
  )
}

The second layout is the <PostLayout> component that will override the title defined in the <DefaultLayout> with the post title and render the HTML of the post. It also includes a link back to the homepage.

// _layouts/post.js
import DefaultLayout from '@layouts/default'
import Head from 'next/head'
import Link from 'next/link'


export default function PostLayout(props) {
  return (
    <DefaultLayout>
      <Head>
        <title>{props.title}</title>
      </Head>
      <article>
        <h1>{props.title}</h1>
        <div dangerouslySetInnerHTML={{__html:props.content}}/>
        <div><Link href='/'><a>Home</a></Link></div> 
      </article>
    </DefaultLayout>
  )
}

next/head is a built-in component to append elements to the <head> of the page. next/link is a built-in component that handles client-side transitions between the routes defined in the pages directory.

Homepage

As part of the index page, aka homepage, we will list all the posts inside the _posts directory. The list will contain the post title and the permalink to the individual post page. The index page will use the <DefaultLayout> and we’ll import the config in the homepage to pass the title and description to the layout.

// pages/index.js
import DefaultLayout from '@layouts/default'
import Link from 'next/link'
import { getConfig, getAllPosts } from '@api'


export default function Blog(props) {
  return (
    <DefaultLayout title={props.title} description={props.description}>
      <p>List of posts:</p>
      <ul>
        {props.posts.map(function(post, idx) {
          return (
            <li key={idx}>
              <Link href={'/posts/'+post.slug}>
                <a>{post.title}</a>
              </Link>
            </li>
          )
        })}
      </ul>
    </DefaultLayout>
  )
} 


export async function getStaticProps() {
  const config = await getConfig()
  const allPosts = await getAllPosts()
  return {
    props: {
      posts: allPosts,
      title: config.title,
      description: config.description
    }
  }
}

getStaticProps is called at the build time to pre-render pages by passing props to the default component of the page. We use this function to fetch the list of all posts at build time and render the posts archive on the homepage.

Screenshot of the homepage showing the page title, a list with two post titles, and the footer.

Post page

This page will render the title and contents of the post for the slug supplied as part of the context. The post page will use the <PostLayout> component.

// pages/posts/[slug].js
import PostLayout from '@layouts/post'
import { getPostBySlug, getAllPosts } from "@api"


export default function Post(props) {
  return <PostLayout title={props.title} content={props.content}/>
}


export async function getStaticProps(context) {
  return {
    props: await getPostBySlug(context.params.slug)
  }
}


export async function getStaticPaths() {
  let paths = await getAllPosts()
  paths = paths.map(post => ({
    params: { slug:post.slug }
  }));
  return {
    paths: paths,
    fallback: false
  }
}

If a page has dynamic routes, Next.js needs to know all the possible paths at build time. getStaticPaths supplies the list of paths that has to be rendered to HTML at build time. The fallback property ensures that if you visit a route that does not exist in the list of paths, it will return a 404 page.

Screenshot of the blog page showing a welcome header and a hello world blue above the footer.

Production ready

Add the following commands for build and start in package.json, under the scripts section and then run npm run build followed by npm run start to build the static blog and start the production server.

// package.json
"scripts": {
  "dev": "next",
  "build": "next build",
  "start": "next start"
}

The entire source code in this article is available on this GitHub repository. Feel free to clone it locally and play around with it. The repository also includes some basic placeholders to apply CSS to your blog.

Improvements

The blog, although functional, is perhaps too basic for most average cases. It would be nice to extend the framework or submit a patch to include some more features like:

  • Pagination
  • Syntax highlighting
  • Categories and Tags for posts
  • Styling

Overall, Next.js seems really very promising to build static websites, like a blog. Combined with its ability to export static HTML, we can built a truly standalone app without the need of a server!

The post Building a Blog with Next.js appeared first on CSS-Tricks.

An Eleventy Starter with Tailwind CSS and Alpine.js

Post pobrano z: An Eleventy Starter with Tailwind CSS and Alpine.js

When I decided to try to base my current personal website on Eleventy, I didn’t want to reinvent the wheel: I tested all the Eleventy starters built with Tailwind CSS that I could find in Starter Projects from the documentation.

Many of the starters seemed to integrate Tailwind CSS in a contrived way. Also, some of them seemed to assume that no one updates Tailwind’s configuration on the fly while working on a website. That’s why I integrated Eleventy with Tailwind CSS and Alpine.js myself. I have reason to believe that you’ll like the simplicity of my solution.

Good design is as little design as possible.

—Dieter Rams, 10 Principles for Good Design

If you’re uninterested in the details, feel free to grab my starter and jump right in.

Getting started

I’m going to assume you have a general understanding of Tailwind CSS, HTML, JavaScript, Nunjucks, the command line, and npm.

Let’s start by with a new a folder, then cd to it in the command line, and initialize it with a package.json file:

npm init -y

Now we can install Eleventy and Tailwind CSS. We’ll throw in PostCSS as well:

npm install --save-dev @11ty/eleventy tailwindcss postcss-cli

We need to create a page to test whether we’ve successfully set things up. In a real use case, our pages will use templates, so we’ll do that here as well. That’s where Nunjucks fits into the mix, serving as a templating engine.

Let’s make a new file called index.njk in the project folder. We’ll designate it as the homepage:

{% extends "_includes/default.njk" %}


{% block title %}It does work{% endblock %}


{% block content %}
  <div class="fixed inset-0 flex justify-center items-center">
    <div>
      <span class="text-change">Good design</span><br/>
      <span class="change">is<br/>as little design<br/>as possible</span>
    </div>
  </div>
{% endblock %}

Basic templating

Now let’s create a new folder in the project folder called _includes (and yes, the folder name matters). Inside this new folder, we’ll create a file called default.njk that we’ll use as the default template for our layout. We’ll keep things simple with a basic HTML boilerplate:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>
      {% block title %}Does it work?{% endblock %}
    </title>
    <meta charset="UTF-8"/>
    {% if description %}
      <meta name="description" content="{{description}}"/>
    {% endif %}
    <meta http-equiv="x-ua-compatible" content="ie=edge"/>
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, viewport-fit=cover"/>
    <link rel="stylesheet" href="/style.css?v={% version %}"/>
    {% block head %}{% endblock %}
  </head>
  <body>
    {% block content %}
      {{ content | safe }}
    {% endblock %}
  </body>
</html>

Configuring Tailwind CSS

Let’s take care of a test for Tailwind CSS in as few moves as possible. First, create a new subfolder called styles and a file in it called tailwind.config.js:

module.exports = {
  theme: {
    colors: {
      change: "transparent"
    }
  },
  variants: {},
  plugins: [],
}

Then, create a file called tailwind.css in that same styles folder:

/* purgecss start ignore */
@tailwind base;
/* purgecss end ignore */


.change {
  color: transparent;
}


/* purgecss start ignore */
@tailwind components;
/* purgecss end ignore */


@tailwind utilities;

We’re done with the styles folder for now. What we do need is a configuration file that tells PostCSS to use Tailwind CSS, which we can get by creating a new file in the root directory of the project folder called postcss.config.js. Here’s how we require Tailwind CSS and its configuration file with PostCSS:

module.exports = {
  plugins: [
    require(`tailwindcss`)(`./styles/tailwind.config.js`)
  ],
};

Starting and building the project

Now let’s create another new file in the same root directory called .gitignore. This will allow us to define what files to skip when committing the project to a repo, like on GitHub:

_site/
_tmp/
.DS_Store
node_modules/
package-lock.json

Next, is another new file, this time one that tells Eleventy what it can ignore, called .eleventyignore. It only needs one line:

node_modules

OK, now we will create a file called .eleventy.js (note the leading dot!) that basically configures Eleventy, telling it what files to watch and where to save its work:

module.exports = function (eleventyConfig) {
  eleventyConfig.setUseGitIgnore(false);


  eleventyConfig.addWatchTarget("./_tmp/style.css");


  eleventyConfig.addPassthroughCopy({ "./_tmp/style.css": "./style.css" });


  eleventyConfig.addShortcode("version", function () {
    return String(Date.now());
  });
};

We can now update the package.json file with all of the scripts we need to start and build the site during development. The dependencies should already be there from the initial setup.

{
  "scripts": {
    "start": "eleventy --serve & postcss styles/tailwind.css --o _tmp/style.css --watch",
    "build": "ELEVENTY_PRODUCTION=true eleventy & ELEVENTY_PRODUCTION=true postcss styles/tailwind.css --o _site/style.css"
  },
  "devDependencies": {
    "@11ty/eleventy": "^0.11.0",
    "postcss-cli": "^7.1.0",
    "tailwindcss": "^1.4.6"
  }
}

Hey, great job! We made it. Build the project to generate the initial CSS — this step is only required the very first time we set up. From the command line:

npm run build

And — drumroll, please — let’s officially start the site:

npm run start

Open the page http://localhost:8080 in your browser. It’s not gonna look like much, but check out the page title in the browser tab:

It does work!

We can still do a little more checking to make sure everything’s good. Open up /styles/tailwind.config.js and change the transparent color value to something else, say black. Tailwind’s configuration should reload, along with the page in your browser.

Don’t lose sight of your browser and edit /styles/tailwind.css by changing transparent to black again. Your CSS file should reload and refresh in your browser.

Now we can work nicely with Eleventy and Tailwind CSS!

Optimizing the output

At this point, Tailwind CSS works with Eleventy, but the CSS file is huge. The generated HTML isn’t perfect either because it contains stuff like redundant newline characters. Let’s clean it up:

npm install --save-dev @fullhuman/postcss-purgecss postcss-clean html-minifier

Open up postcss.config.js and replace what’s in there with this:

module.exports = {
  plugins: [
    require(`tailwindcss`)(`./styles/tailwind.config.js`),
    require(`autoprefixer`),
    ...(process.env.ELEVENTY_PRODUCTION
      ? [
          require(`postcss-clean`),
          require(`@fullhuman/postcss-purgecss`)({
            content: ["_site/**/*.html"],
            defaultExtractor: (content) =>
              content.match(/[\w-/:]+(?<!:)/g) || [],
            whitelist: [],
            whitelistPatterns: [/body/, /headroom/, /ril/],
          }),
        ]
      : []),
  ],
};

In the future, in the process of creating your website, if something looks wrong after you build the project and you have the impression that fragments of CSS are missing, add “ignored” class names to the whitelist in the file above.

Add the following line to the beginning of the .eleventy.js file:

const htmlmin = require("html-minifier");

We also need to configure htmlmin in .eleventy.js as well:

eleventyConfig.addTransform("htmlmin", function (content, outputPath) {
    if (
      process.env.ELEVENTY_PRODUCTION &&
      outputPath &&
      outputPath.endsWith(".html")
    ) {
      let minified = htmlmin.minify(content, {
        useShortDoctype: true,
        removeComments: true,
        collapseWhitespace: true,
      });
      return minified;
    }


    return content;
});

We’re using a transform here which is an Eleventy thing. Transforms can modify a template’s output. At this point, .eleventy.js should look like this:

const htmlmin = require("html-minifier");


module.exports = function (eleventyConfig) {
  eleventyConfig.setUseGitIgnore(false);


  eleventyConfig.addWatchTarget("./_tmp/style.css");


  eleventyConfig.addPassthroughCopy({ "./_tmp/style.css": "./style.css" });


  eleventyConfig.addShortcode("version", function () {
    return String(Date.now());
  });


  eleventyConfig.addTransform("htmlmin", function (content, outputPath) {
    if (
      process.env.ELEVENTY_PRODUCTION &&
      outputPath &&
      outputPath.endsWith(".html")
    ) {
      let minified = htmlmin.minify(content, {
        useShortDoctype: true,
        removeComments: true,
        collapseWhitespace: true,
      });
      return minified;
    }


    return content;
  });
};

Alright, let’s run npm run start once again. You’ll see that nothing has changed and that’s because optimization only happens during build. So, instead, let’s try npm run build and then look at the _site folder. There shouldn’t be a single unnecessary character in the index.html file. The same goes for the style.css file.

A project built like this is now ready to deploy. Good job! 🏆

Integrating Alpine.js

I decided to switch to Eleventy from Gatsby.js because it just felt like too much JavaScript to me. I’m more into the reasonable dose of vanilla JavaScript mixed with Alpine.js. We won’t get into the specifics of Alpine.js here, but it’s worth checking out Hugo DiFrancesco’s primer because it’s a perfect starting point.

Here’s how we can install it to our project from the command line:

npm install --save-dev alpinejs

Now we need to update .eleventy.js with this to the function that passes things through Alpine.js:

eleventyConfig.addPassthroughCopy({
  "./node_modules/alpinejs/dist/alpine.js": "./js/alpine.js",
});

Lastly, we’ll open up _includes/default.njk and add Alpine.js right before the closing </head> tag:

<script src="/js/alpine.js?v={% version %}"></script>

We can check if Alpine is working by adding this to index.njk:

{% extends "_includes/default.njk" %}


{% block title %}It does work{% endblock %}


{% block content %}
  <div class="fixed inset-0 flex justify-center items-center">
    <div>
      <span class="text-change">Good design</span><br/>
      <span class="change">is<br/>as little design<br/>as possible</span><br/>
      <span x-data="{message:'🤖 Hello World 🤓'}" x-text="message"></span>
    </div>
  </div>
{% endblock %}

Launch the project:

npm run start

If Alpine.js works, you’ll see “Hello World” in your browser. Congratulations, times two! 🏆🏆


I hope you can see how quick it can be to set up an Eleventy project, including integrations with Nunjucks for templating, Tailwind for styles and Alpine.js for scripts. I know working with new tech can be overwhelming and even confusing, so feel free to email me at csstricks@gregwolanski.com if you have problems starting up or have an idea for how to simplify this even further.

The post An Eleventy Starter with Tailwind CSS and Alpine.js appeared first on CSS-Tricks.

We need more inclusive web performance metrics

Post pobrano z: We need more inclusive web performance metrics

Scott Jehl argues that performance metrics such as First Contentful Paint and Largest Contentful Paint don’t really capture the full picture of everyone’s experience with websites:

These metrics are often touted as measures of usability or meaning, but they are not necessarily meaningful for everyone. In particular, users relying on assistive technology (such as a screenreader) may not perceive steps in the page loading process until after the DOM is complete, or even later depending on how JavaScript may block that process. Also, a page may not be usable to A.T. until it becomes fully interactive, since many applications often deliver accessible interactivity via external JavaScript

Scott then jots down some thoughts on how we might do that. I think this is always so very useful to keep in mind: what we experience on our site, and what we measure too, might not be the full picture.

Direct Link to ArticlePermalink

The post We need more inclusive web performance metrics appeared first on CSS-Tricks.

Memorize Scroll Position Across Page Loads

Post pobrano z: Memorize Scroll Position Across Page Loads

Hakim El Hattab tweeted a really nice little UX enhancement for a static site that includes a scrollable sidebar of navigation.

The trick is to throw the scroll position into localStorage right before the page is exited, and when loaded, grab that value and scroll to it. I’ll retype it from the tweet…

let sidebar = document.querySelector(".sidebar");

let top = localStorage.getItem("sidebar-scroll");
if (top !== null) {
  sidebar.scrollTop = parseInt(top, 10);
}

window.addEventListener("beforeunload", () => {
  localStorage.setItem("sidebar-scroll", sidebar.scrollTop);
});

What is surprising is that you don’t get a flash-of-wrong-scroll-position. I wonder why? Maybe it has to do with fancy paint holding stuff browsers are doing now? Not sure.

The post Memorize Scroll Position Across Page Loads appeared first on CSS-Tricks.

Fluid Images in a Variable Proportion Layout

Post pobrano z: Fluid Images in a Variable Proportion Layout

Creating fluid images when they stand alone in a layout is easy enough nowadays. However, with more sophisticated interfaces we often have to place images inside responsive elements, like this card:

Screenshot. A horizontal card element with an image of two strawberries against a light blue background to the left of text that contains a heading and two sentences.

For now, let’s say this image is not semantic content, but only decoration. That’s a good use for background-image. And because in this context the image contains an object, we can’t allow any parts to be cropped out when it’s responsive, so we’d pick background-size: contain.

Here’s where it starts to get tricky: on mobile devices, this card shifts direction and becomes vertical, with the image on top. We can make that happen with any sort of CSS layout technique, and probably best handled with CSS grid or flexbox.

Screenshot. The same strawberry card but in a vertical format.

But as we test for smaller screens, because of the contain property, this is what we get:

The same card element in vertical but now the strawberry image is not flush with the top border of the card.
Hey, get back up there!

That’s not very nice. The image resizes to maintain its aspect ratio without cutting off any details, and if the image is important content and should not be cropped, we can’t change background-size to cover.

At this point, our next attempt might be familiar to you: placing the image inline, instead the background. 

On desktop, this works fine:

It’s not bad on mobile either:

But on smaller screens, because of all the fixed sizes, the image’s proportions get distorted.

Screenshot. The vertical card element with the strawberry image out of proportion, causing the strawberries to appear stretched vertically.
Hmm, those strawberries are not as appetizing when stretched.

We could spend hours fiddling with the image, the card, the flex properties, going back and forth. Or, we could…

Separate main content from the background

This is the base for obtaining much more flexibility and resilience when it comes to responsive images. It might not be possible 100% of the time but, in many cases, it can be achieved with a little effort on the design side of things, especially if this approach is planned beforehand.

For our next iteration, we’re placing our strawberries image on a transparent background and setting what was the blue color in the raster image with CSS instead. Go ahead and play with viewport sizes in this demo by adjusting the size of the sample space!

CodePen Embed Fallback

Looking deeper at the styles, notice that we’ve also added padding to the div that holds the image, so the strawberries don’t come too close to the edges. We have full control of how close or distant we want them to be, through this padding.

Note how we’re also using negative margins to compensate for the padding on our outer card wrapper, otherwise we’d get white space all around the image.

Use the object-fit property for inline images

As much as the previous demo works, we can still improve the approach. Up to now, we’ve assumed that the image was un-semantical content — but with this layout, it’s also likely that the image illustration could be more than decoration.

If that’s the case, we definitely don’t want the image to get cut off because that would essentially amount to data loss. It’s semantically better to put the image inline instead of a background to prevent that, and we can use the object-fit property to make it happen.

CodePen Embed Fallback

We’ve extracted the strawberries from the background and it’s now an inline <img> element, but we kept the background color in that same image div. 

Finally, combining the object-fit: contain with a 100% width makes it possible to resize the window and keep the aspect ratio of the strawberries. The caveat of this approach, however, is that we need to set a fixed height for the image on the desktop version — otherwise it’s going to follow the proportion of the set width (and reducing it will alter the layout). That might make things too constrained if we need to generate these cards with a variable amount of text that breaks into several lines.

Coming soon: aspect-ratio

The solution for the concern above might be just around the corner with the upcoming aspect-ratio property. This will enable setting a fixed ratio for an element, like this:

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

This means we’ll be able to eliminate fixed height and replace it with our calculated aspect ratio. For example, the dimensions in the desktop breakpoint of our last example looked like this:

.image {
  /* ... */
  height: 184px;
  width: 318px;
}

With aspect-ratio, we could remove the height declaration and do the math to get the closest ratio that amounts to 184:

.image {
  /* ... */
  width: 318px; /*  Base width */
  height: unset; /* Resets the height that was set outside the media query */
  aspect-ratio: 159 / 92; /* Amounts close to a 184px height */
}

The upcoming property is better explored in this article, if you want to learn more about it.

In the end, there are multiple ways to achieve reliably responsive images in a variable proportion layout. However, the trick to make this job easier — and better — does not necessarily lie with CSS; it can be as simple as adapting your images, whether that’s by separating the foreground from background (like we did) or selecting specific images that will still work if a fair portion of the edges get cropped.

The post Fluid Images in a Variable Proportion Layout appeared first on CSS-Tricks.

Settling down in a Jamstack world

Post pobrano z: Settling down in a Jamstack world

One of the things I like about Jamstack is that it’s just a philosophy. It’s not particularly prescriptive about how you go about it. To me, the only real requirement is that it’s based on static (CDN-backed) hosting. You can use whatever tooling you like. Those tools, though, tend to be somewhat new, and new sometimes comes with issues. Some pragmatism from Sean C Davis here:

I have two problems with solving problems using the newest, best tool every time a problem arises.

1. It’s simply not productive. Introducing new processes and tools takes time. Mastery and efficiency are built over time. If we’re trying to run a profitable business, we shouldn’t start from scratch every time.

2. We can’t know everything, all the time. With the rapidity at which we’re seeing new tools, there’s simply no way of knowing the best tool for the job because there’s no way to know all the tools available.

The trick is to settle into some tools you’ve proved that work and then keep using them to increase that level of expertise.

The post Settling down in a Jamstack world appeared first on CSS-Tricks.

Some Performance Links

Post pobrano z: Some Performance Links

Just had a couple of good performance links burning a hole in my pocket, so blogging them like a good little blogger.

Web Performance Recipes With Puppeteer

Puppeteer is an Node library for spinning up a copy of Chrome “headlessly” (i.e. no UI) and controlling it. People use it for stuff like taking a screenshot of a website or running integration tests. You can even run it in a Lambda.

Another use case is running synthetic (i.e. not based on real-users) performance tests, like some of these new Web Core Vitals

Addy Osmani lists out a bunch of these “recipes” for measuring certain performance things in Puppeteer. These would be super useful as part of a build process alongside other tests. Did the unit tests pass? Did the integration tests pass? Did the accessibility tests pass? Did the performance metrics tests pass?


BrowserStack SpeedLab

BrowserStack released a thing to measure your site and give you a performance score.

You get the tests back super quick which is cool. I can see how tools like this are good for starting conversations with teams about improving performance.

But… that number seems a little weird. They don’t exactly document how it’s calculated, but it seems to be based on stuff like Time to First Byte (TTFB) and the page load event, which aren’t particularly useful performance metrics.

It’s not bad that this tool exists or anything, but I don’t think it’s for practitioners doing performance work.


5 Common Mistakes Teams Make When Tracking Performance

Karolina Szczur from Calibre documents some common team struggles like, for example, having a team be able to identify real issues from variability noise.

Many people from different backgrounds can view performance dashboards. Not knowing what constitutes a meaningful change that needs investigation can result in false positives, lack of trust in monitoring and cycles spent looking for reasons for performance regressions or upgrades that aren’t there.


Are your JavaScript long tasks frustrating users?

50ms. That’s how long until any particular JavaScript task starts affecting user experience. Might as well track and (ideally) fix them.

When the browser’s main thread hits max CPU for more than 50ms, a user starts to notice that their clicks are delayed and that scrolling the page has become janky and unresponsive. Batteries drain faster. People rage click or go elsewhere.

The post Some Performance Links appeared first on CSS-Tricks.

The Return of the 90s Web

Post pobrano z: The Return of the 90s Web

One of my forever-lessons here on CSS-Tricks is that having your own website and blogging on it is a good idea. It’s probably one of the best decisions I’ve ever made, as it’s been a direct source of fun, career development and, eventually, income.

I always chuckle at little blogging is cool again declarations from the community. It’s always cool, my friends. But it is always nice to see more people pick it back up.

I enjoyed this post from Max Böck that gets into how what is old is new again. Server side rendering! Personal websites! Blogging! Heck yes.

Direct Link to ArticlePermalink

The post The Return of the 90s Web appeared first on CSS-Tricks.

In Defense of a Fussy Website

Post pobrano z: In Defense of a Fussy Website

The other day I was doom-scrolling twitter, and I saw a delightful article titled “The Case for Fussy Breakfasts.” I love food and especially breakfast, and since the pandemic hit I’ve been using my breaks in between meetings (or sometimes on meetings, shh) to make a full bacon, poached egg, vegetable plate, so I really got into the article. This small joy of creating a bit of space for myself for the most important meal of the day has been meaningful to me — while everything else feels out of control, indulging in some ceremony has done a tiny part to offset the intensity of our collective situation.

It caused me to think of this “fussiness” as applied to other inconsequential joys. A walk. A bath. What about programming?

While we’re all laser-focused on shipping the newest feature with the hottest software and the best Lighthouse scores, I’ve been missing a bit of the joy on the web. Apps are currently conveying little care for UX, guidance, richness, and — well, for humans trying to communicate through a computer, we’re certainly bending a lot to… the computer.

I’m getting a little tired of the web being seen as a mere document reader, and though I do love me a healthy lighthouse score, some of these point matrixes seem to live and die more by our developer ego in this gamification than actually considering what we can do without incurring much weight. SVGs can be very small while still being impactful. Some effects are tiny bits of CSS. JS animations can be lazy-loaded. You can even dazzle with words, color, and layout if you’re willing to be a bit adventurous, no weight at all!

A few of my favorite developer sites lately have been Josh Comeau, Johnson Ogwuru and Cassie Evans. The small delights and touches, the little a-ha moments, make me STAY. I wander around the site, exploring, learning, feeling actually more connected to each of these humans rather than as if I’m glancing at a PDF of their resume. They flex their muscles, show me the pride they have in building things, and it intrigues me! These small bits are more than the fluff that many portray any “excess” as: they do the job that the web is intending. We are communicating using this tool- the computer- as an extension of ourselves.

Nuance can be challenging. It’s easy as programmers to get stuck in absolutes, and one of these of late has been that if you’re having any bit of fun, any bit of style, that must mean it’s “not useful.” Honestly, I’d make the case that the opposite is true. Emotions attach to the limbic system, making memories easier to recall. If your site is a flat bit of text, how will anyone remember it?

Don’t you want to build the site that teams in companies the world over remember and cite as an inspiration? I’ve been at four different companies where people have mentioned Stripe as a site they would aspire to be like. Stripe took chances. Stripe told stories. Stripe engaged the imagination of developers, spoke directly to us.

I’m sad acknowledging the irony that after thinking about how spot on Stripe was, most of those companies ignored much of what they learned while exploring it. Any creativity, risk, and intention was slowly, piece by piece, chipped away by the drumbeat of “usefulness,” missing the forest for the trees.

When a site is done with care and excitement you can tell. You feel it as you visit, the hum of intention. The craft, the cohesiveness, the attention to detail is obvious. And in turn, you meet them halfway. These are the sites with the low bounce rates, the best engagement metrics, the ones where they get questions like “can I contribute?” No gimmicks needed.

What if you don’t have the time? Of course, we all have to get things over the line. Perhaps a challenge: what small thing can you incorporate that someone might notice? Can you start with a single detail? I didn’t start with a poached egg in my breakfast, one day I made a goofy scrambled one. It went on from there. Can you challenge yourself to learn one small new technique? Can you outsource one graphic? Can you introduce a tiny easter egg? Say something just a little differently from the typical corporate lingo?

If something is meaningful to you, the audience you’ll gather will likely be the folks that find it meaningful, too.

The post In Defense of a Fussy Website appeared first on CSS-Tricks.