I have a v-card that I want to animate back and forth with one click.
If I click on an arrow to the left, the card should scroll to the right and then immediately scroll back in from the left. The other button should work the other way around.
The problem is, nothing happens here. What am I doing wrong?
My template:
<v-card>
<v-btn icon #click="back = false">
<v-icon>mdi-arrow-left</v-icon>
</v-btn>
<v-btn icon #click="back = true">
<v-icon>mdi-arrow-right</v-icon>
</v-btn>
</v-card>
<transition :name="back ? 'slide-fade' : 'slide-fade-reverse'">
<v-card max-width="200" class="mx-auto mt-5" height="80">
<span class="d-flex justify-center pt-7">{{back}}</span>
</v-card>
</transition>
My script:
data() {
return {
back: false,
}
},
My css:
/* Prev */
.slide-fade-enter-active {
transition: all .3s ease;
}
.slide-fade-leave-active {
transition: all .3s ease;
}
.slide-fade-enter {
transform: translateX(100px);
opacity: 0;
}
.slide-fade-leave-to {
transform: translateX(-100px);
opacity: 0;
}
/* Next */
.slide-fade-reverse-enter-active {
transition: all .3s ease;
}
.slide-fade-reverse-leave-active {
transition: all .3s ease;
}
.slide-fade-reverse-enter {
transform: translateX(-100px);
opacity: 0;
}
.slide-fade-reverse-leave-to {
transform: translateX(100px);
opacity: 0;
}
I made a Pen for this: https://codepen.io/Tenarius/pen/WNwdEve
In order for leave and enter transition to work, the <transition> element has to have a v-if condition. When it changes from false to true, the element gets inserted into DOM and animates according to enter transition. When the condition changes from true to false, the leaving transition is performed and, when it ends, the element is removed from DOM.
However, you don't have such a condition. You're simply updating the cards contents and expect it to be removed from DOM and replaced by a new one.
In order to achieve the expected functionality you should use a list of cards (which would only contain the currently active card), coupled with using <transition-group> which, internally, uses the same mechanics as transition but the v-if condition is whether the element is part of the collection or not.
In your case, the "collection" would be a filtered list of cards, containing only one card. With this technique, the leaving element gets the leave animation, while the entering element gets the enter animation, as the elements are actually removed and added to DOM, according to changes in your model.
See it working here.
Since a transition needs leave and enter and thus the element has to "disappear" and "reappear", setTimout can be used to build a workaround.
data() {
return {
back: false,
loading: false
}
},
methods: {
loadTimeout() {
this.loading = true
setTimeout(function(){
this.loading = false
}.bind(this), 500);
}
}
The card can then be expanded with v-show="!loading" and the left- and right-buttons have to call the loadTimeout() function.
Working example here
Related
I'm trying to achieve a slide transition between two tabs. One tab is supposed to come from the left pushing the other one to the right and the opposite for the other one.
The leave transition goes well but the tab just pop in straight away without starting where it is supposed to...
I have made a CodePen to reproduce what I've tried : Slide transition test on CodePen
Here is the HTML, it is just a div containting 2 buttons that change the visibility of two div that represents my tabs content.
<div id="transition-test" class="demo">
<div class="tabs">
<button v-for="tab in tabs" class="tab" :key="tab.id" #click="selectedTab = tab.id"> {{tab.text}}</button>
<transition name="slide-right">
<div v-show="1 === selectedTab" class="tab1" key="tab1"></div>
</transition>
<transition name="slide-left">
<div v-show="2 === selectedTab" class="tab2" key="tab2"></div>
</transition>
</div>
</div>
In order to do the transition I do have the following css :
.slide-left,
.slide-right{
position: absolute;
}
.slide-right-enter-to,
.slide-right-leave {
opacity: 1;
transform: translateX(0);
}
.slide-right-enter,
.slide-right-leave-to {
opacity: 0;
transform: translateX(100%);
}
.slide-left-enter-active,
.slide-left-leave-active,
.slide-right-enter-active,
.slide-right-leave-active {
transition: all 500ms ease-in-out;
}
.slide-left-enter-to,
.slide-left-leave {
opacity: 1;
transform: translateX(0);
}
.slide-left-enter,
.slide-left-leave-to {
opacity: 0;
transform: translateX(-100%);
}
Does anyone have an idea about what I'm missing here ?
I found the issue. I don't know why in the Vue transition documentation the css class added at enter is v-enter but the class applied in reality is v-enter-from...
this css class :
.slide-left-enter
becomes :
.slide-left-enter-from
Instead of coding it by yourself, you can use npm version of the transition. It will also help you with its API, Guides and Examples, so that you don't have to worry about those.
I have a simple VueJS application where I have multiple routes. For a pair of routes, I want to have a scroll down and scroll up animation while routes change.
For example, I have a search/dropdown page, where after the search result from the dropdown is selected, I want to take him to the details page but with a scroll down animation. So that the user feels he is still on the same page.
I have tried using the VuePageTransition library. That is indeed a great library but does not have this specific animation that I need.
Update:
I tried the following code. It gives a scroll-like animation but the leaving page is shown going down but the coming page is not shown during the animation.
In the template in App.vue
<template>
<div id="app">
<transition name="slide" mode="out-in">
<router-view></router-view>
</transition>
</div>
</template>
In the style tag,
.slide-enter {
}
.slide-enter-active {
animation: slide-in-coming 2s ease-out forwards;
}
.slide-leave {
}
.slide-leave-active {
animation: slide-in 2s ease-out forwards;
}
#keyframes slide-in {
from {
transform: translateY(0);
}
to {
transform: translateY(800px);
}
}
#keyframes slide-in-coming {
from {
transform: translateY(-800px);
}
to {
transform: translateY(0);
}
}
Explanation
I have an icon in a button that when clicked opens a menu.
If I want to get the menu correctly under button I need to change the height, because there doesn't seem to be another option (margin doesn't work). But then I get A background hover effect while not hovering over the button.
So to understand.
Once I click on the button but don't hover over the button I get the effect.
The shape is because I changed the height en width.
Background-color: transparant !important; Doens't work
This is the component file
<template>
<v-menu
:close-on-click="true"
offset-y
transition="slide-y-transition"
v-model="language_menu_open"
return-value="language_menu_open = false">
<template v-slot:activator="{ on }">
<v-btn
v-on="on"
v-ripple="false"
icon
depressed
class="language-button">
<v-icon>{{functionMenu(language_menu_open)}}</v-icon>
<v-icon style="margin-right: 10px !important;">mdi-web</v-icon>
</v-btn>
</template>
<v-list class="language-dropdown">
<v-list-item
v-for="language in languages"
:key="language.title"
v-ripple="false"
#click="language">
<v-list-item-title>{{language.title}}</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
</template>
<script>
export default {
name: "Languages",
data() {
return {
language_menu_open: false,
languages: [
{title: "Nederlands"},
{title: "English"},
{title: "Deutsch"}
],
functionMenu: function chevronChanger(menu_state) {
if (menu_state) {
return "mdi-chevron-up"
} else {
return "mdi-chevron-down"
}
}
}
}
}
</script>
<style scoped>
.language-button {
height: 45px !important;
width: 25px !important;
background-color: transparent !important;
}
.language-button:hover {
background-color: transparent !important;
}
.language-button:hover:before {
background-color: transparent !important;
display: none !important;
}
.language-dropdown {
padding: 0 !important;
}
.language-dropdown:hover:before {
background-color: transparent !important;
display: none;
}
</style>
Any help would be appreciated.
You stated that your primary goal is correct positioning of the menu below the button. Instead of changing the height/width of the button, you should use the nudge-top, nudge-bottom, nudge-right, and/or nudge-left props of the v-menu component. These allow you to precisely tune the positioning of the menu. For example, if you want to move the menu down (further below the button), set the nudge-bottom prop to the number of pixels you want it to move. See documentation here. Using this approach, you don't need to modify the hover effect of your button, which will result in a better user experience.
Also, there is one portion of your template that is inefficient. For one of the icons, you have {{functionMenu(language_menu_open)}}. If possible, you should not use methods in a template except as event handlers, because methods break Vue's reactivity engine. In this case, it would be much better to define a computed property, like this:
computed: {
buttonChevron: function() {
if (this.language_menu_open) {
return "mdi-chevron-up"
} else {
return "mdi-chevron-down"
}
}
}
Then, in your template, instead of {{functionMenu(language_menu_open)}}, use {{ buttonChevron }}. This will have the same effect as your current function, but Vue will handle the changes much more efficiently. (In this particular component, the change will be negligible, but it's a good habit to build.) You can read more about computed properties here.
Try This:
.language-button:active {
background-color: transparent !important;
}
I am trying to apply toggle between divs. But I couldn't make it happen. I know I can't put two elements inside the transition. I need to a transition-group for that. But when I use group, then it says bind the elements inside group. But I am not looping the elements... So I am a bit stuck about solving this...
template
<div>
<transition name="view">
<map-view
v-show="this.screenView == 'map'"
:changeView="changeView" />
<list-view
v-show="this.screenView === 'list'"
:changeView="changeView" />
</transition>
</div>
script
methods: {
changeView(screen){
this.screenView = screen;
}
}
styles
.view-enter-active, .view-leave-active {
transition: opacity .5s
}
.view-enter, .view-leave-to {
opacity: 0
}
By the way changeView() is working. No problem about that part. Just trying to toggle between divs.
Thanks to advice in the comments.
I tried to give different keys to elements.
<transition-group name="view">
<map-view
key="maps"
v-show="this.screenView == 'map'"
:changeView="changeView" />
<list-view
key="list"
v-show="this.screenView === 'list'"
:changeView="changeView" />
</transition-group>
and changed the styles like below.
.view-enter-active, .view-leave-active {
transition: opacity 0.5s ease-in-out, transform 0.5s ease;
}
.view-enter-active {
transition-delay: 0.5s;
}
.view-enter, .view-leave-to {
opacity: 0;
}
.view-enter-to, .view-leave {
opacity: 1;
}
It's working now.
To illustrate what I'm trying to achieve but also discuss and learn about each mechanism separately, I split the issue into two independent challenges:
1. Keep previous route visible until new route has transitioned in
Whether the transition is sliding, what I'm trying here, or just fading; mode in-out doesn't work as I would expect it, namely that the existing route stays visible until the next route has finished its transition (e.g. overlaid itself over the previous one), exactly as illustrated here in the last example of this section https://v2.vuejs.org/v2/guide/transitions.html#Transition-Modes, showing two buttons with in-out mode. Instead no transition is happening but it just flips the routes statically at half of the given transition time.
Is there any caveat with routes and an obvious reason why this wouldn't work the same way, e.g. that a single router-view can only hold one at the time and therefore in-out is not possible?
EDIT 1:
I figured out that in-out would actually only work with position:absolute on both elements, otherwise they will not overlay. Any idea how I could elegantly include such a behavior, potentially setting that absolute position during router-transition only?
Current hack that has the visual slide-up modal effect (mode: in-out) I'm looking for: adding style="position:absolute; z-index:2100" to the dialog route. Then I would need to change the underlying transition once it's shown in order to have the reverse hide effect (mode: out-in).
Also see EDIT 2 below.
2. Creating a modal-like page (route) which opens above another existing page when navigated to
I tried to hack that behavior by adding a second router-view in App.vue
<router-view />
<router-view name="dialog" />
The particular component is added to my routes like this
{
path: 'records/new',
components: {
dialog: () => import('layouts/NewRecord.vue')
},
children: [
{
name: 'new-record',
path: '',
component: () =>
import('src/pages/NewRecord.vue')
}
]
}
I'm not sure whether this approach even makes sense but I couldn't make it work properly. The aim would be to just overlay another router-view name="dialog whenever a "dialog"-path is pushed, so while it can be animated (slide-up) the other router-view stays visible below. In the end I guess I'm facing the same issue here: once the route changes, the initial router-view discards its component because the path does not match the current location anymore.
Either way, there are people out there with more experience and expertise so I hope I could illustrate what I'm trying to achieve and I'm just curious and thankful to read your inputs.
EDIT 2
I could make it work the way I wanted with simply one , wrapped in a custom page-transition component. It is quite a hack though AND I needed to add position: absolute to may page-layouts, to all of them actually (both the "leaving" and the "entering" component need position: absolute) when showing the dialog component. I'm sure there's a better way but I haven't found it so far.
Custom page-transition component:
<template>
<transition :name="name" :mode="mode">
<slot/>
</transition>
</template>
<script lang="ts">
import { Component, Watch } from 'vue-property-decorator'
import Vue from 'vue'
import { Route } from 'vue-router'
#Component({
components: {}
})
export default class PageTransition extends Vue {
NAME_FADE = 'fade'
NAME_SLIDE_UP = 'slide-up'
NAME_SLIDE_DOWN = 'slide-down'
MODE_OUT_IN = ''
MODE_IN_OUT = 'in-out'
name = this.NAME_FADE
mode = this.MODE_OUT_IN
#Watch('$route', { immediate: true, deep: true })
onRouteChanged(newVal: Route, oldVal: Route) {
if (newVal.meta.transition === 'dialog') {
this.name = this.NAME_SLIDE_UP
this.mode = this.MODE_IN_OUT
} else if (oldVal && oldVal.meta.transition === 'dialog') {
this.name = this.NAME_SLIDE_DOWN
// shift next page in immediately below dialog
this.mode = this.MODE_IN_OUT
} else {
// default
this.name = this.NAME_FADE
this.mode = this.MODE_OUT_IN
}
}
}
</script>
<style lang="scss" scoped>
.fade-enter, .fade-leave-to {
opacity: 0;
}
.fade-enter-active, .fade-leave-active {
transition: all 0.1s ease;
}
// start of enter element
.slide-up-enter {
transform: translateY(60%);
opacity: 0;
}
.slide-up-enter-active {
transition: all 0.3s ease-out;
z-index: 2100;
}
// start of leave element
.slide-up-leave, .slide-up-leave-active {
opacity: 0;
}
// start of leave element
.slide-down-leave {
z-index: 2100;
}
.slide-down-leave-to {
transform: translateY(60%);
opacity: 0;
z-index: 2100;
}
.slide-down-leave-active {
transition: all 0.3s ease-in;
}
// start of enter element
.slide-down-enter {
opacity: 0;
}
.slide-down-enter-active {
/* show immediately behind existing page (lower z-index) */
transition: all 0s;
}
</style>
I have a similar task. I was able to complete it using fixed containers and z-index shuffle. I met a number of issues related to scroll and vertical alignment, and, in my case, solving it using absolute position during router-transition only was not possible.
Here's the demo: https://kasheftin.github.io/vue-router-in-out-slide-scroll.
Also, I had to use localStorage to keep & restore page scroll position.
In my case page content has to be vertically aligned. That's why I could not use one global scrollable container (e.g. <body>). In-out mode transition works rather simple - it just appends the content, adds some classes and then removes the first child. That means in the middle there're two page containers side by side, and if one of them is tall (and forces the body to have scroll), then the other one appears in the middle of the body and has wrong vertical alignment.
So I just wrapped every page with fixed scrollable container. Assume we have a List and an Item pages, and the last should slide from the right and overlay the list. Then, the right-to-left animation is very simple:
.slide-right-enter-active {
transition: transform 1s ease;
.slide-right-enter {
transform: translateX(100%);
}
Left-to-right animation (overlay disappearing) has the wrong z-index. During the animation we have the following in the DOM:
<transition>
<Item />
<List />
</transition>
By default List will be shown over the Item, but it has to be below. So there're the rules:
.slideable-page {
position: fixed;
overflow: auto;
z-index: 2;
}
.slide-left-enter {
z-index: 1;
}
.slide-left-enter-active {
z-index: 1;
}
.slide-left-leave-active {
transition: transform 1s ease;
z-index: 3;
}
.slide-left-leave-to {
transform: translateX(100%);
}
For question 1: Have you added the CSS with it? The transition by itself only handles timing, you need to add the CSS for the transition to work (example: https://v2.vuejs.org/v2/guide/transitions.html#Transitioning-Single-Elements-Components).
.fade-enter-active, .fade-leave-active {
transition: opacity .5s;
}
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
opacity: 0;
}
For question 2:
I don't know if I understood correctly your situation, but if I did, here is what I would do, using nested routes.
layouts/NewRecord.vue
<template>
<router-view name="dialog"></dialog>
</template>
Routes
const routes = {
path: 'records/new',
component: () => import('layouts/NewRecord.vue'),
children: [
{
path: 'dialog',
components: {
dialog: () => import('src/pages/NewRecord.vue'),
},
},
],
}