How to chain transitions/animation in Vue? - vue.js

Target
On click "Open menu" button:
Dim overlay appearing with fade-in animation
Once dim overlay animation done, from the top, dim overlay is appearing with the sliding animation from the top to bottom:
Solution attempt and problem
<template lang="pug">
transition(name="fade")
.DrawerMenu-DimUnderlay(v-if="displayFlag")
.DrawerMenu-Body Drawer menu
</template>
Before slide down the .DrawerMenu-Body, .DrawerMenu-DimUnderlay must be mounted and rendered.
I don't know how to implement it.
🌎 Fiddle

You can achieve that by using CSS Animations and Vue Transition.
First, separate your overlay and content into different transitions:
<template lang="pug">
div
transition(name="overlay")
.DrawerMenu-Overlay(v-if="displayFlag")
transition(name="content")
.DrawerMenu-Body(v-if="displayFlag") Drawer menu
</template>
Then define your animations:
.DrawerMenu {
&-Overlay {
...
display: none;
}
...
}
.overlay-enter-active {
display: block;
animation: fade-in-and-slide-down 2s;
}
.content-enter-active {
animation: wait-and-fade-in 3s;
}
.content-leave-active {
animation: fade-out 1s;
}
#keyframes fade-in-and-slide-down {
0% {
opacity: 0;
}
50% {
opacity: 1;
transform: translateY(0);
}
100% {
transform: translateY(100%);
}
}
#keyframes wait-and-fade-in {
0% {
opacity: 0;
}
66% {
opacity: 0;
}
100% {
opacity: 1;
}
}
#keyframes fade-out {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
Example in CodeSandbox.
Another solution is using JavaScript animation library (such as animejs) combine with Vue Transition Hooks. I would prefer this solution for a complex animation.
<template lang="pug">
transition(#enter='enter' #leave='leave')
div(v-if='displayFlag')
.DrawerMenu-Overlay(ref='overlay')
.DrawerMenu-Body(ref='content' #click="displayFlag = false") Drawer menu
</template>
import anime from "animejs";
...
methods: {
enter(el, done) {
anime
.timeline({
easing: "linear",
duration: 1000,
complete: done
})
.add({
targets: this.$refs.overlay,
opacity: [0, 1]
})
.add({
targets: this.$refs.overlay,
translateY: "100%"
})
.add({
targets: this.$refs.content,
opacity: [0, 1]
});
},
leave(el, done) {
anime({
targets: el,
duration: 2000,
opacity: 0,
complete: done
});
},
...
}
...
You can also use without transition component but you have to handle v-if variable by yourself.
Example in CodeSandbox.

Not sure if there are 2 questions here, but for your last question, I would say that is because that ref component does not have a property display.
It does however, have a function display()
Therefore, change your button click to this:
<button #click="$refs.drawerMenu.display()">Open menu</button>

Related

How can I animate the changed element with vuejs?

Using Vuejs the data of some elements on the page is changing.
However, this change is not understood by the user.
For example, I am making a counter by clicking a button. I am printing data as {{counter}} to span element.
But this change is not noticed by the user. How can I give it various animations?
I tried to combine a css that I found have an animation I wanted, but was unsuccessful.
The Vuejs documentation says you can do it with toggleCss, but that's not what I want.
var app=new Vue({
el: "#app",
data: {
myCount:0
}
,
methods: {
btnCount() {
this.myCount+=1
}
}
})
<div id='app'>
<button #click='btnCount'>
+++++
</button>
<span class='myAnimSpan'>{{myCount}}</span>
</div>
span:hover {
animation: shake 0.5s;
}
#keyframes shake {
0% { transform: translate(1px, 1px) rotate(0deg); }
10% { transform: translate(-1px, -2px) rotate(-1deg); }
20% { transform: translate(-3px, 0px) rotate(1deg); }
30% { transform: translate(3px, 2px) rotate(0deg); }
40% { transform: translate(1px, -1px) rotate(1deg); }
50% { transform: translate(-1px, 2px) rotate(-1deg); }
60% { transform: translate(-3px, 1px) rotate(0deg); }
70% { transform: translate(3px, 1px) rotate(-1deg); }
80% { transform: translate(-1px, -1px) rotate(1deg); }
90% { transform: translate(1px, 2px) rotate(0deg); }
100% { transform: translate(1px, -2px) rotate(-1deg); }
}
Jsfiddle : https://jsfiddle.net/au4x031g/
You can use Vue transitions (see https://v3.vuejs.org/guide/transitions-enterleave.html#transitioning-single-elements-components).
Using a <transition> tag with name, and the element with key (each change to that key will trigger a transition update)
<transition name="shaketext">
<span class='myAnimSpan' :key="myCount">{{myCount}}</span>
</transition>
The name property is used for css declarations for enter/leave events, for example:
.shaketext-enter-active {
animation: shake 0.9s;
}
.shaketext-leave-to, .shaketext-leave-active{
display: none;
}
Also, to make transforms work, the span element should be a block element (display:inline-block for example)
.myAnimSpan {
display: inline-block;
}
https://jsfiddle.net/udctfs49/1/

How to change vue-toasted pop-up direction

I'm using vue-toasted to show notifaction.
By default, it pops down to top.
Code:
<v-btn #click="onTest" />
...
onTest() {
this.$toast.info('Test!', {
containerClass: 'toasted-container',
className: 'toasted',
keepOnHover: true,
})
},
nuxt.config.js:
toast: {
duration: 3800,
action: {
icon: 'mdi-close-circle',
onClick: (e, toastObject) => {
toastObject.goAway(0)
},
},
iconPack: 'mdi',
icon: 'mdi-check-circle',
},
The toast pops down to top.
I tried to add some css like transition.
I'm not familiar with CSS, so I guess it's wrong.
How can I change it to right to left?
I couldn't find any info on their GitHub.
vue-toasted v1.1.27 only supports animating upward or downward. It has no API to add new animations.
I added some #keyframes and animation, it worked.
#keyframes in {
0% {
opacity: 0;
left: 100%
}
100% {
opacity: 1;
left: 0%
}
}
.some-class{
animation: in 300ms;
}

why transition methods like `beforeLeave` not working in nuxt

I want to code to achieve such an effect: Advanced fade transition (with height)
I follow the nuxt document add the transition in layouts/default.vue
<template>
<main>
...
<nuxt/>
...
</main>
</template>
<script>
export default{
data(){
return {
prevHeight: 0
}
},
transition: {
beforeLeave(element) {
console.log(element)
this.prevHeight = getComputedStyle(element).height;
},
enter(element) {
console.log(element)
const { height } = getComputedStyle(element);
element.style.height = this.prevHeight;
setTimeout(() => {
element.style.height = height;
});
},
afterEnter(element) {
console.log(element)
element.style.height = 'auto';
}
}
}
</script>
<style type="text/css">
.page-enter-active,
.page-leave-active {
transition-duration: 0.3s;
transition-property: height, opacity;
transition-timing-function: ease;
overflow: hidden;
}
.page-enter,
.page-leave-active {
opacity: 0
}
</style>
but none of console.log(element) work.
I already googled for it but nothing helped.
Ok, I fixed it. The transition should add in nuxt page-component instead of layout-component

How to trigger function on viewport visible with Vue viewport plugin

I am using an counter to display some numbers, but they load up when the page loads, so it loads unless I do some button to trigger it.
Found this viewport plugin (https://github.com/BKWLD/vue-in-viewport-mixin) but I weren't able to use it. That's what I need to do, trigger a function when I reach some element (entirely), how to achieve it?
You don't necessarily need a package to do this. Add an event listener to listen to the scroll event, and check if the element is in the viewport every time there's a scroll event. Example code below - note that I've added an animation to emphasize the "appear if in viewport" effect.
Codepen here.
new Vue({
el: '#app',
created () {
window.addEventListener('scroll', this.onScroll);
},
destroyed () {
window.removeEventListener('scroll', this.onScroll);
},
data () {
return {
items: [
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12
],
offsetTop: 0
}
},
watch: {
offsetTop (val) {
this.callbackFunc()
}
},
methods: {
onScroll (e) {
console.log('scrolling')
this.offsetTop = window.pageYOffset || document.documentElement.scrollTop
},
isElementInViewport(el) {
var rect = el.getBoundingClientRect();
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
);
},
callbackFunc() {
let items = document.querySelectorAll(".card");
for (var i = 0; i < items.length; i++) {
if (this.isElementInViewport(items[i])) {
items[i].classList.add("in-view");
}
}
}
}
})
.card {
height: 100px;
border: 1px solid #000;
visibility: hidden;
opacity: 0
}
.in-view {
visibility: visible;
opacity: 1;
animation: bounce-appear .5s ease forwards;
}
#keyframes bounce-appear {
0% {
transform: translateY(-50%) translateX(-50%) scale(0);
}
90% {
transform: translateY(-50%) translateX(-50%) scale(1.1);
}
100% {
tranform: translateY(-50%) translateX(-50%) scale(1);
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app" onscroll="onScroll">
<div v-for="item in items" class="card">
{{item}}
</div>
</div>
Another option is to use an intersection observer - I haven't explored this yet but this tutorial seems good: alligator.io/vuejs/lazy-image. Note that you will need a polyfill for IE.

Sticky navigation which appears on scroll

I am trying to create a menu which is hidden but appears, fixed to the top, once the user begins scrolling down the page. So far I have managed to create a menu which sticks to the top upon scrolling but am stuck on how to hide this menu initially.
This is the code I am using so far:
(I am using wordpress-headway)
JQuery:
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script type="text/javascript">
jQuery(document).ready(function($) {
//STICKY NAV
var isMobile = {
Android: function() {
return navigator.userAgent.match(/Android/i) ? true : false;
},
BlackBerry: function() {
return navigator.userAgent.match(/BlackBerry/i) ? true : false;
},
iOS: function() {
return navigator.userAgent.match(/iPhone|iPad|iPod/i) ? true : false;
},
Windows: function() {
return navigator.userAgent.match(/IEMobile/i) ? true : false;
},
any: function() {
return (isMobile.Android() || isMobile.BlackBerry() || isMobile.iOS() || isMobile.Windows());
}
};
//Calculate the height of <header>
//Use outerHeight() instead of height() if have padding
var aboveHeight = $('.top-row').outerHeight();
//when scroll
$(window).scroll(function(){
//if scrolled down more than the header’s height but this isn't mobile
if ($(window).scrollTop() > aboveHeight && !isMobile.any()){
// if yes, add “fixed” class to the <nav>
// add padding top to the #content
// (value is same as the height of the nav)
$('.block-type-navigation').addClass('fixed').css('top','0').next()
.css('padding-top','42px');
} else {
// when scroll up or less than aboveHeight,
// remove the “fixed” class, and the padding-top
$('.block-type-navigation').removeClass('fixed').next()
.css('padding-top','0');
}
});
});
</script>
CSS:
.fixed {
position:fixed !important;
left: 0;
text-align: center;
}
.fixed .block-content {
display: inline-block;
text-align: left;
width: 940px; /* This should be the width of your grid!!! */
float:none;
}
.fixed {
position:fixed !important;
left: 0;
text-align: center;
display: block !important;
}
It's driving me crazy so I'd appreciate ANY help!
Thank you!
If you don't want the nav to show unless the user has scrolled passed a certain point then couldn't it always be fixed just off the top of the screen:
.menu {
position:fixed;
top:-42px;
}
then shown or hidden by toggling a class
.menu.is-visible {
top:0;
}
using a scroll listener.
$win = $(window);
$win.on('scroll', function() {
$(".menu").toggleClass('is-visible', $win.scrollTop() > 42);
});
You could even add some CSS animation to the top property
.menu {
-webkit-transition: top 0.2s ease-in-out;
}
to get a cool transition.
Note: All this code is typed out right in here and not tested.
Note: You should definitely put a throttle on the scroll handler too.