Tag: Named

Named Element IDs Can Be Referenced as JavaScript Globals

Did you know that DOM elements with IDs are accessible in JavaScript as global variables? It’s one of those things that’s been around, like, forever but I’m really digging into it for the first time.

If this is the first time you’re hearing about it, brace yourself! We can see it in action simply by adding an ID to an element in HTML:

<div id="cool"></div>

Normally, we’d define a new variable using querySelector("#cool") or getElementById("cool") to select that element:

var el = querySelector("#cool");

But we actually already have access to #cool without that rigamorale:

So, any id — or name attribute, for that matter — in the HTML can be accessed in JavaScript using window[ELEMENT_ID]. Again, this isn’t exactly “new” but it’s really uncommon to see.

As you may guess, accessing the global scope with named references isn’t the greatest idea. Some folks have come to call this the “global scope polluter.” We’ll get into why that is, but first…

Some context

This approach is outlined in the HTML specification, where it’s described as “named access on the Window object.”

Internet Explorer was the first to implement the feature. All other browsers added it as well. Gecko was the only browser at the time to not support it directly in standards mode, opting instead to make it an experimental feature. There was hesitation to implement it at all, but it moved ahead in the name of browser compatibility (Gecko even tried to convince WebKit to move it out of standards mode) and eventually made it to standards mode in Firefox 14.

One thing that might not be well known is that browsers had to put in place a few precautionary measures — with varying degrees of success — to ensure generated globals don’t break the webpage. One such measure is…

Variable shadowing

Probably the most interesting part of this feature is that named element references don’t shadow existing global variables. So, if a DOM element has an id that is already defined as a global, it won’t override the existing one. For example:

<head>   <script>     window.foo = "bar";   </script> </head> <body>   <div id="foo">I won't override window.foo</div>   <script>     console.log(window.foo); // Prints "bar"   </script> </body>

And the opposite is true as well:

<div id="foo">I will be overridden :(</div> <script>   window.foo = "bar";   console.log(window.foo); // Prints "bar" </script>

This behavior is essential because it nullifies dangerous overrides such as <div id="alert" />, which would otherwise create a conflict by invalidating the alert API. This safeguarding technique may very well be the why you — if you’re like me — are learning about this for the first time.

The case against named globals

Earlier, I said that using global named elements as references might not be the greatest idea. There are lots of reasons for that, which TJ VanToll has covered nicely over at his blog and I will summarize here:

  • If the DOM changes, then so does the reference. That makes for some really “brittle” (the spec’s term for it) code where the separation of concerns between HTML and JavaScript might be too much.
  • Accidental references are far too easy. A simple typo may very well wind up referencing a named global and give you unexpected results.
  • It is implemented differently in browsers. For example, we should be able to access an anchor with an id — e.g. <a id="cool"> — but some browsers (namely Safari and Firefox) return a ReferenceError in the console.
  • It might not return what you think. According to the spec, when there are multiple instances of the same named element in the DOM — say, two instances of <div class="cool"> — the browser should return an HTMLCollection with an array of the instances. Firefox, however, only returns the first instance. Then again, the spec says we ought to use one instance of an id in an element’s tree anyway. But doing so won’t stop a page from working or anything like that.
  • Maybe there’s a performance cost? I mean, the browser’s gotta make that list of references and maintain it. A couple of folks ran tests in this StackOverflow thread, where named globals were actually more performant in one test and less performant in a more recent test.

Additional considerations

Let’s say we chuck the criticisms against using named globals and use them anyway. It’s all good. But there are some things you might want to consider as you do.

Polyfills

As edge-case-y as it may sound, these types of global checks are a typical setup requirement for polyfills. Check out the following example where we set a cookie using the new CookieStore API, polyfilling it on browsers that don’t support it yet:

<body>   <img id="cookieStore"></img>   <script>     // Polyfill the CookieStore API if not yet implemented.     // https://developer.mozilla.org/en-US/docs/Web/API/CookieStore     if (!window.cookieStore) {       window.cookieStore = myCookieStorePolyfill;     }     cookieStore.set("foo", "bar");   </script> </body>

This code works perfectly fine in Chrome, but throws the following error in Safari.:

TypeError: cookieStore.set is not a function

Safari lacks support for the CookieStore API as of this writing. As a result, the polyfill is not applied because the img element ID creates a global variable that clashes with the cookieStore global.

JavaScript API updates

We can flip the situation and find yet another issue where updates to the browser’s JavaScript engine can break a named element’s global references.

For example:

<body>   <input id="BarcodeDetector"></input>   <script>     window.BarcodeDetector.focus();   </script> </body>

That script grabs a reference to the input element and invokes focus() on it. It works correctly. Still, we don’t know how long it will continue to work.

You see, the global variable we’re using to reference the input element will stop working as soon as browsers start supporting the BarcodeDetector API. At that point, the window.BarcodeDetector global will no longer be a reference to the input element and .focus() will throw a “window.BarcodeDetector.focus is not a function” error.

Bonus: Not all named elements generate global references

Want to hear something funny? To add insult to the injury, named elements are accessible as global variables only if the names contain nothing but letter. Browsers won’t create a global reference for an element with a ID that contains special characters and numbers, like hello-world and item1.

Conclusion

Let’s sum up how we got here:

  • All major browsers automatically create global references to each DOM element with an id (or, in some cases, a name attribute).
  • Accessing these elements through their global references is unreliable and potentially dangerous. Use querySelector or getElementById instead.
  • Since global references are generated automatically, they may have some side effects on your code. That’s a good reason to avoid using the id attribute unless you really need it.

At the end of the day, it’s probably a good idea to avoid using named globals in JavaScript. I quoted the spec earlier about how it leads to “brittle” code, but here’s the full text to drive the point home:

As a general rule, relying on this will lead to brittle code. Which IDs end up mapping to this API can vary over time, as new features are added to the web platform, for example. Instead of this, use document.getElementById() or document.querySelector().

I think the fact that the HTML spec itself recommends to staying away from this feature speaks for itself.


Named Element IDs Can Be Referenced as JavaScript Globals originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

CSS-Tricks

, , , ,

Using Grid Named Areas to Visualize (and Reference) Your Layout

Whenever we build simple or complex layouts using CSS Grid, we’re usually positioning items with line numbers. Grid layouts contain grid lines that are automatically indexed with positive and negative line numbers (that is unless we explicitly name them). Positioning items with line numbers is a fine way to lay things out, though CSS Grid has numerous ways to accomplish the same with an undersized cognitive encumbrance. One of those ways is something I like to think of as the “ASCII” method.

The ASCII method in a nutshell

The method boils down to using grid-template-areas to position grid items using custom-named areas at the grid container level rather than line numbers.

When we declare an element as a grid container using display: grid, the grid container, by default, generates a single-column track and rows that sufficiently hold the grid items. The container’s child elements that participate in the grid layout are converted to grid items, irrespective of their display property.

For instance, let’s create a grid by explicitly defining columns and rows using the grid-template-columns and grid-template-rows properties.

.grid {   display: grid;   grid-template-columns: 1fr 1fr;   grid-template-rows: repeat(3, 200px); }

This little snippet of CSS creates a 3×2 grid where the grid items take up equal space in the columns, and where the grid contains three rows with a track size of 200px.

We can define the entire layout with named grid areas using the grid-template-areas property. According to the spec, the initial value of grid-template-areas is none.

grid-template-areas = none | <string>+

<string>+ is listing the group of strings enclosed with a quote. Each string is represented as a cell, and each quoted string is represented as a row. Like this:

grid-template-areas: "head head" "nav main" "foot foot";

The value of grid-template-areas describes the layout as having four grid areas. They are,

  • head
  • nav
  • main
  • foot

head and foot span two column tracks and one row track. The remaining nav and main each span one column track and one row track. The value of grid-template-areas is a lot like arranging ASCII characters, and as Chris suggested a while back, we can get a visualization of the overall structure of the layout from the CSS itself which is the most trouble-free way to understand it.

(Full size GIF)

OK, so we created our layout with four named grid areas: head, nav, main, foot.

Now, let’s start to position the grid items against named grid areas instead of line numbers. Specifically, let’s place a header element into the named grid area head and specify the named grid area head in the header element using the grid-area property.

Named grid areas in a grid layout are called idents. So, what we just did was create a custom ident named head that we can use to place items into certain grid tracks.

header { grid-area: head; }

We can other HTML elements using other custom idents:

nav { grid-area: nav; } main { grid-area: main; } footer { grid-area: foot; }

Writing named area values

According to CSS Grid Layout Module Level 1, all strings must be defined under the following tokens:

  • Named cell token: This represents the named grid area in the grid. For instance, head is a named cell token.
  • Null cell token: This represents the unnamed grid area in the grid container. For instance, an empty cell in the grid is a null cell token.
  • Trash token: This is a syntax error, such as an invalid declaration. For instance, a disparate number of cells and rows compared to the number of grid items would make a declaration invalid.

In grid-template-area, every quoted string (the rows) must have the same number of cells and define the complete grid without ignoring any cell.

We can ignore a cell or leave it as an empty cell using the full-stop character (.)

.grid {    display: grid;   grid-template-areas:     "head head"     "nav main"     "foot ."; }

If that feels visually awkward or imbalanced to you, we can use multiple full-stop characters without any whitespaces separating them:

.grid {   display: grid;   grid-template-areas:     "head head"     "nav main"     "foot ...."; }

A named cell token can span multiple grid cells, But those cells must form a rectangular layout. In other words, we’re unable to create “L” or “T”-shaped layouts, although the spec does hint at support for non-rectangular layouts with disconnected regions in the future.

ASCII is better than line-based placement

Line-based placement is where we use the grid-column and grid-row properties to position an element on the grid using grid line numbers that are automatically indexed by a number:

.grid-item {   grid-column: 1 / 3; /* start at grid column line 1 and span to line 3 */ }

But grid item line numbers can change if our layout changes at a breakpoint. In those cases, it’s not like we can rely on the same line numbers we used at a specific breakpoint. This is where it takes extra cognitive encumbrance to understand the code.

That’s why I think an ASCII-based approach works best. We can redefine the layout for each breakpoint using grid-template-areas within the grid container, which offers a convenient visual for how the layout will look directly in the CSS — it’s like self-documented code!

.grid {   grid-template-areas:     "head head"     "nav main"     "foot ...."; /* much easier way to see the grid! */ }  .grid-item {   grid-area: foot; /* much easier to place the item! */ }

We can actually see a grid’s line numbers and grid areas in DevTools. In Firefox, for example, go to the Layout panel. Then, under the Grid tab, locate the “Grid display settings” and enable the “Display line number” and “Display area names” options.

Enabling grid settings.

This ASCII approach using named areas requires a lot less effort to visualize and easily find the placement of elements.

Line-based placement versus ASCII Art placement.

Let’s look at the “universal” use case

Whenever I see a tutorial on named grid areas, the common example is generally some layout pattern containing header, main, sidebar, and footer areas. I like to think of this as the “universal” use case since it casts such a wide net.

The Holy Grail layout in rectangles.

It’s a great example to illustrate how grid-template-areas works, but a real-life implementation usually involves media queries set to change the layout at certain viewport widths. Rather than having to re-declare grid-area on each grid item at each breakpoint to re-position everything, we can use grid-template-areas to “respond” to the breakpoint instead — and get a nice visual of the layout at each breakpoint in the process!

Before defining the layout, let’s assign an ident to each element using the grid-area property as a base style.

header {   grid-area: head; }  .left-side {   grid-area: left; }  main {   grid-area: main; }  .right-side {   grid-area: right; }  footer {   grid-area: foot; }

Now, let’s define the layout again as a base style. We’re going with a mobile-first approach so that things will stack by default:

.grid-container {   display: grid;   grid-template-areas:     "head"     "left"     "main"     "right"     "foot"; }

Each grid item is auto-sized in this configuration — which seems a little bit weird — so we can set min-height: 100vh on the grid container to give us more room to work with:

.grid-container {   display: grid;   grid-template-areas:     "head"     "left"     "main"     "right"     "foot";   min-height: 100vh; }

Now let’s say we want the main element to sit to the right of the stacked left and right sidebars when we get to a slightly wider viewport width. We re-declare grid-template-areas with an updated ASCII layout to get that:

@media (min-width: 800px) {   .parent {     grid-template-columns: 0.5fr 1fr;     grid-template-rows: 100px 1fr 1fr 100px;     grid-template-areas:       "head head"       "left main"       "right main"       "foot foot";   } }

I tossed some column and row sizing in there purely for aesthetics.

As the browser gets even wider, we may want to change the layout again, so that main is sandwiched between the left and right sidebars. Let’s write the layout visually!

.grid-container {   grid-template-columns: 200px 1fr 200px; /* again, just for sizing */   grid-template-areas:     "head head head"     "left main right"     "left main right"     "foot foot foot"; }

Leveraging implicit line names for flexibility

According to the spec, grid-template-areas automatically generates names for the grid lines created by named grid areas. We call these implicitly-named grid lines because they are named for us for free without any additional work.

Every named grid area gets four implicitly-named grid lines, two in the column direction and two in the row direction, where -start and -end are appended to the ident. For example, a grid area named head gets head-start and head-end lines in both directions for a total of four implicitly-named grid lines.

Implicitly assigned line names.

We can use these lines to our advantage! For instance, if we want an element to overlay the main, left, and right areas of our grid. Earlier, we talked about how layouts have to be rectangular — no “T” and “L” shaped layouts allowed. Consequently, we’re unable to use the ASCII visual layout method to place the overlay. We can, however, use our implicit line names using the same grid-area property on the overlay that we use to position the other elements.

Did you know that grid-area is a shorthand property, sort of the same way that margin and padding are shorthand properties? It takes multiple values the same way, but instead of following a “clockwise” direction like, margin — which goes in order of margin-block-start, margin-inline-end, margin-block-end, and margin-inline-startgrid-area goes like this:

grid-area: block-start / inline-start / block-end / inline-end;
Showing the block and inline flow directions in a left-to-right writing mode.

But we’re talking about rows and columns, not block and inline directions, right? Well, they correspond to one another. The row axis corresponds to the block direction, and the column axis corresponds to the inline direction:

grid-area: grid-row-start / grid-column-start / grid-row-end / grid-column-end;
Block and inline axis.

Back to positioning that overlay element as a grid item in our layout. The grid-area property will be helpful to position the element using our implicitly-named grid lines:

.overlay {   grid-area: left-start / left-start / right-end / main-end; }

Creating a minimal grid system

When we focus on layouts like the “universal” use case we just saw, it’s tempting to think of grid areas in terms of one area per element. But it doesn’t have to work like that. We can repeat idents to reserve more space for them in the layout. We saw that when we repeated the head and foot idents in the last example:

.grid-container {   grid-template-areas:     "head head head"     "left main right"     "left main right"     "foot foot foot"; }

Notice that main, left, and right are also repeated but in the block direction.

Let’s forget about full page layouts and use named grid areas on a component. Grid is just as good for component layouts as full pages!

Here’s a pretty standard hero component that sports a row of images followed by different blocks of text:

A row of weightlifting photos above a heading, blurb, then a row of three links.

The HTML is pretty simple:

<div class="hero">   <div class="image">     <img src="..." alt="" />   </div>   <div class="text">     <!-- ... -->   </div> </div>

We could do this for a real fast stacked layout:

.hero {   grid-template-areas:     "image"     "text"; }

But then we have to reach for some padding, max-width or whatever to get the text area narrower than the row of images. How about we expand our ASCII layout into a four-column grid instead by repeating our idents on both rows:

.hero {   display: grid;   grid-template-columns: repeat(4, 1fr); /* maintain equal sizing */   grid-template-areas:     "image image image image"     "text  text  text  text"; }

Alright, now we can place our grid items into those named areas:

.hero .image {   grid-area: image; }  .hero .text {   grid-area: text; }

So far, so good — both rows take up the entire width. We can use that as our base layout for small screens.

Showing grid lines on the stacked mobile version of the page.

But maybe we want to introduce the narrower text when the viewport reaches a larger width. We can use what we know about the full-stop character to “skip” columns. Let’s have the text ident skip the first and last columns in this case.

@media (min-width: 800px) {   main {     grid-template-columns: repeat(6, 1fr); /* increase to six columns */     grid-template-areas:       "image image image image image image"       "..... text  text  text  text  .....";   } }

Now we have the spacing we want:

Showing grid lines for a table-sized layout of the page.

If the layout needs additional tweaking at even larger breakpoints, we can add more columns and go from there:

.hero {   grid-template-columns: repeat(8, 1fr);   grid-template-areas:     "image image image image image image image image"     "..... text  text  text  text  text  text  ....."; }

Dev tool visualization:

Showing grid lines for a large table sized layout of the page.

Remember when 12-column and 16-column layouts were the big things in CSS frameworks? We can quickly scale up to that and maintain a nice visual ASCII layout in the code:

main {   grid-template-columns: repeat(12, 1fr);   grid-template-areas:     "image image image image image image image image image image image image"     "..... text  text  text  text  text  text  text  text  text  text  ....."; }

Let’s look at something more complex

We’ve looked at one fairly generic example and one relatively straightforward example. We can still get nice ASCII layout visualizations with more complex layouts.

Let’s work up to this:

Three images positioned around a fancy heading.

I’ve split this up into two elements in the HTML, a header and a main:

<header>   <div class="logo"> ... </div>   <div class="menu"> ... </div> </header> <main>   <div class="image"> ... </div>   <h2> ... </h2>   <div class="image"> ... </div>   <div class="image"> ... </div> </main>

I think flexbox is more appropriate for the header since we can space its child elements out easily that way. So, no grid there:

header {   display: flex;   justify-content: space-between;   /* etc. */ }

But grid is well-suited for the main element’s layout. Let’s define the layout and assign the idents to the corresponding elements that we need to position the .text and three .image elements. We’ll start with this as our baseline for small screens:

.grid {   display: grid;   grid-template-columns: repeat(4, 1fr);   grid-template-areas:     "image1 image1 .....  image2"     "texts  texts  texts  texts"     ".....  image3 image3 ....."; }

You can already see where we’re going with this, right? The layout is visualized for us, and we can drop the grid items into place with the custom idents:

.image:nth-child(1) {   grid-area: image1; }  .image:nth-last-child(2) {   grid-area: image2; }  .image:nth-last-child(1) {   grid-area: image3; }  h2 {   grid-area: texts; }
Showing grid lines on a mobile layout of the page.

That’s our base layout, so let’s venture into a wider breakpoint:

@media (min-width: 800px) {   .grid {     grid-template-columns: repeat(8, 1fr);     grid-template-areas:       ". image1 image1 ...... ......  ...... image2 ."       ". texts  texts  texts  texts   texts  image2 ."       ". .....  image3 image3 image3  image3 ...... .";   } }

I bet you know exactly how that will look because the layout is right there in the code!

Showing grid lines for a table-sized layout of the page.

Same deal if we decide to scale up even further:

.grid {   grid-template-columns: repeat(12, 1fr);   grid-template-areas:     ". image1 image1 .....  .....   .....  .....  .....  .....  .....  .....  ."     ". texts  texts  texts  texts   texts  texts  texts  texts  texts  image2 ."     ". .....  image3 image3 image3  image3 .....  .....  .....  .....  .....  ."; }
Showing grid lines for a desktop-sized layout of the page.

Here’s the full demo:

I’m using the “negative margin hack” to get the first image to overlap the heading.

Wrapping up

I’m curious if anyone else is using grid-template-areas to create named areas for the benefit of having an ASCII visual of the grid layout. Having that as a reference in my CSS code has helped de-mystify some otherwise complex designs that may have been even more complex when dealing with line numbers.

But if nothing else, defining grid layouts this way teaches us some interesting things about CSS Grid that we saw throughout this post:

  • The grid-template-areas property allows us to create custom idents — or “named areas” — and use them to position grid items using the grid-area property.
  • There are three types of “tokens” that grid-template-areas accepts as values, including named cell tokens, null cell tokens, and trash cell tokens.
  • Each row that is defined in grid-template-areas needs the same number of cells. Ignoring a single cell doesn’t create a layout; it is considered a trash token.
  • We can get a visual ASCII-like diagram of the grid layout in the grid-template-areas property value by using required whitespaces between named cell tokens while defining the grid layout.
  • Make sure there is no whitespace inside a null cell token (e.g. .....). Otherwise, a single whitespace between null cell tokens creates unnecessary empty cells, resulting in an invalid layout.
  • We can redefine the layout at various breakpoints by re-positioning the grid items using grid-area, then re-declaring the layout with grid-template-areas on the grid container to update the track listing, if needed. There’s no need to touch the grid items.
  • Custom named grid areas automatically get four implicitly assigned line names — <custom-ident>-start and <custom-ident>-end in both the column and row directions.

Using Grid Named Areas to Visualize (and Reference) Your Layout originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

CSS-Tricks

, , , , , ,
[Top]

The Power of Named Transitions in Vue

Vue offers several ways to control how an element or component visually appears when inserted into the DOM. Examples can be fading in, sliding in, or other visual effects. Almost all of this functionality is based around a single component: the transition component.

A simple example of this is with a single v-if based on a Boolean. When the Boolean is true, the element appears. When the Boolean is false, the element disappears. Normally, this element would just pop in and out of existence, but with the transition component you can control the visual effect.

<transition>   <div v-if="isVisible">is this visible?</div> </transition>

Several articles have been written that cover the transition component quite well, like articles from Sarah Drasner, Nicolas Udy, and Hassan Djirdeh. Each article covers different aspects of Vue’s transition component in detail. This article will expand on the topic by focusing on one aspect of the transition component; the fact that they can be “named.”

<transition name="fade">   <div v-if="isVisible">is this visible?</div> </transition>

The initial change this attribute offers is that the CSS classes injected onto the element during the transition sequence will be prefixed by the given name. Basically, it would be fade-enter instead of v-enter from the example above. This single attribute can go well beyond this simple option. It can be used to leverage certain features of Vue and CSS which allows for some interesting outcomes.

Another thing to consider is that the name attribute can be bound:

<transition v-bind:name="currentTransition">   <div v-if="isVisible">is this visible?</div> </transition>

In this example, the transition will be named the value currentTransition resolves to. This simple change provides another level of options and features to an app’s animations. With static and dynamic named transitions, a project can have a series of prebuilt transitions ready to apply throughout the entire app, components that can extend existing transitions applied to them, switch a transition being used before or after being applied, allowing users to choose transitions, and control how individual elements of a list transition into place based on the current state of that list.

This article is intended to explore these features and explain how to use them.

What happens when transitions are named?

By default, when a transition component is used, it applies specific classes in a specific sequence to the element. These classes can be leveraged in CSS. Without any CSS, these classes, in essence, do nothing for the element. Therefore, there is a need for CSS of this nature:

.v-enter, .v-leave-to {   opacity: 0; }  .v-enter-active, .v-leave-active {   transition: 0.5s; }

This causes the element to fade in and out with a duration of half a second. A minor change to the transition provides for elegant visual feedback to the user. Still, there is an issue to consider. But first, what’s different with a named transition?

.fade-enter, .fade-leave-to {   opacity: 0; }  .fade-enter-active, .fade-leave-active {   transition: 0.5s; }

Essentially the same CSS but with fade- prefixed instead of v-. This naming addresses the potential issue that can happen when using the default class names of the transition component. The v- prefix makes the classes global in effect, especially if the CSS is placed in the style block of the app’s root level. This would, in effect, make *all* transitions without a name attribute throughout the entire app use the same transition effect. For small apps this may suffice, but in larger, more complex apps, it may lead to undesirable visual effects, as not everything should fade in and out over half a second.

Naming transitions provides a level of control for developers throughout the project as to how different elements or components are inserted or removed visually. It is suggested that all transitions be named — even if there is just one — to establish the habit of doing so. Even if an app has only one transition effect, there may be a need to add a new one at a future point. Having already named existing transitions in the project eases the effort of adding a new one.

Building a collection of transition effects

Naming transitions provides for a simple yet very useful process. A common practice might be to create the transition classes as part of the component that is using them. If another common practice of scoping styles for a component is done, those classes will only be available to that particular component. If two different components have similar transitions in their style blocks, then we are just duplicating code.

So, let’s consider keeping CSS for transitions in the style block of the root of the app, typically the app.vue file. For most of my projects, I place them as the last section of the style block, making them easy to locate for adjustments and additions. Keeping the CSS in this location makes the transition effects available to every use of the transition component throughout the entire app. Here are examples from some of my projects.

.fade-enter, .fade-leave-to { opacity: 0; } .fade-enter-active, .fade-leave-active { transition: 0.5s; }  .slide-enter {   opacity: 0;   transform: scale3d(2, 0.5, 1) translate3d(400px, 0, 0); }  .slide-enter-to { transform: scale3d(1, 1, 1); } .slide-enter-active, .slide-leave-active { transition: 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55); } .slide-leave { transform: scale3d(1, 1, 1); }  .slide-leave-to {   opacity: 0;   transform: scale3d(2, 0.5, 1) translate3d(-400px, 0, 0); }  .rotate-enter { transform: perspective(500px) rotate3d(0, 1, 0, 90deg); } .rotate-enter-active, .rotate-leave-active { transition: 0.5s; } .rotate-leave-to { transform: perspective(500px) rotate3d(0, 1, 0, -90deg); }

There are multiple ways to store these transition classes depending on your preferences and the needs of the project. The first, as mentioned earlier, is to keep it all in the style block of the app.vue file. You can also keep a Sass partial of all the transitions in the project’s assets folder and import it into the app’s style block.

<style lang="scss">   @import "assets/_transitions.scss"; </style>

This method allows for adjustments and additions to the collection of transitions outside of the Vue files. Another benefit of this setup is that such a file can be easily transferred between projects if they share transition effects. If one project gets a new transition, then it’s easy enough to transfer the addition to another project without having to touch main project files.

If you’re using CSS instead of Sass, then you can include the file as a requirement of the project. You can accomplish this by keeping the file in the assets folder of the project and placing a require statement in the main.js file.

require("@/assets/transitions.css");

Another option is keep the transition styles in a static CSS file that can be stored elsewhere, either in the public folder of the project or just on the server itself. Since this is a regular CSS file, no building or deployment would be required — just include a link reference in the index.html file.

<link rel="stylesheet" type="text/css" href="/css/transitions.css">

This file could also potentially be stored in a CDN for all projects to share. Whenever the file is updated, the changes are immediately available everywhere it is referenced. If a new transition name is created, then existing projects can start using the new name as needed.

Now, let’s slow down a minute

While we’re building a collection of transitions to use throughout our project, let’s consider users out there who may not want abrupt animations, or who may want no animations at all. Some people could consider our animations over-the-top and unnecessary, but for some, they can actually cause problems. Some time ago, WebKit introduced the prefers-reduced-motion media query to assist with possible Vestibular Spectrum Disorder issues. Eric Bailey also posted a nice introduction to the media query as well.

In most cases, adding the media query as part of our collection of transitions is quite easy and should be considered. We can either reduce the amount of motion involved in the transition to reduce the negative effects or simply turn them off.

Here’s a simple example from one of my demos below:

.next-enter {   opacity: 0;   transform: scale3d(2, 0.5, 1) translate3d(400px, 0, 0); }  .next-enter-to { transform: scale3d(1, 1, 1); } .next-enter-active, .next-leave-active { transition: 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55); } .next-leave { transform: scale3d(1, 1, 1); }  .next-leave-to {   opacity: 0;   transform: scale3d(2, 0.5, 1) translate3d(-400px, 0, 0); }  /* If animations are reduced at the OS level, use simpler transitions */ @media screen and (prefers-reduced-motion: reduce) {   .next-enter {     opacity: 0;     transform: translate3d(100px, 0, 0);   }    .next-enter-active,   .next-leave-active { transition: 0.5s; }    .next-leave-to {     opacity: 0;     transform: translate3d(-100px, 0, 0);   } }

In that example, I took what was a rather exaggerated transition and made it simpler. The animation is a slide that moves to the left with an elastic ease, then scales down and fades out as it moves away. If someone has the reduce motion preference set, then the animation becomes a much simpler transition with a shorter distance (which gives it a slower velocity) and keeps the fade. If we had wanted to turn them off, then we’d only need to reference the classes with the transition property and set their value to none.

To test this requires finding and selecting a checkbox on your respective OS. On Windows, you will find it in Control Panel > Ease of Access Center > Make the computer easier to see section; look for “Turn off all unnecessary animations (when possible).” On a Mac, look under System Preferences > Accessibility > Display; look for “Reduce motion.” The latest iOS devices have a similar setting under Accessibility as well.

Let’s stay flexible with our transitions collection

With this collection of transitions, there is the potential snag of a lack of flexibility with the effects. For instance, what if one element needs a slightly slower fade time? Let’s say that everything else in the effect can stay the same, only the transition-duration needs to be different. There are ways to adjust for that without having to create a whole new transition name.

The easiest method is to use an inline style directly on the element within the transition component.

<transition name="fade">   <div style="transition-duration: 6s;" v-if="isVisible">this has a different duration</div> </transition>

Such a change can also be done through the various ways Vue offers handling styles and classes.

Let’s say you are using the component element with the is attribute for dynamic components such as this:

<transition name="fade" mode="out-in">   <component :is="currentComponent"></component> </transition>

Even with this dynamic component, we have options to adjust properties of the transition effect. Again, we can apply an inline style on the component element, which will be placed on the root element of the component. The root element also receives the transition classes, so we would directly override their properties.

<transition name="fade" mode="out-in">   <component :is="currentComponent" style="transition-duration: 6s;"></component> </transition>

Another option is to pass in props to our components. That way, the desired changes can be applied through the component’s code to its root element.

<transition name="fade" mode="out-in">   <component :is="currentComponent" duration="6s"></component> </transition>
<template>   <div :style="`transition-duration: $  {duration}`">component one</div> </template>  <script> export default {   name: "component-one",   props: {     duration: String   } }; </script>

We can also override the properties of the transition’s classes inside the component’s style block, especially if it is scoped.

<style scoped>   .fade-enter-active,   .fade-leave-active { transition-duration: 1s; } </style>

In this case, the component will have a fade duration of one second instead of the global duration of half-a-second. We can even take it a step further and have different durations for each side of the sequence.

<style scoped>   .fade-enter-active { transition-duration: 1s; }   .fade-leave-active { transition-duration: 2s; } </style>

Any of the global transition classes can be altered within the component when needed. Although this isn’t quite as flexible as changing the property outside of a class structure, it can still be quite useful in certain circumstances.

As you can see, even with our collection of prebuilt transitions, we still have options for flexibility.

Dynamic transitions

Even after all these interesting things we can do with Vue’s transition component, yet another interesting feature waits to be explored. The name attribute on the transition component can be dynamic in nature, meaning we can change the current transition in use at will.

This means that the transition can be changed to have different animation effects with different situations, based in code. For example, we could have a transition change because of the answer to a question, transitions decided from user interaction, and have a list use different transitions based on the current state of the list itself.

Let’s look into these three examples.

Example 1: Change transition based on an answer

In this example, we have a simple math question that must be answered. Two numbers are randomly selected and we are expected to provide the sum. Then the button is clicked to check the answer against the expected answer. A small notification appears above the equation that indicates whether the answer is true or false. If the answer is correct, the notification is given a transition that suggests a head nodding yes with an up and down animation. If your answer is incorrect, the notification goes side-to-side suggesting a head shaking no.

See the Pen
VueJS Dynamic Transitions: Change Transition Based on an Answer
by Travis Almand (@talmand)
on CodePen.

The logic behind this is not overly complicated, nor is the setup of the transition. Here’s the HTML:

<transition :name="currentTransition">   <div id="notification" :class="response.toString()" v-if="answerChecked">{{ response }}</div> </transition>

Rather simple in nature. We have a bound name on the transition and then a v-if on the notification div. We also apply a true or false class to decorate the notification based on the response.

Here’s the CSS for the transitions:

.positive-enter-active { animation: positive 1s; } @keyframes positive {   0% { transform: translate3d(0, 0, 0); }   25% { transform: translate3d(0, -20px, 0); }   50% { transform: translate3d(0, 20px, 0); }   75% { transform: translate3d(0, -20px, 0); }   100% { transform: translate3d(0, 0, 0); } }  .negative-enter-active { animation: negative 1s; } @keyframes negative {   0% { transform: translate3d(0, 0, 0); }   25% { transform: translate3d(-20px, 0, 0); }   50% { transform: translate3d(20px, 0, 0); }   75% { transform: translate3d(-20px, 0, 0); }   100% { transform: translate3d(0, 0, 0); } }

You’ll see that I’m using CSS animations to accomplish the up-and-down and side-to-side effects.

Here’s some of the JavaScript:

methods: {   randomProblem: function () {     this.a = Math.floor(Math.random() * Math.floor(10));     this.b = Math.floor(Math.random() * Math.floor(10));   },   check: function () {     this.response = this.a + this.b === parseInt(this.answer);     this.answerChecked = true;     this.currentTransition = this.response ? 'positive' : 'negative';   },   reset: function () {     this.answer = null;     this.answerChecked = false;     this.randomProblem();   } }

There’s the randomProblem method that sets up our equation. The check method that decides on which transition effect to use based on comparing the provided answer with the correct answer. Then the simple reset method that just, well, resets everything.

This is just a simple example. Another possible example is having a notification that has two different effects based on whether the notification is important or not. If the message is not overly important, then we can have a subtle animation that doesn’t drive the user’s eyes away from the current task. If it is important, we could use an animation that is more direct in nature in an effort to force the eyes up to the notification.

Example 2: Change transition based on user interaction

Another thing we can build is a carousel of some sort. This could be presentation slides, an image gallery, or a series of instructions. The basic idea is that we have a need to present information to the user in a sequence. In this presentation, the user gets to decide when to proceed and whether to move forward or to go backward.

See the Pen
VueJS Dynamic Transitions: Change Transition Based on User Interaction
by Travis Almand (@talmand)
on CodePen.

This, again, is a rather simple setup. The example, more or less, is a slide presentation type of situation. The two buttons at the bottom shift between two components with a sliding transition. A real project would have more components or perhaps logic to change the contents of the components based on the current slide. This example shall stay simple to demonstrate the idea.

Here’s the HTML:

<transition :name="currentTransition" mode="out-in">   <component :is="slides[currentSlide]"></component> </transition>

You’ll see that we’re merely transitioning whenever the component is switched out by a bound is attribute on the component element.

Here’s the CSS:

.next-enter {   opacity: 0;   transform: scale3d(2, 0.5, 1) translate3d(400px, 0, 0); }  .next-enter-to { transform: scale3d(1, 1, 1); } .next-enter-active, .next-leave-active { transition: 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55); } .next-leave { transform: scale3d(1, 1, 1); }  .next-leave-to {   opacity: 0;   transform: scale3d(2, 0.5, 1) translate3d(-400px, 0, 0); }  .prev-enter {   opacity: 0;   transform: scale3d(2, 0.5, 1) translate3d(-400px, 0, 0); }  .prev-enter-to { transform: scale3d(1, 1, 1); } .prev-enter-active, .prev-leave-active { transition: 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55); } .prev-leave { transform: scale3d(1, 1, 1); }  .prev-leave-to {   opacity: 0;   transform: scale3d(2, 0.5, 1) translate3d(400px, 0, 0); }  /* If animations are reduced at the OS level, use simpler transitions */ @media screen and (prefers-reduced-motion: reduce) {   .next-enter { opacity: 0; transform: translate3d(100px, 0, 0); }   .next-enter-active,   .next-leave-active { transition: 0.5s; }   .next-leave-to { opacity: 0; transform: translate3d(-100px, 0, 0); }      .prev-enter { opacity: 0; transform: translate3d(-100px, 0, 0); }   .prev-enter-active,   .prev-leave-active { transition: 0.5s; }   .prev-leave-to { opacity: 0; transform: translate3d(100px, 0, 0); } }

Here we have two transitions, one for when the user clicks on the “next” button and the other is for the “prev” button. Each essentially slides the component in the appropriate direction with the transform property, but with a few extras to create a kind of squeezing effect for a cartoonish feel. We also make use of prefers-reduced-motion to change the animation to be a simpler fade with a small slide to the side in the appropriate direction.

Now, for the JavaScript:

methods: {   changeSlide: function (dir) {     this.currentSlide = dir === 'next' ? this.currentSlide + 1 : this.currentSlide - 1;     this.currentTransition = dir;   } }

Each button calls the changeSlide method on its click event and passes which direction it represents. Then we have some logic to keep track of what the current slide happens to be. A single line controls which transition to use. Since the “next” button passes “next” as the direction it corresponds to the “next” transition in the CSS. Same for the “prev” button. Each time the user clicks a button, the app automatically knows which transition to use. Thus, we have nice transition effects that provide context as to which direction the user is progressing through the sequence.

Example 3: Change transition based on list state

For our final example, we’ll see how to change transitions based on the current state of a list inside a transition-group component. The idea here is a list to be updated an item at a time with a different transition each time.

See the Pen
VueJS Dynamic Transitions: Change Transition Based on List State
by Travis Almand (@talmand)
on CodePen.

In this example, we are presented with a list of cities on the right and a blank list on the left. As cities are chosen on the right, they fill in the blanks on the left. The first city slides in from above while fading into view. The next cities before the last will slide in either from the right or the left, depending on the previous transition, and the last city slides in from below.

Here’s the HTML:

<transition-group :name="currentListTransition" tag="ul" class="list">   <li v-for="(item, index) in selectedItems" :key="item">{{ item }}</li> </transition-group>

As usual, a rather simple setup. Here are the transitions in CSS:

.top-enter-active, .top-leave-active { transition: 0.5s; } .top-enter, .top-leave-to {   opacity: 0;   transform: translate3d(0, -40px, 0); }  .top-move {   opacity: 0.5;   transition: 0.5s; }  .left-enter-active, .left-leave-active { transition: 0.5s; } .left-enter, .left-leave-to {   opacity: 0;   transform: translate3d(-40px, 0, 0); }  .left-move {   opacity: 0.5;   transition: 0.5s; }  .right-enter-active, .right-leave-active { transition: 0.5s; } .right-enter, .right-leave-to {   opacity: 0;   transform: translate3d(40px, 0, 0); }  .right-move {   opacity: 0.5;   transition: 0.5s; }  .bottom-enter-active, .bottom-leave-active { transition: 0.5s; } .bottom-enter, .bottom-leave-to {   opacity: 0;   transform: translate3d(0, 30px, 0); }  .bottom-move {   opacity: 0.5;   transition: 0.5s; }  /* If animations are reduced at the OS level, turn off transitions */ @media screen and (prefers-reduced-motion: reduce) {   .top-enter-active,   .top-leave-active { transition: none; }   .top-move { transition: none; }   .left-enter-active,   .left-leave-active { transition: none; }   .left-move { transition: none; }   .right-enter-active,   .right-leave-active { transition: none; }   .right-move { transition: none; }   .bottom-enter-active,   .bottom-leave-active { transition: none; }   .bottom-move { transition: none; } }

As you can see, a transition for each possible direction of the cities appearing the blank list.

Now, for our JavaScript:

methods: {   chooseCity: function (index) {     let selectedLength = this.selectedItems.length;     let citiesLength = this.cities.length;     let clt = this.currentListTransition;          if (selectedLength === 0) {       clt = 'top';     } else if (selectedLength > 0 && selectedLength < citiesLength - 1) {       clt = clt === 'top' || clt === 'left' ? 'right' : 'left';     } else if (selectedLength === citiesLength - 1) {       clt = 'bottom';     }          this.currentListTransition = clt;     this.selectedItems.push(this.cities[index]);     document.querySelector(`.city:nth-child($  {index + 1})`).classList.add('selected');   },    clearSelection: function () {     this.currentListTransition = 'right';     this.selectedItems = [];     document.querySelectorAll('.city.selected').forEach(element => {       element.classList.remove('selected');     });   } }

The chooseCity method handles what happens as you choose each city. What we mostly care about is the series of if and if/else statements in the middle of the method. As cities are selected, the logic looks at the current length of the selectedItems array that the selected cities eventually get pushed into. If the length is zero, then that’s the first city, so the transition should have it come in from the top. If the length is between zero and the total number of our cities list, then the transition should be right or left. The new direction used is based on the direction of the previous transition direction. Then, finally, if we’re on the last city to be chosen, it’ll change to the bottom transition. Again we use prefers-reduced-motion, in this case to turn off the transitions altogether.

Another option to change transitions for a list is changing according to the type of items chosen; such as east coast versus west coast cities, each having different transitions. Consider changing the transition based on the current number of items added to the list; for instance, a different transition for every five items.

So long, and thanks for all the transitions

After all these examples and ideas, I hope that you will consider leveraging Vue’s transition component in your own projects. Exploring the possibilities of adding transitions and animations to your apps to provide context and interest for your users. In many cases, additions such as these are rather simple to implement, almost to the point of it being a shame not to add them. Vue offers an exciting and highly useful feature, the transition component, out of the box and I can only encourage its usage.

Cheers.

The post The Power of Named Transitions in Vue appeared first on CSS-Tricks.

CSS-Tricks

, ,
[Top]