原始需求来源于一个项目的某个功能,要求实现3D图片轮播效果,而已有的组件大多是普通的2D图片轮播,于是重新造了一个轮子,实现了一个既支持2D,又支持3D的滑动、轮播组件。
刚一开始肯定是无法直接实现3D滑动组件的,所以将功能拆分,如下步骤
以前看到的轮播组件大多是用数组来作为底层数据结构,最近,整合受简书上的某一篇文章启发,觉得链表方式挺不错的,于是先动手造一个JS模拟的链表(一些现有的资源大多不符合预期,于是重新造轮)
双向循环链表原理比较简单,这里不再赘述
实现了一个双向链表,支持循环和非循环,参考 https://github.com/dailc/jsfoundation-datastruct
在H5时代,2D滑动组件大多是通过translate3d 和transition实现的,采用X轴位移来实现滑动,所以虽说最终的效果只是2D得平面滑动,其实也是基于3D变换的。
假设屏幕宽为W,滑动组件中有(A,B,C,D,E )5个item,每一个item的宽度都占满屏幕。采用循环模式(也就是E往右滑动可以到A)
A._next = B;
B._next = C;
...
E._next = A;
A._prev = E;
B._prev = A;
...
E._prev = D;
如上,所以所有元素首尾相连,构成循环
初始化组件时,各自的位置如下(只考虑X轴(→),Y(↓)和Z(垂直屏幕)都是0)。 E(-2W),D(-W),A(0),B(W),C(2W)。 一般只会允许同时显示最多5个元素,其它位置的元素暂时隐藏
通过监听滑动组件的touch,来判断当前组件应该的X轴唯一translateX
touchmove过程中,显示的元素进行相应的translateX位移(向左滑为负,向右滑为正)。比如A的位置为(0+translateX),其它元素类似
touchend时,判断当前的translateX,如果大于item宽度的50%,则判断移到下一个位置,如果大于item宽度的150%,则移到下下个元素。同理,如果小于-50%,则移到到上各元素,小于-150%,则移动到上上个元素
移动到上一个元素,各自item的translateX 减去W即可,移动到下一个元素,加上W。并且移动过程中设置 transition(cubic-bezier(0.165, 0.84, 0.44, 1))
由于是通过双向循环链表构建,所以每次移动只需要通过_next就可找到下一个元素,通过_prev即可找到上一个元素,并且自动循环
以下为上述中向右滑动一位,的过程图述
下图是将item的宽度设为屏幕50%后的2D滑动效果
上述过程中实现了2D滑动组件,那么带3D组件的组件该如何实现呢?毕竟最初的需求就是3D效果。
其实上述2D滑动组件就已经用到了3D变化了,只不过我们只用了X轴变换。所以看起来仍然是2D效果,所以接下来我们就是需要利用到另外两个轴的变换。
最终我们需要实现如图所示的3D滑动效果
我们将屏幕左上角看成原点。那么
对比已经完成的2D效果和最终3D效果的图,我们发现有以下区别
实际实现过程中,和上述分析的过程一致,在2D变换的基础上,针对Y轴进行了旋转计算,针对Z轴进行了纵深计算。
基本过程是:
找到一个合适的Y轴旋转计算公式(比如那左侧的Item调试,找到一个合适的点)
找到合适的适应于不同屏幕分辨率的Z轴纵深以及视点位置,(比如在屏幕320时,找到一个合适的Z轴纵深和perspective,然后再屏幕768时再找到一个,进行线性插值即可)
详细过程不再赘述
初步做出来的效果并不能很好的兼容各种分辨率屏幕以及功能较为简陋。
所以接下来的主要工作是:
基本上述工作完成后,一个崭新的3D滑动组件类库就完成了,它主要有如下功能;
* reset 重置,这个可以重写更换options
* prev 上一个item
* next 下一个item
* moveTo 移动到某一个指定item,会寻找最短路径
* bindItemChangedHandler 绑定item切换的监听回调
* bindItemTapHandler 绑定item的tap监听
* unBindItemTapHandler 解绑item的tap监听,会取消所有的item的tap事件以及item内部的tap
* tap 绑定item内部某元素的tap事件,因为无法用click监听,所以单独提供了监听函数
原文在我个人博客上面