Closed titouancreach closed 6 years ago
Sorry I don't understand chinese. The solution is not to use loop ?
Because, if the loop option is enabled, the swiper will copy two dom nodes at the beginning and end, but the event will not be copied, so any event bound is invalid. In addition, the loop option will cause a lot of unpredictable problems, which is the swiper It is recommended to avoid the use; In addition, the final solution should be:
<swiper :options="swiperOption" @click.native="slideClicked(swiper.activeIndex.slide.link)">
<swiper-slide v-for="(slide, index) in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]">
Slide {{ index }}
</swiper-slide>
</swiper>
<swiper :options="swiperOption" @click.native="slideClicked(swiper.activeIndex)">
<swiper-slide v-for="(slide, index) in data">
{{ data.img}}
</swiper-slide>
</swiper>
在配置中添加
loop : true,//循环
loopAdditionalSlides : this.data.length,//动态编辑数据的长度
在methods方法中
slideClicked(index){
console.log(index)
let dataIndex = index%this.data.length
console.log(dataIndex)
console.log(this.data[dataIndex])
this.$router.push(this.data[dataIndex].url)
},
It seems like a hacky way of getting it to work. And it introduces other issues as the "slideClicked" function will be triggered for any item clicked within the swiper.
An example is if you use navigation: { nextEl, prevEl }.
I'm aware that this can be prevented by stopping event propagation, but that would require extra tweaking of the plugin.
loop functionality is duplicating only DOM nodes, meaning that your duplicated slides for the loop don't have their JS code appended as well, only the rendered result.
I spent 2 days trying to figure this out, so by using the original Swiper's methods and events, I came to a semi-infinite-loop feature:
add the following to your swiper's config
loop: true,
loopedSlides: 1,
on: {
slideChange: function () {
let lastVisibleItem = this.realIndex + this.params.slidesPerView
let slidesLength = this.slides.length - 2
let lastVisibleIndex = this.realIndex + this.params.slidesPerView
// if swiper reaches the end of displayed items, goToNext redirects swiper to the start
if (lastVisibleItem > slidesLength) {
this.slideTo(1)
}
// if swiper wants to go before the first item, then forward swiper to the last item
if (lastVisibleIndex >= this.slides.length) {
this.slideTo((slidesLength - this.params.slidesPerView) + 1)
}
}
}
This is how I ended up handling the issue, however still a bit hacky :)
<template>
<div id="slider">
<swiper
:options="swiperOption"
ref="swiper"
@click.native="sliderClicked"
>
<swiper-slide
v-for="(item, index) in items"
:key="`${index}`"
>
Slide {{index}}
</swiper-slide>
<div class="swiper-pagination" slot="pagination"></div>
<div class="swiper-button-prev" slot="button-prev"></div>
<div class="swiper-button-next" slot="button-next"></div>
</swiper>
</div>
</template>
<script>
export default {
data() {
return {
swiperOption: {
effect: 'slide',
loop: true,
autoplay: {
delay: 5000
},
pagination: {
el: '.swiper-pagination',
clickable: true
},
navigation: {
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev',
}
}
}
},
computed: {
swiper() {
return this.$refs.swiper.swiper
}
},
methods: {
sliderClicked: function (event) {
if(
!event.target.classList.contains('swiper-pagination-bullet') &&
!event.target.classList.contains('swiper-button-next') &&
!event.target.classList.contains('swiper-button-prev')
) {
let item = this.items[this.swiper.realIndex];
console.log('clicked item', item);
//this.$router.push();
}
}
}
}
</script>
This is how I ended up handling the issue, however still a bit hacky :)
<template> <div id="slider"> <swiper :options="swiperOption" ref="swiper" @click.native="sliderClicked" > <swiper-slide v-for="(item, index) in items" :key="`${index}`" > Slide {{index}} </swiper-slide> <div class="swiper-pagination" slot="pagination"></div> <div class="swiper-button-prev" slot="button-prev"></div> <div class="swiper-button-next" slot="button-next"></div> </swiper> </div> </template> <script> export default { data() { return { swiperOption: { effect: 'slide', loop: true, autoplay: { delay: 5000 }, pagination: { el: '.swiper-pagination', clickable: true }, navigation: { nextEl: '.swiper-button-next', prevEl: '.swiper-button-prev', } } } }, computed: { swiper() { return this.$refs.swiper.swiper } }, methods: { sliderClicked: function (event) { if( !event.target.classList.contains('swiper-pagination-bullet') && !event.target.classList.contains('swiper-button-next') && !event.target.classList.contains('swiper-button-prev') ) { let item = this.items[this.swiper.realIndex]; console.log('clicked item', item); //this.$router.push(); } } } } </script>
This is how I ended up handling the issue, however still a bit hacky :)
<template> <div id="slider"> <swiper :options="swiperOption" ref="swiper" @click.native="sliderClicked" > <swiper-slide v-for="(item, index) in items" :key="`${index}`" > Slide {{index}} </swiper-slide> <div class="swiper-pagination" slot="pagination"></div> <div class="swiper-button-prev" slot="button-prev"></div> <div class="swiper-button-next" slot="button-next"></div> </swiper> </div> </template> <script> export default { data() { return { swiperOption: { effect: 'slide', loop: true, autoplay: { delay: 5000 }, pagination: { el: '.swiper-pagination', clickable: true }, navigation: { nextEl: '.swiper-button-next', prevEl: '.swiper-button-prev', } } } }, computed: { swiper() { return this.$refs.swiper.swiper } }, methods: { sliderClicked: function (event) { if( !event.target.classList.contains('swiper-pagination-bullet') && !event.target.classList.contains('swiper-button-next') && !event.target.classList.contains('swiper-button-prev') ) { let item = this.items[this.swiper.realIndex]; console.log('clicked item', item); //this.$router.push(); } } } } </script>
you are so so so so so so so Smart !!! thank you, bsthomsen!
If anyone is reading this and it's not too late, I'd strongly suggest you don't use this plugin. It's got so many bugs and issues that it will cause you way more problems than just building your own solution.
Having said that, if you are trying to solve this solution, I think I have a better way, kind of taking the idea of jQuery's .on()
method.
<template>
<div id="slider">
<swiper
ref="swiper"
v-swiper:swiper="swiperOption"
@click.native="sliderClicked"
>
<swiper-slide
v-for="(item, index) in items"
:key="`${index}`"
>
<span class="go-home">
Go back to start of slideshow
</span>
<span
class="some-thing"
:data-index="index"
>
Slide {{ index }}
</span>
</swiper-slide>
</swiper>
</div>
</template>
<script>
import { delegate } from "~/utils/tools.js"
export default {
data() {
return {
swiperOption: {
effect: "slide",
loop: true,
autoplay: {
delay: 5000
}
}
}
},
mounted() {
// Below we monitor for clicks on name because Awesome Swiper sucks!
// Have to store this so we can access it in click handlers
const $this = this
// Go back to start
delegate(this.$el, "click", ".go-home", function(event) {
$this.goToSlide(0)
})
// Scroll down link
delegate(this.$el, "click", ".some-thing", function(event) {
console.log("This was clicked on:", this)
console.log("Slide index:", parseInt(this.dataset.index))
})
},
methods: {
goToSlide(index) {
this.swiper.slideToLoop(index)
}
}
}
</script>
And then in my ~/utils/tools.js
file I have this function, which I took from here.
export function delegate(el, evt, sel, handler) {
// Shim Matches
if (!Element.prototype.matches) {
Element.prototype.matches =
Element.prototype.matchesSelector ||
Element.prototype.webkitMatchesSelector ||
Element.prototype.mozMatchesSelector ||
Element.prototype.msMatchesSelector ||
Element.prototype.oMatchesSelector ||
function(s) {
var matches = (
this.document || this.ownerDocument
).querySelectorAll(s),
i = matches.length
while (--i >= 0 && matches.item(i) !== this) {}
return i > -1
}
}
// Loop through everything of type event until it matches the selector
el.addEventListener(evt, function(event) {
var t = event.target
while (t && t !== this) {
if (t.matches(sel)) {
handler.call(t, event)
}
t = t.parentNode
}
})
}
I used a similar but less verbose method of resolving the problem.
<swiper
ref="swiper"
v-swiper:swiper="swiperOption"
@click="sliderClicked"
>
and
<swiper-slide
v-for="(item, index) in items"
:key="`${index}`" v-bind-data-index="index"
>
and
methods: {
sliderClicked(e) {
/* You may need to change e.target to something more specific, or bind the data attribute at the various layers that the click can occur at */
let index = e.target.getAttribute('data-index');
/* Now that you have the index / object that was clicked, handle the click here instead of at the slide level */
}
@drewbaker vue-awesome-swiper is just a thin wrapper over swiper.js library which actually has design issue described here, so you should give your "sucks" credits to them. Btw, if you have better alternatives for swiper/slider lib that could cover so many use cases as swiper.js - I would gladly look into it as I guess thousands of other people who starred this and original library
I recently try this hacky tricks to make click event propagated to all slides after a full loop or loop is set to true , I hope it will help to someone else, like me who's struggling this problem.
<template>
<Swiper
ref="swiper"
class="swiper"
:options="swiperOptions"
@ready="onSwiperReady"
>
<swiper-slide v-for="(slide, key) in bannerSlider" :key="key">
<div class="swiper-img">
<img
:src="slide.background_img"
:data-banner="JSON.stringify(slide)"
/>
</div>
</swiper-slide></Swiper></template>`
`<template>
<Swiper
ref="swiper"
class="swiper"
:options="swiperOptions"
@ready="onSwiperReady"
><swiper-slide v-for="(slide, key) in bannerSlider" :key="key">
<div class="swiper-img">
<img
:src="slide.background_img"
:data-banner="JSON.stringify(slide)"
/>
</div>
</swiper-slide>
</Swiper>
</template>
<script>export default {
data(){
return {
swiperOptions: {
autoplay: true,
slidesPerView: 3,
slidesPerGroup: 1,
disableOnInteraction: true,
loop: true,
navigation: {
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev',
},
},
bannerSlider:[
{
"banner_name":"What is Lorem Ipsum?",
"banner_explanation":"Lorem Ipsum is simply dummy text of the printing and typesetting industry.",
"background_img":"image.png"
},
{
"banner_name":"What is Lorem Ipsum?",
"banner_explanation":"Lorem Ipsum is simply dummy text of the printing and typesetting industry.",
"background_img":"image.png"
},
{
"banner_name":"What is Lorem Ipsum?",
"banner_explanation":"Lorem Ipsum is simply dummy text of the printing and typesetting industry.",
"background_img":"image.png"
},
]
}
},
methods:{
onSwiperReady(){
//manually add click event to every slider
this.$refs.swiper.$swiper.$el[0].children[0].onclick = this.onClickPerSlide
},
onClickPerSlide(event){
console.log(JSON.parse(event.target.dataset.banner))
}
}
}
</script>
@drewbaker vue-awesome-swiper is just a thin wrapper over swiper.js library which actually has design issue described here, so you should give your "sucks" credits to them. Btw, if you have better alternatives for swiper/slider lib that could cover so many use cases as swiper.js - I would gladly look into it as I guess thousands of other people who starred this and original library
Yes, swiper clones HTML nodes and vue doesn't control duplicated elements. But, this is the exactly vue-awesome-swiper issue, because that wrapper should solve vue-adaptation problems. For example, swiper/vue handle duplications https://github.com/nolimits4web/swiper/blob/master/src/vue/loop.js#L32. Why I don't use swiper/vue? because it works only with vue3, so vue-awesome-swiper still needed package.
Also, a have a temporary solution for fixing problems with duplicated slides with missed vue handlers, reactivity, etc:
mounted() {
swiper.once('loopFix', () => {
swiper.loopDestroy();
swiper.loopCreate();
});
swiper.on('slideChangeTransitionEnd', () => {
if (swiper.slides[swiper.activeIndex].classList.contains('swiper-slide-duplicate')) {
swiper.slideToLoop(swiper.realIndex, 0);
swiper.autoplay.start();
}
});
}
Vue.js version and component version:
Vuejs: 2.5.2 Component: V4.0.7 (for the repro) and ^3.0.6 (for my project)
Reproduction Link
https://jsfiddle.net/Lvev4md5/
Steps to reproduce
Do a a list of slide with the infinite loop mode one. When doing the whole loop, the click event is not propagated. In the repro link:
The bug also appears when rotating from the right to left.
What is Expected?
Message should be displayed in any case, even after a full loop.
What is actually happening?
Click event not propagated