Problem: I'm trying to make a carousel slider animation with Vue, where upon clicking "next" a "slide-forward" animation is going to be played, and upon "prev" - "slide-back" animation. Despite all attempts, I still get this very strange behaviour. Will really appreciate if anybody can put me on the right track here:
My approach:
<template>
<div class="h-[100px] overflow-hidden w-full relative">
<div v-for="(slide, index) in slides" :key="slide.id">
<transition :name="back ? 'slideback' : 'slide'">
<div class="h-[100px] w-full absolute bg-blue-500" v-if="index === currentIndex">{{ slides[currentIndex]['name'] }}</div>
</transition>
</div>
</div>
<div class="flex flex-row gap-x-2">
<button #click="moveSlide(-1)" >prev</button>
<button #click="moveSlide(1)">next</button>
</div>
</template>
<script>
export default {
data () {
return {
slides: [
{name:'akml;ll;m;l'},
{name:'bwerrrrrrrrrrrr'},
{name:'werwerwrwwrc'}
],
back: false,
currentIndex: 0
}
},
methods: {
moveSlide(offset) {
this.back = offset < 0
console.log(this.back)
let temp_index = this.currentIndex + offset
this.currentIndex = temp_index >= this.slides.length ? 0 : temp_index < 0 ? this.slides.length - 1 : temp_index
},
},
}
</script>
<style scoped>
.slide-leave-active,
.slide-enter-active {
transition: 1s;
}
.slide-enter {
transform: translate(100%, 0);
}
.slide-leave-to {
transform: translate(-100%, 0);
}
.slideback-leave-active,
.slideback-enter-active {
transition: 1s;
}
.slideback-enter {
transform: translate(-100%, 0);
}
.slideback-leave-to {
transform: translate(100%, 0);
}
</style>
Problem was related to me using a Vue 2 class for entering animation mode instead of the one from Vue 3. I just followed the migration tips here and now everything works as intended: https://v3-migration.vuejs.org/breaking-changes/transition.html
Related
I was wondering how to change the navbar behavior based on the webpage position. I've seen a lot of tutorials about how to hide/show a navbar on scroll, which vie managed to implement successfully, however that seems to just cover two states. I would like my navbar to have three: show when I scroll up, hide when I scroll down, and change opacity if I'm at the top of the website. I've tried using a variety of useEffects and event listeners but I'm quite new to react and js.
Here's a good example of the effect I'm looking for https://brittanychiang.com/. As you can see once you are at the top of the webpage the opacity changes as well as the drop shadow, it all seems to be 'stuck' at the top instead of the floating look when you scroll up/down. Using dev tools I've found that the navbar class does seem to have three different names depending on the location of the scrollbar.
Thanks in advance!
use blank array in useEffect to run it once when mounted
then use one state to set as (top,down,up) from window scroll event
working demo
js
export function headerSystem(){
const [hState,sethState] = useState("top")
useEffect(()=>{
var lastVal = 0
window.onscroll = function(){
let y = window.scrollY
if(y > lastVal){sethState("down")}
if(y < lastVal) {sethState("up")}
if(y === 0) {sethState("top")}
lastVal = y
}
},[])
return (
<div className="root">
<div className={"header text-center p-4 bg-blue-200 fixed w-full text-lg font-semibold " + hState}>this is header</div>
<div className="pageContent bg-green-500">
<div className="each h-screen"></div>
<div className="each h-screen"></div>
<div className="each h-screen"></div>
<div className="each h-screen"></div>
<div className="each h-screen"></div>
</div>
</div>
)
}
css
.header {
background: rgba(0, 0, 0, 0);
transition: all 0.3s ease;
}
.top {
top: 0px;
}
.down {
top: -80px;
}
.up {
top: 0px;
}
.up,
.down {
background: rgba(0, 255, 242, 0.363);
box-shadow: 0px 0px 5px black;
padding: 8px;
}
You have to handle 'scroll' events using your component's lifecycle functions.
Please find this example for your header component. With some tweaks you may be able to use it.
Class component:
componentDidMount: function() {
window.addEventListener('scroll', this.handleScroll);
},
componentWillUnmount: function() {
window.removeEventListener('scroll', this.handleScroll);
},
handleScroll: function(event) {
let scrollTop = event.srcElement.body.scrollTop,
itemTranslate = Math.min(0, scrollTop/3 - 60);
this.setState({
transform: itemTranslate // and use this on your header
});
},
Functional component:
const [headerPosition, setHeaderPosition] = useState(0)
const handleScroll = useCallback((event) => {
let scrollTop = event.srcElement.body.scrollTop,
itemTranslate = Math.min(0, scrollTop/3 - 60);
setHeaderPosition({
transform: itemTranslate // and use this on your header
});
}, [setHeaderPosition])
useEffect(() => {
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, [handleScroll])
I am trying to implement a simple animation with Vue but unsuccessful so far. I have two divs which are rendered depending on the value of a given data property
<!--Step 1-->
<div v-if="step == 1" :transition="'slide'">
<select class="form-control" v-model="id.category">
<option value="null">Please Select</option>
<option v-for="cat in cats" :value="cat.id">#{{cat.name}}</option>
</select>
</div>
<!--Step 2-->
<div v-if="step==2" :transition="'slide'" style="background: red">
<select #change="fixImage(id.subcategory)" class="form-control quoteForm" v-model="id.subcategory">
<option value="null">Please Select</option>
<option v-for="subcat in filtered_subcat" :value="subcat.id">#{{subcat.name}}</option>
</select>
</div>
I effectively have a "next button" that will increment the value of step and then show the relevant div. I would like to create like a slide type of effect whereby when the user clicks next, step 1 slides to the left and step 2 slides in. My animation css is as follows:
.slide-fade-enter-active {
transition: all .3s ease;
}
.slide-fade-leave-active {
transition: all .8s cubic-bezier(1.0, 0.5, 0.8, 1.0);
}
.slide-fade-enter, .slide-fade-leave-to
/* .slide-fade-leave-active below version 2.1.8 */ {
transform: translateX(10px);
opacity: 0;
}
Can anyone point in the right direction?
Wrap the changing content in a <transition> component:
<transition name="slide-fade">
<div v-if="step == 1">
STEP 1 CONTENT
</div>
</transition>
<transition name="slide-fade">
<div v-if="step == 2">
STEP 2 CONTENT
</div>
</transition>
The name of the transition, slide-fade, matches the CSS you supplied. Since you probably want the sliding content to overlap, one should have position: absolute. For example, the content sliding out:
.slide-fade-leave-active {
position: absolute;
transition: all .8s cubic-bezier(1.0, 0.5, 0.8, 1.0);
}
Here's a Demo:
new Vue({
el: "#app",
data() {
return {
step: 1
}
}
});
.slide-fade-enter-active {
transition: all .3s ease;
}
.slide-fade-leave-active {
position: absolute;
transition: all .8s cubic-bezier(1.0, 0.5, 0.8, 1.0);
}
.slide-fade-enter, .slide-fade-leave-to
/* .slide-fade-leave-active below version 2.1.8 */ {
transform: translateX(10px);
opacity: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<!--Step 1-->
<transition name="slide-fade">
<div v-if="step == 1">
STEP 1 CONTENT
</div>
</transition>
<!--Step 2-->
<transition name="slide-fade">
<div v-if="step == 2">
STEP 2 CONTENT
</div>
</transition>
<button #click="step++">
Next >
</button>
</div>
I have quick question regarding transitions with Vue.js.
In my boostrap template I'm trying to add one more select option dropdown based on selected option. So, I added change event on first select option. So, if I select 'first item' then change classes and add dropdown in row, or else hides it.
Something like this:
selectTodo: function(e) {
let selectValue = e.target.options[e.target.selectedIndex].text
if (selectValue === 'Learn Vue') {
this.styleObject.display = 'unset';
this.col_md = 'form-group col-md-4';
this.showCropStageList = true;
}
else {
this.showCropStageList = false;
this.styleObject.display = 'none';
this.col_md = 'form-group col-md-6';
this.cropStageList = null;
}
}
I managed to do that but I want more smoother transitions on the first two dropdowns.
I've managed to set up some fiddle where you can see smooth slide transition for third select dropdown but if if I change select from "Learn Vue" to something else, the third dropdown hides it but without any animation as well.
You can see it in the snippet below, too!
new Vue({
el: "#app",
data: {
todos: [
{ id:1 ,text: "Learn JavaScript", done: false },
{ id:2 ,text: "Learn Vue", done: false },
{ id:3 ,text: "Play around in JSFiddle", done: true },
{ id:4 ,text: "Build something awesome", done: true }
],
col_md: 'form-group col-md-6',
styleObject: {
display: 'none'
},
showCropStageList: false,
},
methods: {
toggle: function(todo){
todo.done = !todo.done
},
selectTodo: function(e) {
let selectValue = e.target.options[e.target.selectedIndex].text
if (selectValue === 'Learn JavaScript') {
this.styleObject.display = 'unset';
this.col_md = 'form-group col-md-4';
this.showCropStageList = true;
}
else {
this.showCropStageList = false;
this.styleObject.display = 'none';
this.col_md = 'form-group col-md-6';
this.cropStageList = null;
}
}
}
})
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
.fade-enter {
opacity: 0;
}
.fade-enter-active {
transition: opacity 1s;
}
.fade-leave {}
.fade-leave-active {
transition: opacity 1s;
opacity: 0;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
li {
margin: 8px 0;
}
h2 {
font-weight: bold;
margin-bottom: 15px;
}
del {
color: rgba(0, 0, 0, 0.3);
}
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div class="container">
<!-- Content here -->
<div id="app">
<div class="form-row">
<transition name="fade" appear>
<div v-bind:class=col_md>
<label for="cropType" class="col-form-label-sm font-weight-bold">Select Learn Javascript </label>
<select class="form-control" v-on:change="selectTodo" id="cropType" v-model="pickedCropType" #change="getCropsByType()">
<option v-for="(todo, index) in todos" :key="index" :value="todo.id" >
{{todo.text}}
</option>
</select>
</div>
</transition>
<div v-bind:class=col_md>
<label for="cropCulture" class="col-form-label-sm font-weight-bold">2. Second</label>
<select class="form-control" id="cropCulture">
<option v-for="(todo, index) in todos" :key="index" :value="todo.id" >
{{todo.text}}
</option>
</select>
</div>
<transition
enter-active-class="animated fadeInLeft"
leave-active-class="animated fadeOutLeft"
>
<div class="form-group col-md-4" v-if="showCropStageList" v-bind:class="{styleObject }">
<label for="cropStage" class="col-form-label-sm font-weight-bold">3. Third</label>
<select class="form-control" id="cropStage">
<option v-for="(todo, index) in todos" :key="index" :value="todo.id" >
{{todo.text}}
</option>
</select>
</div>
</transition>
</div>
</div>
</div>
You can see change class for first two dropdowns but without any transitions. So, is it possible to add some transitions for first two dropdowns?
EDIT :
As you can see I'm changing class from *form-group col-md-6* to *form-group col-md-4* and I' wondering if is it possible that that tranisition from md-6 to md-4 can be a bit smoother.
https://jsfiddle.net/Loque/akt0su98/
In your codes, you directly update the class from col-md-4 to col-md-6 in selectTodo. It will cause first two <div> take full width of the row before 3rd div finishes its animation.
The solution, uses this.$nextTick and setTimeout.
when add the third div, delay execute show the third div before first two <div> finish their animations.
when remove the third div, delay apply col-md-6 to the first two <div> before the third <div> finishes its animations.
Below is one sample: (PS: I used transition-group here, because it will be better for Reusable Transitions)
Warning: Please test below demo under full page, otherwise bootstrap will treat it as extre-small screen (place each <div> in each row), though I think the effects in small screen is still not bad.
new Vue({
el: "#app",
data: {
todos: [
{ id:1 ,text: "Learn JavaScript", done: false },
{ id:2 ,text: "Learn Vue", done: false },
{ id:3 ,text: "Play around in JSFiddle", done: true },
{ id:4 ,text: "Build something awesome", done: true }
],
col_md: 'form-group col-md-6',
styleObject: {
display: 'none'
},
showCropStageList: false,
pickedCropType: ''
},
methods: {
getCropsByType: function () {},
toggle: function(todo){
todo.done = !todo.done
},
selectTodo: function(e) {
let selectValue = e.target.options[e.target.selectedIndex].text
if (selectValue === 'Learn Vue') {
this.col_md = 'form-group col-md-4';
this.$nextTick(() => { //delay display the third div before first two div finish animation
setTimeout(()=>{
this.styleObject.display = 'unset';
this.showCropStageList = true;
}, 500)
})
}
else {
this.showCropStageList = false;
this.styleObject.display = 'none';
this.$nextTick(() => { //delay apply `md-6` to first two div before 3rd div finish animation
setTimeout(()=>{
this.col_md = 'form-group col-md-6';
}, 1000)
})
this.cropStageList = null;
}
}
}
})
.sample {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
.fade-item {
transition: all 0.5s;
display:inline-block;
}
.fade-enter {
opacity: 0;
transform: translateX(-130px);
}
.fade-enter-active {
transition: all 1.5s;
}
.fade-leave-to {
transform: translateX(130px);
opacity: 0;
flex: 0 0 20%;
}
.fade-leave-active {
transition: all 1s;
}
#app {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
li {
margin: 8px 0;
}
h2 {
font-weight: bold;
margin-bottom: 15px;
}
del {
color: rgba(0, 0, 0, 0.3);
}
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<!-- Content here -->
<div id="app">
<div class="container">
<div class="sample">
<transition-group name="fade" tag="div" class="form-row">
<div v-bind:class="col_md" :key="1" class="fade-item">
<label for="cropType" class="col-form-label-sm font-weight-bold">Select Learn Javascript </label>
<select class="form-control" v-on:change="selectTodo" id="cropType" v-model="pickedCropType" #change="getCropsByType()">
<option v-for="(todo, index) in todos" :key="index" :value="todo.id" >
{{todo.text}}
</option>
</select>
</div>
<div v-bind:class="col_md" :key="2" class="fade-item">
<label for="cropCulture" class="col-form-label-sm font-weight-bold">2. Second</label>
<select class="form-control" id="cropCulture">
<option v-for="(todo, index) in todos" :key="index" :value="todo.id" >
{{todo.text}}
</option>
</select>
</div>
<div class="form-group col-md-4 fade-item" v-if="showCropStageList" v-bind:class="{styleObject }" :key="3">
<label for="cropStage" class="col-form-label-sm font-weight-bold">3. Third</label>
<select class="form-control" id="cropStage">
<option v-for="(todo, index) in todos" :key="index" :value="todo.id" >
{{todo.text}}
</option>
</select>
</div>
</transition-group>
</div>
</div>
</div>
I don't know if there is a specific way to do in Vue but what you want can be easily achieved using some CSS magic. It is simple in a matter of fact, all you are going to do is add a class to your changing div let's say for example the class name is transition and in your CSS you define transition styles as follow:
.transition {
transition: all 0.25s linear;
}
You can find an updated fiddle here
Note you can change the timing function linear and the time 0.25s to suit your needs, you can read more about CSS transitions and effects here
i am trying to apply animation width:20% on wrong class when v-if condition becomes false , how can i watch v-if change .
export default {
data() {
return {
ok: true
}
},
methods: {
toggle() {
this.ok = !this.ok;
}
},
watch: {
ok() {
// apply animation after v-if is false
}
}
}
.wrong {
background-color: #fdd;
transition: width 2s;
width: 100%;
}
<template>
<div class="view1">
<div v-if="ok">
<p class="right">OK</p>
</div>
<div v-else>
<p class="wrong">NO</p>
</div>
</div>
<button #click="toggle">Toggle</button>
</template>
You can do this with CSS rather than jQuery. The trick is to have a width transition, and a delay in applying the width style. You listen to a change by using a watch.
new Vue({
el: '.view1',
data: {
ok: true,
delayedReaction: null
},
methods: {
toggle() {
this.ok = !this.ok;
}
},
watch: {
ok() {
if (this.ok) {
this.delayedReaction = null;
} else {
// nextTick didn't suffice
setTimeout(() => {
this.delayedReaction = {
width: '20%'
};
}, 0);
}
}
}
});
.wrong {
background-color: #fdd;
transition: width 2s;
width: 100%;
}
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<div class="view1">
<div v-if="ok">
<p class="right">OK</p>
</div>
<div v-else>
<p class="wrong" :style="delayedReaction">NO</p>
</div>
<button #click="toggle">Toggle</button>
</div>
I have a Basket component which needs to toggle a BasketContents component when clicked on. This works:
constructor() {
super();
this.state = {
open: false
}
this.handleDropDown = this.handleDropDown.bind(this);
}
handleDropDown() {
this.setState({ open: !this.state.open })
}
render() {
return(
<div className="basket">
<button className="basketBtn" onClick={this.handleDropDown}>
Open
</button>
{
this.state.open
?
<BasketContents />
: null
}
</div>
)
}
It uses a conditional to either display the BasketContents component or not. I now want it to fade in. I tried adding a ComponentDidMount hook to BasketContents to transition the opacity but that doesn't work. Is there a simple way to do this?
An example using css class toggling + opacity transitions:
https://jsfiddle.net/ybktodLc/
Here's the interesting CSS:
.basket {
transition: opacity 0.5s;
opacity: 1;
}
.basket.hide {
opacity: 0;
pointer-events:none;
}
And the render function:
render() {
const classes = this.state.open ? 'basket' : 'basket hide'
return(
<div className="basket">
<button className="basketBtn" onClick={this.handleDropDown}>
{this.state.open ? 'Close' : 'Open'}
</button>
<BasketContents className={classes}/>
</div>
)
}
I would use react-motion like this:
<Motion style={{currentOpacity: spring(this.state.open ? 1 : 0, { stiffness: 140, damping: 20 })}}>
{({currentOpacity}) =>
<div style={{opacity: currentOpacity}}>
<BasketContents />
</div>
}
</Motion>
I haven't tested it, but it should work.
I was doing this for a mobile menu hamburger button for expanding and closing the nav. I wanted to keep rendering the contents but just want a smooth transition every time I opened/closed the menu. This is my solution. On compontentDidMount() and on every menu hamburger button click and close button click I set the opacity to 0 and wait for 1 millisecond in setTimeout before adding the transition:
handleMenuExpand = () => {
this.handleTransition(false);
}
handleMenuShrink = () => {
this.handleTransition(true);
}
handleTransition = (isHidden) => {
this.setState({
transitionStyle: {
opacity: '0'
},
isNavHidden: isHidden
});
setTimeout(() => this.setState({
transitionStyle: {
transition: 'opacity 0.8s',
opacity: '1'
}
}), 1
);
}
componentDidMount() {
this.handleTransition(this._isMobile);
}
return(
<nav className="navbar-container" style={this.state.transitionStyle}>
{ (this.state.isNavHidden) ?
<ul className="navbar-content">
<li className="menu-expand-container" style={topBarStyle} >
<img
src={MenuHamburgerPic}
style={menuButtonStyle}
alt="Menu Pic"
onClick={this.handleMenuExpand}
/>
</li>
</ul>
:
<ul className="navbar-content">
{(this._isMobile) &&
<li style={closeButtonContainerStyle} >
<img
src={MenuClosePic}
style={closeMenuButtonStyle}
alt="Menu Pic"
onClick={this.handleMenuShrink}
/>
</li>
}
<li>NAV ELEMENT 1</li>
<li>AOTHER NAV ELEMENT</li>
</ul>
}
</nav>
);