Vue 实现无缝轮播

作者 : 开心源码 本文共2981个字,预计阅读时间需要8分钟 发布时间: 2022-05-12 共211人阅读

很多网站都会有轮播图的需求,而简单的轮播图实现通常会在展现完最后一个子项后中止轮播,或者者跳回到第一个子项重复轮播过程,这样的交互效果往往是存在断层的。接下来详情如何实现一个无缝的轮播图,达到这样的效果:

预览地址:https://jsfiddle.net/JunreyCen/qxogapws/

核心思想其实非常简单:

  1. 当轮播到边界子项(Item 3),并继续进行横移时,把即将要展现的子项(Item 1)挪到紧挨着 Item 3 的位置,执行横移,如下图 Step 1

  2. 因为此时活跃子项的索引(index > 2)已经超出范围,在下一次横移进行前,需要把索引调整到正当范围内,并重置子项的位置,如下图 Step 2。注意,这一步需要把 transition 关闭,不然 “偷梁换柱” 的过程会被一览无遗。

“偷梁换柱” 过程

这里提供一份完整的代码实现。我略微做了点优化,支持

  • 左、右两个方向轮播
  • 一次切换多个子项

原理上无非是支持多个子项的同时 “偷梁换柱” 罢了,详细的可以关注代码中的 next 函数。

<html>  <meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=0">  <head>    <style>      ul {        list-style: none;      }      .swipe {        position: absolute;        left: 0;        right: 0;        margin: 40px auto;        width: 90%;        max-width: 375px;        height: 200px;        overflow: hidden;      }      .swipe-group {        display: flex;        margin: 0;        padding: 0;        width: 100%;        height: 100%;      }      .swipe-item {        flex: 0 0 100%;        height: 100%;        line-height: 200px;        text-align: center;        font-size: 40px;        font-weight: 600;        color: #fff;      }      .swipe-item:nth-child(1) {        background-color: aquamarine;      }      .swipe-item:nth-child(2) {        background-color: chocolate;      }      .swipe-item:nth-child(3) {        background-color: darksalmon;      }    </style>  </head>  <body>    <div id="app"></div>    <template id="tpl">      <div class="swipe">        <ul class="swipe-group"          :style="groupStyle">          <li             class="swipe-item"             v-for="item in items"            ref="item">            {{item}}          </li>        </ul>      </div>    </template>    <script src="https://cdn.jsdelivr.net/npm/vue"></script>    <script>      new Vue({        el: '#app',        template: '#tpl',        computed: {          groupStyle() {            return {              'transform': `translate3d(${this.offset}px, 0, 0)`,              'transition-duration': `${this.duration}ms`,            };          },        },        data() {          return {            items: [1, 2, 3],            index: 0,           // 当前轮播项索引            offset: 0,          // swipe组的偏移量            duration: 0,        // 过渡动画时长            itemWidth: 0,       // 轮播项宽度          };        },        mounted() {          if (this.$el) {            this.itemWidth = this.$el.getBoundingClientRect().width;          }          this.autoplay();        },        methods: {          // index 超出范围时调整          correctIndex() {            this.duration = 0;            const total = this.items.length;            if (this.index < 0) {              this.next(total, true);            } else if (this.index > total - 1) {              this.next(-total, true);            }          },          // 移动到达目标 index 途中的所有 swipe-item          moveItems(indexOffset) {            const targetIndex = this.index + indexOffset;            if (this.index < targetIndex) {              // 向左              for (let i = this.index; i < targetIndex; i++) {                this.moveItem(i + 1);              }            } else {              // 向右              for (let i = targetIndex; i < this.index; i++) {                this.moveItem(i);              }            }          },          // 移动 swipe-item          moveItem(index) {            const total = this.items.length;            const itemIndex = index % 3 < 0 ? index % 3 + 3 : index % 3;            // 目标 index 超出范围时调整对应 swipe-item 的偏移值            if (index > total - 1) {              this.$refs.item[itemIndex].style.transform = `translateX(${total * this.itemWidth}px)`;            } else if (index < 0) {              this.$refs.item[itemIndex].style.transform = `translateX(${-total * this.itemWidth}px)`;            } else {              this.$refs.item[itemIndex].style.transform = 'translateX(0px)';            }          },          resetItems() {            this.$refs.item.forEach($item => {              $item.style.transform = 'translateX(0px)';            });          },          // 向左/右方向切换 indexOffset 个 swipe-item          next(indexOffset, isCorrect) {            isCorrect ? this.resetItems() : this.moveItems(indexOffset);            this.index += indexOffset;            this.offset = -this.index * this.itemWidth;          },          autoplay() {            this.player = setInterval(() => {              this.duration = 0;              this.correctIndex();              // 30ms延时是为了屏蔽 reset 过程中的过渡动画              setTimeout(() => {                this.duration = 500;                this.next(1);              }, 30);            }, 1000);          },        },      });    </script>  </body></html>

代码已发布在 github 上,欢迎大家提 issue 交流。

说明
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » Vue 实现无缝轮播

发表回复