Vue组件开发系列之Carousel轮播组件

组件源码:
https://github.com/AntJavascript/widgetUI/tree/master/Carousel

20181030_170612.gif

基本用法:(必须使用 class="carousel-item" 包裹)

 <wt-carousel @swiper="swiper">
            <div class="carousel-item"><img src='https://ss3.bdstatic.com/iPoZeXSm1A5BphGlnYG/skin/822.jpg'></div>
            <div class="carousel-item"><img src='https://ss3.bdstatic.com/iPoZeXSm1A5BphGlnYG/skin/823.jpg'></div>
            <div class="carousel-item"><img src='https://ss3.bdstatic.com/iPoZeXSm1A5BphGlnYG/skin/824.jpg'></div>
            <div class="carousel-item"><img src='https://ss3.bdstatic.com/iPoZeXSm1A5BphGlnYG/skin/825.jpg'></div>
        </wt-carousel>

组件结构:

<template>
<div class="wt-carousel" @touchstart="touchStart" @touchmove="touchMove" @touchend="touchEnd">
    <div class="wrapper" :style="{transform: 'translate3d('+ distance +'px, 0px, 0px)'}">
      <slot></slot>
    </div>
    <div class="carousel-pagination" v-if="options.pagination">
      <p v-for="(item, index) in itemCount" :key="index" :class="{'active':index === currentIndex}"></p>
    </div>
  </div>
</template>

代码分析:

props参数:

props: {
    options: {
      type: Object,
      default: () => {
        return {
          loop: false, // 是否循环轮播
          pagination: false, // 是否显示指示器
          auto: false, // 是否自动轮播
          time: 3000 // 每次轮播时间
        }
      }
    }
  }

data参数:

data () {
    return {
      itemWidth:'', // item的宽度
      speed: 1, // 速度
      currentIndex: 0, // 当前处于第几个
      distance: 0, // 滑动距离
      itemCount: 0, // 有多少item
      thisDistance: 0, // 本次滑动距离
      start: { // 触摸坐标
        X: 0,
        Y: 0
      },
      move: { // 移动坐标
        X: 0,
        Y: 0
      },
      slots: [], // 父组件的slot
      autoPlay: '', // 自动轮播定时器
      status: '' // 当前状态
    };
  }

mounted 生命周期:

mounted () {
    this.itemWidth = this.$el.offsetWidth;
    var len = this.$slots.default.length;
    // 处理每个item的宽度
    for (let i = 0; i < len; i++) {
      if (this.$slots.default[i].data && this.$slots.default[i].data.staticClass === 'carousel-item') {
        this.$slots.default[i].elm.style.width = this.itemWidth + 'px'; // 设置每个item的宽度
        this.slots.push(this.$slots.default[i].elm);
        this.itemCount++;
      }
    }
    // 需要重复轮播
    if (this.options.loop) {
      // 克隆第一个item
      var first = this.slots[0].cloneNode(true);
      this.$el.firstChild.appendChild(first);
      // 克隆最后一个item
      var last = this.slots[this.slots.length - 1].cloneNode(true);
      this.$el.firstChild.insertBefore(last, this.$el.firstChild.childNodes[0]);
      this.$el.firstChild.style.transitionDuration = '0ms';
      this.distance = -this.itemWidth;
    }
    // 是否自动轮播
    if (this.options.auto) {
      var self = this;
      this.autoPlay = setInterval(self.autoPlayFn, this.options.time);
    }
  }

methods函数:

methods: {
    // 定时器函数
    autoPlayFn () {
      var self = this;
      self.$el.firstChild.style.transitionDuration = '300ms';
      // 循环轮播
      if (self.options.loop) {
        self.currentIndex++;
        // 当前索引大于itemCount ,就返回到第一个
        if (self.currentIndex > self.itemCount) {
          self.currentIndex = 0;
          self.$el.firstChild.style.transitionDuration = '0ms';
          self.distance = -self.itemWidth;
        } else {
          this.distance = -((this.currentIndex + 1) * this.itemWidth);
          if (self.currentIndex === self.itemCount) {
            self.currentIndex = 0;
            // 400ms之后滚动到第一张,并且过渡时间为0, 时间一定要设置大于300ms,否则会有闪屏问题
            setTimeout(() => {
              self.$el.firstChild.style.transitionDuration = '0ms';
              self.distance = -self.itemWidth;
            }, 400);
          }
        }
      } else {
        // 不循环轮播
        if (self.currentIndex === (self.itemCount - 1)) {
          self.currentIndex = 0;
          this.distance = -(this.currentIndex * this.itemWidth);
        } else {
          self.currentIndex++;
          this.distance = -(this.currentIndex * this.itemWidth);
        }
      }
      self.$emit('swiper', self.currentIndex);
    },
    touchStart () {
      // 关闭定时器
      clearInterval(this.autoPlay);
      // 触摸坐标
      this.start.X = event.touches[0].clientX;
      this.start.Y = event.touches[0].clientY;
    },
    touchMove () {
      var self = this;
      self.status = 'moveing';
      self.$el.firstChild.style.transitionDuration = '0s';
      // 滑动时候的坐标
      this.move.X = event.touches[0].clientX;
      this.move.Y = event.touches[0].clientY;
      // 如果是横着滑动则阻止浏览器默认行为
      if ((Math.abs(this.move.Y - this.start.Y) < Math.abs(this.move.X - this.start.X))) {
        event.preventDefault();
        event.stopPropagation();
        console.log(Event.cancelable);
      } else {
        // 如果是竖着滑动,则 return
        return;
      }
      // 本次滑动距离
      var thisDistance = this.move.X - this.start.X;
      this.thisDistance = thisDistance;
      // 如果是循环轮播,则是currentIndex + 1
      if (this.options.loop) {
        this.distance = -((this.currentIndex + 1) * this.itemWidth) + thisDistance;
      } else {
        this.distance = -(this.currentIndex * this.itemWidth) + thisDistance;
      }
    },
    touchEnd () {
      // 重新开启定时器
      if (this.options.auto) {
        this.autoPlay = setInterval(this.autoPlayFn, this.options.time);
      }
      // 如果是竖着滑动,则 return
      if ((Math.abs(this.move.Y - this.start.Y) > Math.abs(this.move.X - this.start.X))) {
        return;
      }
      var self = this;
      // 如果this.move.X === 0说明没有移动
      if (self.status === '') {
        return;
      }
      self.$el.firstChild.style.transitionDuration = '300ms';
      // 往左滑
      if (this.thisDistance < 0) {
        if (this.thisDistance <= -(this.itemWidth / 3)) {
          // 当前index 必须小于item数量减1
          if (this.currentIndex < (this.itemCount - 1)) {
            this.currentIndex++;
          } else {
            if (this.options.loop && this.currentIndex < this.itemCount) {
              this.currentIndex++;
            }
          }
          self.$emit('swiper', self.currentIndex);
          self.isLoop();
        } else {
          // this.distance = -(this.itemWidth * this.currentIndex);
          self.isLoop();
        }
        // 如果是无限循环,则需要把索引变成0
        if (this.options.loop) {
          if (this.itemCount === this.currentIndex) {
            this.currentIndex = 0;
            setTimeout(() => {
              self.$el.firstChild.style.transitionDuration = '0s';
              this.distance = -this.itemWidth;
            }, 300);
          }
        }
      } else {
        // 往右滑
        if (this.thisDistance >= this.itemWidth / 3) {
          // 当前index 必须小于item数量减1
          this.currentIndex--;
          if (!this.options.loop && this.currentIndex <= 0) {
            this.currentIndex = 0;
          }
          self.$emit('swiper', self.currentIndex);
          self.isLoop();
        } else {
          self.isLoop();
        }
        // 如果是无限循环,则需要把索引变成最后一个
        if (this.options.loop) {
          if (this.currentIndex < 0) {
            this.currentIndex = this.itemCount - 1;
            self.$emit('swiper', self.currentIndex);
            setTimeout(() => {
              self.$el.firstChild.style.transitionDuration = '0s';
              this.distance = -(this.itemWidth * (this.currentIndex + 1));
            }, 300);
          }
        }
      }
      this.start.X = 0;
      this.move.X = 0;
      this.status = '';
    },
    // 用于判断是否需要循环轮播的处理,如果需要轮播 currentIndex + 1
    isLoop () {
      if (this.options.loop) {
        this.distance = -(this.itemWidth * (this.currentIndex + 1));
      } else {
        this.distance = -(this.itemWidth * this.currentIndex);
      }
    }
  }

css代码:

<style lang="less" rel="stylesheet/less" scoped>
.wt-carousel {
    width: 100%;
    // height: 10rem;
    position: relative;
    overflow: hidden;
  .wrapper {
    width: 100%;
    height: 100%;
    display: -webkit-box;
    transition-duration: 300ms;
    transition-timing-function: ease-out;
    transition-property: transform;
    .carousel-item {
      text-align: center;
      font-size: 0.8rem;
      height: 100%;
      width: 100%;
      background: #fff;
    }
    img {
      height: 100%;
      width: 100%;
      float: left;
    }
  }
  .carousel-pagination {
    display: flex;
    justify-content: center;
    position: absolute;
    bottom: 0.3rem;
    width: 100%;
    p {
      margin: 0.2rem;
      height: 0.3rem;
      width: 0.3rem;
      border-radius: 0.3rem;
      background: #000;
      opacity: 0.4;
      &.active {
        background: #40cfa0;
        opacity: 1;
      }
    }
  }
}
</style>

组件源码:
https://github.com/AntJavascript/widgetUI/tree/master/Carousel

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,165评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,720评论 1 298
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,849评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,245评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,596评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,747评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,977评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,708评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,448评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,657评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,141评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,493评论 3 258
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,153评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,890评论 0 198
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,799评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,685评论 2 272