Frontend Masters Boost RSS Feed https://frontendmasters.com/blog Helping Your Journey to Senior Developer Wed, 03 Apr 2024 18:01:32 +0000 en-US hourly 1 https://wordpress.org/?v=6.5.2 225069128 The View Transitions API https://frontendmasters.com/blog/the-view-transition-api/ https://frontendmasters.com/blog/the-view-transition-api/#comments Wed, 03 Apr 2024 17:50:59 +0000 https://frontendmasters.com/blog/?p=1541 Like anyone, I love a good “native-feeling” experience on the web. I’ve really enjoyed creating that vibe with the new View Transitions API. Luckily it’s is pretty clean to work with, both with the Astro framework, and out of the box!

View Transition Options

Transitioning UI between states has been something that developers have been doing for ages, and yes, you can use things like the Web Animation API as well as CSS transitions and animations for that. But, handling transitions when elements are removed from the page, or you navigate away from a given page, is much harder, particularly when you’re thinking about assistive tech, scroll position, and focus… it’s gnarly.

Untiiiiil now! The View Transitions API has been evolving in the browser both as a proper W3C Candidate Recommendation, and as of this week, the draft for cross-document navigation is in a public working draft!

What does that mean? It means you can have that “native” animation experience, right in the browser! But more specifically, currently you can use the View Transitions API:

  1. For transitions on normal page-to-page loading
  2. For transitions on a single page application (SPA)
  3. For transitions of the DOM changes without a page change

View Transitions + Astro

My favorite way to actually see this implemented so far is using the Astro framework. They have a really great API that allows you to use View Transitions on a single page, or turn on “SPA mode” and navigate across your entire application and have transitions between pages and elements.

Out of the box, Astro supports:

  • Out of the box animations like fade and slide
  • Navigation forwards and backwards
  • Support for prefers-reduced-motion
  • And moooore

Using View Transitions in Astro

Adding the View Transitions API to an Astro page is just two lines of code: importing it in your frontmatter, and adding it to the <head>.

---
import { ViewTransitions } from 'astro:transitions';
---

<html lang="en">
  <head>
    <!-- ... -->
    <ViewTransitions />
  </head>
  <body>
    <!-- ... -->
  </body>
</html>

If you put these lines in a layout component that’s shared across pages, that automatically enables View Transition-powered client-side navigation, fading between pages.

Astro automatically corresponds elements between two pages by their type and location within the DOM, so if you want something simple, this is all you need!

Customize more with directives

You can have more fine-grained control of what your transitions do by using the transition:* directives.

  • transition:name overrides the default matching that Astro does between elements. Make sure when you use this one, you use unique names, kind of like an id.
  • transition:animate overrides the default fade animation, and you can customize what animations you want.
  • transition:persist overrides how Astro replaces elements across pages and lets you persist components and elements across pages.

Example

I built a little demo for you to see the View Transitions API in action!

There’s two main pages to make this little “city directory” site, index.astro, and [city].astro, which pull from a locations.json file.

In index.astro, we loop through the locations:

<ul>
    {locations.map((city) => {
        return (
            <li>
                <span transition:name={`${city.emoji} emoji`}>{city.emoji}</span>
                <a href={`/city/${city.city.toLowerCase()}`} transition:name={`${city.city} link`}>
                    {city.city}, {city.country}
                </a>
            </li>
        )
    })}
</ul>    

In this, note the two transition names on the emoji and on the link, unique for each city.

In [city].astro (which is a dynamic route generated per city), we map those transition names to different elements:

<h2 transition:name={`${city} link`}>{city}, {country}</h2>

<p>
    <span transition:name={`${city} emoji`}>{emoji}</span> {description}
</p>

So now, when we navigate between the home page and a city page (and back), the heading transitions from the link, and the emoji moves from the link to the paragraph!

Using View Transitions without Astro

If your framework of choice is not Astro, but you still want to try out the View Transitions API, it does work in Chrome, Edge, and Opera right now out of the box (Safari and Firefox require polyfilling).

You can check out the documentation here. It’s a bit more manual when you want to build with it from scratch, but you do get a lot of control!

Here’s a very basic example of transitioning between some text values with the API:

Note that the CSS lets you control the transition, similar to how traditional CSS animations work! You assign an animation with view-transition-name, and then you give the view transition pseudo-elements get assigned animations in and out (via ::view-transition-old and ::view-transition-new)!

Read more

This is just scratching the surface of what the View Transitions API can do! You can find some more resources here:

]]>
https://frontendmasters.com/blog/the-view-transition-api/feed/ 1 1541
View transitions: Handling aspect ratio changes https://frontendmasters.com/blog/view-transitions-handling-aspect-ratio-changes/ https://frontendmasters.com/blog/view-transitions-handling-aspect-ratio-changes/#respond Thu, 29 Feb 2024 15:12:52 +0000 https://frontendmasters.com/blog/?p=1082 Jake Archibald has some great examples of awkward gotchas with View Transitions. View Transitions are naturally great at tweening one state of a DOM element to the next, but you’ll see likely-unwanted ghosting if the element changes shape from one state to the next. You can either prevent it from changing shape by changing either the starting or ending state, or in the case that you need the different shapes, grab ahold of the parts and have them move individually how you want them to.

I found it satisfying that the answers weren’t in more new exotic web technologies, but just in understanding and using existing CSS layout better.

]]>
https://frontendmasters.com/blog/view-transitions-handling-aspect-ratio-changes/feed/ 0 1082
Building a TODO App from Scratch — Step 4 — Styling & Interactive Choices https://frontendmasters.com/blog/building-a-todo-app-from-scratch-step-4-styling-interactive-choices/ https://frontendmasters.com/blog/building-a-todo-app-from-scratch-step-4-styling-interactive-choices/#respond Wed, 28 Feb 2024 21:17:43 +0000 https://frontendmasters.com/blog/?p=1020 I’m already a little over all the green. What was I thinking? I’m much more of a nice gray color palette kinda guy with some fun poppy colors. That’s what we need to get to here. When we left off, we just had wireframes going. It’s time to get some style on this thing!

Article Series

Custom Properties

We can get rid of that wireframe look by removing all those 1px borders and applying some backgrounds. Let’s give ourselves some Custom Properties to work with, so we can apply colors in such a way they are easy to change later.

html {
  --gray800: oklch(10% 0% 0);
  --gray600: oklch(40% 0% 0);
  --gray100: oklch(92% 0% 0);
  --brand: oklch(85% 0.3 145);

  background: var(--gray100);
}

You can see we’re using the lightest gray right away in colorizing the background.

I’m using OKLCH color function here because I find it the generally-nicest of all the color models. One very immediate thing it does for us here is that it unlocks P3 colors. That --brand color you see there is actually a bit more bright and vibrant than is even possible in the sRGB color space (like a hex code or any of the more common color functions you may be familiar with, like rgb()).

Applying those colors to the relevant sections gets us a start:

Fonts

We don’t need to go too far down the typography rabbit hole here, as all our design calls for is the header, one input, and a list of to-do items. Keeping it simple and fast (not loading fonts is… fast), is ideal, so:

html {
  font-family: system-ui, sans-serif;
}

You’d think system-ui would be boring, but as a little fun and progressive enhancement, we can use some font-variation-settings on it in the case that the font that loads supports them. It’s not the only one, but Apple’s San Francisco font ships as a variable font, so we can do fun things like stretch it out.

header {
  background: var(--gray800);
  color: white;
  h1 {
    font-variation-settings: "wght" 900, "wdth" 700;
  }
}

I love this look, myself:

SVGs

We need SVG’s for the “check” of the logo and that we’ll use when a user checks off a to-do. I’m kinda thinking we just re-use the same one for both as that just makes good sense. Plus we’ll make a “+” SVG too. It might be tempting to find unicode characters for these, but I’m telling you, just use SVG. The predictable bounding box of SVG will be your friend (no fighting against weird space).

There are loads of resources for finding SVGs to use. I’m usually a Noun Project guy, but if we needed, say, 10 or more icons I’d probably try to find a more cohesive set somewhere. These are also so simple that could (should) draw them ourselves (even directly in code), but we’ll save that for another day.

Ultimately, we’ll just get our hands on a “check” icon and use it in the logo…

<header>
  <svg ... class="svg-check site-logo">
    <polyline ... />
  </svg>
  <h1>TODOs</h1>
</header>

… in our template literal for each to-do …

HTML += `<li id="${todo.id}">
  ${todo.title}
  <button aria-label="Complete" class="button-complete">
    <svg ... class="svg-check">
      <polyline ... />
    </svg>
  </button>
</li>`;

… and then our “+” in the form:

<form id="todo-form" class="todo-form">
  <label>
    <span class="screen-reader-text">New TODO</span>
    <input type="text" name="todo">
  </label>
  <button id="button-add-todo" class="button-add-todo">
    <svg ... class="svg-plus">
      <g>
        <line ... />
        <line ... />
      </g>
    </svg>
  </button>
</form>

There is a lot to know about SVG so I’m really just dropping the basic usage in here. I’ve chosen to drop the SVG code right “inline” with the HTML because I generally think that’s best because it’s fast and offers the most styling control. If you wanna dig more into SVG, I did write a book about it one time you could check out.

With the SVG’s I chose in there:

Adding To-Do Interactions

When we add a new to-do, I want to give the adding button a temporary class so we can do something fun with it. So we’ll use a saved reference to that button and do that:

buttonAddTodo.classList.add("added");

But this is an opportunity! We could add a different class upon a failure. What failure? Not entering any text, for one. We could make the call not to allow entering blank to-dos, which at this point I think is a decent idea.

So the form event handler then becomes like:

form.addEventListener("submit", (event) => {
  event.preventDefault();
  // Don't allow empty todo
  if (!form[0].value) {
    buttonAddTodo.classList.add("shake");
    return;
  }
  addTodo(event);
  form.reset();
});

Now check out these little @keyframe animations I can do with those classes:

.button-add-todo {
  ...

  &.shake {
    rotate: 0deg;
    transform-origin: bottom right;
    animation: shake 0.4s cubic-bezier(0.36, 0.07, 0.19, 0.97) both;
  }
  &.added {
    transform-origin: center center;
    animation: added 0.4s cubic-bezier(0.36, 0.07, 0.19, 0.97) both;
  }
}
@keyframes shake {
  50% {
    rotate: -12deg;
  }
}
@keyframes added {
  50% {
    rotate: 1turn;
    translate: 0 50px;
  }
}

A little fun, maybe?

Let’s be careful to remove those classes after the animation happens, so that another animation can be triggered freshly when needed:

buttonAddTodo.addEventListener("animationend", () => {
  buttonAddTodo.classList.remove("shake", "added");
});

Completing To-Do Interactions

We’ve got a big ol’ box sitting there waiting for to-do items to be checked. And now that we’re using SVG, my temptation is to do a little fun “line drawing” with that. That is, see the checkbox be “drawn” as we complete the to-do, before removing it.

Here’s the trick.

First we go find the SVG itself in our template literal, and add this special pathLength attribute to the actual shape:

<svg width="20" height="20" viewBox="0 0 241.44 259.83" class="svg-check">
  <polyline pathLength="1" points="16.17 148.63 72.17 225.63 225.17 11.63" />
</svg>

This just makes animating it much easier.

Then we set and animate some specific SVG properties to do the drawing:

.todo-list {
  ...
  li {
    ...
    .svg-check {
      opacity: 0;
    }
    &.complete {
      .svg-check {
        opacity: 1;
        stroke-dasharray: 1;
        stroke-dashoffset: 1;
        animation: do-check 1s infinite alternate;
      }
    }
  }
}

@keyframes do-check {
  from {
    stroke-dashoffset: 1;
  }
  to {
    stroke-dashoffset: 0;
  }
}

Check out the drawing effect at work:

The trick to getting the animation to run before being removed is just to … wait.

function removeTodo(event) {
  const listItem = event.target.parentElement;
  listItem.classList.toggle("complete");
  setTimeout(() => {
    TODOs = TODOs.filter((todo) => todo.id !== event.target.parentElement.id);
    localStorage["data"] = JSON.stringify(TODOs);
    buildUI();
  }, 1000);
}

You can see I’m just waiting for a second there, which is about the same, a little longer, than the animation itself. We could tie it to the animationend event here also, but for some reason it felt better to me to leave this bit of business logic in the code here rather than tying it to a CSS thing.

Really smoooooth completions

What if we remove a to-do from the middle of the list. It’ll be jerky won’t it? It certainly could be. This is one of those classic situations in CSS where being able to “animate to/from auto” would be nice. In this case, we want to animate from “however tall a list item is” to zero. That way the bottom items in the list would slide up, making a much more comfortable animation. But honestly, even animating the height here feels like slight of hand, what I actually want is just not to think very hard about it and for the list items to just slide up into place, if at all possible.

Turns out this is a lovely use-case for View Transitions. Without getting very deep at all into it, essentially all we need to do is:

  1. Give all our to-dos a unique view-transition-name
  2. When we update the DOM, e.g. buildUI(), wrap that in a startViewTransition() function.

So our template literal is updated to be like…

  TODOs.forEach((todo) => {
    HTML += `<li id="${todo.id}" style="view-transition-name: list-item-${todo.id};">

We already have a unique ID so we’ll steal that for the dual purpose here.

Then wrap our DOM updating function like:

if (!document.startViewTransition) {
  buildUI();
} else {
  document.startViewTransition(() => {
    buildUI();
  });
}

That’s the same and progressive-enhancement friendly way to do it. It’s kind of required here, as that startViewTransition will throw in browsers that don’t support it and it could break our whole site.

Look at us know:

This is friggin’ cool if you ask me.

So far

Just a little bit more functionality to go!

Article Series

]]>
https://frontendmasters.com/blog/building-a-todo-app-from-scratch-step-4-styling-interactive-choices/feed/ 0 1020
Velvette https://frontendmasters.com/blog/velvette/ https://frontendmasters.com/blog/velvette/#respond Fri, 09 Feb 2024 16:40:52 +0000 https://frontendmasters.com/blog/?p=768 While I was over on Codrops reading Adam’s article about Scroll-Driven animation, I also read Noam Rosenthal’s article about View Transitions. Noam is also at Google and working on the View Transitions API, so knows it pretty intimately, and found enough gaps that a library around it was warranted. That’s what Velvette is: a library around making certain things easier with the View Transitions API.

The #1 thing I wanted to try was “generate unique view-transition-names”. If you want a whole bunch of elements to all transition, every single one of them needs to have a unique view-transition-name. This is annoying verging on untenable in CSS, so the usual approach is using inline styles in the HTML. Unfortunately, the best Velvette can do is map the id of the element to the view-transition-name, so you’re having to craft unique identifiers anyway. Why not just toss a UUID on them or something?

I was able to get it working, but ultimately didn’t find it very useful.

]]>
https://frontendmasters.com/blog/velvette/feed/ 0 768