小白探索大前端--使用vue实现简单轮播图

最近看完了《Vue实战》这本书,这也是我第一次完整的看完的一本关于前端的书籍(现在还在看的是《CSS世界》,有点想放弃了)。照着书中的demo都写了一遍,虽然很多栗子非常简单,但是值得学习的地方还是挺多的。当然也不得不吐槽很多用法都有点过时,尤其是关于webpack的配置。不过现在的前端发展很快,各种工具版本不断更新,代码过时也是能够理解的。对于学习者来说当然得学习最新的东西。

这篇文章主要是用来记录在自己实现一个【知乎日报移动版】的过程中遇到的一个坑,说坑也算不上,只能叫做坎坷之路吧。因为书中给的实战demo是一个pc版的,自己跟着写了一遍,觉得没有什么难度。然后在github上找了一下,发现用知乎日报来练手的项目很多,原因也很简单--api是现成的。于是我也蠢蠢欲动,决定使用vue亲自撸一个【知乎daily】。

虽然gayhub很多现成的项目,但是我并不打算去抄一遍。我在手机上下载了一个知乎日报的APP,按照不同功能截屏来一一实现。没办法,这就是工(zhuang)匠(bi)情(xin)怀(tai).

开发过程中,整体的框架功能上基本没什么问题,该自己写组件就自己写,绝没有偷懒。也遇到很多问题,比如开发环境下的接口代理、知乎图片的同源策略等。这些问题肯定难不倒我,都不值一提。让我觉得难受的还是首页的轮播图实现。这个问题困扰了我2天(准确时间是一天半,还有半天在工作:逃)。

遇到这个功能的时候第一反应是使用一个开源库,分分钟就搞定了。但是经过10s的思想斗争,工匠情怀终于战胜了理智,下定决心打算自己手动实现。然而,带来的结果就是卡顿了半天,没任何进展。网上也有很多教程,也是很简单的,可以说是基础操作了。可惜我被一篇文章带偏了,走了一点弯路。

这篇文章的思路很常规,很有道理。首先将一个框框用来装你要显示的图片,仅仅只能显示这个框框,而图片呢就放后面排排坐,通过定时器去移动图片,就像一格格的胶卷一样,轮到谁谁就被看到了。

我一开始就是按照这种思路去整,结果怎么整都实现不了(在移动端)。在一筹莫展之际,发现了一个更加屌的思路。这个思路和之前的不一样,区别在于后面的图片不是排排坐好,而是叠加到一块去。下面通过代码去一探究竟。

首先得整一个窗口,简单来说就是用来显示一张图片的容器,它的宽度对移动端而言就是屏幕宽度。他得有一个非常重要的属性overflow:hidden,当子元素尺寸超过其父亲的大小多余内容就会被隐藏。这里用ul标签来装图片,其实使用div也是没问题的。元素liposition属性得设置为absolute.因为使用这个属性就能脱离文档流,也就不会“排排坐”了,而是叠加到一起了,当然,最后的肯定叠在最上面,前提是没有显示的设置z-index属性。还有很重要的一点,子元素定位设置为absolute父元素记得也设置一下定位属性,因为子元素的相对位置是按照第一个祖先元素不为static的元素来的,不然会粗大事。 html代码结构如下:

    
  • {{e.title}}
* {
  padding: 0;
  margin: 0;
  list-style-type: none;
}

img {
  width: 100%;
}
.container {
  position: relative;
  min-height: 100%;
}
.container li {
  width: 100%;
  height: 100%;
  position: absolute;
  top: 0;
  left: 0;
  transform: translateX(100%);
}

.window {
  position: relative;
  height: 100%;
  margin: 0 auto;
  overflow: hidden;
}

这里有一个小技巧,在静态样式中并没有将图片直接展示出来,而是将所有图片向右偏移一个窗口宽度。这是有玄机的。

说了这么多,还没谈到vue的部分。在container容器中有一个ref标签。这是在vue中用来获取dom元素的。也许在vue中操作dom是不被推荐的,但是对于这种动态生成的li元素我找不到怎么去动态绑定其样式。因此采用了这种比较low的办法。

    init() {
      let wrappers = this.$refs.imagesWrapper;
      let children = wrappers.children;
      let total = this.imgs.length;
      // 纯js操作 只需要先将三张图片位置确定好
      // 最左边按道理说是没有图片的 但是为了无限滚动效果 这里将其置为最后一张
      let left = total - 1;
      let center = 0;
      let right = 1;

      // 初始化的时候将li左移动了一个屏幕宽度,就是为了防止叠加的元素挡住要显示的图
      // 现在第一张显示的图片实际是最后一张 3张轮播起来就行了 没必要对每个图片进行位置计算
      // left处于最左边的位置 不显示
      children[left].style.transform = "translateX(" + -this.distance + "px)";
      // center处于中间位置 显示
      children[center].style.transform = "translateX(" + 0 + "px)";
      // right处于右边 不显示
      children[right].style.transform = "translateX(" + this.distance + "px)";
      this.sliderItem = children;
      // this.play();
    }

首先就是初始化显示的内容,之前在静态css样式中将图片移动到右边看不到的地方去了,现在就得手动操作让其可见。刚开始我以为直接将这些图片全部“铺开”,然后滚动,后来发现这么做很笨。直接操作三张就行了!为了实现无限滚动的效果,第一张的逻辑上的前一张是最后一张,因此将第一张的“上一张”给放到屏幕左边,下一张放到屏幕右边,要显示的也就是第一张(下标为0)归位到窗口,这和之前设置的全局的样式transform: translateX(100%)对应起来了。

如此以来,初始化的三张图片就定位好了,逻辑上也是没什么问题的。其余的改不显示还是显示不了,也不会参与移动。下面看看怎么“滚”。

    next() {
      this.currentIndex++;
      // 边界判断
      if (this.currentIndex > this.imgs.length - 1) {
        this.currentIndex = 0;
      }

      // center 为显示的图片
      let center = this.currentIndex;
      // 左边的 如果为负数 就取最后一张图片下标
      let left = center - 1 < 0 ? this.imgs.length - 1 : center - 1;
      // 右边的 如果超过了最大图片数量 取第一张图片下标
      let right = center + 1 == this.imgs.length ? 0 : center + 1;

      let children = this.sliderItem;
      // 给元素添加过渡
      children[center].style.transition = "transform .5s";
      children[left].style.transition = "transform .5s";
      // 右边的图片是替补图片,不需要走过渡
      children[right].style.transition = "none";
      // 3张图片同时移动
      children[left].style.transform = "translateX(" + -this.distance + "px)";
      children[center].style.transform = "translateX(0px)";
      children[right].style.transform = "translateX(" + this.distance + "px)";
    },

currentIndex为全局变量,指当前显示的图片下标,每次调用next会子增,到上限后会回归到0,这些都是很常规的操作。接下来就是计算上一张,下一张图片的下标,也是很容易理解,无非多了一点判断,在最后一张显示的时候下一张的下标得置为0,上一张也是同理,不然就回"空指针"了。接下来就是针对这三张图片改变样式,原则就是移动到哪个下标就显示哪个图片,上一张就移到左边,下一站移动到右边,顺便给加个动画效果。如此而已!

最后就是自动播放的逻辑,也是非常简单,一个定时器就搞定:

    play() {
      if (this.timer) {
        window.clearInterval(this.timer);
        this.timer = null;
      }
      this.timer = window.setInterval(() => {
        this.next();
      }, this.interval);
    }

然后在钩子函数中将这方法加上去就完事了。一个自制的轮播组件就写完了。简陋但是简单。这里有一个不太重要的细节,针对窗口变化的时候得动态改变偏移量。

    // 窗口变化 重新初始化
    windowChange() {
      const that = this;
      window.onresize = () => {
        return (() => {
          window.screenWidth = document.body.clientWidth;
          that.distance = window.screenWidth;
          this.init();
        })();
      };
    },

这样在pc端下也能正常“滚”动了。

最后,做一下小小的总结。这个组件虽然简单,但是也花了一定时间,毕竟踩坑的路是不能跳过的。其中花了很多时间纠结布局和样式,很是难受,都怪我没有把《CSS世界》看完。虽然简单,但是功能也很局限,比如没有实现手动去滑动。APP上是有这个功能的,那是因为我还没学会怎么在vue下使用touch事件(实际上是懒)。比如没有代码优化等等,总不能要求一个新手来造一个完美的轮子吧(给自己一点上升的空间咯)。造轮子不是目的,理解其中的所以然才是目的,现成的库有很多,完成功能也很容易,但是不能仅仅满足于此,我觉得作为手艺人得有一种格(xi)物(huan)致(zhuang)知(B)的精神。

小白探索大前端--使用vue实现简单轮播图_第1张图片
不能动的效果图

参考资料

CSS深入理解之relative定位

几种原生js轮播图

源码地址

你可能感兴趣的:(小白探索大前端--使用vue实现简单轮播图)