最近的一个活动页面需要做一个可以左右滑动的抽签效果, 故通过用 CSS 的 transform 属性和 JS 结合来模拟可以无限滚动的效果.
先上效果:
demo 地址:
实现过程
1. 结构与样式
结构: 卡片分前后两排, 每列插入 10 个 div 结点, 以便做左右位移效果.
样式: 设置每一列都恰好好在中间位置(或中间位置附近), 如下所示.
a. 前排 (cardFrond) 相对于视口的初始位置(left:-255.5%;):
b. 后排 (backFrond) 相对于视口的初始位置(left:-228.3%;):
2. 无限滚动原理
由于这里的停止位置是固定的, 前排永远是当前卡片相对于视口居中, 后排永远是两个卡片相对于视口居中, 且每个卡片是一样的, 所以当卡片列表向前或向右移动到一个目标位置时, 都将列表重置为初始位置继续滚动. 如下图以前排卡片为例:
所以当滚动停止后会统一将列表样式设置为 transform: translateX(0). 而对于用户这一操作是无感知的, 认为已经滑动到了新的位置.
3. 滑动过程实现
a. 目标位移与帧位移
为了做出滑动后到停留位置的缓动效果, 所以当用户左右滑动屏幕时, 会记录滑动距离, 计算出卡片该到的目标位移位置, 目标位移位置是有规则的, 因为这里有 10 张卡片均分宽度, 位置必须是 (100%/10) 的整数倍, 例如 - 40%,-30%,......40%, 这样才能保证目标位置与初始位置相重合.
目标位移代码片段
- onDocumentMouseUp : function(e){
- // 如果是点击事件 不设置移动
- if (!this.fingerTouch)
- return;
- this.moveDirect = this.lon> 0 ? 1 : -1;
- this.transNum = this.lon/10 + this.moveDirect;
- this.lon = Math.round(this.transNum) * 10;
- this.fingerTouch = false;
- }
记录了目标位移后, 每一帧会以一定的帧位移不断靠近目标位移, 使其在手指离开屏幕时仍有慢慢滑动到目标位置的缓动效果. 此时需要判断当前位置是否大于 40% 或者小于 - 40%, 若超过这个极限值需要重设目标位移及帧位移, 使其在极限值内.
- animate: function(){
- this.prePos += (this.lon - this.prePos) * 0.1;
- if (this.prePos> 40) {
- this.lon = this.lon - 40;
- this.prePos = this.prePos - 40;
- }else if (this.prePos <-40) {
- this.lon = this.lon + 40;
- this.prePos = this.prePos + 40;
- }
- // 判断是否到达了目标位置
- if (Math.abs(this.prePos - this.lon) < 0.01 && Math.abs(this.lon)> 0.01 && (!this.fingerTouch))
- {
- this.ani_move = false;
- this.prePos = 0;
- this.frondCard.style = "transform: translateX("+ this.prePos +"%)";
- this.backCard.style = "transform: translateX("+ this.prePos +"%)";
- }else{
- this.frondCard.style = "transform: translateX("+ this.prePos +"%)";
- this.backCard.style = "transform: translateX("+ (-this.prePos) +"%)";
- requestAnimationFrame(this.animate.bind(this));
- }
- },
b. 连续滑动判断
当在上次滑动动画还未播放结束时用户又进行了第二次滑动时, 需要执行一下操作:
1). 判断滑动时机处于上次滑动手指已离开屏幕但动画还未结束, 此时需要记录两个 flag, 一个是 ani_move, 记录动画是否仍在进行, fingerTouch 记录手指是否停留屏幕.
2). 判断第二次滑动是否与第一次不同方向, 若不同向需重置上次帧位移为 0. 以免上次帧位移太大影响移动方向.
1)与 2)代码片段:
- if( this.ani_move && this.fingerTouch == false) {
- // 判断是否不同向
- if (((e.clientX - prex)> 0 ? 1: -1) == -this.moveDirect ) {
- this.lon = 0;
- this.prePos = 0;
- this.moveDirect = -this.moveDirect;
- }
- }
3). 取消第二次滑动时的动画播放和位移重置
- // 若是上次动画未结束不需要再次启动动画和重置目标位移
- if( this.ani_move && this.fingerTouch == false) {
- }
- else {
- this.lon = 0;
- cardAnimate.animate();
- }
写在最后
目前这个滑动效果只能针对卡片相同, 停留位置固定的情况, 因为需要做到位置重合. 使用 CSS transform 来做无限滚动的效果, 可以避免改变 dom 结点带来的页面重新布局.
下图是 Chrome cpu6 倍减速调试效果, 没有触发 layout,FPS 基本维持在 60 左右.
代码地址:
https://github.com/kiroroyoyo/cardTransform
来源: https://www.qcloud.com/developer/article/1372667