I've been working with Flickity in my Vue 3 app, and it works fine when I use a hard-coded HTML carousel with static cells. I need to add cells programmatically at runtime, however, and I can't get the Flickity API to work correctly.
I'm trying to follow the append()
example, but I get the error:
flickity.js?1385:72 Bad element for Flickity: .carousel
in my inspector during runtime. I tried to follow the solution here and here, but neither have been able to run successfully. It looks like it's due to TypeScript errors on the Flickity lib. I also installed @types/flickity
, fyi.
What can I do to fix my append
logic below?
<template>
<div class="row">
<div class="col d-block m-auto payment-option">
<flickity ref="carousel" :options="flickityOptions">
</flickity>
</div>
</div>
</template>
<script lang="ts">
import {defineComponent} from "vue";
//import Flickity from 'vue-flickity/src/flickity.vue';
import Flickity from 'flickity'
export default defineComponent({
name: "PageName",
components: {
Flickity
},
data() {
return {
flickityOptions: {
initialIndex: 3,
prevNextButtons: false,
pageDots: true,
wrapAround: true
}
};
},
methods: {
createBankAccountCarousel(flkty: Flickity) {
flkty.append(this.makeFlickityCell())
},
makeFlickityCell() {
const cell = document.createElement('div');
cell.className = 'carousel-cell'
cell.textContent = "Hi"
return cell
}
},
mounted() {
let flkty = new Flickity(this.$refs.carousel)
this.createBankAccountCarousel(flkty)
}
});
</script>
It looks like you were possibly trying vue-flickity
in Vue 3, but that component was built for Vue 2.
You can create your own Flickity
component in Vue 3:
Create Flickity.vue
with the following template and script, which applies a template ref on the root element, and contains a slot to receive .carousel-cell
elements:
<template>
<!-- 👇 template ref -->
<div ref="root" class="flickity">
<slot />
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue'
export default defineComponent({
setup() {
const root = ref<HTMLElement | null>(null) // reference to template ref named "root"
return {
root,
}
}
})
</script>
Declare an options
prop, which we'll pass to the Flickity
constructor later.
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
props: {
options: Object,
}
})
</script>
In the component's mounted
hook, instantiate Flickity
with the "root"
template ref and the options
prop; and in unmounted
, destroy the Flickity
instance:
<script lang="ts">
import { defineComponent, onMounted, onUnmounted } from 'vue'
import Flickity from 'flickity'
export default defineComponent({
setup(props) {
let flickity: typeof Flickity | null = null
onMounted(() => flickity = new Flickity(root.value as HTMLElement, props.options))
onUnmounted(() => flickity?.destroy())
}
})
</script>
Add a method named "append"
to allow appending new elements to the carousel:
<script lang="ts">
import { defineComponent } from 'vue'
import Flickity from 'flickity'
export default defineComponent({
setup() {
let flickity: typeof Flickity | null = null
return {
append(element: HTMLElement) {
flickity?.append(element)
flickity?.select(-1)
}
}
}
})
</script>
Create src/flickity.d.ts
with the following type declarations (if using VS Code, you'll have to restart the IDE for these typings to be indexed):
declare module 'flickity' {
const Flickity: {
new (el: string | HTMLElement, options?: Record<string, unknown>): this
append(element: HTMLElement)
destroy()
select(id: string | number)
}
export = Flickity
}
Add the following <style>
block, which pulls in the flickity
default styles, and styles the .carousel-cell
elements that would be received in the slot:
<style scoped>
@import '~flickity/dist/flickity.css';
.flickity .carousel {
background: #EEE;
margin-bottom: 40px;
}
/* use ::v-deep to get at slot elements */
.flickity::v-deep .carousel-cell {
height: 200px;
width: 25%;
margin: 0 10px;
background: #6C6;
display: flex;
align-items: center;
justify-content: center;
border-radius: 8px;
}
</style>
<template>
<div class="app">
<flickity ref="flickity" :options="flickityOptions">
<div class="carousel-cell">1</div>
<div class="carousel-cell">2</div>
<div class="carousel-cell">3</div>
</flickity>
<div class="actions">
<button @click="addElement">Append element</button>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import Flickity from './components/Flickity.vue'
export default defineComponent({
name: 'App',
components: {
Flickity
},
data() {
return {
flickityOptions: {
pageDots: true,
wrapAround: true,
}
}
},
methods: {
addElement() {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(this.$refs.flickity as any).append(this.makeFlickityCell())
},
makeFlickityCell() {
const cell = document.createElement('div')
cell.className = 'carousel-cell'
cell.textContent = 'Hi'
return cell
}
}
})
</script>
<style scoped>
.app {
display: flex;
flex-direction: column;
justify-content: space-around;
height: 50vh;
}
</style>