Categories
Vue 3

Vue 3 — Basic Transitions

Vue 3 is in beta and it’s subject to change.

Vue 3 is the up and coming version of Vue front end framework.

It builds on the popularity and ease of use of Vue 2.

In this article, we’ll look at how to add transitions with Vue 3.

Transitions

We can add transitions to our Vue app with various components

The transition component helps us add enter and leave transitions which are triggered by v-if .

Transition modes let us change the ordering of a transition.

Transitions with multiple components can be added with the transition-group component.

We can transition to different states in an app with watchers .

Class-based Animations & Transitions

We can add transitions by binding to a class.

For example, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
    <style>
      .shake {
        animation: shake 1s cubic-bezier(0.31, 0.07, 0.39, 0.97) both;
        transform: translate3d(0, 0, 0);
        backface-visibility: hidden;
        perspective: 1000px;
      }

      @keyframes shake {
        10%,
        90% {
          transform: translate3d(-1px, 0, 0);
        }

        20%,
        80% {
          transform: translate3d(2px, 0, 0);
        }

        30%,
        50%,
        70% {
          transform: translate3d(-4px, 0, 0);
        }

        40%,
        60% {
          transform: translate3d(4px, 0, 0);
        }
      }
    </style>
  </head>
  <body>
    <div id="app">
      <div :class="{ shake: on }">
        <button @click="on = !on">toggle</button>
        <p v-if="on">hello world!</p>
      </div>
    </div>
    <script>
      const app = Vue.createApp({
        data() {
          return {
            on: false
          };
        }
      });
      app.mount("#app");
    </script>
  </body>
</html>

to add the shaking transition effect to our div.

We have the translate3d to add the shaking left and right,

Negative is left and positive is right.

The shake class has the shake transition.

cubic-bezier is the easing function, which makes the transition non-linear.

transform keeps the div in place.

We apply the shake class when on is true , so when we click the toggle button to set on to true , we’ll see the elements shake.

Transitions with Style Bindings

We can also add transitions with style bindings.

For example, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
  </head>
  <body>
    <div id="app">
      <div
        @mousemove="onMouseMove"
        :style="{ backgroundColor: `rgb(${x}, 80, 50)` }"
        class="movearea"
      >
        <p>x: {{x}}</p>
      </div>
    </div>
    <script>
      const app = Vue.createApp({
        data() {
          return {
            x: 0
          };
        },
        methods: {
          onMouseMove(e) {
            this.x = e.clientX;
          }
        }
      });
      app.mount("#app");
    </script>
  </body>
</html>

When our mouse moves, then the onMouseMove method is called.

This gets the x coordinate of the mouse and set it to the this.x state.

Then we used x to set the background color by changing the first value of rgb .

Therefore, when we move our mouse, the background color will change.

Performance

CSS animations has better performance than using JavaScript to change the position of our elements.

Every redraw with JavaScript is an expensive operation, so we try to eliminate them if we can.

CSS animations is also faster because browsers have hardware acceleration capabilities.

Properties like perspective, backface-visibility, and transform: translateZ(x) will trigger a browser’s hardware acceleration capabilities.

Conclusion

Vue lets us animate our elements with CSS and class or style bindings.

Categories
Vue 3

Vue 3 — Slot Shorthands

Vue 3 is in beta and it’s subject to change.

Vue 3 is the up and coming version of Vue front end framework.

It builds on the popularity and ease of use of Vue 2.

In this article, we’ll look at slot shorthands with Vue 3.

Shorthand Syntax for Default Slots

If we have one slot, then we don’t need the template element.

We can put the v-slot directive right on the opening component tag.

For instance, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
  </head>
  <body>
    <div id="app">
      <items v-slot="slotProps">
        <b>{{ slotProps.item.name }}</b>
      </items>
    </div>
    <script>
      const app = Vue.createApp({});

      app.component("items", {
        template: `
          <ul>
            <li v-for="( item, index ) in items">
              <slot v-bind:item="item"></slot>
            </li>
          </ul>
        `,
        data() {
          return {
            items: [{ name: "apple" }, { name: "orange" }, { name: "grape" }]
          };
        }
      });

      app.mount("#app");
    </script>
  </body>
</html>

We have the v-slot directive on the component tag, which is only allowed when we have one slot in our component.

Destructuring Slot Props

Slot props can be destructured.

Since we have an object as the value of v-slot , we can destructure its properties.

For example, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
  </head>
  <body>
    <div id="app">
      <items v-slot="{ item: { name } }">
        <b>{{ name }}</b>
      </items>
    </div>
    <script>
      const app = Vue.createApp({});

      app.component("items", {
        template: `
          <ul>
            <li v-for="( item, index ) in items">
              <slot v-bind:item="item"></slot>
            </li>
          </ul>
        `,
        data() {
          return {
            items: [{ name: "apple" }, { name: "orange" }, { name: "grape" }]
          };
        }
      });

      app.mount("#app");
    </script>
  </body>
</html>

We destructured the properties of the slotProps object, so instead of writing:

<items v-slot="slotProps">
  <b>{{ slotProps.item.name }}</b>
</items>

we have:

<items v-slot="{ item: { name } }">
  <b>{{ name }}</b>
</items>

This can make templates much cleaner especially if we have many slots.

It’s also easy for us to provide fallbacks.

For example, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
  </head>
  <body>
    <div id="app">
      <items v-slot="{ item: { name = 'placeholder' } }">
        <b>{{ name }}</b>
      </items>
    </div>
    <script>
      const app = Vue.createApp({});

      app.component("items", {
        template: `
          <ul>
            <li v-for="( item, index ) in items">
              <slot v-bind:item="item"></slot>
            </li>
          </ul>
        `,
        data() {
          return {
            items: [{ name: "apple" }, { name: "orange" }, {}]
          };
        }
      });

      app.mount("#app");
    </script>
  </body>
</html>

We set the default value of the name property to 'placeholder' so that we can display that if nothing is set for the name property.

Dynamic Slot Names

Slot names can be dynamic.

So we can write:

<base-layout>
  <template v-slot:[dynamicSlotName]>
    ...
  </template>
</base-layout>

where dynamicSlotName is the variable with the slot name.

Named Slots Shorthand

Named slots can be shorted with the # sign.

For example, instead of writing:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
  </head>
  <body>
    <div id="app">
      <blog-post>
        <template v-slot:header>
          <h1>header</h1>
        </template>
        <template v-slot:default>
          <p>lorem ipsum.</p>
        </template>
        <template v-slot:footer>
          <p>footer</p>
        </template>
      </blog-post>
    </div>
    <script>
      const app = Vue.createApp({});
      app.component("blog-post", {
        template: `
          <header>
            <slot name="header"></slot>
          </header>
          <main>
            <slot></slot>
          </main>
          <footer>
            <slot name="footer"></slot>
          </footer>
        `
      });
      app.mount("#app");
    </script>
  </body>
</html>

We can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
  </head>
  <body>
    <div id="app">
      <blog-post>
        <template #header>
          <h1>header</h1>
        </template>
        <template #default>
          <p>lorem ipsum.</p>
        </template>
        <template #footer>
          <p>footer</p>
        </template>
      </blog-post>
    </div>
    <script>
      const app = Vue.createApp({});
      app.component("blog-post", {
        template: `
          <header>
            <slot name="header"></slot>
          </header>
          <main>
            <slot></slot>
          </main>
          <footer>
            <slot name="footer"></slot>
          </footer>
        `
      });
      app.mount("#app");
    </script>
  </body>
</html>

# is short for v-slot: .

Conclusion

There’s a shorthand for named slots and also there’s a shorthand for default slots.

Categories
Vue 3

Vue 3 — Slots

Vue 3 is in beta and it’s subject to change.

Vue 3 is the up and coming version of Vue front end framework.

It builds on the popularity and ease of use of Vue 2.

In this article, we’ll look at how to use slots to populate content with Vue 3.

Slots

We can add slots to let us distribute content to the location we want.

For instance, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
  </head>
  <body>
    <div id="app">
      <click-button>click me</click-button>
    </div>
    <script>
      const app = Vue.createApp({});

      app.component("click-button", {
        template: `
          <button>
            <slot></slot>
          </button>`
      });

      app.mount("#app");
    </script>
  </body>
</html>

We created the click-button component with a slot inside so that we can add our own content between the tags.

It’ll render to the button with ‘click me’ as the content text.

We can also add HTML between the tags.

For instance, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
  </head>
  <body>
    <div id="app">
      <click-button><b>click me</b></click-button>
    </div>
    <script>
      const app = Vue.createApp({});

      app.component("click-button", {
        template: `
          <button>
            <slot></slot>
          </button>`
      });

      app.mount("#app");
    </script>
  </body>
</html>

We have the ‘click me’ text in bold since we have the b tag.

Other components can also be between the tags.

So we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
  </head>
  <body>
    <div id="app">
      <click-button><hello-world></hello-world></click-button>
    </div>
    <script>
      const app = Vue.createApp({});

      app.component("click-button", {
        template: `
          <button>
            <slot></slot>
          </button>`
      });

      app.component("hello-world", {
        template: `
          <b>click me</b>
        `
      });

      app.mount("#app");
    </script>
  </body>
</html>

We have the hello-world component in between the click-button tags and we’ll see the same result as before.

If a component’s template didn’t contain the slot element, then any content in between the tags would be discarded.

So if we have:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
  </head>
  <body>
    <div id="app">
      <click-button>click me</click-button>
    </div>
    <script>
      const app = Vue.createApp({});

      app.component("click-button", {
        template: `
          <button></button>
        `
      });

      app.mount("#app");
    </script>
  </body>
</html>

Then ‘click me’ wouldn’t be displayed and we have an empty button.

Render Scope

We can access items from the child component from the parent if we have a slot.

For instance, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
  </head>
  <body>
    <div id="app">
      <click-button>
        delete {{item.name}}
      </click-button>
    </div>
    <script>
      const app = Vue.createApp({
        data() {
          return {
            item: {
              name: "this"
            }
          };
        }
      });

      app.component("click-button", {
        template: `
          <button>
            <slot></slot>
          </button>
        `
      });

      app.mount("#app");
    </script>
  </body>
</html>

Then we passed in the item.name string as the part of the content of the slot in the click-button component.

This is only for display. item is still in the parent’s scope.

Fallback Content

Fallback content can be added to a slot.

For example, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
  </head>
  <body>
    <div id="app">
      <click-button> </click-button>
    </div>
    <script>
      const app = Vue.createApp({
        data() {
          return {
            item: {
              name: "this"
            }
          };
        }
      });

      app.component("click-button", {
        template: `
          <button>
            <slot>submit</slot>
          </button>
        `
      });

      app.mount("#app");
    </script>
  </body>
</html>

to add default content between the slots.

Since we have nothing between the click-button tags, we’ll see ‘submit’ displayed.

Conclusion

Slots are useful for letting us distribute content within a component.

Categories
Vue 3

Vue 3 — Provide and Inject

Vue 3 is in beta and it’s subject to change.

Vue 3 is the up and coming version of Vue front end framework.

It builds on the popularity and ease of use of Vue 2.

In this article, we’ll look at providing and injecting data with Vue 3.

Provide and Inject

To make passing data easier between deeply nested components, we can bypass props and use the provide property to expose some data.

And then we can use the inject property to get the data.

For example, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
  </head>
  <body>
    <div id="app">
      <blog-post></blog-post>
    </div>
    <script>
      const app = Vue.createApp({});

      app.component("blog-post", {
        data() {
          return {
            post: { title: "hello world" }
          };
        },
        provide: {
          author: "james"
        },
        template: `
          <div>
            {{ post.title }}
            <blog-post-statistics></blog-post-statistics>
          </div>
        `
      });

      app.component("blog-post-statistics", {
        inject: ["author"],
        template: `
          <p>{{author}}</p>
        `,
        created() {
          console.log(this.author);
        }
      });

      app.mount("#app");
    </script>
  </body>
</html>

We have the blog-post and blog-post-statistics components.

In the blog-post component, we have the provide property with the author property.

This is the data that we’re providing to child components.

Therefore, in the blog-post-statistics component, we have the inject property with an array of the property names that are in provide that we want to get.

So author in blog-post-statistics would be author in the template or this.author in component methods.

However, this won’t work with Vue instance properties.

So if we have:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
  </head>
  <body>
    <div id="app">
      <blog-post></blog-post>
    </div>
    <script>
      const app = Vue.createApp({});

      app.component("blog-post", {
        data() {
          return {
            post: { title: "hello world", author: "james" }
          };
        },
        provide: {
          post: this.post
        },
        template: `
          <div>
            {{ post.title }}
            <blog-post-statistics></blog-post-statistics>
          </div>
        `
      });

      app.component("blog-post-statistics", {
        inject: ["post"],
        template: `
          <p>{{post.author}}</p>
        `,
        created() {
          console.log(this.post.author);
        }
      });

      app.mount("#app");
    </script>
  </body>
</html>

Then nothing will be displayed.

To fix this, we got to pass in the property that has the value to provide and change provide to a function:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
  </head>
  <body>
    <div id="app">
      <blog-post></blog-post>
    </div>
    <script>
      const app = Vue.createApp({});

      app.component("blog-post", {
        data() {
          return {
            post: { title: "hello world", author: "james" }
          };
        },
        provide() {
          return {
            author: this.post.author
          };
        },
        template: `
          <div>
            {{ post.title }}
            <blog-post-statistics></blog-post-statistics>
          </div>
        `
      });

      app.component("blog-post-statistics", {
        inject: ["author"],
        template: `
          <p>{{author}}</p>
        `,
        created() {
          console.log(this.author);
        }
      });

      app.mount("#app");
    </script>
  </body>
</html>

Now the provide is a method of the blog-post component instead of a property.

And we return what we want to provide to child components.

Working with Reactivity

Changes with reactive properties in the parent won’t be reflected in child components when we update them.

This is a problem that can be fixed by making the reactive property a computed property.

For instance, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
  </head>
  <body>
    <div id="app">
      <counter></counter>
    </div>
    <script>
      const app = Vue.createApp({});

      app.component("counter", {
        data() {
          return {
            count: 0
          };
        },
        provide() {
          return { count: Vue.computed(() => this.count) };
        },
        template: `
          <div>
            <button @click='count++'>increment</button>
            <count-display></count-display>
          </div>
        `
      });

      app.component("count-display", {
        inject: ["count"],
        template: `
          <p>{{count.value}}</p>
        `
      });

      app.mount("#app");
    </script>
  </body>
</html>

We have a counter component with a provide method that returns an object with the count property.

Its value is a computed property with this.count returned in the callback.

This way, the latest value will be propagated to the child component.

Then in the count-display component, we can get the value with the value property.

Conclusion

Provide and inject gives is a way to pass data to deeply nested components without using props.

Categories
Vue 3

Vue 3 — Named Slots and Slot Props

Vue 3 is in beta and it’s subject to change.

Vue 3 is the up and coming version of Vue front end framework.

It builds on the popularity and ease of use of Vue 2.

In this article, we’ll look at how to use slots to populate content with Vue 3.

Named Slots

We can name our slots so that we can have multiple slots in one component.

For instance, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
  </head>
  <body>
    <div id="app">
      <blog-post>
        <template v-slot:header>
          <h1>header</h1>
        </template>

        <template v-slot:default>
          <p>lorem ipsum.</p>
        </template>

        <template v-slot:footer>
          <p>footer</p>
        </template>
      </blog-post>
    </div>
    <script>
      const app = Vue.createApp({});

      app.component("blog-post", {
        template: `
          <header>
            <slot name="header"></slot>
          </header>
          <main>
            <slot></slot>
          </main>
          <footer>
            <slot name="footer"></slot>
          </footer>
        `
      });

      app.mount("#app");
    </script>
  </body>
</html>

The blog-post component has the header, main, and footer elements.

Inside each, they have their own slots.

We name the top slot with the name header .

And we named the bottom slot with the name footer .

The main tag has the slot with no name.

Then we can populate them with the template element.

The v-slot directive lets us populate the slots by passing in the slot name as the argument.

If a slot has no name, then we can refer to it with default .

Therefore, we get the h1 displayed on top.

‘lorem ipsum’ is displayed in the middle.

And ‘footer’ is displayed at the bottom.

Scoped Slots

Scoped slots let us make states from the child element available to the parent.

For example, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
  </head>
  <body>
    <div id="app">
      <items>
        <template v-slot:default="slotProps">
          <b>{{ slotProps.item.name }}</b>
        </template>
      </items>
    </div>
    <script>
      const app = Vue.createApp({});

      app.component("items", {
        template: `
          <ul>
            <li v-for="( item, index ) in items">
              <slot v-bind:item="item"></slot>
            </li>
          </ul>
        `,
        data() {
          return {
            items: [{ name: "apple" }, { name: "orange" }, { name: "grape" }]
          };
        }
      });

      app.mount("#app");
    </script>
  </body>
</html>

We have the items component with the items that we want to render.

Instead of rendering them directly, we pass them into the slot with the v-bind directive so that we can change how they’re rendered in the parent.

The argument of v-bind will be the property name in the slotProps variable in the parent’s template.

The value will be the property value of the slotProps.item property.

Therefore, we can add the template tag in between the items tag.

The template tag has the v-slot:default directive with the slotProps value.

And we can use that to customize the rendering of the properties passed in from v-bind in items .

If there’s only one slot, then we can shorter the syntax for accessing the lone default slot.

For instance, we can write:

v-slot="slotProps"

instead of:

v-slot:default="slotProps"

So we can have:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
  </head>
  <body>
    <div id="app">
      <items>
        <template v-slot="slotProps">
          <b>{{ slotProps.item.name }}</b>
        </template>
      </items>
    </div>
    <script>
      const app = Vue.createApp({});

      app.component("items", {
        template: `
          <ul>
            <li v-for="( item, index ) in items">
              <slot v-bind:item="item"></slot>
            </li>
          </ul>
        `,
        data() {
          return {
            items: [{ name: "apple" }, { name: "orange" }, { name: "grape" }]
          };
        }
      });

      app.mount("#app");
    </script>
  </body>
</html>

which is slightly shorter.

Conclusion

We can add named slots to our component and access data from the child in the parent with slot props.