GSAP animation not working with $ref in Nuxt - vue.js

I am having an issue with a GSAP animation in a Nuxt build. The animation is to start when the
overlay navigation panel is brought into view with v-if="isMenuOpen". The nav works and shows as expected, but I want to animate the different items within the nav. The problem is GSAP isn't finding the items to animate. I've tried different approaches:
using TL.fromTo('.nav__panel-nav ul li a', { but this returns the error GSAP target .nav__panel-nav ul li a not found.
using TL.fromTo(this.$refs.li, { on both the components and wrapping HTML li element, which returns the error GSAP target undefined not found.
I'm new to working with Vue/Nuxt so i'm not sure o the best way to rectify the issue. Any direction on how to get this working, or a better approach would be appreciated.
<template>
<transition name="card">
<div class="nav" v-if="isMenuOpen">
<div class="nav__panel">
<div class="nav__panel-inner">
<div class="nav__panel-contact">
<div class="h-3">Contact Us</div>
<ul>
<li>
xxx
</li>
</ul>
<div class="nav__panel-social">
<SocialLinks />
</div>
</div>
</div>
</div>
<div class="nav__panel">
<div class="nav__panel-inner">
<nav class="nav__panel-nav">
<ul>
<li ref="li">
<NuxtLink to="/" ref="test">Home</NuxtLink>
</li>
<li>
<NuxtLink to="/about-us">About Us</NuxtLink>
</li>
<li>
<NuxtLink to="/our-expertise">Our Expertise</NuxtLink>
</li>
<li>
<NuxtLink to="/contact-us">Contact Us</NuxtLink>
</li>
</ul>
</nav>
</div>
</div>
</div>
</transition>
</template>
<script>
import { gsap } from "gsap";
export default {
computed: {
isMenuOpen() {
return this.$store.getters['navState'];
}
},
watch: {
isMenuOpen(val) {
if (val) {
document.getElementsByTagName('body')[0].classList.add('no-scroll');
this.animateNavIn();
} else {
document.getElementsByTagName('body')[0].classList.remove('no-scroll');
this.animateNavOut();
}
}
},
methods: {
animateNavIn() {
// https://blog.logrocket.com/animating-vue-with-greensock/
console.log('animate in');
const TL = gsap.timeline();
// TL.fromTo('.nav__panel-nav ul li a', {
TL.fromTo(this.$refs.li, {
duration: 1,
autoAlpha: 1,
y: '15px',
stagger: 0.05,
ease: "Power2.out"
},
{
autoAlpha: 1,
y: 0,
})
.to('.nav__panel-contact .h-3, li, .social__title, .social__links', {
duration: 1,
autoAlpha: 1,
y: 0,
stagger: 0.05,
ease: "Power2.out"
}, '-=.9');
},
animateNavOut() {
console.log('animate out');
// const TL = gsap.timeline();
// TL.to('.nav__panel-nav ul li a', {
// duration: 1,
// autoAlpha: 1,
// y: 0,
// stagger: 0.05,
// ease: "Power2.out"
// });
}
},
}
</script>

I've worked out what part of the issue is. The problem was with using v-if to render the nav. By using this, the elements I was trying to target weren't in the DOM so they couldn't be found. My solution was to use v-show instead. The difference is that an element with v-show will always be rendered and remain in the DOM; v-show only toggles the display CSS property of the element.
I am now able to target the HTML a tags using GSAP. I'm still not sure why $refs wouldn't work though...

Related

Init Vue Awesome swiper on product card hover

ideally I need vue awesome swiper to init on product card hover, so until the user hovers, catalog loads only one img per product instead of multiple.
HTML
<div
#mouseover="handleMouseOver"
#mouseleave="handleMouseLeave"
/>
<div
v-if="media.length > 1"
ref="swiper"
v-swiper:mySwiper="swiperOption"
>
<div class="swiper-wrapper">
<div
v-for="image in images"
:key="image.id"
class="swiper-slide image__wrapper"
>
<img
:src="image.attributes.src"
:width="imgWidth"
:height="imgHeight"
:alt="imgAlt"
>
</div>
</div>
</div>
</div>
Component
data: () => ({
swiperOption: {
loop: true,
slidesPerView: 1,
centeredSlides: true,
spaceBetween: 30,
},
}),
methods: {
slideStart() {
if (this.$refs.swiper) {
this.mySwiper.activeIndex = 2;
this.mySwiper.autoplay.start();
}
},
slideStop() {
if (this.$refs.swiper) {
this.mySwiper.autoplay.stop();
this.mySwiper.activeIndex = 1;
}
},
handleMouseOver() {
this.isHovered = true;
this.slideStart();
},
handleMouseLeave() {
this.isHovered = false;
this.slideStop();
},
},
What I've tried and what problems encountered:
At first, I've added isHovered condition to v-if and used element in v-else, however after hover swiper refuses to autoplay (but reacts on activeIndex change)
After that I've tried adding init:false to swiperOption and this.$mySwiper.init() on hover, but it crashes whenever I'm trying to leave the page:
Would appreciate any ideas.
Solved by creating a parent div, and moving 'v-if' logic with hover condition there.

Vue.js transition and GSAP3 stagger usage

I'm using Vue.js and gsap for listing card elements but it is not animating one by one.
Here it is Vue.js codes:
<transition-group appear #before-enter="beforeEnter" #enter="enter" :css="false">
<div class="card" v-for="card in cards" :key="card.id">
<span>{{ card.name }}</span>
</div>
</transition-group>
vue js and gsap parts:
beforeEnter(el) {
console.log(el);
gsap.from(el, {
duration: 1
opacity:0,
ease: "power2.inOut",
stagger: {
grid: "auto",
from: "start",
amount: 1.5
}
})
},
enter(el, done) {
gsap.to(el, {
duration: 1,
opacity: 1
})
}
Animation is working but all listed at the same time. I want to be listed one by one

Vue bind url not functioning properly with image src

<template>
<div id="app">
<b-container >
<b-row>
<div class="product">
<img src='./assets/SocksG.jpg' alt="">
</div>
<div class="cart">
<button #click="addToCart">Add To Cart</button>
<br>
<p>Cart({{cart}})</p>
</div>
<div v-for="variant in variants"
:key="variant.variantId"
class="color-box"
:style="{backgroundColor:variant.variantColor}"
#mouseover ="updateProduct(variant.variantImage)"
>
</div>
</b-row>
</b-container>
</div>
</template>
export default {
name: 'App',
data() {
return {
cart: 0,
variants: [{
variantId: 2234,
variantColor: 'green',
variantImage: './assets/SocksG.jpg'
}, {
variantId: 2235,
variantColor: 'blue',
variantImage: './assets/SocksB.jpg'
}]
}
},
methods: {
addToCart() {
this.cart += 1
},
updateProduct(variantImage) {
this.image = variantImage
}
}
}
At first I applied :src to the socksG image and in the script I had to use img:require("./assets/SocksG.jpg" and it worked.
Now with the #mouseover event handler that's supposed to activate the updateProduct() function, I get the feeling that I'm doing something wrong with the Vue url handler that should fetch the variantImage(s) because when I use the Vue devtools the image url is img/SocksG.jpg and not ./assets/SocksG.jpg. What am I doing wrong?
Add a data property for image:
return {
image: ..., // Set some initial image filename here
cart: 0,
...
}
Use require like this:
<img :src="require('#/assets/' + this.image)" alt="">
and remove the paths from the urls:
variantImage: 'SocksG.jpg'
...
variantImage: 'SocksB.jpg'
Now the image src is bound to this.image, which you are changing with the mouseover.

How to access a function variable as a data property in Vue

I have this accordion with three arrows. When you click on the arrow it should transform 180deg. I want to have a function that takes the parameter set in the function and tells the data property to change to zero.
I've tried
let vm = this;
vm.$data.arrowOne = 0;
And this[arrow] = 0,
And this.arrow = 0
And it's not working.
Here is my code.
<div class="uk-accordion-ekstra">
<ul uk-accordion="multiple: true">
<li>
<a #click="rotate(arrowOne)" class="uk-accordion-title" href="#">Headline
<img :style="{ transform: 'rotate('+ arrowOne + 'deg)'}" src="IMGSRC"></a>
<div class="uk-accordion-content">
<p>TEXT</p>
</div>
</li>
<li>
<a #click="rotate(arrowTwo)" class="uk-accordion-title" href="#">Headline
<img :style="{ transform: 'rotate('+ arrowTwo + 'deg)'}" src="IMGSRC"></a>
<div class="uk-accordion-content">
<p>TEXT</p>
</div>
</li>
<li>
<a #click="rotate(arrowThree)" class="uk-accordion-title" href="#">Headline
<img :style="{ transform: 'rotate('+ arrowThree + 'deg)'}" src="IMGSRC"></a>
<div class="uk-accordion-content">
<p>TEXT</p>
</div>
</li>
</ul>
</div>
And VUE
new Vue({
el: '.uk-accordion-ekstra',
data: {
arrowOne: 180,
arrowTwo: 180,
arrowThree: 180
},
methods: {
arrow: function(arrow) {
if(this.arrow = 0) {
return this.arrow = 180;
}
}
}
});
I have succesfully managed to do it with a switch. But it aren't as beautiful. And im unsure of what i don't get. So help is appreciated.
You have a structural and a scope problem.
You are referencing this.arrow in a method called arrow, and you pass an argument called arrow - try to differentiate with the names, or you can get messed up
You are repeating stuff that you don't need to. Vue is great for creating components for the smallest of elements - you can use it to make your code shorter, more readable and more effective.
The snippet below makes everything easier:
you have one arrow per component, and if you solve the rotation problem in one place, then it's solved everywhere, and the arrow referenced always connects to the component you edit
you can encapsulate your arrow rotation into one component - no need to "litter" the Vue instance with such small animation
I added #click.prevent that's like preventDefault(), so there won't be a jump to the top when you click an <a></a>
Vue.component('accordion', {
props: ['title', 'text'],
template: '<div><a #click.prevent="rotateArrow()" class="uk-accordion-title" href="#">{{ title }} <i class="fa fa-arrow-circle-o-up" :style="`transform: rotate(${rotation}deg)`"></i></a><div class="uk-accordion-content"><p>{{ text }}</p></div></div>',
data() {
return {
rotation: 0
}
},
methods: {
rotateArrow() {
this.rotation = !this.rotation ? 180 : 0
}
}
})
new Vue({
el: '.uk-accordion-ekstra',
data: {
accordionItems: [{
title: 'Headline 1',
text: 'Text 1'
},
{
title: 'Headline 2',
text: 'Text 2'
},
{
title: 'Headline 3',
text: 'Text 3'
},
]
}
});
<link href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" integrity="sha384-wvfXpqpZZVQGK6TAh5PVlGOfQNHSoD2xbE+QkPxCAFlNEevoEH3Sl0sibVcOQVnN" crossorigin="anonymous">
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div class="uk-accordion-ekstra">
<ul uk-accordion="multiple: true">
<li v-for="accordionItem in accordionItems" :key="accordionItem.title">
<accordion :title="accordionItem.title" :text="accordionItem.text"></accordion>
</li>
</ul>
</div>

Vue + Vuetify expansion panel enter/leave animation

I'm currently developing a webapp in Vue.js with the help of Vuetify.
I found the expansion panel element very usefull and I wanted to use it in my website.
So I easily get the data i want to display from Firebase and i proceed to load the items with this template:
<v-expansion-panel popout>
<v-expansion-panel-content
v-for="doc in filteredclienti"
:key="doc.id">
<div slot="header">{{doc.data().Nome}} {{doc.data().Cognome}}</div>
<v-card>
Card content
</v-card>
</v-expansion-panel-content>
</v-expansion-panel
Everything works fine, the panel is ok and the popup animation works fine too.
Now I'd like to display a simple enter/leave animation to each item.
I tried to add a <transition-group> tag after the <v-expansion-panel popuot> but the console tells me want only a <v-expansion-panel-content> element.
So i tried to add <transition-group> inside <v-expansion-panel-content> but in this case the layout is no more correct and the popup animation is not working anymore.
How can i do it? Thanks!
That should do the trick, I've added a delete button, that will slide the "deleted" doc out.
enjoy.
<template>
<v-app>
<v-expansion-panel popout>
<transition-group name="list" tag="v-expansion-panel">
<v-expansion-panel-content v-for="doc in filteredclienti" :key="doc.id">
<div slot="header">
<span>{{doc.data}}</span>
<v-btn class="error" #click="deleteItem(doc.id)">
<v-icon>delete</v-icon>
</v-btn>
</div>
<v-card>Card content</v-card>
</v-expansion-panel-content>
</transition-group>
</v-expansion-panel>
</v-app>
</template>
<script>
export default {
data: () => ({
filteredclienti: [
{ id: 1, data: "data1" },
{ id: 2, data: "data1" },
{ id: 3, data: "data1" },
{ id: 4, data: "data1" }
]
}),
methods: {
deleteItem(id) {
this.filteredclienti = this.filteredclienti.filter(d => d.id !== id);
}
}
};
</script>
<style>
.list-enter-active,
.list-leave-active {
transition: all 1s;
}
.list-enter,
.list-leave-to {
opacity: 0;
transform: translateX(100vw);
}
</style>