最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 手把手教你使用JavaScript实现一个slider无限滚动插件

    正文概述 掘金(赵_叶紫)   2021-05-24   521

    1. 滚动插件的使用缘由

    在项目开发中,图片滚动的应用场景特别多。有很多设计依靠滚动来实现。那么此时我们可以选择不同的滚动插件达到相同的效果。但是,外部插件却是是比较优秀,但是同时因为功能性代码太多,根据我们 组的开发情况,我们基本杜绝了外部插件,除非特殊情况必须使用。
    而我编写的这个插件,可以实现目前设计中的各种滚动场景,支持自定义滚动距离,只要你是以下结构,你就可以直接将该滚动插件套用。当然,不是说一定要url>li 结构,而是只要是这种列表形式的dom结构都可以。

    手把手教你使用JavaScript实现一个slider无限滚动插件

    PC

    手把手教你使用JavaScript实现一个slider无限滚动插件

    Mobile

    手把手教你使用JavaScript实现一个slider无限滚动插件

    2. 需求驱动开发

    我的页面并不是只有一个滚动,而是有很多个滚动效果。因此,并不能将代码写死在具体某一个dom(或者class名称)上。毕竟,在不同的结构中,class名称不可能一模一样。也不能因为滚动,就添加额外的class名称来控制。最简单的就是按照需要,提取公共的参数。

    • parent: 滚动列表的父容器,
    • children: 具体滚动列表集合,
    • scrollStep:每次滑动的宽度,
    • currentIndex:当前滚动显示的索引,
    • childCls: child列表的class名称,
    • isInfiniteScroll:是否需要支持无限滚动,
    • paginationMethods:该插件根据需求支持3钟分页按钮,该方法可以自定义需要哪些,不局一个分页,
    • paginationStep:每一页显示的个数(总共有4个card,默认4页,值设置为2,那么页数为2页),
    • customInitDom:当进行无限循环,每页有多个card, 当最后一个只有1个card,需要将剩下的card使用占位符占位,该值表示使用什么节点进行占位,默认为li

    tip: 我们利用CSS transform 来实现移动。

    3. 基本结构

    下面呈现了该组件的基本结构,其实就是一个 slider 组件类,然后new 了一个组件出来。 我们还在组件内部,进行初始化了一系列的私有变量,每一个变量已近进行了详细注释。看到这里,可能对变量不是很清楚使用,这个没关系,我们后续每隔方法中会使用到。 但是注意 prototype上的 originListDoms 变量,它的作用是:当界面进行大小拖拽时,界面处于PC/Mobile的样式之间来回切换,如果mobile和pc一页显示的数量不一致,例如上面示例图中的情况,那么就需要存储原有的dom,这个参数就是用于存储改值的。

    // 组件
    function Slider(parent, children, scrollStep, currentIndex, childCls, isInfiniteScroll, paginationMethods, paginationStep, customInitDom) {
     // dom 相关参数初始化
      paginationStep = paginationStep || 1; // 默认每页1个card
      var parent = parent;
      var childCount = Math.ceil(children.length / paginationStep); // 多少个card
      var scrollStep = scrollStep; // 每次滑动的距离
      var currentIndex = currentIndex; // 当前选中的index(分页显示使用或者 正常滑动的索引)
      var infiniteCurrentIndex = currentIndex; // 无限滚动的索引(无限滚动时,分页的索引使用currentIndex)
    
     // 位置相关参数初始化
      var startPosition, // 滑动开始的位置
      endPosition, // 滑动结束位置
      deltaX, // 横向滑动距离
      deltaY, // 纵向滑动距离
      isTouchStartFirst = 1, // 是否第一次touch滚动
      isScroll; // isScroll 为0时,表示纵向滑动,1为横向滑动
      var isScrollProgress = false; // 是否在滑动中,用于计算滑动时不能点击card
    
     // 如果滑动元素有a标签,滑动中禁止点击
      var hrefAs = parent.find('a'); // 计算滑动中,将card link禁用
      var isPc = window.iSPc();
     
      this.initMobileOrPcSlider = function() {
     
      }
    }
    Slider.prototype.originListDoms = {};
    
    
    //实例化Slider
    var slider = new Slider(gallery, children, scrollStep, 0, childCls, isInfiniteScroll, paginationMethods, paginationStep, customInitDom);
    
    slider.initMobileOrPcSlider();
    

    4. 组件初始化

    下面进行了伪代码的编写,我们首先进行了事件 dom等一系列的初始化,为什么呢?为了当自适应的时候,pc/mobile样式切换时进行,重置之前所有的参数,方便后续初始化。

    为什么需要将 手机滚动事件、pc滚动事件也一起注册,同样是为了应付PC 调整尺寸大小时,为了平板尺寸大小时,能够进行滑动。

    this.initMobileOrPcSlider = function() {
    
      // 清除所有的事件,dom初始化等
      gallery.siblings('.gallery-pagination').remove();
      gallery.siblings('.gallery-pagination-circle').remove();
      gallery.css('marginLeft', '0px');
      gallery.css('transform', ' translate3d(0px, 0px, 0px)');
      // 2. 先卸载移动事件,避免resize时,重复注册
      gallery.off('touchstart');
      gallery.off('mousedown');
    
    
    
      // 是如果无限滚动处,进行dom处理
      isInfiniteScroll && infiniteScrollDomInit();
    
      // 注册动画滚动结束事件 
      initTransitionend();
      
      // 注册手机滑动效果
      registerMobileScroll();
     
    
     // 注册PC 滑动效果
     registerPCScroll();
    
    // 动态添加小圆点
    if (paginationMethods) {
     //初始化自定义的小圆点
    getCustomPagination(paginationMethods);
    } else {
     // 根据pc mobile 初始化小圆点或者分页按钮
    
    isPc ? initPCGalleryPagination() : initMobileGalleryPagination();}
    
    }
    preventHrefLink();
    

    处理card上的a标签,滑动不能点击

      var preventHrefLink = function() {
        hrefAs.on('click', function(e) {
          if (isScrollProgress) {
            e.preventDefault();
            return false;
          }
        });
      };
    

    好的,那么接下来,我们就开始按照上面的伪代码,丰满每一个小的步骤。

    4.1 无限循环处理

    无限滚动,实际利用了一个视觉的障眼法。按照以下步骤实现:

    • 将第一张,拷贝到最后一个位置,最后一个张内容,拷贝到第一个位置。例如下面的卡片。
    • dom准备好后,将滚动区域向前移动一个card距离(每次滚动的的间距)
    • 注册滚动结束时的事件。滚动可以向前滚动,可以向后滚动。如果向前滚动到最前面的一张(card3备份,索引为0),那么将其索引设置为3(card3原图)。并在滚动结束时滚动到card3原图。因为滚动的时候使用了动画,那么在滚动到card3原图时,不使用动画,直接跳转,视觉上和card3备份图内容一致,感觉不到任何差异,就完成了无缝切换的效果。当然,滚动到最后一张图也是类似操作,当最后一张card1复制 滚动结束时,将索引设置为Card1,并消除动画,移动到card1原图,实现无缝切换。

    手把手教你使用JavaScript实现一个slider无限滚动插件
    到此,无限滚动处理完成,相关代码如下:

     /**
       * 无限循环gallery, 初始化时,将第一个添加到最后,将最后一个添加到第一个
       */
      var infiniteScrollDomInit = function() {
        
        var children = parent.find(childCls);
    
        // 获取第一页元素和最后一页元素
        var preToEndDom = children.slice(0, paginationStep);
        var endToPreDom = children.slice(paginationStep * (childCount - 1), paginationStep * (childCount - 1) + 2);
    
        // endToPreDom 如果不够一页内容,则使用li填充
        var endPageCount = endToPreDom.length;
        if (endPageCount < paginationStep) {
    
          // 最后一页数量只有一条,但是每一页需要显示paginationStep,则现在父组件末尾添加一些空的占位符
          for (var i = endPageCount; i < paginationStep; i++ ) {
            customInitDom = customInitDom || '<li></li>';
            parent.append($(customInitDom));
          }
          endToPreDom = parent.find(childCls).slice(paginationStep * (childCount - 1), paginationStep * (childCount - 1) + 2);
        }
        
        // 添加到最前面和最后面
        parent.prepend($(endToPreDom.clone()));
        parent.append($(preToEndDom.clone()));
        parent.css('marginLeft', -(scrollStep) + getUnit());
    
        // 更新a标签(添加的dom也需要追加)
        hrefAs = parent.find('a');
      }
    
    
    
      /**
       * 当动画滚动结束后,将isScrollProgress设置为false,表示滚动结束
       * - 当滚动到最前面或者最后面,初始化index索引,并将其滚动到与其内容相同的card上
       */
      var initTransitionend = function() {
        parent.on('transitionend', function() {
    
    
          // 移动完成,将表示设置为false
          isScrollProgress = false;
    
          // 如果滑动到最最后面,索引修改为 第一个
          if (infiniteCurrentIndex == childCount) {
            infiniteCurrentIndex = 0;
            move(infiniteCurrentIndex, 0);
          }
      
          // 如果滑动到最前面,索引修改为 最后一个
          if (infiniteCurrentIndex < 0) {
            infiniteCurrentIndex = childCount - 1;
            move(infiniteCurrentIndex, 0);
          }
        })
      }
    

    4.2 给slider注册滑动事件

    现在,我们需要给Slider注册上滚动事件,让我们手动滑动时,能够进行滚动。当然,如果你是希望自动播放,那么你可以通过setInterval等相关的操作实现。
    我们上面在定义参数时,isScrollPrgress就是表示,是否在滚动中。滚动中就不进行第二次滚动触发。不管是PC/Mobile都是一样的。
    我们还需要注意一点:滚动时,如果滚动的角度小(例如是纵向滑动,就不应该滚动),那么避免滚动。

    • Mobile 滑动效果,通过 touchstart, touchmove, touched来实现。
         // -------手机滑动效果 start
        parent.on('touchstart', function(e) {
          // 如果在移动中,不再进行下一次移动
          if (isScrollProgress) {
            return;
          }
          var touch = e.originalEvent.targetTouches[0];
          startPosition = {
              x: touch.clientX,
              y: touch.clientY
          }
          isTouchStartFirst = 1;
    
          parent.on('touchmove', function(e) {
            isScrollProgress = true;
            var touch = e.targetTouches[0];
            endPosition = {
                x: touch.clientX,
                y: touch.clientY
            };
            deltaX = endPosition.x - startPosition.x;
            deltaY = endPosition.y - startPosition.y;
            //  只有刚开始的touchstart,才去判断滑动的方向
            if(isTouchStartFirst === 1){
                isScroll = (Math.abs(deltaX)  * 1.3- Math.abs(deltaY)) > 0 ? 1 : 0;
            }
            isTouchStartFirst++;
            //  isScrolling为0时,表示纵向滑动,1为横向滑动
            if (isScroll === 1) {
              e.preventDefault();
              if (deltaX !== 0 && isInfiniteScroll) {
                mouseMoveTransation(deltaX);
              }
            }
          });
          parent.on('touchend', function(){
            if ((Math.abs(deltaY) > 10 && Math.abs(deltaX) < 10) || isScroll === 0) {
                return;
            }
            if(deltaX < 0) {
              movePre();
            } else if(deltaX > 0) {
              moveNext();
            }
    
            parent.off('touchmove');
            parent.off('touchend');
          });
        });
        // --------Mobile滑动效果 end
    
    • PC 滑动效果,通过 mousedown, mousemove, mouseup来实现。
        parent.on('mousedown', function (ev) {
          // 如果在移动中,不再进行下一次移动
          if (isScrollProgress) {
            return;
          }
          ev.preventDefault();
          ev.stopPropagation();
          ev.cancelable = false;
          startPosition = {
              x: ev.pageX
          }
          $("body").on('mousemove', function(e) {
            isScrollProgress = true;
              endPosition = {
                  x: e.pageX,
              };
              deltaX = endPosition.x - startPosition.x;
              if (deltaX !== 0 && isInfiniteScroll) {
                mouseMoveTransation(deltaX);
              }
          });
    
          $("body").on('mouseup', function() {
            if(deltaX < 0) {
              movePre();
            } else if(deltaX > 0) {
              moveNext();
            }
    
            $("body").off('mousemove');
            $("body").off('mouseup');
          });
        });
    

    4.3 根据拖拽移动

    加入我们开始滑动,但是鼠标左右拖拽,那么此时将card随着鼠标的位置进行移动,这样会显得我们的slider比较活跃。当然,该操作是在 mousemove/touchmove时触发的。

     var mouseMoveTransation =  function(mouseMoveWidth) {
        mouseMoveWidth = isPc ? ( mouseMoveWidth / parent.width() * 100) : mouseMoveWidth / 100;
        var transtationWidth = tranlateX + mouseMoveWidth;
        parent.css('transform', 'translate3d(' + transtationWidth + getUnit()  + ', 0px, 0px)');
        parent.css('transitionDuration', '0s');
      }
    

    4.4 移动处理

    上面的准备操作依据完成了,movePremoveNext还是一个空壳。 那么下面开始直接实现移动效果。

    每次移动后,进行了分页刷新 reloadPagination,这里先知道就可以了,分页的方法放在最后。

    /**
       * 计算滚动距离
       * @param{*}current 当前索引
       */
    vargetScrollWidth = function(current) {
    returnscrollStep * current;
      }
    
    vargetUnit = function() {
    // var unit = isPc ? '%' : '%';
    return'%';
      }
    
    /**
       * 移动一个图片
       * @param{*}current 当前选中页面
       * @param{*}transitionDuration 滑动消费时间
       */
    varmove = function(current, transitionDuration) {
    tranlateX = -getScrollWidth(current);
    parent.css('transform', 'translate3d(' + tranlateX + getUnit()  + ', 0px, 0px)');
    varnewTransitionDuration = (transitionDuration === undefined || transitionDuration === null) ? 0.6 : transitionDuration;
    parent.css('transitionDuration', (parseFloat(newTransitionDuration) * paginationStep) + 's');
    reloadPagination();
      };
    

    上面的方法,是移动一个图片,通过css的属性实现的。对应无限滚动和普通滚动都是一致的。那么接下来我们就处理movePremoveNext方法。

    /**
       * 滑动方式左移动
       */
      var movePre = function() {
        isInfiniteScroll ? infiniteMovePre() : finiteMovePre();
      };
    
      /**
       * 滑动方式右移动
       */
      var moveNext = function() {
        isInfiniteScroll ? infiniteMoveNext() : finiteMoveNext();
      } 
    

    普通滑动事件处理

      /**
       * 向左边移动
       */
      var finiteMovePre = function() {
        if (currentIndex >= childCount - 1) {
          isScrollProgress = false;
          return;
        }
        currentIndex++;
        move(currentIndex);
      }
      
      /**
       * 向右边移动
       */
      var finiteMoveNext = function() {
        if (currentIndex <= 0) {
          isScrollProgress = false;
            return;
        }
        currentIndex--;
        move(currentIndex);
      }
    

    无限滚动滑动事件处理

    /** 
       * 无限循环向左移动
       * curentIndex 用于显示pagination,因此保持更新
      */
      var infiniteMovePre = function() {
    
        // 处理pagination 的index,逻辑保持不变
        if (currentIndex >= childCount - 1) {
          currentIndex = 0;
        } else {
          currentIndex++;
        }
    
        infiniteCurrentIndex++;
        move(infiniteCurrentIndex);
      }
      
      /**
       * 无限循环向右边移动
       * curentIndex 用于显示pagination,因此保持更新
       */
      var infiniteMoveNext = function() {
        if (currentIndex <= 0) {
          currentIndex = childCount - 1;
        } else {
          currentIndex--;
        }
    
        infiniteCurrentIndex--;
        move(infiniteCurrentIndex);
      }
    

    按钮方式点击分页

      /**
       * 通过按钮左移动
       */
      var clickPreMove = function() {
        if (!isScrollProgress) {
          isScrollProgress = true;
          movePre();
        }
      };
      /**
       * 通过按钮右移动
       */
      var clickNextMove = function() {
        if (!isScrollProgress) {
          isScrollProgress = true;
          moveNext();
        }
      }  
    

    4.5 分页处理

    这里,我选择了2中分页方式,一种小圆点方式,一种是按钮方式。小圆点方式可以用于PC,也可以用于Mobile。而按钮建议用于PC.

    小圆点分页

       var initMobileGalleryPagination = function() {
        var pagination = '<div class="gallery-pagination-circle">${items}</div>', items = '';
        for(var i = 0; i < childCount; i++) {
          let spanDom = i === 0 
            ? '<span class="select-span" indexValue="' + i + '" ></span>' 
            : '<span indexValue="' + i + '"></span>';
          items += spanDom;
        }
        pagination = pagination.replace('${items}', items);
        parent.after(pagination);
        
        // pagination 注册点击事件
        var paginationDom = parent.next()[0];
        if (paginationDom) {
          $(paginationDom).on('click', 'span', function(e) {
            var index = $(e.target).attr('indexValue');
            currentIndex = index;
            infiniteCurrentIndex = index;
            move(index);
          })
        }
      }
    

    按钮分页

      var initPreAndNextPagination = function() {
        
        var prePagination = $('<span class="pre-pagination-btn"></span>');
        var nextPagination = $('<span class="next-pagination-btn"></span>');
    
        prePagination.on('click', clickNextMove);
        nextPagination.on('click', clickPreMove);
    
        // 在父级的父级身上添加按钮
        var wrapper = parent.parent();
        wrapper.parent().css('position', 'relative');
    
        wrapper.siblings('.pre-pagination-btn').remove()
        wrapper.siblings('.next-pagination-btn').remove();
    
        wrapper.after(nextPagination);
        wrapper.after(prePagination);
      }
    

    **通过名字,自定义分页 **

     /**
       * 自定义分页:分页有很多种,当想自定义不同类型的分页,可以传递方法名称
       * 
       * @param {*} paginationArr 自定义方法名称字符串 [initMobileGalleryPagination, initPCGalleryPagination, initPreAndNextPagination]
       */
      var getCustomPagination = function(paginationArr) {
        var originPagiantions = {
          initMobileGalleryPagination: initMobileGalleryPagination,
          initPCGalleryPagination: initPCGalleryPagination,
          initPreAndNextPagination: initPreAndNextPagination,
        };
        paginationArr.map(function(method) {
          originPagiantions[method] && originPagiantions[method]();
        });
      }
    

    不同分页方式分页reload 该方法在move函数中使用到的,每次通过说移动结束,需要手动更新分页显示。

    var reloadPagination = function() {
        var paginationDom = parent.next();
    
        // PC 页码方式分页
        var currentPage = paginationDom.find('.current-page');
        if (currentPage.length > 0) {
          currentPage.text(currentIndex + 1);
        }
    
        // 底部Circle分页 点击分页后,更新页码
    
        if (paginationDom.hasClass('gallery-pagination-circle')) {
          var circleSpans = paginationDom.children('span');
          circleSpans.removeClass('select-span');
          circleSpans.eq(currentIndex).addClass('select-span');
        }
      }
    

    5. 窗口调整Resize初始化

    用户在使用的过程中,肯定会遇到拖拽,那么在拖拽的时候,我们不仅仅是css进行适应,还应该有JS注册事件。在上面我们已经在初始化中清除了所有的事件,方便resize时,重新初始化。但是我们可能做得更好,在进行resize时,如果是PC样式,那么就不在重复渲染。

    我们公共resize和节流函数进行配合达到这个目的。

    定义全局变量,通过 windowIsResize ,我们可以直到窗口是否移动。

    window.windowIsResize = false;
    ; (function ($) {
      // 上一次记录
      var preWindowWidth = $(window).width();
      var preDevice = iSPc();
    
    
    
      $(window).resize(throttle(function () {
    
        // resize 获取
        var currentWindowWidth = $(window).width();
        var currentDdevice = iSPc();
    
        // 宽度相等或者设备相等,直接设置为false
        if (currentWindowWidth === preWindowWidth || currentDdevice === preDevice) {
          window.windowIsResize = false;
        } else {
          window.windowIsResize = true;
        }
    
        // 更新
        preWindowWidth = currentWindowWidth;
        preDevice = currentDdevice;
      }, 200));
    })($);
    

    通过设备宽度判断,是否重新渲染slider

    ;(function($, document) {
      resizeGallery();
      $(window).resize(throttle(function() {
        // 宽度变化且设备变化,重新初始化gallery
        if (window.windowIsResize) {
          
    
        var slider= new Slider(gallery, children, scrollStep, 0, childCls, isInfiniteScroll, paginationMethods, paginationStep, customInitDom);
        slider.initMobileOrPcGallery();
    
        }
      }, 200));
    })($);
    

    至此,Slider组件已经完成,肯定会存在一些小的问题,大家事件了可以告知我~~


    起源地下载网 » 手把手教你使用JavaScript实现一个slider无限滚动插件

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元