I'm developing a single page application / mobile app, with Vue.JS. I want a slide effect when changing the pages, and I can do it like this:
transition name="slide"
router-view transition
transition
But I wanted the reverse effect of the slide when the user returns the page. In other words, when the user opens a new page, the page will come from the right, but when they go back, the page will come from the left.
There is a plugin for Vue router, called vue-router-transition, but it does not work. It is out of date, it only works with very old versions of Vue.
Also there is a tutorial on dynamic transitions, but only works when it is parents routes, ex: example.com/rota1/rota2/rota3, which is not my case.
I thought of the following logic in the before.each.router, set the transition class (slide-left or slide-right) depending on whether the user clicked on a go back button.
The problem is that I do not know how to apply this logic in code. I would have to pass the value of a variable that is in main.js to app.vue and I do not know how to do this.
A while ago I've used the meta object in vue-router and added a "fake" depth, because I haven't any children objects. If you use children, then go with this example: https://github.com/vuejs/vue-router/blob/dev/examples/transitions/app.js
export default () => {
return [{
meta: {
depth: 0
},
path: '/home',
component: Home
},
{
meta: {
depth: 1
},
path: '/about',
component: About
}]
}
Now you can check it by your own like this in your App.vue.
watch: {
$route(to, from) {
const toDepth = to.meta.depth || 0;
const fromDepth = from.meta.depth || 0;
this.transitionName = toDepth >= fromDepth ? 'slide-left' : 'slide-right';
}
}
I see 2 options:
Store a variable in Vuex "transitionBack" and set it to true. Change the router-link to a #click method. In the method, first save the variable and then navigate to the link. Check that variable on your beforeRouteUpdate(to, from, next) method.
Implement some logic in your beforeRouteUpdate(to, from, next) method that will check on the name of the link (e.g. if you can only go "back" from a certain page, then keep a list of all these type of pages)
Hope this helps:
new Vue({
el: '#demo',
data: {
slideTransition: 'slide-left',
showChild: false,
},
watch: {
showChild(value) {
if (value) {
this.setSlideTransition('slide-right');
} else {
this.setSlideTransition('slide-left');
}
},
},
methods: {
setSlideTransition(slideDirection) {
// Note: 300ms mentioned below is matching with css transition timing
setTimeout(() => { this.slideTransition = slideDirection; }, 300);
},
},
});
body {
margin: 0;
color: #bdbdbd;
background-color: #161616;
font-family: Helvetica neue, roboto;
}
.container {
width: 500px;
background: #161616;
}
main {
width: 60%;
height: 300px;
background-color: #333;
}
aside {
width: 40%;
background-color: #555;
}
.container, main, .parent, .child, .content {
display: flex;
align-items: center;
justify-content: center;
}
.parent {
background-color: deepskyblue;
}
.child {
background-color: deeppink;
}
.container, main, aside {
position: relative;
height: 199px;
}
.pin {
top: 0;
left: 0;
right: 0;
bottom: 0;
position: absolute;
}
.pt-50 {
padding-top: 50px;
}
/* transitions */
.fade-enter-active,
.fade-leave-active {
transition-property: opacity;
transition-duration: 0.25s;
}
.fade-enter-active {
transition-delay: 0.25s;
}
.fade-enter,
.fade-leave-active {
opacity: 0;
}
.slide-left-leave-active,
.slide-left-enter-active,
.slide-right-leave-active,
.slide-right-enter-active {
transition: 0.3s;
}
.slide-left-enter {
transform: translate(100%, 0);
}
.slide-left-leave-to {
transform: translate(-100%, 0);
}
.slide-right-enter {
transform: translate(-100%, 0);
}
.slide-right-leave-to {
transform: translate(100%, 0);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="demo" class="container">
<aside>
<transition name="fade">
<div class="pin parent" v-if="!showChild">
<div>
<h1>Parent</h1>
Go To Child
</div>
</div>
</transition>
<transition :name="slideTransition">
<div class="pin child" v-if="showChild">
<div>
<h1>Child</h1>
Go To Parent
</div>
</div>
</transition>
</aside>
<main>
<div>
<h1>Main</h1>
<transition name="fade">
<div v-if="showChild" class="pin content pt-50" key="child">
<h4>Child Content here</h4>
</div>
<div v-else="" class="pin content pt-50" key="parent">
<h4>Parent Content here</h4>
</div>
</transition>
</div>
</main>
</div>
Related
How can I change css width from 50% to 100 % when click the button see more detail here >>> Sample sandbox
<template>
<div id="theSpecial">Hello World Special</div>
<button #click="changeWidth">Change width</button>
</template>
<script>
export default {
data() {
return {
testBoolean: false,
};
},
methods: {
changeWidth() {
this.testBoolean = true;
//change width to 100%
},
},
};
</script>
CSS
#theSpecial {
background-color: purple;
color: white;
width: 50%;
}
You have to make some change on your code
First of all add this to your css
.theSpecial{width:50%}
.fullWidth{width:100%}
To toggle the full width modify the method
changeWidth() {
this.testBoolean = !this.testBoolean;
//this will toggle the width on every click
},
and then use this in your component template
<div class="theSpecial" v-bind:class="{fullWidth:testBoolean}">
N.B. change the id into class, beacuse id has more css specifity.
This will toggle the class full width accordly to the value of testBoolean.
This is your Sandbox
Here you can find documentation about class binding
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<div id="theSpecial" :class="{ 'full-width': testBoolean }">
Hello World Special
</div>
<button #click="changeWidth">Change width</button>
</div>
</template>
<script>
export default {
name: "HelloWorld",
props: {
msg: String,
},
data() {
return {
testBoolean: false,
};
},
methods: {
changeWidth() {
this.testBoolean = true;
},
},
};
</script>
#theSpecial {
background-color: purple;
color: white;
width: 50%;
}
#theSpecial.full-width {
width: 100%;
}
h3 {
margin: 40px 0 0;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
data() {
return {
testBoolean: false,
};
},
methods: {
changeWidth() {
this.testBoolean = !this.testBoolean;
//change width to 100%
},
},
.theSpecial {
background-color: purple;
color: white;
width: 50%;
}
.fullwidth {
background-color: purple;
color: white;
width: 100%;
}
<div :class="(this.testBoolean === true)? 'fullwidth':'theSpecial'">Hello World Special</div>
<button #click="changeWidth">Change width</button>
I created a Modal template in vueJS and I want to set the content inside of it dynamically.
Sometimes I will want the Modal to delete some kind of data.
E.g: Do you want to delete the user with ID 5?
But other times i will want the Modal to act as a Form input data.
The Modal template I'm using is this Modal.vue
<template>
<div class="vue-modal" v-show="open">
<div class="vue-modal-inner">
<div class="vue-modal-content">
<button type="button" click="$emit('close')">Close</button>
</div>
</div>
</div>
</template>
<script>
export default {
name: "Modal",
props: {
open: {
type: Boolean,
default: false
}
}
}
</script>
<style scoped>
*,
::before,
::after {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.vue-modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow-x: hidden;
overflow-y: auto;
background-color: rgba(0, 0, 0, 0.4);
z-index: 1;
}
.vue-modal-inner {
max-width: 500px;
margin: 2rem auto;
}
.vue-modal-content {
position: absolute;
min-width: 300px;
left: 50%;
top: 35%;
-webkit-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
background-color: #fff;
border: 1px solid rgba(0, 0, 0, 0.3);
background-clip: padding-box;
border-radius: 0.3rem;
padding: 1rem;
}
</style>
The content inside of vue-modal-content is what I want to set dynamically depending on how I am going to use the Modal (Delete, Update, Create)
Here's how I create it in parent component
<Modal :open="modalEditar.mostrar" #close="modalEditar.mostrar=false">
</Modal>
Probably easiest is to use slots.
There's a description of how to set this up at digitalocean so I won't go through it, since it's all there, but would add scope slots to the implementation. The issue is that you want to drive the kinds of buttons from the parent and the example only shows the close button as being available from within the the modal component.
To achieve that, you can pass the function to the slot like <slot :close="close">
<template>
<div class="vue-modal" v-show="open">
<div class="vue-modal-inner">
<div class="vue-modal-content">
<slot :close="close"></slot>
</div>
</div>
</div>
</template>
<script>
export default {
name: "Modal",
props: {
open: {
type: Boolean,
default: false
}
},
methods: {
close() {
this.$emit('close');
},
},
}
</script>
and then you can have the function available in the parent through <template v-slot={close} >
<script setup>
import { ref } from 'vue'
import Modal from "./Modal.vue"
const isModalVisible = ref(true);
function closeModal() {
isModalVisible.value = false;
}
</script>
<template>
<button #click="isModalVisible = true">
open
</button>
<Modal :open="isModalVisible" #close="closeModal">
<template v-slot={close} >
This is a body<br/>
<button #click="close" class="btn-green">
CLOSE
</button>
</template>
</Modal>
</template>
here is a working example
I have spent countless hours trying to figure out why my dropdown that is opened/closed by a burger menu icon click is sitting in front of the navbar even though I have specified z-indexes, overflows and positions. This issue is only happening on the MobileNav component below. MobileNav consists of a burger icon and the actual dropdown. Once the burger icon is clicked, the dropdown will either close or open. Currently It is displaying above the nav component and I am having a very hard time figuring out why. Any help will be much appreciated.
Vid to see the dropdown's behavior: https://www.youtube.com/watch?v=zOBnb6r_RN4&ab_channel=TylerOreskey
The dropdown is supposed to come out from the bottom of the navbar and close up into the bottom of the navbar.
Navbar Component: Renders MobileNav component
const Navbar = (props) => {
const [showDropdown, setShowDropdown] = useState(false);
const dropdownToggleHandler = () => setShowDropdown(!showDropdown);
const dropdownClosedHandler = () => setShowDropdown(false);
return (
<header
className={classes.Navbar}
style={{
position: props.passedNavbar ? "fixed" : "relative",
}}
>
<nav className={classes.MobileNav}>
<MobileNav
allNavigationRefs={props.allNavigationRefs}
scrollToDiv={props.scrollToDiv}
open={showDropdown}
closed={dropdownClosedHandler}
dropdownToggleHandler={dropdownToggleHandler}
/>
</nav>
</header>
);
};
export default memo(Navbar);
CSS file for Navbar component: z-index is not working in here.
.Navbar {
top: 0;
height: 50px;
background-color: hsl(213, 27%, 15%);
border-bottom: #00bfff 3px solid;
width: 100%;
z-index: 500;
}
#media (max-width: 500px) {
.DesktopNav {
display: none;
}
}
#media (min-width: 500px) {
.MobileNav {
display: none;
}
}
MobileNav component
const MobileNav = (props) => {
return (
<div className={classes.MobileNav}>
<DropdownToggle clicked={props.dropdownToggleHandler} />
<Dropdown open={props.open} allNavigationRefs={props.allNavigationRefs} />
</div>
);
};
export default MobileNav;
CSS file for MobileNav component
.MobileNav {
overflow: hidden;
}
Dropdown component: (This is displayed above the Navbar component and I cannot get it to be behind the navbar component).
const Dropdown = (props) => {
let attachedClasses = [classes.Dropdown, classes.Close];
if (props.open) {
attachedClasses = [classes.Dropdown, classes.Open];
}
return (
<div className={attachedClasses.join(" ")}>
<NavigationItems allNavigationRefs={props.allNavigationRefs} />
</div>
);
};
export default Dropdown;
CSS file for Dropdown component: z-index is not working in here.
.Dropdown {
background: hsl(212, 87%, 3%);
height: 200px;
transition: transform 0.3s ease-out;
z-index: 400;
display: block;
}
.Open {
transform: translate(0, 25%);
}
.Close {
transform: translate(0, -75%);
}
You are confused on how z-index works.
Consider each level in your tree as a layer.
lets say that Navbar is layer 0, MobileNav is then layer 1, and its children are on layer 2.
By default z-index is calculated among children of the same layer. This is true when the position attribute is on default static. When you alter this to relative you can instruct which layers are going to interuct with each other in a more immediate way.
Having 500 z-index on Navbar will make no sense to MobileNav. It is not his sibling, it's his child.
Here is a possible solution if you can alter the DOM tree
<header
className={classes.Navbar}
style={{
position: props.passedNavbar ? "fixed" : "relative"
}}
>
<nav className={classes.Navbar}>
<DropdownToggle clicked={props.dropdownToggleHandler} />
</nav>
<MobileNav
className={classes.MobileNav}
allNavigationRefs={props.allNavigationRefs}
scrollToDiv={props.scrollToDiv}
open={showDropdown}
closed={dropdownClosedHandler}
/>
</header>
and here is an answer if you can alter the CSS
.Navbar {
top: 0;
height: 50px;
background-color: hsl(213, 27%, 15%);
border-bottom: #00bfff 3px solid;
width: 100%;
position: relative;
}
...
.Dropdown {
background: hsl(212, 87%, 3%);
height: 200px;
transition: transform 0.3s ease-out;
display: block;
position: relative;
z-index: -1;
}
Good day. I have been attempting to create the animation demonstrated here in ReactJS. I have using that codepen as a loose reference, yet the animation doesn't work when I press the login button on my own project implementing this effect on the login page.
What have I done wrong?
Thanks in advance,
Duke J. Morgan.
The specific aspects of my project that, as far am I aware, are primary components in the login page that, in this problem, might be the cause of the problem I mentioned above:
import React, { Component } from 'react';
class LoginComponent extends Component {
constructor(props) {
super(props)
this.state = {
incorrect: false
}
}
onLoginButtonClick() {
let passwordInput = document.getElementById("passwordInput");
if (passwordInput.innerHTML !== "test") {
// passwordInput.classList.add("incorrect-login");
let copy = this.state;
copy.incorrect = true;
console.log(`Incorrect boolean: ${copy.incorrect}`);
this.setState(copy);
return;
}
this.props.setPage();
}
render() {
return (<div className="box" id="loginBox">
<h2 className="title">Username</h2>
<input type="text" placeholder="Username" className="input"/>
<h2 className="title">Password</h2>
<input id="passwordInput" placeholder="Password" className={`input ${this.state.incorrect ? 'incorrect-login' : ''}`} type="password"/>
{/* Forgot Password? */}
<button className="button" id="login" onClick={() => {this.onLoginButtonClick()}}>Login</button>
</div>);
}
}
export default LoginComponent;
//The CSS of the login box. incorrect-login is the class added to the password input element when the login button is clicked but the incorrect password, or no password, has been entered.
#loginBox {
.title:nth-child(1) {
margin-bottom: 10px;
}
.title:nth-child(3) {
margin-bottom: 10px;
margin-top: 10px;
}
.incorrect-login {
margin: 4px auto;
width: 70%;
height: 15%;
display: block;
padding: 5px;
padding-left: 10px;
border: 2px solid red;
animation: move 10s;
}
}
#keyframes move {
0%, 100% { left: 0px;}
20% , 60%{left: 15px;}
40% , 80%{left: -15px;}
}
The problem is in the css. I think you should make separated classes for the login and incorrect-login.
.login {
margin: 4px auto;
width: 70%;
height: 15%;
display: block;
padding: 5px;
padding-left: 10px;
position: absolute;
}
.incorrect-login {
border: 2px solid red;
animation: move 10s;
}
If you want to use property "left" in animation you have also add property position: absolute; to the component's class.
And code for the password input class name should be:
className={`input ${this.state.incorrect ? 'login incorrect-login' : 'login'}`}
Also you should not use document.getElementById in React applications directly. If you really need to get reference of the dom element created by React, you should use ref prop instead.
I prepared example of image slider what I need.
I encounter with styling issue when using images with various dimensions. When element leaving the array, its location is set as absolute value, what is necessary for smooth transition, tho, but the image is also moved up.
I would like to have nice vertical align into middle even leave or enter the array, but could not get on any way.
Another issue, what I would like to solve is when left the window and then went back after a while. The animation running all cycles at once to reach current state instead just stop animation and run after. Maybe it is my responsibility, but browsers doesn't offer nice event to catch blur window or am I wrong?
According to this discussion
Thank you for any ideas.
let numbers = [{key:1},{key:2},{key:3},{key:4},{key:5},{key:6},{key:7}]
let images = [
{ key:1,
src:"http://lorempixel.com/50/100/sports/"},
{ key:2,
src:"http://lorempixel.com/50/50/sports/"},
{ key:3,
src:"http://lorempixel.com/100/50/sports/"},
{ key:4,
src:"http://lorempixel.com/20/30/sports/"},
{ key:5,
src:"http://lorempixel.com/80/20/sports/"},
{ key:6,
src:"http://lorempixel.com/20/80/sports/"},
{ key:7,
src:"http://lorempixel.com/100/100/sports/"}
]
new Vue({
el: '#rotator',
data: {
items: images,
lastKey: 7,
direction: false
},
mounted () {
setInterval(() => {
if (this.direction) { this.prevr() } else { this.nextr() }
}, 1000)
},
methods: {
nextr () {
let it = this.items.shift()
it.key = ++this.lastKey
this.items.push(it)
},
prevr () {
let it = this.items.pop()
it.key = ++this.lastKey
this.items.unshift(it)
}
}
})
.litem {
transition: all 1s;
display: inline-block;
margin-right: 10px;
border: 1px solid green;
background-color: lightgreen;
padding: 10px 10px 10px 10px;
height: 100px;
}
.innerDiv {
border: 1px solid red;
}
.container {
overflow: hidden;
white-space: nowrap;
height: 250px;
border: 1px solid blue;
background-color: lightblue;
}
.list-enter {
opacity: 0;
transform: translateX(40px);
}
.list-leave-to {
opacity: 0;
transform: translateX(-40px);
}
.list-leave-active {
position: absolute;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.11/vue.js"></script>
<div id="rotator">
<button #click="direction = !direction">
change direction
</button>
<transition-group
name="list"
tag="div"
class="container">
<div
v-for="item in items"
:key="item.key" class="litem">
<!--
<div
class='innerDiv'>
{{ item.key }}
</div>
-->
<div class='innerDiv'>
<img :src='item.src'>
</div>
</div>
</transition-group>
</div>
It tooks a while but on the end I think that I have got better result for sliding animation with changing direction feature.
One annoying think is when I swithing the sliding direction so animation is for a 'microsecond' changed to next state and than return to correct one, after it the animation continue as expected. It is happening only in one direction and I don't know how to fix it. Also last box behave differently too only once. No clue at all.
So just 98% solution :-)
let images = [
{key:1, domKey:1, src:"http://lorempixel.com/50/100/sports/" },
{key:2, domKey:2, src:"http://lorempixel.com/50/50/sports/" },
{key:3, domKey:3, src:"http://lorempixel.com/100/50/sports/" },
{key:4, domKey:4, src:"http://lorempixel.com/20/30/sports/" },
{key:5, domKey:5, src:"http://lorempixel.com/80/20/sports/" },
{key:6, domKey:6, src:"http://lorempixel.com/20/80/sports/" },
{key:7, domKey:7, src:"http://lorempixel.com/100/100/sports/" }
]
let setPositionRelative = el => el.style.position = "relative"
new Vue({
el: '#rotator',
data: {
items: images,
lastKey: 7,
direction: true,
changeDirectionRequest: false
},
mounted () {
Array.from(this.$el.querySelectorAll("div[data-key]")).map(setPositionRelative)
setInterval(() => {
if(this.changeDirectionRequest) {
this.changeDirectionRequest = false
this.direction = !this.direction
if (this.direction)
Array.from(this.$el.querySelectorAll("div[data-key]")).map(setPositionRelative)
else
Array.from(this.$el.querySelectorAll("div[data-key]")).map(el => el.style.position = "")
}
if (this.direction) this.prevr()
else this.nextr()
}, 1000)
},
methods: {
nextr () {
let it = this.items.shift()
it.key = ++this.lastKey
this.items.push(it)
},
prevr () {
let it = this.items.pop()
it.key = ++this.lastKey
this.items.unshift(it)
setPositionRelative(this.$el.querySelector("div[data-domkey='"+it.domKey+"']"))
}
}
})
.container {
overflow: hidden;
white-space: nowrap;
height: 200px;
border: 1px solid blue;
background-color: lightblue;
display: flex;
align-items: center;
}
.innerDiv {
border: 1px solid red;
width: auto;
height: auto;
display:-moz-box;
-moz-box-pack:center;
-moz-box-align:center;
display:-webkit-box;
-webkit-box-pack:center;
-webkit-box-align:center;
display:box;
box-pack:center;
box-align:center;
}
.litem {
transition: all 1s;
margin-right: 10px;
border: 1px solid green;
background-color: lightgreen;
padding: 10px 10px 10px 10px;
}
.list2-enter, .list-enter {
opacity: 0;
transform: translateX(40px);
}
.list2-leave-to, .list-leave-to {
opacity: 0;
transform: translateX(-40px);
}
.list-leave-active {
position: absolute;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.11/vue.js"></script>
<div id="rotator">
<button #click="changeDirectionRequest = true">change direction</button>
<transition-group name="list" tag="div" class="container">
<div v-for="item in items"
:key="item.key"
:data-domkey="item.domKey"
class="litem">
<div class='innerDiv'>
<img :src='item.src'>
</div>
</div>
</transition-group>
</div>