I've some problems with vue-router and gsap's scrolltrigger plugin.
I have some vue components using scroltrigger and when I go to a different page and came back to the page having the scrolltrigger effect it doesn't trigger, but work if I manualy refresh the page.
I find this topic with people having the same problem with NuxtJs, but the ScrollTrigger.disable() and ScrollTrigger.kill() solutions not working for me :
https://greensock.com/forums/topic/24886-in-nuxt-when-using-scrolltriggerkill-how-can-it-run-again-when-page-is-viewed/
Here is a component I made with ScrollTrigger :
Template Part
<template>
<section class="marquee">
<div class="marquee__inner" aria-hidden="true" ref="inner">
<div class="marquee__part">food wine fish beef vegetables</div>
<div class="marquee__part">food wine fish beef vegetables</div>
<div class="marquee__part">food wine fish beef vegetables</div>
<div class="marquee__part">food wine fish beef vegetables</div>
<div class="marquee__part">food wine fish beef vegetables</div>
</div>
</section>
</template>
Script part
<script>
import gsap from "gsap"
import ScrollTrigger from 'gsap/ScrollTrigger'
gsap.registerPlugin(ScrollTrigger)
export default {
name: 'ServicesMarquee',
data() {
return {
currentScroll: 0,
isScrollingDown: true,
test: null,
}
},
methods: {
scrollAnim() {
gsap.to(this.$refs.inner, {
xPercent: -65,
scrollTrigger: {
trigger: ".marquee",
start: "top bottom",
end: "top top",
scrub: 0,
}
})
},
},
mounted() {
gsap.set(this.$refs.inner, {xPercent: -50});
let tween = gsap.to(this.$refs.inner.querySelectorAll('.marquee__part'), {xPercent: -100, repeat: -1, duration: 10, ease: "linear"}).totalProgress(0.5);
let self = this
window.addEventListener("scroll", function(){
if ( window.pageYOffset > self.currentScroll ) {
self.isScrollingDown = true;
} else {
self.isScrollingDown = false;
}
gsap.to(tween, {
timeScale: self.isScrollingDown ? 1 : -1
});
self.currentScroll = window.pageYOffset
});
gsap.to(this.$refs.inner, {xPercent: -65 });
this.scrollAnim()
}
}
</script>
I read all the articles on this and similar problems on Stackoverflow and on GSAP forums, and YES setTimeout working, but this is not the answer.
I was also confused that the problem does not affect a large number of people, but they describe almost the same situations, when vue + router + gsap + scrolltrigger stops working when you go through the pages (routes) and return to previous one with animations (animations don't work in this case).
So guys, first of all look at <transition> above <router-view>. This is the problem when you have components from previous page AND the next page at the same time, and ScrollTrigger calculates the values (offsetTop) in that time
Related
I have vuejs based application with with a form with a drop-down menu. The options in the menu should eventually be populated from a rest call. Right now I use a very hard coded list:
<template>
<ModalForm v-show="visible" #close="visible = false" title="Add location">
<div class="form-outline mb-4">
<select
class="form-control form-control-lg"
placeholder="Organisation"
v-model="formData.organisation"
>
<option value="1">One</option>
<option value="2">Two</option>
<option value="3">Three</option>
</select>
</div>
<div class="text-center mt-5">
<button class="btn btn-lg btn-primary">
Add location
</button>
</div>
</ModalForm>
</template>
<script setup>
import { reactive, ref } from "#vue/reactivity";
import { watch } from "#vue/runtime-core";
import ModalForm from "../../../components/ModalForm.vue";
const visible = ref(false);
const formDataDefaults = {
organisation : "",
};
const formData = reactive({ ...formDataDefaults });
watch(visible, async (newValue) => {
if (newValue == false) {
for (var key in formData) formData[key] = formDataDefaults[key];
}
});
function show() {
visible.value = true;
}
defineExpose({ show });
</script>
and that kind of works - but when I try to populate the select options in a slightly more dynamic way things fail in the build stage. From googling around I have tried this:
<option v-for="org in organisations" :value="org.name" :key="org.id">
...
<script setup>
export default {
data() {
return { organisation: [{name: "Org1", id: 1}, {name: "Org2", id: 2}] };
}
};
</script>
When compiling/running this with npm run serve I get the following error message:
error in ./src/pages/Locations/components/AddLocationModal.vue?vue&type=script&setup=true&lang=js
Syntax Error: TypeError: Cannot read properties of null (reading 'content')
which I do not understand, but I think it is related to the data export in the <script> section. Of course the next step is to actually populate with a rest call - but one step at a time.
Any hints appreciated!
Disclaimer: I am quite new to vue (and anything beyond basic JS), so this is in it's entirety based on CopyPaste and Google.
Update: the error message comes when I only add the
<script setup>
export default {
data() {
return { organisation: [{name: "Org1", id: 1}, {name: "Org2", id: 2}] };
}
};
</script>
part - without making any changes to the <template> part. So it seems there is "something" wrong with the data export. I guess problem is I struggle to understand the model for flow of data/state in this framework.
Update 2: As pointed out in answer below there was a missing { in the original data export section. Fixing that did not solve the problem.
Update 3/solution: As pointed out by several I had a mix of vue2 and vue3 syntax. This example solved it for me.
<script setup> is only for use in Vue 3 projects using the Composition API. Your current code within <script setup> however is written using the Options API. You can either:
Refactor your code to use the Composition API using script setup,
Remove the keyword setup from <script setup> to stick with your current code which should then work using the Options API. Vue 2 projects must use the Options API.
In the example code you have a syntax error, you're missing a brace:
data() {
˅
return { organisation: [{name: "Org1", id: 1}, {name: "Org2", id: 2}] };
˄
}
Check the official documentation about SELECT or the dynamic example
I'm trying to use the PrimeVue Chart component in my VueJS app, but to do that I need to reference the underlying chart, which requires a template reference. Unfortunately, as soon as I add that, I get "Maximum recursive updates exceeded." Here's a super-simple component that causes it:
<script setup>
import { ref } from "vue"
const pvChart = ref(null)
let basicData = {
datasets: [{
label: "Setpoints",
data: [{ x: -10, y: 0 },
{ x: 0, y: 10 },
{ x: 10, y: 5 },
{ x: 0.5, y: 5.5 }],
showLine: true,
},
]
}
</script>
<template>
<div>
<div class="card">
<pv-chart ref="pvChart" type="scatter" :data="basicData" />
</div>
</div>
</template>
I don't even try to update anything (AFAICT).
I tried to follow the example in this SO question, but it's written in a slightly different style, and I'm not sure I didn't miss something in the translation.
Update:
Changing the name of the ref from pvChart to primeChart fixes all the issues. I just grepped my whole codebase and there is no other pvChart in it anywhere. I don’t know why this is an issue.
I've started working with Vue.js version 3 and making a simple signup form. I need to implement a password strength meter for my password field but seems there isn't any compatible such component with Vue.js 3 version.
I've found few good components for password strength meter to use with Vue.js but they all seems to have compatibility with Vue.js 2.
I've tried
https://awesomeopensource.com/project/skegel13/vue-password
its working good in DEMO but not compatible with my Vue.js 3.
I'm stuck here. Any help/suggestions ?
Are you looking for a visual component or something that actually computes password strength?
zxcvbn is fairly well-known as a strength calculator - it outputs a score from 0-4 for how strong a password is. You could then roll a simple Vue component that outputs a different value depending on that score.
Below example uses Tailwind CSS classes for styling the visual meter. I wrote this in the browser and haven't tested the Vue but it's fairly simple and you should be able to get the idea.
<!-- PasswordStrengthMeter.vue -->
<template>
<div>
<div class="w-full h-4 flex">
<div :class="style"></div>
<div class="flex-1"></div>
</div>
<div>{{ strength }}</div>
</div>
</template>
<script>
props: {
score: {
required: true,
default: 0,
}
},
computed: {
strength() {
return [
'Very Weak', // 0
'Weak', // 1
'Moderate', // 2
'Strong', // 3
'Very Strong' // 4
][this.score];
},
style() {
return [
'w-1 bg-red-500', // 0
'w-1/4 bg-yellow-500', // 1
'w-1/2 bg-yellow-300', // 2
'w-3/4 bg-green-500', // 3
'w-full bg-blue-500' // 4
][this.score];
},
},
</script>
Here's what it might look like.
This one works nicely with Vue3.
https://github.com/miladd3/vue-simple-password-meter/tree/next
Sample code from the repository:
<template>
<div id="app">
<label>Password</label>
<input type="password" v-model="password" />
<password-meter :password="password" />
</div>
</template>
<script>
import { defineComponent, ref } from 'vue';
import PasswordMeter from 'vue-simple-password-meter';
export default defineComponent({
components: {
PasswordMeter,
},
setup() {
const password = ref('');
return {
password,
};
},
});
</script>
i Want to make a progress bar using vue strap . i install vue strap on this link
this link
now i add a progress bar, this progress bar is showing , this bar is only showing color primary and cant showing animated .
<template>
<div class="progress">
<progressbar now="99" type="danger" striped animated ></progressbar>
</div>
</template>
<script>
import { progressbar } from 'vue-strap'
export default {
components: {
progressbar
},
mounted() {
console.log('Component mounted.')
}
}
</script>
with this code , this type is primary and this animated didnt work .
i change browser from chrome to mozila , but its still didnt work . my browser is newest .
whats wrong about this ? i dont know why animated didnt work
There is a bug in VueStrap library when it comes to progress bar animations. The template for progress bar in VueStrap uses class active to animate, whereas, in Bootstrap 4 we have to use class progress-bar-animated. A work around of this problem is to created your own Progress Bar component which makes use of the Bootstrap 4.
Custom Progress Bar component could be written as:
Vue.component('c-progressbar', {
template: `
<div class="progress">
<div class="progress-bar" :class="progressClasses"
role="progressbar"
:style="progressStyle"></div>
</div>`,
props: {
striped: Boolean,
animated: Boolean,
now: {
type: Number,
required: true
},
contextType: {
type: String,
default: 'primary'
}
},
data: function() {
let context = 'bg-' + this.contextType
return {
progressClasses: {
'progress-bar-striped': this.striped,
'progress-bar-animated': this.animated,
[context]: true
},
progressStyle: {
width: this.now + '%'
}
}
}
})
new Vue({ el: '#app' })
You can use this pen for testing: https://codepen.io/abdullah-shabaz/pen/YzXdYgd
I've created a component in vue which wraps a vue-apexchart donut graph. As soon as the page loads and this component is loaded, the vue-apexchart animates and displays a small graph.
Now I would like to instantiate multiple of these components from a dataset side by side. Instead of the components to all load an animate at the same time, I would like a small rendering delay to give it an overall nice effect. Something like this would be nice:
<donut :items="series1"></donut>
<donut :items="series2" delay=1500></donut>
The vue-apexchart doesent support initialization delays, and as far as I can see there isn't any vue-specific official solution to delay the rendering of components.
I've tried to put a setTimeout in any of the component hooks to stall the initialization,
I´ve also tried to inject the all the graph DOM in the template element on a v-html tag in a setTimeout, but apexchart doesent notice this new dom content, and vue doesent notice the html bindings either.
I´ve created this fiddle which loads two instances of a graph:
https://jsfiddle.net/4f2zkq5c/7/
Any creative suggestions?
There are several ways you can do this, and it depends on whether you can actually modify the <animated-component> logic yourself:
1. Use VueJS's built-in <transition-group> to handle list rendering
VueJS comes with a very handy support for transitions that you can use to sequentially show your <animated-component>. You will need to use a custom animation library (like VelocityJS) and simply store the delay in the element's dataset, e.g. v-bind:data-delay="500". VueJS docs has a very good example on how to introduce staggered transitions for <transition-group>, and the example below is largely adapted from it.
You then use the beforeAppear and appear hooks to set the opacity of the individual children of the <transition-group>.
Vue.component('animated-component', {
template: '#animatedComponentTemplate',
props: {
data: {
required: true
}
}
});
new Vue({
el: '#app',
data: {
dataset: {
first: 'Hello world',
second: 'Foo bar',
third: 'Lorem ipsum'
}
},
methods: {
beforeAppear: function(el) {
el.style.opacity = 0;
},
appear: function(el, done) {
var delay = +el.dataset.delay;
setTimeout(function() {
Velocity(
el, {
opacity: 1
}, {
complete: done
}
)
}, delay)
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.3/velocity.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<transition-group name="fade" v-on:before-appear="beforeAppear" v-on:appear="appear">
<animated-component v-bind:data="dataset.first" v-bind:key="0"> </animated-component>
<animated-component v-bind:data="dataset.second" v-bind:key="1" v-bind:data-delay="500"> </animated-component>
<animated-component v-bind:data="dataset.third" v-bind:key="2" v-bind:data-delay="1000"> </animated-component>
</transition-group>
</div>
<script type="text/x-template" id="animatedComponentTemplate">
<div>
<h1>Animated Component</h1>
{{ data }}
</div>
</script>
2. Let <animated-component> handle its own rendering
In this example, you simply pass the a number to the delay property (remember to use v-bind:delay="<number>" so that you pass a number and not a string). Then, in the <animated-component>'s mounted lifecycle hook, you use a timer to toggle the visibility of the component itself.
The technique on how you want to show the initially hidden component is up to you, but here I simply apply an initial opacity of 0 and then transition it after a setTimeout.
Vue.component('animated-component', {
template: '#animatedComponentTemplate',
props: {
data: {
required: true
},
delay: {
type: Number,
default: 0
}
},
data: function() {
return {
isVisible: false
};
},
computed: {
styleObject: function() {
return {
opacity: this.isVisible ? 1 : 0
};
}
},
mounted: function() {
var that = this;
window.setTimeout(function() {
that.isVisible = true;
}, that.delay);
}
});
new Vue({
el: '#app',
data: {
dataset: {
first: 'Hello world',
second: 'Foo bar',
third: 'Lorem ipsum'
}
}
});
.animated-component {
transition: opacity 0.25s ease-in-out;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<animated-component v-bind:data="dataset.first"> </animated-component>
<animated-component v-bind:data="dataset.second" v-bind:delay="500"> </animated-component>
<animated-component v-bind:data="dataset.third" v-bind:delay="1000"> </animated-component>
</div>
<script type="text/x-template" id="animatedComponentTemplate">
<div class="animated-component" v-bind:style="styleObject">
<h1>Animated Component, delay: {{ delay }}</h1>
{{ data }}
</div>
</script>
If you have the possibility to reformat your data, you can build an array of series objects, add a show: true/false property and iterate it:
//template
<div v-for="serie in series">
<donut :items="serie.data" v-if="serie.show"></donut>
</div>
//script
data: function() {
return {
series: [
{ data: [44, 55, 41, 17, 15], show: false },
{ data: [10, 20, 30], show: false },
]
}
}
Now you can create a setTimeout function which will change the serie.show to true by incrementing the delay based on the serie index.
Then add the function on the mounted hook:
methods: {
delayedShow (serie, idx) {
let delay = 1500 * idx
setTimeout(() => {
serie.show = true
}, delay)
}
},
mounted () {
this.series.forEach((serie, idx) => {
this.delayedShow(serie, idx)
})
}
Live example
Faced the same problem with ApexCharts Pie Charts being redrawn rapidly in sequence due to data being pulled from a pinia store mutating too quickly for the chart to keep up, leading to ugly errors in the console.
I resolved the issue by using a boolean ref in a v-if="showChart" on the component and then using a setTimeout to trigger a delayed drawing of the chart:
import { ref } from "vue";
import useStore from "#/store/myChartStore";
const store = useStore();
const showChart = ref(false);
store.$subscribe((mutation, state) =>{
showChart.value = false;
setTimeout(()=> {
showChart.value = true;
}
, 100);
});
If you're not using a store, you may find another way to watch the initial availability of the chart data and then delay the rendering using that same approach.