起因
我司最近在做一个 H5, 有一个模拟微信对话框的需求,具体需求如下。
- 对话内容固定,但需要一句一句显示
- 对话内容超过一屏后,需要使对话内容上移
- 对话内容结束后,用户可以上下滑动对话框,查看详细对话内容
- 图中灰色头像表示获取的用户头像
示例:请看第二屏
初步设想
- 使对话内容一句一句显示,脑子里立马闪现出 setInterval 定时器。
- 对话内容超过一屏,使对话内容上移,当然是改变父元素的 scrollTop 值啦
- 用户可以上下滑动对话框,就类似于滚动条效果,设置父元素高度并且 overflow:hidden,子元素高度 auto 即可。
- 获取用户头像,这个薛微复杂,留做下一篇文章。
遇到问题
局部滚动效果,以上想法(设置父元素高度并且 overflow:hidden)在 PC 端可以正常滑动,但 在移动端失效 。
这种写法,单独写没有问题,但是 IOS 端出现卡顿现象,可以添加 -webkit-overflow-scrolling:touch; 解决。
但是,我司的 H5 页面使用的 swiper 制作,大概是这个有一些影响,用户滑动屏幕首先触发了 swiper 的事件。(仅做设想,后续做进一步实践)
于是在网上查了几番,有以下几种解决方法
- 用户在解发 touchmove 事件时,改变元素的 transform 值
- 使用 iscroll.js
- 使用 swiper
改变元素的 transform 值
改变元素的 transform 值,需要判断用户的滑动方向。
判断滑动方向时,先了解两个事件
- touchstart : 用户手指按在屏幕上时触发
- touchmove:用户滑动屏幕时触发
了解了这两个事件,我们可以在用户触发 touchstart 事件时,记录手指位置,在 touchmove 记录获取手指最后停留的位置
判断 最后停留位置 – 初始位置 = pageY- startY = 即用户滑动方向
(pageY-startY)为正数时,说明用户向下滑动; 为负数时,说明用户向上滑动。
$(".message-wrapper").on("touchstart", function (e) {startY = e.type === 'touchmove' ? e.targetTouches[0].pageY : e.pageY;
})
$(".message-wrapper").on("touchmove", function (e) {pageY = e.type === 'touchmove' ? e.targetTouches[0].pageY : e.pageY;
})
使用 iscroll.js
网上有很多关于 iscroll 的资料,但是我查了一下官方的 github, 最近的更新在 5 年前,果断不敢用~
使用 swiper
查了一番,swiper 里的 swiper-scrollbar可以完美的实现这一功能,单独写这一功能,在真机测试没有问题。然后移入到我们的项目中。出现以下几个问题
局部滚动后的 slide 元素不显示
分析原因
出现这个问题的原因,是由于我司的 H5 项目也是由 swiper 制作,这意味着每一屏就相当于一个 slide, 当添加用 swiper 完成的局部滚动时,会造成后面父元素的 slide 元素不显示。
解决办法
这涉及到多个 swiper 嵌套使用的问题,具体修改如下:
- 当页面存在多个 swiper,初始化时,尽量避免使用一样的类名,如 .swiper-container, 每个 swiper 有它单独的类名
<div class="swiper-container main-swiper"> // 父元素 swiper
<div class="swiper-wrapper">
<div class="swiper-slide slide1"></div>
<div class="swiper-slide slide2">
<div class="swiper-container message-warp"> // 子元素 swiper
<div class="swiper-wrapper message-wrapper">
<div class="swiper-slide message-slide"></div>
</div>
</div>
</div>
<div class="swiper-slide slide3"></div>
</div>
</div>
//-------------------------------------------------------------swiper 初始化
- 如果类名分开,父元素后续 slide 元素依然无法显示
将子元素的初始化,写在父元素初始化之前, 加载时,优先初始化子元素 swiper
// 初始化子 swiper
var scrollSwiper = new Swiper('.message-warp', {
observer: true,
observeParents: false,
scrollbar: '.swiper-scrollbar',
direction: 'vertical',
slidesPerView: 'auto',
mousewheelControl: true,
freeMode: true,
})
var swiper = new Swiper('.main-swiper', {
direction: 'vertical',
touchRatio: 0.5,
loop: false,
on: {init: function () {swiperAnimate(this);
},
slideChangeTransitionEnd: function (e) {swiperAnimate(this)
}
}
});
- 以上方法都不能使后续 元素显示
swiper 运行时,会先给元素添加 visiblity:hidden; 使元素隐藏,只给当前页的 visiblity 设置为 visible; 而 swiper 中,改元素显示状态的依据就是 swiper-slide-active;
swiper 默认给当前的 slide 添加 swiper-slide-active 类名。当页面中存在 swiper 嵌套时,父元素的当前 slide 会添加该类名,子元素的当前 slide 也会添加该类名。
这样当用户滑出父元素的当前 slide 时父元素的 swiper-slide-active 被移除,而子元素的 swiper-slide-active 类名并没有移除,造成 swiper 混乱,所以父元素后续 slide 的元素会无法显示
解决办法
我的做法是在父元素切换 slide 后,判断页面中 swiper-slide-active 的个数,如果存在一个以上,则说明子元素的类名没有移除。
手动将子元素的 swiper-slide-active 类名移除即可。
暂时还没有想到更好的方法,如果你有更好的方法,欢迎一起讨论。
if ($(".swiper-slide-active").length == 2) {$(".message-slide").removeClass("swiper-slide-active")
}