Slide transition on tab (one pushing the other) - vue.js

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.

Related

How to animate a transition from one Vue component to the same component (with a different parameter) using router?

I have a Vue 2 application. It uses router to manage pages. On one page, you can click to go to the next page, and the next page is the same component. You can think of it like a folder page going to a new sub folder page. The URL is the mostly same, except for the folder ID.
I want this animated, so the new component slides in from the right, over-top the old page.
But I think the router likes to reuse the same component, so how can I make multiple pages of the same component?
You can use the key attribute on your component, keyed to the route's folder ID param so that every new page load causes Vue to re-render the component which should also trigger your animation.
codesandbox
<template>
<div class="container">
<Transition name="slide">
<h1 class="text" :key="$route.params.id">Page {{ $route.params.id }}</h1>
</Transition>
</div>
</template>
.container {
position: relative;
}
.text {
position: absolute;
}
.slide-enter-active {
animation: slide 1s;
}
.slide-leave-active {
transition: all 1s;
opacity: 0;
}
#-webkit-keyframes slide {
0% {
-webkit-transform: translateX(200px);
transform: translateX(200px);
}
100% {
-webkit-transform: translateX(0px);
transform: translateX(0px);
}
}
#keyframes slide {
0% {
-webkit-transform: translateX(200px);
transform: translateX(200px);
}
100% {
-webkit-transform: translateX(0);
transform: translateX(0);
}
}

transition toggle between divs in vue js

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.

Vue.js router transition fade gap

I'm using the amazing transition to slide router pages in vue.js
<template>
<div>
<header-comp></header-comp>
<transition
name="custom-classes-transition"
mode="out-in"
enter-active-class="animated slideInLeft"
leave-active-class="animated slideOutRight"
>
<router-view></router-view>
</transition>
<footer-comp></footer-comp>
</div>
</template>
<style>
#import 'https://cdn.jsdelivr.net/npm/animate.css#3.5.1';
</style>
It works very nice and smooth, but... the new coming page enter when the first one is totally gone. This made a gap between transition.
In Vue manual: Transition-Modes there are a few examples. I need to replicate the third button example but I'm missing the mode I have to use.
Any suggestion?
The main problem with your transitioning elements is that you want them to occupy the same space in DOM at the same time (even if, visually, one enters and one exists - that's only done through transforms but the two elements need to occupy the same space in DOM).
Therefore you need to give one of them position:absolute and use CSS to size and position it correctly, to match the exact position and size it would have when not having position:absolute (which is what it will have when not trasitioning).
Here's a working example. Note yours might need different styles applied to a different element.
Since you haven't provided a minimal, reproducible example with your own markup, there's no way to know.
In the example above, I gave the subsequent <div> (the entering one)
position: absolute;
width: 100%;
top: 60px;
left: 0;
If you choose to wrap all your <router-view>s into a common wrapper element with position:relative, top would need to be 0 (in the example 60px is accounting for <nav>'s height).
Note: and yes, as others already pointed, you don't need mode="in-out". But that still leaves you with the positioning issue.
Edit: I've played with two more examples.
one using a flexbox container of height:100vh where top and bottom elements don't grow and middle one does. When middle element is too big, it becomes scrollable.
another one where I played with the transition effects and Bootstrap Vue.
Actually since you don't need any special behaviour and actually want both transitions to happen at the same time, you shouldn't be using the mode at all. Just remove it and it should work as you described. From the docs link you pasted:
Simultaneous entering and leaving transitions aren’t always desirable though, so Vue offers some alternative transition modes
in-out: New element transitions in first, then when complete, the current element transitions out.
out-in: Current element transitions out first, then when complete, the new element transitions in.
mode="in-out": New element transitions in first, then when complete, the current element transitions out.
new Vue({
el: '#app',
data: {
message: 'Hello Vue!',
showOn: true
},
methods: {
handleClick() {
console.log(this.message);
}
}
})
.slide-fade-enter-active {
transition: all .3s ease;
position: absolute;
left: 0;
top: 0;
}
.slide-fade-leave-active {
position: absolute;
left: 0;
top: 0;
}
.slide-fade-enter, .slide-fade-leave-to {
transform: translateX(10px);
opacity: 0;
}
#app {
position: relative;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<body>
<div id="app">
<transition name="slide-fade" mode="in-out">
<button v-if="showOn"
key="on"
type="button"
#click="showOn=false">On</button>
<button v-else type="button"
key="off"
#click="showOn=true">Off</button>
</transition>
</div>
</body>

Vue js: how to load a css animation only on the first page load

In the Home Page component, I've a <div> element with an animation, like this:
<div class="square">
//content
</div>
and in the CSS:
.square
height: 200px
width: 200px
margin: 0 auto
z-index: 0
position: absolute
top: 38vh
left: 43vw
margin-left:auto
margin-right:auto
animation-name: stretch
animation-duration: 0.3s
animation-timing-function: ease-out
animation-delay: 0
animation-direction: alternate
animation-fill-mode: forwards
animation-play-state: running
#keyframes stretch
from
transform: scale (1)
to
transform: scale(1.8)
It is a simple square that grows, with some other <div> elements inside.
Now, I would like to play the animation only on initial login and opening of app, not each time the view appears (like is working now).
Could you help me? Please, don't take anything for granted, because I am a beginner. Thank you in advance.
its simple. create a flag in the data of the vue property. you can call it animate. add a function that will turn it off after the login, somthing like:
data:{
animate:true
},
methods:{
turnOffAnimate:{
this.animate=false;
}
}
call this.turnOffAnimate when you want to turn off your animation. now, in your html div, bind the class in this way:
<div :class="{square : animate}">
//content
</div>
this condition means that your div will own square class only while animate flag is true. you can read more about class binding in vue.

Scroll bar below fixed header with Vuetify + Electron

I am using Vuetify and Electron to make an app to help me with certain tasks at my job. I have disable the browserWindow frame and made my header the draggable area with a button to close the window. I am using the electron vuetify template
vue init vuetifyjs/electron
My problem is the scrollbar reaches all the way to the top but I would like it below my fixed header.
I have tried playing with overflow properties on the html, body, app div, and content div tags but i have not been successful.
How would I accomplish this?
This is purely a CSS question really as you can see this behaviour in the browser too with similar layouts. The easiest way to fix this is using a flex layout:
HTML:
<div class="container">
<div class="titlebar"></div>
<div class="content">
<h1>So much content we scroll</h1>
<h1>So much content we scroll</h1>
<!-- etc -->
</div>
</div>
CSS:
body {
margin: 0;
padding: 0;
overflow: hidden;
}
.container {
width: 100vw;
height: 100vh;
display: flex;
flex-direction: column;
}
.titlebar {
background-color: blue;
height: 35px;
flex-shrink: 0;
}
.content {
flex-grow: 1;
overflow-x: auto;
}
Check out this out in this CodePen
I'd like to offer a Vuetify specific answer for this question, this should apply whether or not Electron is involved.
Vuetify's default styles make this a bit more difficult than a simple CSS solution can give you, especially when the layout gets more complex.
For this example I'm using the complex layout from Vuetify's pre-defined themes here
Vuetify ships with an overflow-y: scroll on the html element so the first step is adding an override for this.
html {
overflow: hidden;
}
This will get rid of the bar on the right side that spans the whole height of the app.
Next you will want to set your v-content area as the scrollable area. There are a few gotchas to watch out for when you're setting this area:
Display flex is already declared
Vuetify sets padding in the style attribute so you'll need to override depending on your case
You'll need a margin the height of your header(only matters if you're changing header height from 64px)
You'll need to remove the header height from the height of the content container using calc(Same as above)
If you have a nav drawer on the right side you'll need to bind a class to take care of this.
My CSS for v-content looks like this, you will need an important to override the padding since it is set by Vuetify through style binding:
main.v-content {
width: 100vw;
height: calc(100vh - 64px);
flex-direction: column;
overflow: scroll;
margin-top: 64px;
padding-top: 0 !important;
}
I also have a class bound to the state of the temporary right drawer on the v-content tag in the template, this makes sure that the scroll bar doesn't disappear underneath the right nav drawer when it's open:
<v-content :class="{ draweropen: drawerRight }">
And the CSS for that bound class, once again you'll need an important to remove the default right padding Vuetify puts on v-content when the drawer is open:
.draweropen {
width: calc(100vw - 300px) !important;
padding-right: 0 !important;
}
You can optionally set the flex-direction to column-reverse if your content is bottom loaded like a chat which is what I'm doing in this CodePen Example
I built a little component that wraps the v-main and moves the scrollbar to the main container instead of the default (the entire html).
Simply replace v-main with this and you're done.
<template>
<v-main class="my-main">
<div class="my-main__scroll-container">
<slot />
</div>
</v-main>
</template>
<script>
export default {
mounted: function() {
let elHtml = document.getElementsByTagName('html')[0]
elHtml.style.overflowY = 'hidden'
},
destroyed: function() {
let elHtml = document.getElementsByTagName('html')[0]
elHtml.style.overflowY = null
},
}
</script>
<style>
.my-main
height: 100vh
.my-main__scroll-container
height: 100%
overflow: auto
</style>