The opposite of ‘appear’ for Vue transitions? - vue.js

I’m trying to create a ‘modal’ component. It’s using a ‘v-if’ attribute for deciding whether the component is shown or not.
The Vue docs mention you can use ‘appear’ for transitions (https://vuejs.org/guide/built-ins/transition.html#transition-on-appear), which works well.
However, when I close the modal, the element gets removed from the DOM, and the ‘leave’ transition is not trigged.
Is there an opposite of ‘appear’ for Vue transitions? How can I apply a leave transition when an element is removed from the DOM?
Link of example: https://jsfiddle.net/jelledv4/3Lr79hyg/4/
<transition
appear
enter-active-class="transition duration-1000 ease-out"
enter-from-class="transform scale-95 opacity-0"
enter-to-class="transform scale-100 opacity-100"
leave-active-class="transition duration-1000 ease-in"
leave-from-class="transform scale-100 opacity-100"
leave-to-class="transform scale-95 opacity-0"
>
PS I could use ‘v-show’ instead of ‘v-if’ to mitigate this, but for this use-case ‘v-if’ suits better. Plus I’m wondering how I can tackle this issue

In your provided jsfiddle, you use v-if on the parent element of the Transition components. Instead, use v-if on the child/slot of the Transition component. Otherwise, the transition components will be removed immediately.
const app = Vue.createApp({
data() {
return {
show: false,
};
},
methods: {
toggle() {
this.show = !this.show;
},
}
});
app.mount('#app');
<div id="app">
<button #click="toggle" class="fixed z-10 bg-white ml-3 mt-3 border border-gray-800">Toggle</button>
<div>
<!-- Modal background -->
<transition
appear
enter-active-class="transition duration-1000 ease-out"
enter-from-class="opacity-0"
enter-to-class="opacity-100"
leave-active-class="transition duration-1000 ease-in"
leave-from-class="opacity-100"
leave-to-class="opacity-0"
>
<div v-if="show" class="absolute left-0 top-0 w-full h-full bg-black bg-opacity-50"></div>
</transition>
<!-- Modal content -->
<transition
appear
enter-active-class="transition duration-1000 ease-out"
enter-from-class="transform scale-95 opacity-0"
enter-to-class="transform scale-100 opacity-100"
leave-active-class="transition duration-1000 ease-in"
leave-from-class="transform scale-100 opacity-100"
leave-to-class="transform scale-95 opacity-0"
>
<div v-if="show" class="fixed bg-red-500 w-52 h-52 z-10 mt-14">
<h2 >test</h2>
</div>
</transition>
</div>
</div>
<script src="https://unpkg.com/vue#3"></script>
<script src="https://cdn.tailwindcss.com"></script>

Related

JAMStack: how to impelement E-Mail Services or simple reactive variables?

I'm currently working on a marketing website for a client. Because SSR would be over-engineered and I'm curious about JAMStack, I decided to make a static website using Nuxt, because I'm already familiar with it.
So I managed to implement Tailwind, which works fine, also when the static site is generated and live on Github pages for example. Now I want to add a feature like a send e-mail form or a menu with a toggle icon.
The simplest solution I could think of for the menu would be a reactive variable and conditional rendering using tailwind. This works fine on localhost using npm run dev, but I can't figure out how to implement this on the generated static site.
I couldn't even manage to fire a click event to a defined method in the script tag.
That's how I would do the burger menu with nuxt SRR, how can I implement something like this in static generated nuxt?
<template>
<div>
<nav
class="flex p-5 items-center fixed w-full border-b-4 border-green bg-white z-50 duration-300 transform justify-between">
<div>
<div><img src="logo.png" class="max-h-6 md:max-h-10" /></div>
</div>
<div class="w-10 h-5 flex flex-col relative cursor-pointer" id="nav-menu" #click="menuOpen = !menuOpen">
<div class="w-full h-1 bg-green absolute duration-200 transform "
:class="[menuOpen ? 'rotate-45 translate-y-2' : 'burger-hover1']" id="menu-first"></div>
<div class="w-full h-1 bg-green absolute bottom-0 duration-200 transform "
:class="[menuOpen ? '-rotate-45 -translate-y-2' : 'burger-hover2']" id="menu-second"></div>
</div>
</nav>
<Transition>
<div v-if="menuOpen" class="fixed w-screen h-screen bg-white pt-20 z-30 space-y-10 text-xl text-center">
<div class="mt-20" #click="menuOpen = !menuOpen">
<nuxt-link to="/">Start</nuxt-link>
</div>
<div #click="menuOpen = !menuOpen">
<nuxt-link to="/partner">Partner</nuxt-link>
</div>
<div #click="menuOpen = !menuOpen">
<nuxt-link to="/kontakt">Kontakt</nuxt-link>
</div>
<div #click="menuOpen = !menuOpen">
<nuxt-link to="/impressum">Impressum</nuxt-link>
</div>
</div>
</Transition>
</div>
</template>
<script>
export default {
name: 'Navbar',
data() {
return {
menuOpen: false
}
}
}
</script>
That's how it looks on localhost. When the static site is hosted, it won't toggle the menu.

Vue: How to trigger transitions for dynamic components instances that use the same template

I'm using Vue 3 with tailwind and I'm having trouble transitioning between dynamic components. As described in the documentation, I tried the following:
<transition
enter-active-class="duration-500 ease-out"
enter-class="translate-x-full opacity-0"
enter-to-class="translate-x-0 opacity-100"
leave-active-class="duration-500 ease-in"
leave-class="translate-x-0 opacity-100"
leave-to-class="translate-x-full opacity-0"
mode="out-in"
>
<component :is="elementFormat" :key="elementKey" />
</transition>
Where elementFormat obviously refers to the component template that should be rendered. This is a computed property from the vuex store.
Although I can see that elementFormat is updated in the console, the transition won't play, unless I wrap a v-if or v-show around it, and hide it. What am I missing?
Check if you have imported the tailwind css correctly because I have recreated your problem and it seems everything works perfectly,
const Demo = {
data() {
return {
view: 'v-a'
}
},
components: {
'v-a': {
template: '<div>Component A</div>'
},
'v-b': {
template: '<div>Component B</div>'
}
}
}
Vue.createApp(Demo).mount('#demo')
.component-fade-enter-active,
.component-fade-leave-active {
transition: opacity 0.3s ease;
}
.component-fade-enter-from,
.component-fade-leave-to {
opacity: 0;
}
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/vue#next"></script>
<div id="demo">
<h3>Vue.js Demo</h3>
<input v-model="view" type="radio" value="v-a" id="a"><label for="a">A</label>
<input v-model="view" type="radio" value="v-b" id="b"><label for="b">B</label>
<transition name="component-fade" mode="out-in">
<component :is="view"></component>
</transition>
<br>
<br>
<h3>Your Code</h3>
<transition enter-active-class="duration-500 ease-out" enter-class="translate-x-full opacity-0" enter-to-class="translate-x-0 opacity-100" leave-active-class="duration-500 ease-in" leave-class="translate-x-0 opacity-100" leave-to-class="translate-x-full opacity-0"
mode="out-in">
<component :is="view" />
</transition>
</div>

Vue Sidebar Transition with tailwind leaving not works

I am trying to get it work sidebar related to this link
Entering works but leaving not works on both overlay and slide back.
<div class="fixed inset-0 flex z-40 lg:hidden" role="dialog" aria-modal="true" v-show="mobileMenuOpen">
<transition
enter-class="opacity-0"
enter-active-class="transition-opacity ease-linear duration-300"
enter-to-class="opacity-100"
leave-class="opacity-0"
leave-active-class="transition-opacity ease-linear duration-300"
leave-to-class="opacity-0"
>
<div class="fixed inset-0 bg-black bg-opacity-25" aria-hidden="true" v-show="mobileMenuOpen" #click="closeMobileMenu"></div>
</transition>
<transition
enter-active-class="transition ease-in-out duration-300 transform"
enter-class="-translate-x-full"
enter-to-class="translate-x-0"
leave-class="translate-x-0"
leave-active-class="transition ease-in-out duration-300 transform"
leave-to-class="-translate-x-full"
appear
> sidebar code
Your starting state for leave is opacity-0 try changing it to opacity-100
...
leave-class="opacity-100"
...
v-leave: Starting state for leave. Added immediately when a leaving transition is triggered, removed after one frame.

What is the correct way to use vue component libraries?

I am trying to use some external vue component libraries in a Laravel project but my first few attempts with vue-avatar, vue notification bell and pwa-install have all been unsuccessful.
With the first two, everything appears to check out, there're no build errors and the components actually get displayed in the html, but without the avatar or notification bell. There's no indication that the external packages where imported into my components.
These were the steps I took:
npm install of vue-avatar and notification-bell as required.
I created a component for each, imported the installed each package and did a default export.
I then utilised my component in another file as normal.
The codes are basically boilerplate with nothing to add so I'm at a loss as to why I didn't get the expected output.
Any help would be appreciated.
This is my avatar component code:
<template>
<div></div>
</template>
<script>
import Avatar from 'vue-avatar'
export default {
components: {
Avatar
}
}
</script>
And this is the code of the layout file where I used it:
<template>
<div>
<div class="min-h-screen bg-gray-100">
<nav class="bg-white border-b border-gray-100">
<!-- Primary Navigation Menu -->
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between h-16">
<div class="flex">
<!-- Logo -->
<div class="flex-shrink-0 flex items-center">
<inertia-link :href="route('dashboard')">
<breeze-application-logo class="block h-9 w-auto" />
</inertia-link>
</div>
<!-- Navigation Links -->
<div class="hidden space-x-8 sm:-my-px sm:ml-10 sm:flex">
<breeze-nav-link :href="route('dashboard')" :active="route().current('dashboard')">
Dashboard
</breeze-nav-link>
</div>
</div>
<div v-if="$page.props.flash.message" class="alert">
{{ $page.props.flash.message }}
</div>
<!-- <div>
<broadcast-message></broadcast-message>
</div> -->
<avatar :username="$page.props.auth.user.first_name"></avatar>
<notification-bell />
<div class="hidden sm:flex sm:items-center sm:ml-6">
<!-- Settings Dropdown -->
<div class="ml-3 relative">
<breeze-dropdown align="right" width="48">
<template #trigger>
<span class="inline-flex rounded-md">
<button type="button" class="inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium rounded-md text-gray-500 bg-white hover:text-gray-700 focus:outline-none transition ease-in-out duration-150">
{{ $page.props.auth.user.first_name }}
<svg class="ml-2 -mr-0.5 h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd" />
</svg>
</button>
</span>
</template>
<template #content>
<breeze-dropdown-link :href="route('logout')" method="post" as="button">
Log Out
</breeze-dropdown-link>
</template>
</breeze-dropdown>
</div>
</div>
<!-- Hamburger -->
<div class="-mr-2 flex items-center sm:hidden">
<button #click="showingNavigationDropdown = ! showingNavigationDropdown" class="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-none focus:bg-gray-100 focus:text-gray-500 transition duration-150 ease-in-out">
<svg class="h-6 w-6" stroke="currentColor" fill="none" viewBox="0 0 24 24">
<path :class="{'hidden': showingNavigationDropdown, 'inline-flex': ! showingNavigationDropdown }" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
<path :class="{'hidden': ! showingNavigationDropdown, 'inline-flex': showingNavigationDropdown }" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
</div>
</div>
<!-- Responsive Navigation Menu -->
<div :class="{'block': showingNavigationDropdown, 'hidden': ! showingNavigationDropdown}" class="sm:hidden">
<div class="pt-2 pb-3 space-y-1">
<breeze-responsive-nav-link :href="route('dashboard')" :active="route().current('dashboard')">
Dashboard
</breeze-responsive-nav-link>
</div>
<!-- Responsive Settings Options -->
<div class="pt-4 pb-1 border-t border-gray-200">
<div class="flex items-center px-4">
<div class="font-medium text-base text-gray-800">{{ $page.props.auth.user.first_name }}</div>
</div>
<div class="mt-3 space-y-1">
<breeze-responsive-nav-link :href="route('logout')" method="post" as="button">
Log Out
</breeze-responsive-nav-link>
</div>
</div>
</div>
</nav>
<!-- Page Heading -->
<header class="bg-white shadow" v-if="$slots.header">
<div class="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8">
<slot name="header" />
</div>
</header>
<!-- Page Content -->
<main>
<slot />
</main>
</div>
</div>
</template>
<script>
import BreezeApplicationLogo from '#/Components/ApplicationLogo'
import BreezeDropdown from '#/Components/Dropdown'
import BreezeDropdownLink from '#/Components/DropdownLink'
import BreezeNavLink from '#/Components/NavLink'
import BreezeResponsiveNavLink from '#/Components/ResponsiveNavLink'
//import BroadcastMessage from '#/Components/BroadcastMessage'
import Avatar from '#/Components/DashboardAvatar'
import NotificationBell from '#/Components/DashboardNotificationBell'
export default {
props: {
username: String,
},
components: {
BreezeApplicationLogo,
BreezeDropdown,
BreezeDropdownLink,
BreezeNavLink,
BreezeResponsiveNavLink,
//BroadcastMessage,
Avatar,
NotificationBell,
},
data() {
return {
showingNavigationDropdown: false,
}
},
}
</script>
Following your inputs, I tried to use the import in my component as suggested but I got a 'maximum call stack exceeded' error in console. See my new code:
<template>
<avatar :username="username"/>
</template>
<script>
import Avatar from 'vue-avatar'
export default {
props: {
username: String
},
name: 'DashboardAvatar',
components: {
Avatar
}
}
</script>
When I imported and used it directly, I got a type error 'cannot read property _c of undefined.
I corrected an error in my component: <avatar :username="username" /> not <dashboard-avatar> and the error is now the same type error as using the component directly that I referred to above.
I think you might misunderstand how Vue SFCs work.
You are importing the avatar component, but aren't actually using it.
A correct way would be for example:
<template>
<avatar v-bind="$attrs" v-on="$listeners"/>
</template>
<script>
import Avatar from 'vue-avatar'
export default {
name:'Avatar',
components: {
Avatar
}
}
</script>
This way you import the component from vue-avatar, and actually use it in the Avatar SFC, ($attrs and $listeners just bind all properties and events to the component). Once you have done this, your import should work.
Or you can simply import it directly in the parent component without creating a child component first (since you aren't customising the vue-avatar functionality but are just using what comes out of the box):
<script>
import BreezeApplicationLogo from '#/Components/ApplicationLogo'
import BreezeDropdown from '#/Components/Dropdown'
import BreezeDropdownLink from '#/Components/DropdownLink'
import BreezeNavLink from '#/Components/NavLink'
import BreezeResponsiveNavLink from '#/Components/ResponsiveNavLink'
//import BroadcastMessage from '#/Components/BroadcastMessage'
import Avatar from 'vue-avatar' <----------------------Use the module here directly.
import NotificationBell from '#/Components/DashboardNotificationBell'
export default {
props: {
username: String,
},
components: {
BreezeApplicationLogo,
BreezeDropdown,
BreezeDropdownLink,
BreezeNavLink,
BreezeResponsiveNavLink,
//BroadcastMessage,
Avatar,
NotificationBell,
},
...
You can use vue components directly, at least until you reach their limit and add some customization or whatever you feel like. You don't have to create a component for that component.
Your template for that component is just an empty div.
If you want to create a component for the component you should use it, not just import it. I'm a tiny bit rusty on Vue but I'm sure you can do this for the template instead:
<template>
<Avatar someAttribute="value" />
</template>
But you can just as well import the avatar component and use <Avatar> where you need it directly.
Creating a component like you did is useful for passing default parameters if you want to have some app-wide attributes you don't want to repeat. In this case you should also forward props to that component so it stays customizable for others.

Can't get Vue.js transition work without a warning message

I am using Vue.js transitions to fade elements in/out based on conditional rendering.
First, I am transitioning a group of components. This is working perfectly!
<div>
<transition-group name="component-fade" mode="out-in">
<component-one key="1" v-show="foo === 'one'" :type="type"/>
<component-one key="2" v-show="foo === 'two'" :type="type"/>
<component-one key="3" v-show="foo === 'three'" :type="type"/>
</transition-group>
</div>
Each component <component-one.../> is identical. I am rendering a bunch of <div> elements:
// component-one.vue
<template>
<div>
<div id="div-one">
<transition name="fade" mode="out-in">
<div key="one" v-if="foo === 'bar'">
<h3>My First Div</h3>
...
</div>
<div key="two" v-if="foo === 'bazz'">
<h3>My Second Div</h3>
...
</div>
<div key="three" v-if="foo === 'other'">
<h3>My Third Div</h3>
...
</div>
...
</transition>
</div>
</div>
</template>
The functionally works great. The components fade in/out nicely as to the div elements. However, I am getting a warning from vue:
[Vue warn]: can only be used on a single element. Use for lists.
This makes sense as I am transitioning a group of div elements. However, if I use <transition-group> the mode of out-in is no longer working. As I toggle through my div elements, they snap in/out of position as they are fading in/out. I have tried every combination -- including using v-if or v-show to see if that made a difference.
How can I use the transitions I have (that work) but not generate the warning?
`Adding "keys" on each item.
try this.
new Vue({
el: "#app",
data: {
foo: "bar"
},
methods: {
}
})
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.5s;
}
.fade-enter,
.fade-leave-to {
opacity: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<button #click="foo = 'bar'">show bar</button>
<button #click="foo = 'bazz'">show bazz</button>
<button #click="foo = 'other'">show other</button>
<transition name="fade" mode="out-in">
<div key="one" v-if="foo === 'bar'">
<h3>bar</h3>
</div>
<div key="two" v-if="foo === 'bazz'">
<h3>bazz</h3>
</div>
<div key="three" v-if="foo === 'other'">
<h3>other</h3>
</div>
</transition>
</div>
I believe that the transition element is supposed to wrap just one element and that the reason v-show isn't working is because v-show doesn't actually add/remove anything from the DOM, it just toggles the visibility attribute. If you use any logic that could theoretically show more than one at the same time, you'll get a warning. So what I think you'd really want is for your transition element to wrap each of the individual component-one elements. v-show should take care of the individual element's visibility. Basically, you need the final markup to look like this:
<transition name="fade" mode="out-in">
<div key="one" v-if="foo === 'bar'">
<h3>My First Div</h3>
...`enter code here`
</div>
</transition>
<transition name="fade" mode="out-in">
<div key="two" v-if="foo === 'bar'">
<h3>My Second Div</h3>
...
</div>
</transition>
Try doing either:
A v-for on the transition component that loops through a list of all the items you'd like to render. Each transition should have its own component-one child as it loops through the list.
<transition v-for="(item, index) in items" name="fade" mode="out-in">
<component-one :key="index" v-show="foo === item" :type="type"/>
</transition>
Wrapping the contents of component-one inside a transition. That would include the transition inside the component-one element. Then you can render component-one elements without any transition elements as a parent.
component-one
<transition name="fade" mode="out-in">
<div>
<!-- Component One contents... -->
</div>
</transition>
main
<div>
<component-one key="1" v-show="foo === 'one'" :type="type"/>
<component-one key="2" v-show="foo === 'two'" :type="type"/>
<component-one key="3" v-show="foo === 'three'" :type="type"/>
</div>
The above answers are in fact correct and should be considered by anyone who comes across this thread. I also wanted to add what I found was the underlying issue.
I was using <transition-group> on my parent element(s) that I was trying to transition between. Each component that I was transitioning to had multiple div's underneath. This is what was bubbling up (for lack of better words) that was looking for a <transition-group>.
TL;DR:
The error was coming from the child component(s) that needed to make use of <transition-group>.
Seems obvious now, but hope can save other folks some time.