How to handle transitions and animations in Gridsome - vue.js

Currently i have been using this code in the inline CDN version of VueJS and it works fine, but when i add this to gridsome the click event on the animation to open the slide out doesn't work.
I have looked up that animations for page enter is working but i am not sure how to handle component animations.
Are animations working in Gridsome?
<template>
<nav class="navbar fixed-top navbar-expand-lg navbar-light bg-light">
<div class="row">
<div class="col">
<button
#click="show = !show"
class="navbar-toggler"
type="button"
>
<span class="navbar-toggler-icon"></span>
</button>
</div>
<div class="col">
<a class="navbar-brand" href="/">Navbar</a>
</div>
</div>
<div>
<transition name="slidein">
<div v-if="show" class="nav-bar">
<button
#click="show = !show"
class="btn-close pl-4 p-3"
type="button"
></button>
<span class="close-btn">X</span>
<div class="d-flex flex-column"></div>
</div>
</transition>
<transition name="fadein">
<div v-if="show = show" class="bck-close" #click="show = !show"></div>
</transition>
</div>
</nav>
</template>
<script>
export default {
name: "headNav",
data: {
show: false
},
};
</script>
<style>
.nav-bar {
height: 100vh;
width: 80vw;
background: #fff;
position: absolute;
left: 0;
z-index: 10000;
overflow-x: hidden;
overflow-y: auto;
top: 0;
}
.btn-close {
background: none;
border: none;
font-size: 1em;
}
.bck-close {
height: 100vh;
width: 100vw;
background: rgba(0, 0, 0, 1);
position: fixed;
z-index: 9999;
left: 0;
top: 0;
opacity: 0.1;
cursor: pointer;
}
.slidein-enter-active {
animation: slidein 0.2s;
}
.slidein-leave-active {
animation: slidein 0.2s reverse;
}
#keyframes slidein {
from {
left: -100%;
width: 0%;
}
to {
left: 0;
width: 80vw;
}
}
.fadein-enter-active {
animation: fadein 0.2s;
}
.fadein-leave-active {
animation: fadein 0.2s reverse;
}
#keyframes fadein {
from {
opacity: 0;
}
to {
opacity: 0.1;
}
}
</style>

Got the data part wrong, animations do work!
<script>
export default {
name: "headNav",
data () {
return {
show: false
}
},
};
</script>

Related

building a overlay fullscreen navbar in vue.js, by toggle navbar height from 0% to 100%

Trying to implement the overlay fullscreen menu with Vue.js, html/css/js is [this solution]https://www.w3schools.com/howto/tryit.asp?filename=tryhow_js_overlay2
But stuck with how to toggle div height from 0% to 100%. I did create 2 classes,
'navbar'
'navbar_open'
but I am not sure how to toggle between them in Vue. I could see that once #click triggered, the class="navbar navbar_open"
<div id="myNav" class="navbar navbar_open">
<div class="closebtn">
<i class="fa fa-times"></i>
</div>
<div class="navbar-content">
<a>Home</a><a>Contact</a>
</div>
</div>
below is the App.vue file,
<template>
<div id="app">
<!-- Banner/Header -->
<!-- Hamburger -->
<div id="myNav" class="navbar" :class="{ navbar_open: showNavbar }">
<div class="closebtn" v-on:click="showNavbar = !showNavbar">
<i class="fa fa-times"></i>
</div>
<div class="navbar-content">
<a>Home</a>
<a>Contact</a>
</div>
</div>
<div class="openbtn">
<i class="fa fa-bars" v-on:click="showNavbar = !showNavbar"></i>
</div>
<router-view />
</div>
</template>
<script>
export default {
data() {
return {
showNavbar: false
};
},
methods: {
toggleNavbar() {
this.showNavbar = !showNavbar;
},
setHeightValue(showNavbar) {
const heightValue = this.showNavbar ? '100%' : '0%';
return heightValue;
}
},
created() {
this.setHeightValue();
}
};
</script>
<style>
#import url('https://use.fontawesome.com/releases/v5.9.0/css/all.css');
.navbar_open {
width: 100%;
height: 100%;
position: fixed;
z-index: 1;
top: 0;
left: 0;
background-color: rgb(0, 0, 0);
background-color: rgba(0, 0, 0, 0.9);
overflow-y: hidden;
transition: 0.5s;
background-color: #2b4c72;
}
.navbar {
width: 100%;
height: 0%;
position: fixed;
z-index: 1;
top: 0;
left: 0;
background-color: rgb(0, 0, 0);
background-color: rgba(0, 0, 0, 0.9);
overflow-y: hidden;
transition: 0.5s;
background-color: #2b4c72;
}
.navbar-content {
position: relative;
top: 25%;
width: 100%;
text-align: center;
margin-top: 30px;
}
.navbar a {
padding: 8px;
text-decoration: none;
font-size: 36px;
color: #818181;
display: block;
transition: 0.3s;
}
.navbar a:hover,
.navbar a:focus {
color: #f1f1f1;
}
.navbar .closebtn {
position: absolute;
top: 20px;
left: 45px;
font-size: 60px;
color: white;
cursor: pointer;
}
.openbtn {
font-size: 30px;
}
#media screen and (max-height: 450px) {
.navbar {
overflow-y: auto;
}
.navbar a {
font-size: 20px;
}
.navbar .closebtn {
font-size: 40px;
top: 15px;
right: 35px;
}
}
</style>
I see that you have unused method toggleNavbar. You call setHeightValue only when your component is created and then never again (I think you can remove this method). This menu is not 100% height probably because you embedded this component in another container (<div id="app:...) which has smaller height so height: 100% will be 100% height of parent container. Instead of 100% you can use 100vh (vh - is a viewport unit and it will always take 100% viewport height [if 50vh then 50% of total viewport height]). You should also apply only navbar_open but now you are applying bot classes if your navbar is open: navabar navbar_open so you should add this classes conditionally:
:class="{ 'navbar_open': showNavbar, 'navbar': !showNavbar }"
You have also Two different buttons responsible for two different actions:
closebtn -> close navigation
openbtn -> open navigation
So you should create two different methods openNavigation closeNavigation
<div id="app">
<!-- Banner/Header -->
<!-- Hamburger -->
<div id="myNav" :class="{ 'navbar_open': showNavbar, 'navbar': !showNavbar }">
<div class="closebtn" #click="closeNavigation">
<i class="fa fa-times"></i>
</div>
<div class="navbar-content">
<a>Home</a>
<a>Contact</a>
</div>
</div>
<div class="openbtn">
<i class="fa fa-bars" #click="showNavigation"></i>
</div>
<router-view />
</div>
</template>
<script>
export default {
data() {
return {
showNavbar: false
};
},
methods: {
openNavigation() {
this.showNavbar = true;
},
closeNaviagation() {
this.showNavbar = false;
}
}
};
</script>
<style>
...
.navbar_open {
width: 100%;
height: 100vh; // <--- 100vh instead of 100%
position: fixed;
...
</style>

Vue: Get data from parent in <slot>

I don't know if what I want is possible in the way I want to do it. I'm using this Vue plugin for modals: https://www.npmjs.com/package/vue-js-modal
I have a component called ModalButton that is responsible for triggering the modal:
<modal-button :modal-data="{{ collect(['account' => $account]) }}" modal-name="leave-account">Leave Account</modal-button>
As you can see, I'm sending :modal-data as props.
The ModalButton component is the following:
<template>
<button #click.prevent="show" type="button" class="px-3 py-2 bg-gray-200 font-bold text-gray-700 ">
<slot></slot>
</button>
</template>
<script>
// # is an alias to /src
export default {
name: 'ModalButton',
data() {
return {
}
},
props: {
modalName: String,
modalData: Object
},
methods: {
show () {
this.$modal.show(this.modalName, this.modalData);
},
hide () {
this.$modal.hide(this.modalName);
}
}
}
</script>
You can see on the show method which comes from the plugin is sending that data to the modal
this.$modal.show(this.modalName, this.modalData);
In my blade template (i'm using laravel), I have the <modal>:
<modal name="leave-account" class="text-center">
<div class="flex items-center justify-center h-full p-6">
<div>
<div>
<div class="text-3xl font-bold mb-2">Are you sure?</div>
<p>If you leave this account, you won't be able to join back unless invited again. </p>
</div>
<footer class="flex items-center justify-end mb-24 mt-auto">
<button class="px-3 py-2 bg-gray-200 font-bold text-gray-700 mr-3">Cancel</button>
<ajax-button
class-list="px-3 py-2 bg-primary text-white font-bold"
:route=#{{SOMEHOW ACCESS MODAL DATA}}
method="delete">
Yes, leave account
</ajax-button>
</footer>
</div>
</div>
</modal>
Inside my modal, I have another component called AjaxButton which allows me to have a reusable button that handles ajax requests.
I need a way of accessing the modal data that was passed to the modal. I'd like not to have to create an additional specific modal component to handle this.
Is this possible?
Specifically what the :route prop needs to send is the Account since my route requires model binding. i.e it'll look something like /accounts/leave/53
Edit:
So I can do this:
<button #click="$modal.hide('leave-account')" class="px-3 py-2 bg-gray-200 font-bold text-gray-700 mr-3">Cancel</button>
(that's right above my <ajax-button>)
So clearly I'm able to access the $modal properties / methods. So there has to be a way to accomplish what I want
You can use a simpler modal:
<modal-button v-model="showModal">
Leave Account
</modal-button>
<modal v-model="showModal" caption="Leave Account">
{{ collect(['account' => $account]) }}
</modal>
Then modal-button will look like this:
<template>
<button #click.stop="$emit('input',true)" type="button" class="px-3 py-2 bg-gray-200 font-bold text-gray-700 ">
<slot/>
</button>
</template>
<script>
export default
{
name: 'ModalButton',
props:
{
value:
{
type: Boolean,
default: false
}
}
}
</script>
and the modal component will be:
<template>
<transition name="modal">
<div class="modal_overlay" #click="close" v-if="value">
<div class="modal_content" style="overflow: auto;" #click.stop>
<div class="modal_title">{{ caption }} <div class="modal_close" #click="close">✖</div></div>
<slot/>
</div>
</div>
</transition>
</template>
<script>
export default
{
props:
{
value:
{
type: Boolean,
default: false
},
caption:
{
type: String,
default: ''
},
created()
{
document.addEventListener('keydown', this.modal_escape, false);
},
beforeDestroy()
{
document.removeEventListener('keydown', this.modal_escape, false);
},
methods:
{
close()
{
this.$emit('input', false);
},
modal_escape(event)
{
var e = event || window.event;
if(this.value && e.keyCode == 27)
{
this.close();
// All good browsers…
if (e.preventDefault) e.preventDefault();
// …and IE
if (e.returnValue) e.returnValue = false;
return false;
}
}
}
}
</script>
<style>
.modal_overlay
{
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
z-index: 25;
text-align: initial;
background-color: rgba(0,0,0,0.75);
}
.modal_content
{
position: relative;
display: inline-block;
left: 50%;
top: 50%;
transform: translate(-50%,-50%);
background-color: white;
border: 2px solid #000;
border-radius: 5px;
max-width: 90vw;
max-height: 90vh;
}
.modal_title
{
text-align: center;
background-color: #F27935;
color: white;
line-height: 1.65;
padding-left: 6px;
}
.modal_close
{
float: right;
display: inline-block;
cursor: pointer;
padding: 0 5px 0 4px;
}
.modal_close:hover
{
color: white;
background-color: #00B4FF;
}
.modal-enter-active
{
transition: all .4s ease;
}
.modal-leave-active
{
transition: all .2s cubic-bezier(1.0, 0.5, 0.8, 1.0);
}
.modal-enter, .modal-leave-to
{
transform: translateX(10px);
opacity: 0;
}
</style>

How do I open and close a modal programmatically using Vue.js?

I've read through innumerable posts about how to do this with jquery, but Vue.js jealously owns and manages the dom, so I will need a solution that uses standard Vue.js components or libraries.
Thus far, I've located several examples that are what I'd call 'button-event-driven' solutions, but I will need to programmatically handle open and close of the modal.
Problem / Design Requirement: When a public user attempts to interact with a tempting button or other function on my application, and they are not yet logged in, I wish to programmatically launch a modal dialogue to then ask them to log in.
Once successfully, I'll need to programmatically close the same dialogue modal. Or, of course, they can choose to cancel and continue browsing as a public user without the ability to do those functions.
Other Helpful Information: I'm using bootstrap 4.4.1
You can use a watch property. If a user is not logged in as login=false then the modal shows.
// register modal component
Vue.component("modal", {
template: "#modal-template"
});
// start app
new Vue({
el: "#app",
data: {
showModal: false,
login: null
},
created() {
this.login = false;
},
watch: {
"login": function(val) {
this.showModal = !val;
}
}
});
.modal-mask {
position: fixed;
z-index: 9998;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: table;
transition: opacity 0.3s ease;
}
.modal-wrapper {
display: table-cell;
vertical-align: middle;
}
.modal-container {
width: 300px;
margin: 0px auto;
padding: 20px 30px;
background-color: #fff;
border-radius: 2px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.33);
transition: all 0.3s ease;
font-family: Helvetica, Arial, sans-serif;
}
.modal-header h3 {
margin-top: 0;
color: #42b983;
}
.modal-body {
margin: 20px 0;
}
.modal-default-button {
float: right;
}
/*
* The following styles are auto-applied to elements with
* transition="modal" when their visibility is toggled
* by Vue.js.
*
* You can easily play with the modal transition by editing
* these styles.
*/
.modal-enter {
opacity: 0;
}
.modal-leave-active {
opacity: 0;
}
.modal-enter .modal-container,
.modal-leave-active .modal-container {
-webkit-transform: scale(1.1);
transform: scale(1.1);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<!DOCTYPE html>
<html>
<head>
<title>Modal Component</title>
<link rel="stylesheet" type="text/css" href="/style.css" />
<!-- template for the modal component -->
<script type="text/x-template" id="modal-template">
<transition name="modal">
<div class="modal-mask">
<div class="modal-wrapper">
<div class="modal-container">
<div class="modal-header">
<slot name="header">
default header
</slot>
</div>
<div class="modal-body">
<slot name="body">
default body
</slot>
</div>
<div class="modal-footer">
<slot name="footer">
default footer
<button class="modal-default-button" #click="$emit('close')">
OK
</button>
</slot>
</div>
</div>
</div>
</div>
</transition>
</script>
</head>
<body>
<!-- app -->
<div id="app">
<!-- use the modal component, pass in the prop -->
<modal v-if="showModal" #close="showModal = false">
<!--
you can use custom content here to overwrite
default content
-->
<h3 slot="header">custom header</h3>
</modal>
</div>
</body>
</html>
I was able to construct this using a project with a (relatively) recent example. Here is the component, as well as a 'Tester.vue' view that uses that component:
LoginModal.vue:
<template>
<transition name="modal-fade">
<div class="modal-backdrop">
<div
class="modal"
role="dialog"
aria-labelledby="modalTitle"
aria-describedby="modalDescription"
>
<header class="modal-header" id="modalTitle">
<slot name="header">
</slot>
</header>
<section class="modal-body" id="modalDescription">
<slot name="body">
Your Login Form Goes Here
<button type="button" v-on:click="validateLoginForm">
Log In
</button>
<button type="button" #click="close" aria-label="Close modal">
Cancel
</button>
</slot>
</section>
<footer class="modal-footer">
<slot name="footer"> </slot>
</footer>
</div>
</div>
</transition>
</template>
<script>
export default {
name: "loginModal",
data() {
return {
loginValidationAlerts: [],
};
},
methods: {
close() {
this.$emit("close");
},
validateLoginForm() {
//Login Form Validations go here
},
clearAllLoginValidationErrors() {
this.loginValidationAlerts = [];
},
attemptLogin() {
// Your login code
},
},
};
</script>
<style>
.modal-fade-enter,
.modal-fade-leave-active {
opacity: 0;
}
.modal-fade-enter-active,
.modal-fade-leave-active {
transition: opacity 0.5s ease;
}
.modal-backdrop {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
background-color: rgba(0, 0, 0, 0.3);
display: flex;
justify-content: center;
align-items: center;
}
.modal {
background: #ffffff;
box-shadow: 2px 2px 20px 1px;
overflow-x: auto;
display: flex;
flex-direction: column;
}
.modal-header,
.modal-footer {
padding: 15px;
display: flex;
}
.modal-header {
border-bottom: 1px solid #eeeeee;
color: #4aae9b;
justify-content: space-between;
}
.modal-footer {
border-top: 1px solid #eeeeee;
justify-content: flex-end;
}
.modal-body {
position: relative;
padding: 20px 10px;
}
.btn-close {
border: none;
font-size: 20px;
padding: 20px;
cursor: pointer;
font-weight: bold;
color: #4aae9b;
background: transparent;
}
.btn-green {
color: white;
background: #4aae9b;
border: 1px solid #4aae9b;
border-radius: 2px;
}
</style>
Tester.vue:
<template>
<div>
Test Page
<div>
<button type="button" class="btn" #click="showModal">
Open Modal!
</button>
</div>
<div>
<LoginModal v-show="isModalVisible" #close="closeModal"></LoginModal>
</div>
</div>
</template>
<script>
import LoginModal from "#/components/LoginModal.vue";
export default {
components: {
// eslint-disable-next-line vue/no-unused-components
LoginModal,
},
data() {
return {
isModalVisible: false,
};
},
methods: {
showModal() {
// Do something here to determine
// if you should show modal
this.isModalVisible = true;
},
closeModal() {
// this will catch the close event
// after you're done processing the login in the component
this.isModalVisible = false;
},
},
};
</script>
<style scoped></style>
The appearance of the view and its component is pretty rough and without much formatting, but you get the picture.
Hopefully this helps somebody else if they're looking for a straight-forward modal in Vue; I believe it has to be a very common design requirement, and this approach worked for me.
The simple solution is:
add a reference to modal component
<share-modal ref="share-modal-ref"/>
import Modal like this:
import { Modal } from 'bootstrap'
then in your method do this:
let element = this.$refs.listModal.$el
let shareModal = new Modal(element, {})
shareModal.show()
It works on Vue 3 & Boostrap 5

Asynchronous task is not reactive in vuejs view

Default the loading image is true after complete the upload loading image is false, but after update the loading object no effect in view, always show the loading bar.
Where is my mistake, please help anyone,
Note: also try by this.$nextTick() function, same output;
in console the update we got, but no effect in view
Vue.config.devtools=false;
Vue.config.productionTip = false;
new Vue({
el:"#app",
data: {
isloadingImage: [],
property:{
images:[]
}
},
methods: {
addFiles() {
this.$refs.files.click();
},
handleFilesUpload() {
let uploadedFiles = this.$refs.files.files;
let maxLength = uploadedFiles.length <= 4 ? uploadedFiles.length : 4;
for (let i = 0; i < maxLength; i++) {
uploadedFiles[i].url = URL.createObjectURL(uploadedFiles[i]);
this.property.images.push(uploadedFiles[i]);
}
this.uploadImages();
},
removeFile(key) {
this.property.images.splice(key, 1);
delete this.isloadingImage[key];
},
async uploadImages(){
this.property.images.forEach((value, key) => {
if (!this.isloadingImage[key]) {
this.isloadingImage[key] = true;
let myFormData = new FormData();
myFormData.append('title', value);
axios.post('http://localhost:800/uploadimage',
myFormData).then(response => {
this.isloadingImage[key] = false;
}).catch(error=> {
this.isloadingImage[key] = false;
console.log(this.isloadingImage);
})
}
});
}
}
});
.small-image {
max-height: 200px;
max-width: 200px;
}
.post-image button {
padding: 0 5px;
}
.post-image-preview {
max-height: 105px;
}
.post-image .caption {
max-width: 198px;
height: 27px;
}
.lds-facebook {
display: inline-block;
position: relative;
width: 64px;
height: 50px;
}
.lds-facebook div {
display: inline-block;
position: absolute;
left: 6px;
width: 10px;
background: #bfbebe;
animation: lds-facebook 1.2s cubic-bezier(0, 0.5, 0.5, 1) infinite;
}
.lds-facebook div:nth-child(1) {
left: 6px;
animation-delay: -0.24s;
}
.lds-facebook div:nth-child(2) {
left: 26px;
animation-delay: -0.12s;
}
.lds-facebook div:nth-child(3) {
left: 45px;
animation-delay: 0s;
}
#keyframes lds-facebook {
0% {
top: 6px;
height: 51px;
}
50%, 100% {
top: 19px;
height: 26px;
}
}
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="app">
<div class="field-title"><h5>Pictures</h5></div>
<div class="form-group post-image">
<div class="col-md-12">
<div class="upload-btn-wrapper">
<button class="add-photo" v-on:click="addFiles()"><i
class="fas fa-camera"></i></button>
<input type="file" multiple id="file" ref="files"
v-on:change="handleFilesUpload()">
</div>
<div class="brows-image-text"><p>You can upload up to<br>4 pictures per
listing</p></div>
</div>
<div class="row">
<div v-for="(file, key) in property.images" class="col-md-3">
<div class="lds-facebook" v-if="isloadingImage[key]">
<div></div>
<div></div>
<div></div>
</div>
<div v-else>
<button v-on:click="removeFile( key )" type="button">
<i class="fas fa-times text-danger"></i>
</button>
<img :src="file.url" class="small-image post-image-preview">
</div>
</div>
</div>
</div>
</div>
See rule #2 here https://vuejs.org/2016/02/06/common-gotchas/#Why-isn%E2%80%99t-the-DOM-updating
You update isloadingImage array's values using its keys. In such a case for the change to be reactive, you need to replace your whole array after the fact.
Example:
axios.post('http://localhost:800/uploadimage', myFormData)
.then(response => {
this.isloadingImage[key] = false;
this.isloadingImage = this.isloadingImage.slice(0);
// ^^^ this line
}).catch(error=> {
console.log('error', key, error)
this.isloadingImage[key] = false;
this.isloadingImage = this.isloadingImage.slice(0);
// ^^^ and line
});
Use data as function, not as object like you do.
new Vue({
el:"#app",
data () {
return {
imageIsLoading: ...

Vue Accordion with transition

I'm trying to integrate the Accordion component with a body transition, but without success :( . All is working as well except the animation.
template:
<div class="accordion">
<div class="accordion-title" #click="isOpen = !isOpen" :class="{'is-open': isOpen}">
<span>{{title}}</span>
<i class="ic ic-next"></i>
</div>
<div class="accordion-body" :class="{'is-open': isOpen}">
<div class="card">
<slot name="body"></slot>
</div>
</div>
</div>
component:
props: {
title: {
type: String,
default: 'Title'
}
},
data() {
return {
isOpen: false
}
}
And styles:
.accordion-body {
font-size: 1.3rem;
padding: 0 16px;
transition: .3s cubic-bezier(.25,.8,.5,1);
&:not(.is-open) {
display: none;
height: 0;
overflow: hidden;
}
&.is-open {
height: auto;
// display: block;
padding: 16px;
}
}
.card {
height: auto;
}
I tried to use <transition> but it doesn't work with height or display properties.
Help please!
display:none will remove your content and avoid the animation, you should trick with opacity, overflow:hidden and height, but you ll be forced to do a method for that.
For example (not tested, but inspiring):
in template:
<div class="accordion" #click="switchAccordion" :class="{'is-open': isOpen}">
<div class="accordion-title">
<span>{{title}}</span>
<i class="ic ic-next"></i>
</div>
<div class="accordion-body">
<p></p>
</div>
</div>
in component (add a method):
methods: {
switchAccordion: function (event) {
let el = event.target
this.isOpen = !this.isOpen // switch data isOpen
if(this.isOpen) {
let childEl1 = el.childNodes[1]
el.style.height = childEl1.style.height
} else {
let childEl2 = el.childNodes[2]
el.style.height = childE2.style.height // or .clientHeight + "px"
}
}
}
in style:
.accordion {
transition: all .3s cubic-bezier(.25,.8,.5,1);
}
.accordion-body {
font-size: 1.3rem;
padding: 0 16px;
opacity:0
}
.is-open .accordion-body {
opacity:0
}
In this case, your transition should work as you want.
The javascript will change the height value and transition transition: all .3s cubic-bezier(.25,.8,.5,1); will do the animation