工具 – 我又手写轮播了

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第3天,点击查看活动详情

前言

作为一个前端,写轮播(又叫跑马灯)是无法避开的一个需求点。

而随着各种框架以及UI库的出现,已经很少有人会自己去写轮播了。

最近刚好想脱离于VUE/REACT框架轮播图,手写一个基于 jQuery 的扩展,尽可能的提高复用性,并将写的过程中的一些思维发散进行了一个整理,进行记录分享。

需求拆解

其实就是最常见的一个轮播,如下图所示:

image.png

  1. 有一系列的图片做成的轮播,可以逐个展示;
  2. 交互1:左右按钮,点击切换轮播,按照既定的动画/展示效果进行图片的切换,并和下部工具条进行联动;
  3. 交互2:点击下部工具条,可以跳转到指定的图片上;
  4. 交互3:点击每张图片都会有一个回调函数,进行指定操作:跳转/锚点…

就是很常见的一个特效,也很少有人花功夫去重复造轮子了,本着颗粒归仓的原则,简单记录一下。

思路整理

思路主要分为 个部分:

一、技术选型

所谓的技术选型只是因为本次需求希望脱离 VUE/REACT 框架去做,也就是说要基于原生 html+js+css 去实现。这样一来就会脱离 webpack 一类的打包工具,有些兼容性方面的问题就需要去考虑一下了。

最终选择 jQuery 的原因有三个:

  1. 同页面已经引入了 jquery
  2. 站在 html+js+css 的技术栈角度,难免会操作dom,$ 是一个不错的选择
  3. 本案例图片切换效果是 渐入渐出 ,而 jQuery 刚好有这么一对方法 fadeIn/fadeOut

二、使用方法

既然已经确定了技术栈,那么就要想一下,如何去调用这个方法更好呢?而在如何调用方法的前面就是,是直接操作dom,还是以数据去驱动 dom 的渲染?

我经常用的几个方法如下:

  • const loop = new LoopEvent(selector, arr, fn)
  • $(selector).loopEvent(arr, fn)
  • loopEvent(selector, arr, fn)
    第二个问题当然是以数据去驱动dom的渲染了。

思考结束,我先放一下我的使用方法:

<div class="loop"></div>

<script>
  var loop = new LoopEvent(
    $(".loop"), /* 传入 $ 对象,省去方法里面再选择 */
    Array(16).fill({ /* 填充一个长度为16的数组 */
      id: 1,
      imgUrl:
        "https://img2.baidu.com/it/u=1768098264,2761758584&fm=253&fmt=auto&app=120&f=JPEG?w=634&h=420",
    }),
    function (id) { /* 回调函数的方法传入一个 function,点击图片时进行调用 */
      window.open("http://www.baidu.com?id=" + id);
    }
  );

  window.loopT =
    loopT === undefined
      ? setInterval(function () {
          loop.next();
        }, 5000)
      : t;
 </script>

三、切换/交互效果

这一块其实没什么好考虑的,因为一般情况下已经确定了,比如这次做的就是 渐入渐出 的效果。

但是这里会发散一下,如果没有 jQuery 的 fadeIn/fadeOut 如何去做这个 渐入渐出 嘞?

  1. jQuery 有个方法 叫 animate,支持链式调用,在 animate 结束以后,调用切换
  2. 使用 css3 过渡效果,给当前图片加个 样式,再做个定时器,进行切换
  3. 使用周期性定时器修改透明度
  4. … (还有啥?)

四、思维发散

首先是本案例的扩展:

  1. 界面UI效果,支持3D效果,例如:魔方效果,手风琴效果,翻页效果,纸牌效果…
  2. 图片切换效果,渐入渐出、溶解、飞入飞出(参考 PPT 里面切换效果)

然后是对比一下 原生的写法,和框架的写法:

  1. 以VUE为代表的数据双向绑定的的写法,首先是数据驱动,会将当前展示的图片保存在一个变量进行记录,然后通过切换变量结合Vue动画来实现轮播效果,而变量是决定整个 demo 的核心,且父组件可以通过操作 props 来操控单签组件;
  2. 原生则是在封装的方法内部,使用闭包的方式保存一个变量,每次修改变量的同时会操作 dom,实现整个轮播效果;

可见,数据双向绑定即 通过数据劫持结合发布订阅模式 对于原生应用的打击是跨维度的。

代码分析

代码分为两块,一块是结合使用的 css 样式,毕竟把样式都写在 js 里面,不太好看,另一块是 js。

首先是 css,很朴实无华的一段css。

/* loop */
.loop {
    position: relative;
    height: 400px;

}

.loop-item {
    height: 100%;
    width: 100%;
    background-size: 100% auto;
    background-position: center;
    position: absolute;
    background-repeat: no-repeat;
}

.loop-left, .loop-right {
    position: absolute;
    z-index: 999;
    top: 50%;
    margin-top: -25px;
    width: 50px;
    height: 50px;
    transform: rotate(45deg);
    cursor: pointer;
}

.loop-left {
    left: 100px;
    border-bottom: 5px solid #fff;
    border-left: 5px solid #fff;
}

.loop-right {
    right: 100px;
    border-top: 5px solid #fff;
    border-right: 5px solid #fff;
}

.loop-left:hover, .loop-right:hover {
    border-color: blueviolet;
}

.loop-tools {
    position: absolute;
    bottom: 30px;
    display: flex;
    width: 40%;

    z-index: 999;
    margin-left: 30%;
}

.loop-tools div {
    flex: 1;
    height: 6px;
    background: #ccc;
    margin: 0 5px;
    cursor: pointer;
}

.loop-active {
    background: #fff !important;
}

js 是这个demo的核心。本来准备使用 class 关键字构建一个类的,后来考虑到有些浏览器不认识 class。那就先祝福这个浏览器能与世长存吧!!!

最后使用 fuction 写了个构造器,所有的交互都将放在里面,避免对全局造成污染。

具体细节看注释吧:

/**
 * class EventLoop
 * @param {$(Selector)} $wrap
 * @param {any[]} data
 * @param {string | null} clickCallback
 */
function LoopEvent($wrap, data, clickCallback) {
  this.$wrap = $wrap; /* 容器 */
  this._data = data; /* 原始数据 */
  this._active = 0; /* 当前展示项 */
  var that = this; /* 用一个变量保存 this,解决 $ 内部 this 指向问题 */
  
  // 初始化容器
  this.initEvent = function () { 
  // 添加所有的轮播项
    this.$wrap.append(
      this._data
        .map(function (item, i) {
          return (
            '<div data-id="' +
            item.id +
            '" data-idx="' +
            i +
            '" class="loop-item" style="background-image: url(' +
            item.imgUrl +
            ')"></div>'
          );
        })
        .join("")
    );
    
    // 添加左右按钮
    this.$wrap.append(
      '<div class="loop-left"></div><div class="loop-right"></div><div class="loop-tools"></div>'
    );
    
    // 添加底部工具条
    this.$wrap.find(".loop-tools").html(
      this._data.map(function (item, i) {
        return '<div data-idx="' + i + '"></div>';
      })
    );
    
    // 初始化轮播图及工具条展示项
    this.$wrap.find(".loop-item").eq(this._active).show();
    this.$wrap
      .find(".loop-item")
      .eq(this._active)
      .siblings(".loop-item")
      .hide();
    this.$wrap
      .find(".loop-tools")
      .children("div")
      .eq(this._active)
      .addClass("loop-active");
      
    // 为每一张轮播图,绑定回调函数
    this.$wrap.find(".loop-item").each(function () {
      var id = $(this).attr("data-id");
      if (clickCallback) {
        $(this).on("click", function () {
          return clickCallback(id);
        });
      }
    });
  };

  // 调用初始化事件
  this.initEvent();

  // 切换轮播,单独提出切换效果,供切换时回调
  this.autoLoop = function () {
    this.$wrap.find(".loop-item").eq(this._active).fadeIn();
    this.$wrap
      .find(".loop-item")
      .eq(this._active)
      .siblings(".loop-item")
      .fadeOut();

    this.$wrap
      .find(".loop-tools")
      .children("div")
      .eq(this._active)
      .addClass("loop-active");
    this.$wrap
      .find(".loop-tools")
      .children("div")
      .eq(this._active)
      .siblings("div")
      .removeClass("loop-active");
  };
  // 下一张
  this.next = function () {
    this._active =
      this._active + 1 > this._data.length - 1 ? 0 : this._active + 1;
    this.autoLoop();
  };
  
  // 上一张
  this.last = function () {
    this._active =
      this._active - 1 < 0 ? this._data.length - 1 : this._active - 1;
    this.autoLoop();
  };
  
  // 某一张
  this.choice = function (n) {
    this._active = n;
    this.autoLoop();
  };

  // 绑定左按钮 点击事件
  this.$wrap.find(".loop-left").on("click", function () {
    return that.last();
  });
  
  // 绑定右按钮 点击事件
  this.$wrap.find(".loop-right").on("click", function () {
    return that.next();
  });
  
  // 绑定工具条 点击事件
  this.$wrap
    .find(".loop-tools")
    .children("div")
    .each(function () {
      $(this).on("click", function () {
        that.choice(Number($(this).attr("data-idx")));
      });
    });
}

// 暴露 出 initLoop 方法,供外部使用
function initLoop() {
  var loop = new LoopEvent(
    $(".loop"),
    Array(16).fill({
      id: 1,
      imgUrl:
        "https://img2.baidu.com/it/u=1768098264,2761758584&fm=253&fmt=auto&app=120&f=JPEG?w=634&h=420",
    }),
    function (id) {
      window.open("http://www.baidu.com?id=" + id);
    }
  );

  window.loopT =
    loopT === undefined
      ? setInterval(function () {
          loop.next();
        }, 5000)
      : t;
}

// 调用
$(function () {
  initLoop();
});

总结

如上就是本次分享的主要内容了,想要说的其实在下面。

Vue组件-景深卡片轮播,这个是我前段时间写的一个 vue 组件,刚好能和这个轮播进行对比,感兴趣的也可以对比着看一下。

站在看法的角度上对比一下,其实差别还是蛮明显的。在 vue 组件里面,我们只需要写一个静态模板,然后只需要关注每次交互以后数据的变化就好了。而在 jQuery 里面,我们还需要定义一个 autoLoop 的方法来实现 dom 的切换,这么一看,是不是就知道 vue 帮我们做了什么了?

另外,像本文中这个轮播,如果使用 vue 写一个 组件会更简单。

拜拜!

© 版权声明
THE END
喜欢就支持一下吧
点赞8 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容