I'm using a v-carousel with some images and I want the slides between the itens to be slower/smoother.
What would be an elegant way to do it?
<v-carousel interval="5000" :height="window.height - 48" hide-controls hide-delimiters>
<v-carousel-item :src="congresso">
</v-carousel-item>
<v-carousel-item :src="stf">
</v-carousel-item>
<v-carousel-item :src="tse">
</v-carousel-item>
</v-carousel>
I did that with the help of Vuetify’s transition helper function that lets you to define your own transition.
First if you did not add variables.scss file into your project you must do that with the help of this link from official documentation. Then you can add your own custom transition to that file. Here I used the original fade-transition of vuetify and only changed the transition time:
/* the variables.scss file */
.new-transition {
&-leave-active {
position: absolute;
}
&-enter-active, &-leave, &-leave-to {
transition: 100ms; /* here you can define your desired time for transition */
}
&-enter, &-leave-to {
opacity: 0
}
}
After that in your .vue file you can use createSimpleTransition of vuetify and register that in "mounted" hook as follow:
<template>
<section>
<v-carousel>
<v-carousel-item
v-for="(item,i) in items"
:key="i"
:src="item.src"
reverse-transition="new-transition"
transition="new-transition"
></v-carousel-item>
</v-carousel>
</section>
</template>
<script>
import { createSimpleTransition } from 'vuetify/lib/components/transitions/createTransition';
export default {
name: 'Carousel',
data () {
return {
items: [
{
src: 'https://cdn.vuetifyjs.com/images/carousel/squirrel.jpg',
},
{
src: 'https://cdn.vuetifyjs.com/images/carousel/sky.jpg',
},
{
src: 'https://cdn.vuetifyjs.com/images/carousel/bird.jpg',
},
{
src: 'https://cdn.vuetifyjs.com/images/carousel/planet.jpg',
},
],
}
},
mounted() {
const newTransition = createSimpleTransition('new-transition');
this.$once("hook:components", () => {
newTransition
})
}
}
</script>
Now you can use new-transition that were defined in your carousel and the time that you has set in variables.scss will affect in your carousel.
<b-carousel
id="carousel-1"
v-model="slide"
:interval="1000"
controls
indicators
background="#ababab"
img-width="1024"
img-height="480"
style="text-shadow: 1px 1px 2px #333;"
#sliding-start="onSlideStart"
#sliding-end="onSlideEnd"
>
I have just tried this and it works
interval is written like this :interval="nubmer" it has : so try to put that :
Add this to your variables.scss file:
.v-window {
&-x-transition,
&-x-reverse-transition,
&-y-transition,
&-y-reverse-transition {
&-enter-active,
&-leave-active {
transition: 1.1s cubic-bezier(0.25, 0.8, 0.5, 1) !important;
}
}
}
change the first param (here 1.1s) in the transition variable to set the overall speed.
*From https://github.com/vuetifyjs/vuetify/issues/7476
Related
I am using nuxt and bootstrap to build a custom hover dropdown menu for navigation. The issue I have is that my navigation submenu NuxtLinks are refreshing the entire page instead of smoothly changing the app content in my Nuxt block. The nav bar is dynamically generated in the default.vue layout and uses a b-dropdown-hover component where the NuxtLink is wrapped around that content. Why does the page do a full refresh for those links/anchors but my b-navbar-brand image does a smooth transition? I apologize, I am very new to Nuxt. This video # ~1:35:00 demonstrates what I'm trying to do.
components/BDropdownHoverRight.vue
<template>
<nuxt-link :to="aTo">
<div class="ddr-top" #mouseover="onOver1($event.target)" #mouseleave="onLeave1($event.target)">
<b-dropdown ref="dropdown_ddr" :text="cText" class="m-md-2 ddr">
<slot></slot>
</b-dropdown>
</div>
</nuxt-link>
</template>
<script>
export default {
name: 'BDropdownHoverRight',
props: {
cText: {
type: String,
},
aTo: {
type: String,
},
},
methods: {
onOver1(t) {
if (t.nodeName === 'DIV') {
console.log(t)
console.log(t.nodeName)
let num_child_nodes = 0
try {
if (t.querySelectorAll(':scope > ul')[0].getElementsByTagName('div').length >= 0) {
num_child_nodes = t.querySelectorAll(':scope > ul')[0].getElementsByTagName('div').length
}
} catch (e) {
if (t.querySelectorAll(':scope > div > ul')[0].getElementsByTagName('div').length >= 0) {
num_child_nodes = t.querySelectorAll(':scope > div > ul')[0].getElementsByTagName('div').length
}
}
if (num_child_nodes > 0) {
try {
t.querySelectorAll(':scope > div > ul')[0].style.display = 'block'
} catch (e) {
try {
t.querySelectorAll(':scope > ul')[0].style.display = 'block'
} catch (e) {}
}
}
}
},
onLeave1(t) {
try {
t.querySelectorAll(':scope > div > ul')[0].style.display = 'none'
} catch (e) {
try {
t.querySelectorAll(':scope > ul')[0].style.display = 'none'
} catch (e) {}
}
},
},
}
</script>
layouts/default.vue
<template>
<div>
<b-navbar id="top-nav-bar" toggleable="lg" type="light" sticky>
<b-navbar-brand to="/">
<Rabbit id="tl-logo" />
</b-navbar-brand>
<b-navbar-toggle target="nav-collapse"></b-navbar-toggle>
<b-collapse id="nav-collapse" is-nav>
<b-navbar-nav>
<template v-for="dir in navtop_dd">
<b-dropdown-hover
:key="dir.id"
:c-text="dir.name"
:a-to="dir.hasOwnProperty('ato') ? dir.ato : '/nolink'"
>
<template v-if="'submenus' in dir && dir.submenus.length > 0">
<template v-for="dir1 in dir.submenus">
<b-dropdown-hover-right
:key="dir1.id"
:c-text="dir1.name"
:a-to="dir1.hasOwnProperty('ato') ? dir1.ato : '/nolink'"
>
<template v-if="'submenus' in dir1 && dir1.submenus.length > 0">
<template v-for="dir2 in dir1.submenus">
<b-dropdown-hover-right
:key="dir2.id"
:c-text="dir2.name"
:a-to="dir2.hasOwnProperty('ato') ? dir2.ato : '/nolink'"
>
</b-dropdown-hover-right>
</template>
</template>
</b-dropdown-hover-right>
</template>
</template>
</b-dropdown-hover>
</template>
</b-navbar-nav>
<!-- Right aligned nav items -->
<b-navbar-nav class="ml-auto">
<b-nav-form>
<b-form-input size="sm" class="mr-sm-2" placeholder="Search"></b-form-input>
<b-button size="sm" class="my-2 my-sm-0" type="submit">Search</b-button>
</b-nav-form>
<b-nav-item-dropdown right>
<!-- Using 'button-content' slot -->
<template #button-content>
<b-img src="../assets/imgs/account-circle.svg" style="height: 35px"> </b-img>
<!-- <em>User</em> -->
</template>
<b-dropdown-item href="#">Profile</b-dropdown-item>
<b-dropdown-item href="#">Sign Out</b-dropdown-item>
</b-nav-item-dropdown>
</b-navbar-nav>
</b-collapse>
</b-navbar>
<b-container id="app-content">
<Nuxt />
</b-container>
<div id="footer">
<div style="height: 100%; padding: 5px">© 2021</div>
</div>
</div>
</template>
<script>
import BDropdownHover from '#/components/BDropdownHover'
import BDropdownHoverRight from '#/components/BDropdownHoverRight'
export default {
components: {
BDropdownHover,
BDropdownHoverRight,
},
data() {
return {
navtop_dd: [
{
id: 1,
name: 'Transactions',
ato: '/transactions',
submenus: [
{
id: '1a',
name: 'Sales Orders',
ato: '/transactions/salesorders',
submenus: [
{
id: '1b',
name: 'New',
},
{
id: '2b',
name: 'List',
},
],
},
{
id: '2a',
name: 'Item Fulfillments',
ato: '/transactions/itemfulfillments',
submenus: [
{
id: '1b',
name: 'New',
},
{
id: '2b',
name: 'List',
},
],
},
],
},
{
id: 2,
name: 'Inventory',
},
{
id: 3,
name: 'Reports',
},
{
id: 4,
name: 'Setup',
},
{
id: 5,
name: 'Support',
},
],
}
},
mounted() {
var x = document.querySelectorAll('.b-dropdown.navtop-dd')
for (var i = 0; i < x.length; i++) {
if (x[i].querySelectorAll(':scope > ul')[0].getElementsByTagName('div').length == 0) {
var btn = x[i].querySelectorAll(':scope > .btn')[0]
btn.classList += ' no-content-after'
}
}
var x = document.querySelectorAll('.b-dropdown.ddr')
for (var i = 0; i < x.length; i++) {
if (x[i].querySelectorAll(':scope > ul')[0].getElementsByTagName('div').length == 0) {
var btn = x[i].querySelectorAll(':scope > .btn')[0]
btn.classList += ' no-content-after'
}
}
},
}
</script>
<style>
#top-nav-bar {
border-bottom: 1px solid green;
}
#tl-logo {
height: 40px;
margin: 5px;
}
#footer {
height: 40px;
color: black;
border-top: 1px solid green;
margin: auto;
text-align: center;
display: flex;
align-items: center;
justify-content: space-around;
}
.navtop-dd button {
background: none !important;
color: #6c757d !important;
border: none !important;
}
#app-content {
margin: 20px auto;
}
.ddr > button::after {
display: inline-block;
margin-left: 0.555em;
right: 0px;
content: "";
border-top: 0.25em solid transparent;
border-right: 0.3em solid transparent;
border-bottom: 0.25em solid transparent;
border-left: 0.35em solid;
vertical-align: 0.075em;
}
.b-dropdown {
width: 100%;
}
.ddr > button {
text-align: left;
}
.no-content-after::after {
content: none !important;
}
.ddr > ul {
top: -1.2rem;
left: calc(100% - 0.5rem);
}
.dropdown-menu {
min-width: 0 !important;
}
.dropdown-item {
color: #6C757D;
}
.ddr-top:hover {
background-color: #e4ffda;
}
a:hover {
text-decoration: none !important;
}
</style>
There is a LOT of irrelevant code here. I took the time to format it properly. Please make the effort yourself next time (to format and input interesting bits only).
Also, the answer on how to fix the issue was actually given in the video itself. The video is talking about the differences between a and nuxt-link tags.
Which relates to this part of Bootstrap's Vue documentation where you can see that
[to] prop: Denotes the target route of the link. When clicked, the value of the to prop will be passed to router.push() internally, so the value can be either a string or a Location descriptor object
So, you should use something like this
<template>
<b-dropdown>
<template #button-content>
Custom <strong>Content</strong> with <em>HTML</em> via Slot
</template>
<b-dropdown-item to="/test">Go to test page via Vue-router</b-dropdown-item>
</b-dropdown>
</template>
I also saw that your code is rather different from the video. You should not use querySelector, you don't have to import Nuxt components neither and you have several ESlint warning/errors.
I do recommend trying to focus on a single part of learning and not mixing all of them. It's fine to want to go a bit further, but be careful of not being lost with too much abstraction while you do learn a lot of new concepts (Vue/Nuxt).
On a side note, if you want to continue learning Nuxt, you can check this: https://masteringnuxt.com/ (created by a Nuxt ambassador and other well known people in the Vue ecosystem)
Have fun creating projects with Nuxt!
I created this codepen, which is a simple flip card and it works fine in codepen, but when I add this project in my vue project created with cli, everything works fine; upon clicking a card, it shows back of the card, but it doesn't apply that transition so user can visually see that it is rotating. It rotates very fast, sounds like transition is not effecting.
This is the template code
<div v-for="card in cards" #click="toggleCard(card)" :key="card.id">
<transition name="flip">
<div
v-bind:key="card.flipped"
v-html="card.flipped ? card.back : card.front"
></div>
</transition>
</div>
and the script code
export default {
name: "FlipCard",
data() {
return {
cards: [
// cards here
],
};
},
methods: {
toggleCard: function (card) {
const isFlipped = card.flipped;
this.cards.forEach((strategy) => {
strategy.flipped = false;
});
isFlipped === true ? (card.flipped = false) : (card.flipped = true);
},
},
};
and css code:
.flip-enter-active {
transition: all 2s ease;
}
.flip-leave-active {
display: none;
}
.flip-enter,
.flip-leave {
transform: rotateY(180deg) !important;
opacity: 0;
}
can anyone help why in vue cli project the transition is so fast or maybe not applying?
Thank you in advance
The codepen you provided uses Vue 2. Your question is tagged Vue 3, so I assume you are using Vue 3.
Vue 3 made changes to transition class names - https://v3-migration.vuejs.org/breaking-changes/transition.html#_2-x-syntax.
-enter and -leave are now -enter-from and -leave-from.
I tried everything to hide the Navbar each time a user click on a link.
Hope for help. Thanks everyone. I use vue.js
U got any ideas?
HTML
<div class="container-button">
<button class="button" #click="Navbar = !Navbar">Menu</button>
</div>
<div>
<MainMenu v-if="Navbar" class="menu-Display"> </MainMenu>
</div>
JS
import Navbar-Menu from "#*/MainMenu.vue"
export default {
name: "Header",
components: {
MainMenu
},
data() {
return {
Navbar: false
}
}
}
CSS
.menu-Display {
top: 50px;
right: 10px;
bottom: 30px;
width:100px;
}
I am creating a slider in vuejs and am using vue2-dropzone plugin for file uploads where each slide (slide-template.vue) has a vue2-dropzone component.
When app loads, image files are manually added in each vue2-dropzone (manuallyAddFile plugins API) queried from image API (hosted on heroku)
The issue is when I delete the first slide, calling the parent's (slider.vue) method removeSlideFn (passed down to child as prop) from child (slide-template.vue) component first slide is deleted but not entirely the dropzone images of the first slides are not destroyed and remains in the DOM, instead images of slide2, (the next slide) are deleted from the DOM (Pls try it once on codesandbox demo to actually know what I am mean). This does not happen when I delete slide2 or slide3 but only on slide1.
CodeSandBox Demo
App.vue
<template>
<div id="app">
<img width="15%" src="./assets/logo.png">
<slider />
</div>
</template>
<script>
import slider from "./components/slider";
export default {
name: "App",
components: {
slider
}
};
</script>
components\slider.vue (parent)
<template>
<div>
<hooper ref="carousel" :style="hooperStyle" :settings="hooperSettings">
<slide :key="idx" :index="idx" v-for="(slideItem, idx) in slideList">
<slide-template
:slideItem="slideItem"
:slideIDX="idx"
:removeSlideFn="removeCurrSlide" />
</slide>
<hooper-navigation slot="hooper-addons"></hooper-navigation>
<hooper-pagination slot="hooper-addons"></hooper-pagination>
</hooper>
<div class="buttons has-addons is-centered is-inline-block">
<button class="button is-info" #click="slidePrev">PREV</button>
<button class="button is-info" #click="slideNext">NEXT</button>
</div>
</div>
</template>
<script>
import {
Hooper,
Slide,
Pagination as HooperPagination,
Navigation as HooperNavigation
} from "hooper";
import "hooper/dist/hooper.css";
import slideTemplate from "./slide-template.vue";
import { slideShowsRef } from "./utils.js";
export default {
data() {
return {
sliderRef: "SlideShow 1",
slideList: [],
hooperSettings: {
autoPlay: false,
centerMode: true,
progress: true
},
hooperStyle: {
height: "265px"
}
};
},
methods: {
slidePrev() {
this.$refs.carousel.slidePrev();
},
slideNext() {
this.$refs.carousel.slideNext();
},
//Removes slider identified by IDX
removeCurrSlide(idx) {
this.slideList.splice(idx, 1);
},
// Fetch data from firebase
getSliderData() {
let that = this;
let mySliderRef = slideShowsRef.child(this.sliderRef);
mySliderRef.once("value", snap => {
if (snap.val()) {
this.slideList = [];
snap.forEach(childSnapshot => {
that.slideList.push(childSnapshot.val());
});
}
});
}
},
watch: {
getSlider: {
handler: "getSliderData",
immediate: true
}
},
components: {
slideTemplate,
Hooper,
Slide,
HooperPagination,
HooperNavigation
}
};
</script>
components/slide-template.vue (child, with vue2-dropzone)
<template>
<div class="slide-wrapper">
<slideTitle :heading="slideItem.heading" />
<a class="button delete remove-curr-slide" #click="deleteCurrSlide(slideIDX)" ></a>
<vue2Dropzone
#vdropzone-file-added="fileWasAdded"
#vdropzone-thumbnail="thumbnail"
#vdropzone-mounted="manuallyAddFiles(slideItem.zones)"
:destroyDropzone="false"
:include-styling="false"
:ref="`dropZone${ slideIDX }`"
:id="`customDropZone${ slideIDX }`"
:options="dropzoneOptions">
</vue2Dropzone>
</div>
</template>
<script>
import slideTitle from "./slide-title.vue";
import vue2Dropzone from "#dkjain/vue2-dropzone";
import { generate_ObjURLfromImageStream, asyncForEach } from "./utils.js";
export default {
props: ["slideIDX", "slideItem", "removeSlideFn"],
data() {
return {
dropzoneOptions: {
url: "https://vuejs-slider-node-lokijs-api.herokuapp.com/imageUpload",
thumbnailWidth: 150,
autoProcessQueue: false,
maxFiles: 1,
maxFilesize: 2,
addRemoveLinks: true,
previewTemplate: this.template()
}
};
},
components: {
slideTitle,
vue2Dropzone
},
methods: {
template: function() {
return `<div class="dz-preview dz-file-preview">
<div class="dz-image">
<img data-dz-thumbnail/>
</div>
<div class="dz-details">
<!-- <div class="dz-size"><span data-dz-size></span></div> -->
<!-- <div class="dz-filename"><span data-dz-name></span></div> -->
</div>
<div class="dz-progress"><span class="dz-upload" data-dz-uploadprogress></span></div>
<div class="dz-error-message"><span data-dz-errormessage></span></div>
<div class="dz-success-mark"><i class="fa fa-check"></i></div>
<div class="dz-error-mark"><i class="fa fa-close"></i></div>
</div>`;
},
thumbnail: function(file, dataUrl) {
var j, len, ref, thumbnailElement;
if (file.previewElement) {
file.previewElement.classList.remove("dz-file-preview");
ref = file.previewElement.querySelectorAll("[data-dz-thumbnail]");
for (j = 0, len = ref.length; j < len; j++) {
thumbnailElement = ref[j];
thumbnailElement.alt = file.name;
}
thumbnailElement.src = dataUrl;
return setTimeout(
(function(_this) {
return function() {
return file.previewElement.classList.add("dz-image-preview");
};
})(this),
1
);
}
},
// Drag & Drop Events
async manuallyAddFiles(zoneData) {
if (zoneData) {
let dropZone = `dropZone${this.slideIDX}`;
asyncForEach(zoneData, async fileInfo => {
var mockFile = {
size: fileInfo.size,
name: fileInfo.originalName || fileInfo.name,
type: fileInfo.type,
id: fileInfo.id,
childZoneId: fileInfo.childZoneId
};
let url = `https://vuejs-slider-node-lokijs-api.herokuapp.com/images/${
fileInfo.id
}`;
let objURL = await generate_ObjURLfromImageStream(url);
this.$refs[dropZone].manuallyAddFile(mockFile, objURL);
});
}
},
fileWasAdded(file) {
console.log("Successfully Loaded Files from Server");
},
deleteCurrSlide(idx) {
this.removeSlideFn(idx);
}
}
};
</script>
<style lang="scss">
.slide-wrapper {
position: relative;
}
[id^="customDropZone"] {
background-color: orange;
font-family: "Arial", sans-serif;
letter-spacing: 0.2px;
/* color: #777; */
transition: background-color 0.2s linear;
// height: 200px;
padding: 40px;
}
[id^="customDropZone"] .dz-preview {
width: 160px;
display: inline-block;
}
[id^="customDropZone"] .dz-preview .dz-image {
width: 80px;
height: 80px;
margin-left: 40px;
margin-bottom: 10px;
}
[id^="customDropZone"] .dz-preview .dz-image > div {
width: inherit;
height: inherit;
// border-radius: 50%;
background-size: contain;
}
[id^="customDropZone"] .dz-preview .dz-image > img {
width: 100%;
}
[id^="customDropZone"] .dz-preview .dz-details {
color: white;
transition: opacity 0.2s linear;
text-align: center;
}
[id^="customDropZone"] .dz-success-mark,
.dz-error-mark {
display: none;
}
.dz-size {
border: 2px solid blue;
}
#previews {
border: 2px solid red;
min-height: 50px;
z-index: 9999;
}
.button.delete.remove-curr-slide {
padding: 12px;
margin-top: 5px;
margin-left: 5px;
position: absolute;
right: 150px;
background-color: red;
}
</style>
slide-title.vue (not that important)
<template>
<h2 contenteditable #blur="save"> {{ heading }} </h2>
</template>
<script>
export default {
props: ["heading"],
methods: {
save() {
this.$emit("onTitleUpdate", event.target.innerText.trim());
}
}
};
</script>
utils.js (utility)
export async function generate_ObjURLfromImageStream(url) {
return await fetch(url)
.then(response => {
return response.body;
})
.then(rs => {
const reader = rs.getReader();
return new ReadableStream({
async start(controller) {
while (true) {
const { done, value } = await reader.read();
// When no more data needs to be consumed, break the reading
if (done) {
break;
}
// Enqueue the next data chunk into our target stream
controller.enqueue(value);
}
// Close the stream
controller.close();
reader.releaseLock();
}
});
})
// Create a new response out of the stream
.then(rs => new Response(rs))
// Create an object URL for the response
.then(response => {
return response.blob();
})
.then(blob => {
// generate a objectURL (blob:url/<uuid> list)
return URL.createObjectURL(blob);
})
.catch(console.error);
}
Technically this is how the app works, slider.vue loads & fetches data from database (firebase) and stores in a data array slideList, loops over the slideList & passes each slideData (prop slideItem) to vue-dropzone component (in slide-template.vue), when dropzone mounts it fires the manuallyAddFiles(slideItem.zones) on the #vdropzone-mounted custom event.
The async manuallyAddFiles() fetches image from an API (hosted on heroku), creates (generate_ObjURLfromImageStream(url)) a unique blob URL for the image (blob:/) and then calls plugins API dropZone.manuallyAddFile() to load the image into the corresponding dropzone.
To delete the current slide, child's deleteCurrSlide() calls parent's (slider.vue) removeSlideFn (passed as prop) method with the idx of current slide. The removeSlideFn use splice to remove the item at the corresponding array idx this.slideList.splice(idx, 1).
The problem is when I delete the first slide, first slide is deleted but not entirely, the dropzone images of the first slides are not destroyed and still remains in the DOM, instead the images of slide2, (the next slide) are deleted from the DOM.
CodeSandBox Demo
I am not sure what is causing the issue, may it's due to something in the vue's reactivity system OR Vue's Array reactivity caveat that is causing this.
Can anybody pls help me understand & resolve this and if possible point out the reason to the root of the problem.
Your help is much appreciated.
Thanks,
I think you probably missunderstand what is going on:
In VueJS there is a caching method which allow the reusing of existing component generated: - Each of your object are considered equals when rendered (at a DOM level).
So VueJS remove the last line because it is probably ask the least calculation and then recalcul the expected state. There are many side case to this (sometime, the local state is not recalculated). To avoir this: As recommended in the documentation, use :key to trace the id of your object. From the documentation:
When Vue is updating a list of elements rendered with v-for, by default it uses an “in-place patch” strategy. If the order of the data items has changed, instead of moving the DOM elements to match the order of the items, Vue will patch each element in-place and make sure it reflects what should be rendered at that particular index. This is similar to the behavior of track-by="$index" in Vue 1.x.
This default mode is efficient, but only suitable when your list render output does not rely on child component state or temporary DOM state (e.g. form input values).
To give Vue a hint so that it can track each node’s identity, and thus reuse and reorder existing elements, you need to provide a unique key attribute for each item. An ideal value for key would be the unique id of each item. This special attribute is a rough equivalent to track-by in 1.x, but it works like an attribute, so you need to use v-bind to bind it to dynamic values...
new Vue({
el: "#app",
data: {
counterrow: 1,
rows: [],
},
methods: {
addrow: function() {
this.counterrow += 1;
this.rows.push({
id: this.counterrow,
model: ""
});
},
removerows: function(index) {
this.rows.splice(index, 1);
},
},
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app">
<table>
<tr>
<td><input type="text" name="test1" /></td>
<td><button class="btn" #click="addrow">add row</button></td>
</tr>
<tr v-for="(row,index) in rows" :key="row.id">
<td><input type="text" name="test2" v-model="row.model" /></td>
<td><button class="btn" #click="removerows(index)">remove </button></td>
</tr>
</table>
</div>
In this code:
I corrected the fact counterrow was never incremented
I added a :key
The documentation of :key
What did you mean by
The problem is when I delete the first slide, first slide is deleted but not entirely, the dropzone images of the first slides are not destroyed and still remains in the DOM, instead the images of slide2, (the next slide) are deleted from the DOM.
From what I see, the elements are no longer in the DOM
When an element has a computed style, the style changes are not applied if the element is going through a leave transition:
new Vue({
el: "#app",
data: {
selected: 1,
items: [{
color: 'red'
},
{
color: 'blue'
},
{
color: 'green'
},
],
tweened: {
height: 50,
},
},
computed: {
divStyles() {
return {
height: this.tweened.height + 'px',
background: this.displayed.color,
'margin-left': this.selected * 100 + 'px',
width: '100px',
}
},
displayed() {
return this.items[this.selected - 1]
}
},
watch: {
selected(newVal) {
function animate() {
if (TWEEN.update()) {
requestAnimationFrame(animate)
}
}
new TWEEN.Tween(this.tweened)
.to({
height: newVal * 50
}, 2000)
.easing(TWEEN.Easing.Quadratic.InOut)
.start()
animate()
}
},
methods: {
toggle: function(todo) {
todo.done = !todo.done
}
}
})
.colored-div {
opacity: 1;
position: absolute;
}
.switcher-leave-to,
.switcher-enter {
opacity: 0;
}
.switcher-enter-to,
.switcher-leave {
opacity: 1;
}
.switcher-leave-active,
.switcher-enter-active {
transition: opacity 5s linear;
}
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.21/dist/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/tween.js/16.3.5/Tween.min.js"></script>
<div id="app">
<button #click="selected--" :disabled="selected <= 1">
Previous
</button>
<button #click="selected++" :disabled="selected >= 3">
Next
</button>
<span>Selected: {{selected}}</span>
<transition name="switcher">
<div v-for="(item, index) in items" v-if="index + 1 === selected" :key="index" :style="divStyles" class="colored-div" />
</transition>
</div>
https://jsfiddle.net/64syzru5/12/
I would expect the leaving element to continue resizing as it fades out, but it doesn't. What can be done to have the computed styles applied to the leaving element during the leave-active transition?
Since you're using CSS for the transitions, Javascript doesn't execute at each intermediate step. That's a good thing for performance, but it means that the computed properties aren't recomputed. As best as I can tell, though, you're just trying to animate the height. That's easily accomplished in pure CSS. Use a before-leave hook to set it to an initial value via an inline style or CSS variable, and then remove that property in the after-leave hook.
More to the point, though, it looks like your application might be more suitable for a transition-group instead of a simple transition.