Rotate image with #click in Vue2 - javascript

I have a refresh button on dashboard. I want to make it rotate 360 degrees.
How can I rotate image with every click on a refresh button?
This is the code I tried, it only work twice click:
var vm = new Vue({
el: '#app',
data: {
clicked: false,
},
methods: {
rotation() {
this.clicked = !this.clicked
}
}
})
.clicked {
transform: rotate(360deg);
transition: transform 0.5s ease-in-out;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.12/vue.js"></script>
<div id="app">
<img
src="https://via.placeholder.com/100/09f.png/fff"
#click="rotation"
:class="{ clicked }"
alt="refresh-icon-btn"
/>
</div>

This could be one of the approach, have a inline style to toggle between 0 & 360 deg and have a constant class for transition.
var vm = new Vue({
el: '#app',
data: {
deg: 0,
},
methods: {
rotation() {
this.deg += 360;
}
}
})
.transition {
transition: transform 0.5s ease-in-out;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.12/vue.min.js"></script>
<div id="app">
<img src="https://via.placeholder.com/100/09f.png/fff" #click="rotation" class="transition" v-bind:style="{transform: `rotate(${deg}deg)`}" alt="refresh-icon-btn" />
</div>

Another approach is to use setTimeout to remove the class after the animation
var vm = new Vue({
el: '#app',
data: {
clicked: false,
rotationDuration: 500
},
methods: {
rotation() {
this.clicked = !this.clicked
setTimeout(() => this.clicked = !this.clicked, this.rotationDuration)
}
}
})
.clicked {
transform: rotate(360deg);
transition: transform var(--rotation-duration) ease-in-out;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.12/vue.js"></script>
<div id="app">
<img
src="https://via.placeholder.com/100/09f.png/fff"
#click="rotation"
:class="{ clicked }"
:style="`--rotation-duration: ${rotationDuration}ms`"
alt="refresh-icon-btn"
/>
</div>

You can just use a class binding to toggle the class on and off.
var vm = new Vue({
el: '#app',
data: {
isClicked: false,
},
methods: {
rotation() {
this.isClicked = !this.isClicked
}
}
})
.image {
transition: transform 0.5s ease-in-out;
}
.clicked {
transform: rotate(360deg);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.12/vue.js"></script>
<div id="app">
<img
src="https://via.placeholder.com/100/09f.png/fff"
#click="rotation"
class="image"
:class="{ clicked: isClicked }"
alt="refresh-icon-btn"
/>
</div>

Related

How to make carousel with Vue?

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

Transition multiple elements at the same time on Vue component render

Simple case shown in the following jsfiddle:
https://jsfiddle.net/hsak2rdu/
I want to swap and animate two elements, but fail. As you can see after clicking the toggle button in the playground, the second element flashes into the final position.
I want them both animated at the same time, like crossing each other. Is it possible?
Template:
<div id="app">
<div class="dom" v-for="(_d, _i) in list" :key="_d.id" :style="{ top: _i * 50 + 'px'}">
<span>{{_d.text}}{{_i}}</span>
</div>
<button #click="handle">Toggle</button>
{{list}}
</div>
JS:
new Vue({
el: '#app',
data: {
show: true,
list: [
{
id:1,
text:'First'
},
{
id:2,
text:'Second'
}
]
},
methods:{
handle: function (){
console.log("DEBUG", this.list)
let a = JSON.parse(JSON.stringify(this.list[0]));
let b = JSON.parse(JSON.stringify(this.list[1]))
this.$set(this.list, 0, b);
this.$set(this.list, 1, a);
}
}
});
The only necessary change is to wrap the v-for in a <transition-group>:
<transition-group tag="div" name="list">
<div class="dom" v-for="(_d, _i) in list" :key="_d.id" :style="{ top: _i * 20 + 'px' }">
<span>{{_d.text}}{{_i}}</span>
</div>
</transition-group>
From the docs:
This might seem like magic, but under the hood, Vue is using an animation technique called FLIP to smoothly transition elements from their old position to their new position using transforms
Here's a demo:
new Vue({
el: '#app',
data: () => ({
show: true,
list: [
{
id:1,
text:'First'
},
{
id:2,
text:'Second'
}
]
}),
methods:{
handle: function (){
console.log("DEBUG", this.list)
let a = JSON.parse(JSON.stringify(this.list[0]));
let b = JSON.parse(JSON.stringify(this.list[1]))
this.$set(this.list, 0, b);
this.$set(this.list, 1, a);
}
}
});
.dom{
position: absolute;
transition: all 1s linear;
opacity: 1;
}
button{
margin-top: 50px;
}
#app{
margin-top: 50px;
position: relative;
}
<div id="app">
<transition-group tag="div" name="list">
<div class="dom" v-for="(_d, _i) in list" :key="_d.id" :style="{ top: _i * 20 + 'px' }">
<span>{{_d.text}}{{_i}}</span>
</div>
</transition-group>
<button #click="handle">Toggle</button>
{{list}}
</div>
<script src="https://unpkg.com/vue/dist/vue.js"></script>

How to use transitions on select option using Vue.js

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

How to detect V-if content was changed

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>

Vue Transition Collapsing Snap

I'm curious how others have solved this problem. When I transition elements whose layout affects the page, there's a snap--or layout adjustment--when the transitioned element is removed. This makes sense, but I'm not sure how best to approach it. Here's an example:
https://codepen.io/noynek/pen/yXeRBY
HTML
<div id="demo">
<transition name="fade">
<div v-if="show" style="float:left">
<input placeholder="field 1" />
<input placeholder="field 2" />
</div>
</transition>
<button v-on:click="show = !show">Toggle</button>
</div>
JS
new Vue({
el: '#demo',
data: {
show: true
}
})
CSS
.fade-enter-active, .fade-leave-active {
transition: all 1s
}
.fade-enter, .fade-leave-to /* .fade-leave-active in <2.1.8 */ {
opacity: 0;
transform: translateX(-100px);
}

Categories