原理
 
- 页面中设置一个父盒子 bannerBox,固定宽高,相当于一个窗口,超出部分隐藏
 - bannerBox 内部设置一个图片盒子 imgBox,横向(或纵向)依次排列图片
 - 添加定时器,每隔一段时间,imgBox 向左(或向上)移动一段距离
 - 为移动的过程添加 transition 过渡效果
 
 

 
- 如上图所示,一共 4 张图,每张宽度设置为 600px,imgBox 宽度设置为 4*600 = 2400px,使得 4 张图片恰好可以在 imgBox 内横向排开。bannerBox 宽度为 600px,溢出部分隐藏,每隔 2 秒,imgBox 向左移动 600px。
 
 
初步实现
 
DOM 结构搭建
 
- 设置 bannerBox、imgBox、img 的相关属性
 
 
<div class="bannerBox">
  <div
    class="imgBox clearfix"
    :style="{
      width: imgList.length * 600 + 'px',
    }"
  >
    <div class="sinImg" v-for="(item, index) in imgList" :key="index">
      <img :src="item" alt="" />
    </div>
  </div>
</div>
 
data() {
 return {
    banner1: require("@I/banner1.jpg"),
    banner2: require("@I/banner2.jpg"),
    banner3: require("@I/banner3.jpg"),
    banner4: require("@I/banner4.jpg"),
    imgList: [], 
    activeIndex: 0,
  };
},
mounted() {
  this.getImgList();
},
methods: {
  getImgList() {
    this.imgList = [ this.banner1, this.banner2, this.banner3, this.banner4 ];
  },
}
 
.bannerBox {
 width: 600px;
 overflow: hidden;
 border-radius: 16px;
 .imgBox {
   transform: translateX(0);
   .sinImg {
     float: left;
     img {
       width: 600px;
     }
   }
 }
}
 
- imgBox 盒子范围
 
 - 第二张图片位置
 
 
 
添加定时器
 
<div class="bannerBox">
  <div
    class="imgBox clearfix"
    :style="{
      width: imgList.length * 600 + 'px',
      transform: transform,
      transition: transition,
    }"
  >
    <div class="sinImg" v-for="(item, index) in imgList" :key="index">
      <img :src="item" alt="" />
    </div>
  </div>
</div>
 
data() {
 return {
    banner1: require("@I/banner1.jpg"),
    banner2: require("@I/banner2.jpg"),
    banner3: require("@I/banner3.jpg"),
    banner4: require("@I/banner4.jpg"),
    imgList: [], 
    activeIndex: 0,
    timmer: null,
    transition: "transform 1s ease-in-out",
    transform: "translateX(0px)",
  };
},
mounted() {
  this.getImgList();
},
methods: {
  getImgList() {
    this.imgList = [ this.banner1, this.banner2, this.banner3, this.banner4 ];
    this.bannerFunc();
  },
}
bannerFunc() {
  this.timmer = setInterval(() => {
    
    
    this.activeIndex += 1;
    
    if (this.activeIndex > this.imgList.length - 1) {
      
      this.activeIndex = 0;
    }
    
    this.transform = "translateX(" + this.activeIndex * -600 + "px)";
  }, 2000);
},
 
- 上述方法在轮播图播放到最后一张 banner4 时,索引重置,会显示第一张图片 banner1,但是由于有 transition 过渡属性。视觉上的效果是,轮播图播放到 banner4 时,在 1s 的时间内,imgBox 向右移动(与轮播自动播放方向相反),快速依次略过 banner3 和 banner2,到达 banner1
 
 

 
优化无缝衔接
 
- 为了优化视觉效果,在图片数组最后添加一张 banner1,轮播图到达最后一张(banner1)时,关闭 transition 过渡属性,并且瞬间移动到数组第一张(banner1),开启新一轮的轮播
 - 在 setInterval 定时器内部添加 setTimeout 定时器,用延时来模拟 transition 的 1s 过渡
 
 
 getImgList() {
  this.imgList = [ this.banner1, this.banner2, this.banner3, this.banner4, this.banner1 ];
  this.bannerFunc();
},
bannerFunc() {
  this.consoleTimeStr("开始调用 bannerFunc 方法", "red");
  this.timmer = setInterval(() => {
    
    this.consoleTimeStr("开始调用 toNext 方法", "blue");
    
    this.activeIndex += 1;
    
    this.transform = "translateX(" + this.activeIndex * -600 + "px)";
    
    this.transition = "transform 1s ease-in-out";
    
    if (this.activeIndex == this.imgList.length - 1) {
      
      this.activeIndex = 0;
      
      
      this.consoleTimeStr("无缝衔接准备~", "yellowgreen");
      setTimeout(() => {
        this.transform = "translateX(" + this.activeIndex * -600 + "px)";
        this.transition = "none";
        this.consoleTimeStr("偷偷切换回去了!", "green");
      }, 1000);
    }
  }, 2000);
},
consoleTimeStr(flag, color) {
  let time = new Date();
  let minute = time.getMinutes();
  let second = time.getSeconds();
  if (minute < 10) {
    minute = "0" + minute;
  }
  if (second < 10) {
    second = "0" + second;
  }
  let str = minute + ":" + second;
  console.log("%c" + flag + ",当前时间:" + str, "color: " + color + ";");
},
 

 
- 上述打印语句可以看出,在 26:48 时,开启了 1s 的 setTimeout 定时器
 - 在这 1s 内,关闭了 transition 过渡效果,将 imgBox 位移恢复到初始位置,可以看做是瞬间位移
 - 这样做就解决了上一步骤向相反方快速移动的效果
 
 
左右切换按钮
 
- 轮播可添加左右切换按钮
 - 自动切换的效果可封装为右切换效果
 
 
完整代码
 
<template>
  <div class="page">
    <div class="bannerBox">
      <div
        class="imgBox clearfix"
        :style="{
          width: imgList.length * 600 + 'px',
          transform: transform,
          transition: transition,
        }"
      >
        <div class="sinImg" v-for="(item, index) in imgList" :key="index">
          <img :src="item" alt="" />
        </div>
      </div>
    </div>
    <div class="dotBox clearfix">
      <div :class="{ active: activeIndex == 1 }"></div>
      <div :class="{ active: activeIndex == 2 }"></div>
      <div :class="{ active: activeIndex == 3 }"></div>
      <div :class="{ active: activeIndex == 4 }"></div>
    </div>
    <div class="btnBox">
      <div class="left" @click="toPrev('clear')"><</div>
      <div class="right" @click="toNext('clear')">></div>
    </div>
  </div>
</template>
<script>
export default {
  data() {
    return {
      banner1: require("@I/banner1.jpg"),
      banner2: require("@I/banner2.jpg"),
      banner3: require("@I/banner3.jpg"),
      banner4: require("@I/banner4.jpg"),
      imgList: [], 
      activeIndex: 1,
      timmer: null,
      transition: "transform 1s ease-in-out",
      transform: "translateX(-600px)",
    };
  },
  mounted() {
    this.getImgList();
  },
  methods: {
    getImgList() {
      
      this.imgList = [
        this.banner4,
        this.banner1,
        this.banner2,
        this.banner3,
        this.banner4,
        this.banner1,
      ];
      this.bannerFunc();
    },
    
    bannerFunc() {
      this.consoleTimeStr("开始调用 bannerFunc 方法", "red");
      this.timmer = setInterval(() => {
        
        this.toNext();
      }, 2000);
    },
    
    toPrev() {
      this.consoleTimeStr("开始调用 toPrev 方法", "blue");
      
      this.activeIndex -= 1;
      
      this.transform = "translateX(" + this.activeIndex * -600 + "px)";
      
      this.transition = "transform 1s ease-in-out";
      
      if (this.activeIndex == 0) {
        
        this.activeIndex = this.imgList.length - 2;
        
        
        this.consoleTimeStr("无缝衔接准备~", "yellowgreen");
        setTimeout(() => {
          this.transform = "translateX(" + this.activeIndex * -600 + "px)";
          this.transition = "none";
          this.consoleTimeStr("偷偷切换回去了!", "green");
        }, 1000);
      }
      console.log("activeIndex", this.activeIndex, "transform", this.transform);
    },
    
    toNext() {
      this.consoleTimeStr("开始调用 toNext 方法", "blue");
      
      this.activeIndex += 1;
      
      this.transform = "translateX(" + this.activeIndex * -600 + "px)";
      
      this.transition = "transform 1s ease-in-out";
      
      if (this.activeIndex == this.imgList.length - 1) {
        
        this.activeIndex = 1;
        
        
        this.consoleTimeStr("无缝衔接准备~", "yellowgreen");
        setTimeout(() => {
          this.transform = "translateX(" + this.activeIndex * -600 + "px)";
          this.transition = "none";
          this.consoleTimeStr("偷偷切换回去了!", "green");
        }, 1000);
      }
      console.log("activeIndex", this.activeIndex, "transform", this.transform);
    },
    
    consoleTimeStr(flag, color) {
      let time = new Date();
      let minute = time.getMinutes();
      let second = time.getSeconds();
      if (minute < 10) {
        minute = "0" + minute;
      }
      if (second < 10) {
        second = "0" + second;
      }
      let str = minute + ":" + second;
      console.log("%c" + flag + ",当前时间:" + str, "color: " + color + ";");
    },
  },
};
</script>
<style lang="scss" scoped>
.page {
  width: 600px;
  position: relative;
  margin: 20px auto;
  .bannerBox {
    width: 600px;
    overflow: hidden;
    border-radius: 16px;
    .imgBox {
      .sinImg {
        float: left;
        img {
          width: 600px;
        }
      }
    }
  }
  .dotBox {
    width: 70px;
    position: absolute;
    bottom: 10px;
    left: 50%;
    transform: translateX(-50%);
    div {
      float: left;
      width: 10px;
      height: 10px;
      border-radius: 50%;
      margin-right: 10px;
      cursor: pointer;
      background: rgba(255, 255, 255, 0.2);
      &:last-child {
        margin-right: 0;
      }
      &.active {
        background: rgba(255, 255, 255, 0.9);
      }
    }
  }
  .btnBox {
    width: 100%;
    height: 80px;
    line-height: 80px;
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    div {
      position: absolute;
      cursor: pointer;
      font-size: 36px;
      color: #fff;
      font-weight: bold;
      font-family: cursive;
    }
    .left {
      left: 10px;
    }
    .right {
      right: 10px;
    }
  }
}
.clearfix:after {
  content: "";
  display: block;
  clear: both;
  visibility: hidden;
}
</style>
 
未完待续
 
- 上述效果暂时未考虑点击左右切换按钮时,停止定时器的效果,后续有时间再补充