iSlider移动端原生滑动组件源码解读

iSlider的整体思路是,不考虑边界的情况下,我们实际滑动的时候,其实只有三张图片会出现在我们的视角里,所以首先将滑动的内容外层dom拷贝,然后将这个dom的内容置空:

doc.innerHTML=""
复制代码

然后将当前包含有滑动内容的_prev, _current, _next三个dom节点插入外层dom:this.wrap。滑动到下一张,则之前的_prev被删除,下一张dom被添加进this.wrap;滑动到上一张,则之前的_next被删除,上一张dom被添加进this.wrap。

/* iSlider构造函数里面其实已经写好this.opts
 * 用户在new这个构造函数的时候,如果传入opts参数
 * 则使用for in 遍历用户传参
 * 然后覆盖掉默认的opts
**/
//实例化构造函数
new iSlider({
    //some configurations...
})

//构造函数内部
//关于HTML5 storage的详细介绍,点击。。。
function iSlider(opts){
    this.opts = {
        //some configurations...
    };
    /* 这里的this.SS其实是引用了sessionStoratge
     * 为什么要使用sessionStorage呢?
     * 假设用户点击列表的详情button
     * 进入详情页面后,退回滑动列表
     * 用户其实是想回到刚刚的滑动位置
     * 而当前的窗口(浏览器)未关闭
     * sessionStorage还保存着
     * 所以用session是最理想的选择
    **/
    this.SS = false;
    //ios使用私密模式,会报错
    try {
        this.SS = sessionStorage;
        this.['spt'] = 1;
    } catch(e){
        this.SS = 0;
    }
    //初始化
    this.init();
}
复制代码

iSlider构造函数的原型对象,定义了init在内的大部分方法:

//iSlider的原型对象被覆写了
//我觉得这里其实应该将constructor重新指回iSlider
iSlider.prototype = {
    //这里介绍几个属性:
    _sesiionKey: location.host + location.pathname,
    _tpl: [],
    _prev: null,
    _current: null,
    _next: null,
    //_sesiionKey, _tpl参数在init中被赋值
    init: function(){
    /* _sesiionKey: location.host + location.pathname
     * 用location作为ID,更有辨识度
    **/
      this._sesiionKey = btoa(encodeURIComponent(this._sessionKey+this.wrap.id+this.wrap.className));
      var lastLocateIndex = parseInt(this.SS[this._sessionKey]);
      //index代表当前图片, 是当前图片的索引值
      /* this.index = ...这句话很重要
       * 假设详情页有一个“回到列表”的button
       * 点击button, 页面被刷新一遍
       * init被重新执行, this.index被重新赋值
       * this.SS[_sessionKey]会在init和prev, next函数中被赋值
      **/
      if(this.SS){
          this.index = (this.opts.lastLocate && lastLocateIndex>=0) ? lastLocateIndex : 0;
      }else {
          //...
      }
      //querySelector查找".wrap"
      //然后this.wrap查找到wrap的dom节点
      //拷贝一份dom给this._tpl,避免对dom直接操作
      this._tpl = this.wrap.cloneNode(true);
      //将外层wrap dom里面的滑动内容返回给_tpl
      this._tpl = this.opts.item ? this._tpl.querySelectorAll(this.opts.item) : this._tpl.children;
      
      //...
      if(this.opts.fullScr){
          //如果是全屏
          //这里添加css样式
          //html,body{...}
      }
      //...
      
      //初始化DOM
      this._setHTML();
      
      //事件委托的方式,绑定事件
      this._bindEvt();
    },
    
}
复制代码

这几个值,是在this._setHTML()中赋值

    _prev: null,    // 上一个节点
    _current: null, // 当前节点
    _next: null,    // 下一个节点
复制代码

this._setHTML主要是初始化页面滑动区域的dom节点,这里用到了createDocumentFragment 即dom碎片的方式,优化了性能

iSlider.prototype = {
    //...
    _setHTML: function(){
        //对页面的滑动区域置空
        this.wrap.innerHTML = ""
        //创建DOM 碎片
        var initDOM = document.createDocumentFragment();
        
        //下面对上述三个属性赋值
        if(this.index > 0){
        //如果index>0,则表示当前节点包含了至少两个
            this._prev = this._tpl[this.index-1].cloneNode(true);
            //前移clientWidth//clientHeight大小
            this._prev.style.cssText += this._getTransform('-'+this.scrollDist+'px'); 
            initDom.appendChild(this._prev);
        }else{
            /* 重新置为null
             * 主要是为了后面滑动事件时判断
             * if(this._prev){
                 表示当前页面的上一张存在,
                 则可以往前滑动
             }else{...}
            **/
            this._prev = null
        }
        this._current =this._tpl[this.index].cloneNode(true);

        this._current.style.cssText+=this._getTransform(0);
        
        initDom.appendChild(this._current);
        
        //同理,对_next节点赋值
        if (this.index<this.length-1) {
            this._next=this._tpl[this.index+1].cloneNode(true);
            this._next.style.cssText+=this._getTransform(this.scrollDist+'px');
            initDom.appendChild(this._next)
        }else {
            this._next=null;
        }

        this.wrap.appendChild(initDom);
    }
}
复制代码

然后是this._bindEvt函数,该函数将事件绑定到父节点上。即当前页面是全屏滑动的时候,事件绑定在dom上,否则绑定到this.wrap外层dom上。

/* 这里的"data-stop"属性
 * 我理解为作者的业务代码
 * 即如果target设置了data-stop属性
 * 并且该属性值为"true",就不用阻止默认行为
**/
if (this.opts.fullScr || this.opts.preventMove) {
            handlrElm.addEventListener('touchmove', function (e) {
                e.target.getAttribute('data-stop') !== "true" && e.preventDefault(); 
            }, false);
        }
复制代码

_pageInit()函数主要是为每一个滑动dom添加"play"的class, 以及动画回调。

之后是三个滑动事件, 分别是_touchstart, _touchmove, _touchend.

_touchstart: 这里有几个变量需要梳理, lockSlide: 标记变量. 如果lockSlide为true, 则表示当前不允许滑动, 即退出之后的_touchmove, 这里有个疑问: 每次_touchstart都会将该变量置为false _touchmove里面的以下判断, 应该是没有必要的?

_touchmove: function(){
    /*............................*/
    if(e.touches.length !== 1 || this.lockSlide){ 
       return;
    }
}

复制代码
_touchstart: function(){
    this._touchstartX = e.touches[0].pageX;
    this._touchstartY = e.touched[0].pageY;
    //初始化触摸位置
    this.touchInitPos = this.opts.isVertical ? e.touches[0].pageY:e.touches[0].pageX;
    //为了避免卡帧的情况出现
    //我们需要清除动画效果
    //判断_next, _prev是否存在
    //存在的话, duration置为0
    //this._current.style.cssText = ...
    if(this._next){
        //
    }
    if(this._prev){
        //
    }
}
复制代码

_touchmove事件。下面这段代码比较有意思 do-while循环, 循环条件是 parent不为null , 且parent不是外层dom .wrap。结束循环的其中一种可能是: parent为this.wrap。会出现这种情况, 则说明, 用户在滑动组件区域滑动手指的时候, 滑动的范围仍然是在滑动组件的区域里(因为使用了事件委托, e.target是触摸点). 即e.target是外层.wrap的子节点. 所以能正常的触发后面的滑动效果.

还有一种情况. 就是parent为null的时候, 循环终止. 也就是说此时遍历到了document(document.parentNode值为null), 紧接的if判断, 终止touchmove事件. 这种情况只有一种可能, 就是手指滑到了this.wrap的外面.

_touchmove: function(){
    var parent = e.target;
    do{
        parent = parent.parentNode;
    }while(parent && parent != this.wrap)
    
    if(!parent && e.target != this.wrap){
        return ;
    }
复制代码

滑动的时候,需要判断手指滑动方向, 是否与页面滑动方向一致. gx, gy分别代表一个三角形的x, y边. 根据几何知识, 垂直三角形的底边. 哪边长,则边所对应的角大. gx>gy, 则手指滑动方向往x轴倾斜, 即判定手指为横向滑动.

_touchmove: function(){    
    /*********分割线*************/
    var gx=Math.abs(e.touches[0].pageX - this._touchstartX);
    var gy=Math.abs(e.touches[0].pageY - this._touchstartY);
    if (gx>gy && this.opts.isVertical) { 
        //页面是垂直滑动
        //当前手指在做水平滑动
        this.lockSlide=true;
        return ;
    }else if(gx<gy && !this.opts.isVertical){ 
        //页面是水平滑动
        //当前手指在做垂直滑动
        this.lockSlide=true;
        return ;
    }
}
复制代码

接下来, 就主要是计算偏移量, 如果this.totalDist<0, 那么就露出(不是滑到)下一张, 反之上一张. 这里用到了tranlante3d的属性(启用GPU加速的话).

接着再详细讲一下_loading函数: 由于图片的大小不一样, 导致加载成功所耗费的时间也不一样, 这里的思想是, 不管后面的图片加载得怎么样, 首先需要保证首张(首屏)图片加载成功, 所以当this.src===imgurl[0]的时候, 清除回调(置为null, 同时也释放了部分内存)

for (var i=0; i<imgurls.length; i++) {
        imgs[i]=new Image();
        imgs[i].src=imgurls[i];
        imgs[i].onload=imgs[i].onerror=imgs[i].onabort=function (e) {
            loaded++;
            if (this.src === imgurls[0] && e.type === 'load') {
                clearTimeout(fallback)
                }
                checkloaded();
                this.onload=this.onerror=this.onabort=null;
            }
}
复制代码

[0] iSlider源码

转载于:https://juejin.im/post/5aa2ccb751882555784d8fae

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值