I'm quite new to Vue.js and have been using it in work the last number of weeks. I've been asked to do a small update to existing code but cannot get v-bind:class working correctly.
In my simplified example, if a button is clicked, the other classes should become less prominent, i.e.
If 'Colour' is clicked anything with the class 'sound' or 'shape' reduce in opacity
If 'Shape' is clicked anything with the class 'colour or 'sound' reduce in opacity
If 'Sound' is clicked anything with the class 'colour' or 'shape reduce in opacity
Of course, two buttons shouldn't be selected at the same time as can happen in my example, selecting say, 'Shape' after previously selecting 'Colour' should undo the opacity from the class 'shape' and add to the necessary classes.
Whats happening though is that only the 'Sound' button is functioning correctly... 'Colour' does nothing and 'Shape' only half works!
I know I could use JavaScript to write a method to alter the DOM by getting the elementByClassName but I would like to stick to using Vue correctly (if it is possible)
var app = new Vue({
el: '#app',
data: {
colourAddClass: false,
shapeAddClass: false,
soundAddClass: false,
}
})
.opacity {
opacity: 0.3;
}
.colour {
color: blue;
}
.shape {
color: red;
}
.sound {
color: green;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<button #click="colourAddClass = !colourAddClass"> Colours </button>
<button #click="shapeAddClass = !shapeAddClass"> Shapes </button>
<button #click="soundAddClass = !soundAddClass"> Sounds </button>
<br/><br/>
<span class="colour" v-bind:class="{'opacity': shapeAddClass, 'opacity': soundAddClass}">Yellow</span>
<span class="shape" v-bind:class="{'opacity': colourAddClass, 'opacity': soundAddClass}">Triangle</span>
<span class="sound" v-bind:class="{'opacity': colourAddClass, 'opacity': shapeAddClass}">Woof</span>
<span class="shape" v-bind:class="{'opacity': colourAddClass, 'opacity': soundAddClass}">Circle</span>
<br/>
<span class="sound" v-bind:class="{'opacity': colourAddClass, 'opacity': shapeAddClass}">Meow</span>
<span class="colour" v-bind:class="{'opacity': shapeAddClass, 'opacity': soundAddClass}">Purple</span>
<span class="shape" v-bind:class="{'opacity': colourAddClass, 'opacity': soundAddClass}">Circle</span>
<span class="colour" v-bind:class="{'opacity': shapeAddClass, 'opacity': soundAddClass}">Orange</span>
</div>
There are many ways to accomplish these types of tasks with VueJS. Below is one way. I'm sure others have shorter methods. To explain my method, I am using a single variable to track the current active button. I use a method to set that variable since we want to toggle on and off. I then bind the class attribute to some logic.
var app = new Vue({
el: '#app',
data: {
active: null
},
methods: {
toggleActive( type ) {
//if active is already of this type, set to null
this.active = ( this.active == type ) ? null : type;
}
}
})
.opacity {
opacity: 0.3;
}
.colour {
color: blue;
}
.shape {
color: red;
}
.sound {
color: green;
}
<div id="app">
<button #click="toggleActive('colour')"> Colours </button>
<button #click="toggleActive('shape')"> Shapes </button>
<button #click="toggleActive('sound')"> Sounds </button>
<br/><br/>
<span :class="{ colour: true, opacity: active && active != 'colour' }" >Yellow</span>
<span :class="{ shape: true, opacity: active && active != 'shape' }">Triangle</span>
<span :class="{ sound: true, opacity: active && active != 'sound' }">Woof</span>
<span :class="{ shape: true, opacity: active && active != 'shape' }">Circle</span>
<br/>
<span :class="{ sound: true, opacity: active && active != 'sound' }">Meow</span>
<span :class="{ colour: true, opacity: active && active != 'colour' }">Purple</span>
<span :class="{ shape: true, opacity: active && active != 'shape' }">Circle</span>
<span :class="{ colour: true, opacity: active && active != 'colour' }">Orange</span>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
Related
i'm quite new to vue and can't figure out how to do it, i have several buttons and i need to select only one when clicked, i did it via
:class="isActive ? 'on' : 'off'"
v-on:click ="isActive = !isActive"
but this activates all the buttons, then I understand that I need to somehow distinguish the target button from the non-target one, but I can’t figure out how to do this. I can't find suitable implementation examples can you provide code examples
data() {
return {
isActive: true,
color: ‘’,
};
},
<template>
<div id="btn-box">
<button
type="button"
class="btn off"
#click="component='BorderLeftComonent', toggleShowPopup()">
<div
style="padding: 0 5px; width: 25px; margin: 0 auto; font-size: 25px;"
:style="{ 'border-left': `4px solid ${color}` }">A</div>
</button>
<button
type="button"
class="btn off"
#click="component='TextBalloonComponent'">
<div
class="bubble"
style="margin: 0 auto; width: 25px; font-size: 25px;">A</div>
</button>
<button
type="button"
class="btn off"
#click="component='DashedComponent'">
<div
style="border: 4px dashed #f5d018; margin: 0 auto; width: 45px; font-size: 25px;">A</div>
</button>
</div>
</template>
Wrong Syntax:
:class="isActive ? 'on' : 'off'"
v-on:click ="isActive = !isActive"
Correct Syntax (using shorthand):
:class="{ 'on': isActive, 'off': !isActive }"
#click="isActive = !isActive"
OR this, and take a look at the example below:
:class="{ 'on': isActive, 'off': !isActive }"
#click="toggle"
Define the #click event toggle in the methods as this:
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<!-- these buttons have `:class` binding and `#click` event -->
<button :class="{ 'on': isActive, 'off': !isActive }" #click="isActive = !isActive">Toggle</button> OR
<button :class="{ 'on': isActive, 'off': !isActive }" #click="toggle">Toggle</button> OR
<!-- these buttons just have `#click` event -->
<button #click="isActive = !isActive">Toggle</button> OR
<button #click="toggle">Toggle</button> OR
<!-- these buttons do not (have class and event binding) do anything -->
<button>Toggle</button> OR
<button>Toggle</button>
<p v-if="isActive">{{ message }}</p>
</div>
<script>
let app = new Vue({
el: '#app',
data: {
message: 'Hello Vue.js!',
isActive: true,
},
methods: {
toggle() {
this.isActive = this.isActive ? false : true
}
},
})
</script>
<style scoped>
/*simply styling your classes*/
button {
padding: 0.5em;
border-radius: 5px;
color: hsl(205, 46.5%, 73.5%);
background-color: hsl(205, 14%, 28%);
}
.on {
color: green;
}
.off {
color: red;
}
</style>
Use v-for directive to iterate over an array of button objects where each object includes it's own isActive property that can be toggled by the onclick event.
<button
v-for="(button, index) in buttons"
:key="index"
:class="button.isActive ? 'on' : 'off'"
#click="button.isActive = !button.isActive"
>
<div :class="`btn btn-${button.type}`">{{ button.label }}</div>
</button>
data() {
return {
buttons: [
{
label: "A",
isActive: false,
type: "border-left",
},
{
label: "A",
isActive: false,
type: "text-balloon",
},
{
label: "A",
isActive: false,
type: "dashed",
},
],
};
}
<style scoped>
.btn {
padding: 0 5px;
width: 25px;
margin: 0 auto;
font-size: 25px;
}
.btn-border-left {
border-left: 4px solid #f55;
}
.btn-dashed {
border: 4px dashed #f5d018;
width: 45px;
}
</style>
It seems that you need a button group, a classic component in UI libraries.
Have a look at this one for example.
For example below, you have 4 buttons next to each other, and each button is highlighted when you click on it, see gif below.
And in your code, you have access to a property (here "text") that reflects which button is selected.
Code taken from the link above:
<v-btn-toggle
v-model="text"
tile
color="deep-purple accent-3"
group
>
<v-btn value="left">
Left
</v-btn>
<v-btn value="center">
Center
</v-btn>
<v-btn value="right">
Right
</v-btn>
<v-btn value="justify">
Justify
</v-btn>
</v-btn-toggle>
Does that answer your question?
I'm trying to add transitions to my form, so that when a user selects a specific answer to a question, it will dynamically show the next question. Currently I have it looking something like this:
<input type="text" :value="vueModel.question1" />
<div v-if="vueModel.question1 === 'hello'">
//Do something
</div>
<div v-else-if="vueModel.question1 === 'hihi'">
//Do something
</div>
<div v-else>
//Do something
</div>
My question is, should I be adding transitions this way? (and why?)
<input type="text" :value="vueModel.question1" />
<transition-group name="slide-fade" mode="in-out">
<div v-if="vueModel.question1 === 'hello'" key="key1">
//Do something
</div>
<div v-else-if="vueModel.question1 === 'hihi'" key="key2">
//Do something
</div>
<div v-else key="key3">
//Do something
</div>
</transition-group>
or, this way? (and why?)
<input type="text" :value="vueModel.question1" />
<transition name="slide-fade" mode="in-out">
<div v-if="vueModel.question1 === 'hello'" key="key1">
//Do something
</div>
<div v-else-if="vueModel.question1 === 'hihi'" key="key2">
//Do something
</div>
<div v-else key="key3">
//Do something
</div>
</transition>
Or, is there another way I could do this better and which fits with Vue best practices?
So when you we have a list of items and you want to render and filter simultaneously, for example with v-for? In this case, you may use transition-group component. Unlike transition, this will render an actual element: like div in the next sniped. However, you can change the element that is rendered with the tag attribute.
NOTE
Elements inside are always required to have a unique key attribute.
Update
If you just have a question and then you want to "do something" use only transition as in this sample.
The main difference between transition and transition-group is that transition will affect one component. This means if you have a component an you want to replaced with another component you can use transition.
new Vue({
el: '#vue-transition',
data: {
show: false,
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<link href="https://cdn.jsdelivr.net/npm/animate.css#3.5.1" rel="stylesheet" type="text/css">
<div id="vue-transition">
<button #click="show = !show"> Simple Transition </button>
<transition
name="custom-classes-transition"
enter-active-class="animated tada"
leave-active-class="animated bounceOutRight"
>
<p v-if="show">Teocci</p>
</transition>
</div>
On the other hand, transition-group renders an actual element from a list of elements, for that reason elements inside are always required to have a unique key attribute. For example, if you have 9 questions but you want to render a transition of each element moving randomly to another position in the SAME group.
new Vue({
el: '#list-complete-demo',
data: {
items: [1, 2, 3, 4, 5, 6, 7, 8, 9],
},
methods: {
shuffle: function() {
this.items = _.shuffle(this.items)
}
}
})
.list-complete-item {
transition: all 1s;
display: inline-block;
margin-right: 10px;
}
.list-complete-enter,
.list-complete-leave-to {
opacity: 0;
transform: translateY(30px);
}
.list-complete-leave-active {
position: absolute;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.14.1/lodash.min.js"></script>
<div id="list-complete-demo" class="demo">
<button v-on:click="shuffle">Shuffle</button>
<transition-group name="list-complete" tag="p">
<span v-for="item in items" v-bind:key="item" class="list-complete-item">
{{ item }}
</span>
</transition-group>
</div>
For that reason, if you just want to "do something" use transition as shown in this snippet to achieve what you want.
let app = new Vue({
el: '#vue-selector',
data: {
questions: [{
id: 0,
description: 'Question 01',
answer: 'hihi'
},
{
id: 1,
description: 'Question 02',
answer: 'lala'
},
{
id: 2,
description: 'Question 03',
answer: 'hello'
},
{
id: 3,
description: 'Question 04',
answer: 'none'
},
{
id: 4,
description: 'Question 05',
answer: 'teo'
},
],
answer: {
question: -1,
text: '',
},
answerText: '',
selected: '',
},
computed: {
computedQuestions: function() {
let vm = this;
return this.questions.filter(function(item, index) {
return index !== vm.answers;
})
}
},
methods: {
answerQuestion: function(index) {
this.answer.question = index;
this.answer.text = this.answerText;
},
beforeEnter: function(el) {
el.style.opacity = 0
el.style.height = 0
},
enter: function(el, done) {
var delay = el.dataset.index * 150
setTimeout(function() {
Velocity(
el, {
opacity: 1,
height: '1.6em'
}, {
complete: done
}
)
}, delay)
},
leave: function(el, done) {
var delay = el.dataset.index * 150
setTimeout(function() {
Velocity(
el, {
opacity: 0,
height: 0
}, {
complete: done
}
)
}, delay)
},
},
});
div.selector {
display: block;
padding-top: 25px;
padding-bottom: 125px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/velocity/2.0.3/velocity.min.js"></script>
<div id="vue-selector">
<div>Question 01</div>
<input v-model="answerText" placeholder="answer me">
<button #click="answerQuestion(0)"> Answer </button>
<transition name="slide-fade" mode="in-out">
<div v-if="answer.text === 'hello'">
Do something A
</div>
<div v-else-if="answer.text === 'hihi'">
Do something B
</div>
<div v-else>
Waiting for answer
</div>
</transition>
</div>
I want to hide and display when I click on the icon cart. The problem is in hiding that box again,
icon before click : https://imgur.com/RxmcwsX
after click: https://imgur.com/cCt4mk0
Here is css image : https://imgur.com/d6ZPUbY
vue js : https://imgur.com/2kWZdly
mycss code :
<li class="nav-item" id="cart">
<i class="fa fa-shopping-cart fa-lg" #click="showCart"></i>
<div id="list-cart">
<div class="shadow-lg" style="position:absolute;background-color: #FFF;width:300px;height:300px;right:210px;top:60px;border-radius: 5px;" v-bind:style="{ visibility: computedVisibility }"></div>
</div>
vue code
var cart = new Vue({
el: '#cart',
data: {
visibility: 'hidden'
},
computed: {
computedVisibility: function() {
return this.visibility;
}
},
methods: {
showCart: function(event) {
this.visibility = 'visible';
}
}
});
Use v-if instead of directly manipulating the styles:
<li class="nav-item" id="cart">
<i class="fa fa-shopping-cart fa-lg" #click="visible = !visible"></i>
<div id="list-cart">
<div class="shadow-lg" v-if="visible"></div>
</div>
var cart = new Vue({
el: '#cart',
data: () => ({
visible: false
})
});
You could try binding it to a class instead. Then you can have a ternary expression that determines your class.
<li class="nav-item" id="cart">
<i class="fa fa-shopping-cart fa-lg" #click="showCart"></i>
<div id="list-cart">
<div
style="position:absolute;
background-color: #FFF;
width:300px;
height:300px;
right:210px;
top:60px;
border-radius: 5px;"
v-bind:class="[visible ? 'show' : 'hide', 'shadow-lg']">
</div>
</div>
Then you can have a data element, visible, that is set initially to false. You should also make data a function
data: () => ({
visible: false
})
then your show cart function can just be:
showCart() {
this.visible = !this.visible
}
which you can also call to close the cart.
And then set your styles:
<style scoped>
.show {
visibility: visible
}
.hide {
visibility: hidden
}
</style>
That said there are plenty of packages that offer 'modals' where this would largely be handled for you. I'd recommend vuetify but if you're the old fashioned type you could even use bootstrap.
If the given script in your question works, you may just change the showCart function as below.
var cart = new Vue({
el: '#cart',
data: {
visibility: 'hidden'
},
computed: {
computedVisibility: function() {
return this.visibility;
}
},
methods: {
showCart: function(event) {
if(this.visibility ==='visible'){
this.visibility = 'hidden';
}else if(this.visibility==='hidden'){
this.visibility = 'visible'
}
}
}
});
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 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>
);