看到这个标题,很多同学都会疑惑,为什么swiper中要放iframe呢?事实上,当我遇到这个需求前,我也没想到会有这样的骚操作,swiper中嵌入网站,每次滑动切换一个网站?想想听炫酷的,可做起来就不酷了。
当你开开心心的把iframe放到每一个swiper-slide中后,你会发现页面不能滑动了,你哭了,你绝望了。你打开谷歌,搜解决方案时,你会发现这类相关库的作者遇到这种bug比你还绝望:

而swiper的作者回答的更直接:

然后close了这个issue...
难道就没有办法解决这个问题吗?终于,经过各种百度谷歌翻issue,我找到了三种解决这个问题的方法,正好应对了三种不同的情况,且听我娓娓道来。
最简单的办法:pointer-events
pointer-events是css3新增的属性,它可以让设置这个属性的元素无法侦听任何事件,具体的API可以参考这里pointer-events
我们可以给iframe设置这个属性之后,网站之间就可以来回切换。但是,这种方法有个弊端,正如官方介绍所说,它会是整个元素无法侦听事件,这会网站内的所有交互都无法操作了,除非你得页面是个纯展示性质的静态网站。
所以,如果你想用这种方法解决无法滑动的问题,首先要确保你的网站是纯静态没有任何事件需要触发的。
侦听iframe内的滑动事件
这种方法适合iframe内的网址和外部的网址在同一域下,因为我们需要直接在父级页面监听iframe内的事件:
// 用来计算滑动方向的变量
let startX,startY,endX,endY,distanceX,distanceY;
let iframe = document.querySelector('iframe');
const MOVE_RATE = 2;
iframe.contentWindow.addEventListener('touchstart',function (e) {
startX = e.targetTouches[0].pageX;
startY = e.targetTouches[0].pageY;
});
iframe.contentWindow.addEventListener('touchmove',function (e) {
endX = e.targetTouches[0].pageX;
endY = e.targetTouches[0].pageY;
distanceX = endX - startX;
distanceY = endY - startY;
if(Math.abs(distanceX) > Math.abs(distanceY) && distanceX > (body.clientWidth/MOVE_RATE)){
//slideNext 和 slidePrev)()都是swiper的API
myswiper.slideNext()
}else if(Math.abs(distanceX) > Math.abs(distanceY) && distanceX < (body.clientWidth/MOVE_RATE)){
myswiper.slidePrev()
}else{
console.log('点击未滑动');
}
});
这个方案的思路是先获取iframe内部的window,然后监听window的touchstart和touchmove事件,根据滑动方向和距离判断是左滑或者右滑,调用swiper向前或者向后滑的API即可。
这个方案其实百度一搜就有,如果你有研究过swiper中嵌入iframe话一定见过这个方案,但其实,这个方案有两个弊端:
一是当swiper页面3个以上时,由于touchmove事件是多次触发的,导致多次调用slideNext或者slidePrev方法,用户轻轻滑一下页面,直接会滑到最后一页;
第二个问题是是不能处理跨域的网址,事实上有些需求是需要嵌入一些合作方的网址的,而这些网址不一定是同域的;
第一种情况我的解决办法是,声明两个变量,分别作为向左滑和向右滑的计数器,让touchmove事件仅执行一次,同时,当完成一次滑动后清空计数器,保证下次同方向的滑动:
let left_slide_count = 0, right_slide_count = 0;
const COUNT_LIMIT = 2;
...
...
if(Math.abs(distanceX) > Math.abs(distanceY) && distanceX > (body.clientWidth/MOVE_RATE)){
//slideNext 和 slidePrev)()都是swiper的API
left_slide_count ++;
if( left_slide_count < COUNT_LIMIT ){
myswiper.slideNext()
}
}else if(Math.abs(distanceX) > Math.abs(distanceY) && distanceX < (body.clientWidth/MOVE_RATE)){
right_slide_count ++;
if( right_slide_count < COUNT_LIMIT ){
myswiper.slidePrev()
}
}else{
console.log('点击未滑动');
}
同时,我们还需要在swiper中加一些配置:
let swiper = new Swiepr({
...
...
on: {
//当滑动结束时,清空计数器
slideChangeTransitionEnd: function(){
left_slide_count = 0;
right_slide_count = 0;
},
}
})
至于第二个问题,其实可以用postMessage来解决。
使用postMessage解决嵌入跨域网站的滑动
这种方法其实和上种方法的解决思路大致相同,不过是把滑动的监听放到内部网站中,然后把滑动信息通过postMessage传给父级页面,然后父级页面调用swiepr的API:
let left_slide_count = 0, right_slide_count = 0;
const COUNT_LIMIT = 2;
...
...
if(Math.abs(distanceX) > Math.abs(distanceY) && distanceX > (body.clientWidth/MOVE_RATE)){
//slideNext 和 slidePrev)()都是swiper的API
left_slide_count ++;
if( left_slide_count < COUNT_LIMIT ){
window.parent.postMessage("right-move", "*")
}
}else if(Math.abs(distanceX) > Math.abs(distanceY) && distanceX < (body.clientWidth/MOVE_RATE)){
right_slide_count ++;
if( right_slide_count < COUNT_LIMIT ){
window.parent.postMessage("left-move", "*")
}
}else{
console.log('点击未滑动');
}
父级页面需要监听postMessage事件:
window.postMessage("message", function(e){
if(e.data == "right-move"){
if( left_slide_count < COUNT_LIMIT ){
myswiper.slideNext()
}
}else if(e.data == "left-move"){
if( right_slide_count < COUNT_LIMIT ){
myswiper.slidePrev()
}
}
})
当然,这种方法也要做计数器的处理。此外,这种方法也有两个弊端,第一个是当你滑动一下屏幕时页面可以正常切换,但是手一直放在屏幕上滑不离开的话,屏幕达到某个临界值会在两个页面不停切换,直到你松手;第二个弊端是它需要在嵌入的网站中植入一段js脚本才行,如果你想嵌入纯第三方的网站比如淘宝等,那这种方法是行不通的。
那么有没有一种方法可以直接嵌入第三方网站并支持滑动呢,很抱歉确实没有找到,不多我在翻issue时找到了一个叫iframeTracker的库,它可以监听到iframe中的点击事件(当然,这是模拟出来的,事实上并不能监听到),虽然它是一个jquery库,但是内部源码很简单,可以轻松转成js的,做PC端的朋友同时需要监听到iframe中的点击事件的可以看看这个库,我在这里也简单介绍一下这个库的实现:
在PC端上,我们是可以监听到iframe的mouseover和mouseout事件的,然后,在页面外写一个input表单,当我们鼠标悬浮在iframe上时,让input表单执行focus事件,当我们在iframe上点击时,会触发input的blur事件,同时也会触发window的blur 事件,这时我们监听window的blur事件,就算捕获到iframe的click事件了。
至于监听移动端的touch事件,各位朋友们有没有好主意呢?