likes
comments
collection
share

工具-巧用数组滚动实现复杂轮播

作者站长头像
站长
· 阅读数 9

我又来做轮播了,感觉对轮播有着很强的执念。

在上次的一个轮播中使用到了一个很小的单行方法,并没有将之转换为一个独立的函数,后来在评论里面回答小伙伴的评论的时候又关注到了这里,对这个单行方法又多看了一下,整理了成了一个常用的基础工具函数。

数组滚动

首先介绍一下我所说的数组滚动,数组滚动的就是让数组滚动起来,不知道大家有没有遇到这样的需求:

[1,2,3] => [2,3,1] => [3,1,2]

一般来说在做游戏或者做动画效果的时候会用到。

let array = [0, 1, 2, 3, 4];

/**
 * 数组滚动
 * @param {any[]} arr 需要被操作的数组
 * @param {*} times
 * @returns
 */
const rollTimes = (arr = [], times = 1) => {
  // 异常参数处理
  if (!Array.isArray(arr)) {
    console.error(
      "rollTimes(any[], number) 入参错误! 应该是 array 类型,但是收到一个",
      typeof arr,
      "类型。"
    );
    return;
  }

  // 深拷贝数据,避免对源数据造成影响
  let result = JSON.parse(JSON.stringify(arr));

  /* 如果滚动次数为0,返回源数据的拷贝体,即本方法可以实现单层数据的深拷贝 */
  if (times === 0) {
    result = result;
    /* 如果滚动次数小于0,则向左滚动,即第1个变成最后一个,其他依次向左移动一个 */
  } else if (times < 0) {
    for (let i = 0; i > times; i--) {
      result.push(result.shift());
    }
    /* 如果滚动次数大于0,则向右滚动,即最后一个变成第一个,其他依次向左移动一个 */
  } else {
    for (let i = 0; i < times; i++) {
      result.unshift(result.pop());
    }
  }

  return result;
};

console.log(rollTimes(123));

console.log(rollTimes(array, 1));
// [ 4, 0, 1, 2, 3 ]

console.log(rollTimes(array, -1));
//[ 1, 2, 3, 4, 0 ]

如代码所示,方法的核心是 result.push(result.shift())result.unshift(result.pop()) 这两个单行代码。

需要确认的是 result.shift() 和 result.pop() 方法会返回被移除的项,然后被 push 和 unshift 接收,因为push 和 unshift 方法会修改原始数据,所以这里进行了简单的深拷贝数据。

最终返回滚动对应次数的结果。

实际使用

分析以后感觉这个方法和轮播很搭,一些复杂的轮播,可以结合这个滚动函数很容易的就实现。

效果图如下:

工具-巧用数组滚动实现复杂轮播

源码奉上:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>轮播页面</title>
    <style>
      * {
        margin: 0;
        padding: 0;
      }

      #wrap {
        height: 500px;
        width: 1260px;
        border: 1px solid #eee;
        margin: 0 auto;
        position: relative;
        perspective: 800px; /* 景深 */
        transform-style: preserve-3d; /* 让我的元素成3D在舞台上呈现 */
      }
      .wrap-item {
        position: absolute;
        height: 250px;
        width: 20%;
        /* border: 1px solid #000; */
        top: 5%;

        -webkit-box-reflect: below 0px -webkit-gradient(linear, left top, left
              bottom, from(transparent), to(rgba(250, 250, 250, 0.5)));
        background-size: auto 100%;
        background-repeat: no-repeat;
        background-position: center;
        transform-origin: 50% 50% -1000px;

        transition: linear 0.5s;
        left: 40%;
      }
    </style>
  </head>
  <body>
    <div id="wrap"></div>
  </body>

  <script>
    /**
     *
     * @param {any[]} arr 需要被操作的数组
     * @param {*} times
     * @returns
     */
    const rollTimes = (arr = [], times = 1) => {
      // 异常参数处理
      if (!Array.isArray(arr)) {
        console.error(
          'rollTimes(any[], number) 入参错误! 应该是 array 类型,但是收到一个',
          typeof arr,
          '类型。'
        )
        return
      }

      // 深拷贝数据,避免对源数据造成影响
      let result = JSON.parse(JSON.stringify(arr))

      /* 如果滚动次数为0,返回源数据的拷贝体,即本方法可以实现单层数据的深拷贝 */
      if (times === 0) {
        result = result
        /* 如果滚动次数小于0,则向左滚动,即第1个变成最后一个,其他依次向左移动一个 */
      } else if (times < 0) {
        for (let i = 0; i > times; i--) {
          result.push(result.shift())
        }
        /* 如果滚动次数大于0,则向右滚动,即最后一个变成第一个,其他依次向左移动一个 */
      } else {
        for (let i = 0; i < times; i++) {
          result.unshift(result.pop())
        }
      }

      return result
    }

    class LoopEvent {
      constructor(id, arr, styleList) {
        this.$wrap = document.getElementById(id)
        this.dataList = arr
        this.styleList = styleList
        this.wrapList = []
        this.initWrap()
      }

      initWrap() {
        this.initItems()
        this.setStyles()
      }

      setStyles() {
        this.wrapList.forEach((item, i) => {
          Object.keys(this.styleList[0]).forEach((s) => {
            item.style[s] = this.styleList[i][s]
          })
        })
      }

      initItems() {
        console.log(this.dataList)
        this.dataList.forEach((ele) => {
          const item = document.createElement('div')
          item.className = 'wrap-item'
          item.style.backgroundImage = 'url("' + ele.image + '")'
          this.$wrap.appendChild(item)
          this.wrapList.push(item)
        })
      }

      animate(type) {
        this.styleList = type
          ? rollTimes(this.styleList, -1)
          : rollTimes(this.styleList, 1)
        this.setStyles()
      }
    }

    const wrapLoop = new LoopEvent(
      'wrap',
      [1, 2, 3, 4, 5, 6, 7].map((item) => ({
        id: item,
        image: `./static/img/${item}.jpg`,
      })),
      [
        {
          transform: 'rotateY(-45deg)',
        },
        {
          transform: 'rotateY(-30deg)',
        },
        {
          transform: 'rotateY(-15deg)',
        },
        {
          transform: 'rotateY(0deg)',
        },
        {
          transform: 'rotateY(15deg)',
        },
        {
          transform: 'rotateY(30deg)',
        },
        {
          transform: 'rotateY(45deg)',
        },
      ]
    )

    const timer = setInterval(() => {
      wrapLoop.animate(false)
    }, 2000)
  </script>
</html>

通过查看 html 不难看出,动画核心是 LoopEvent 的第三个 参数 styleList,通过滚动 styleList 然后重新赋值给每个滚动项,然后经由 transition 过渡,就可以实现复杂的轮播啦!

首先要把公共的样式在 style 里面写好,然后在数组里面只保留 每个的特殊样式,直接组成复杂轮播的基础结构,然后就滚动样式数组。

当然这个方案也有弊端,就是每一帧的动画效果完全由 transition 来控制,我们是无法去逐帧操作的。如果需要用到这么细的操作则需要在滚动的方法里面进行计算了。

后记

通过这样设计,就实现了动画和数据的解耦。当然,这个只是个demo,真实场景会增加一些复杂的元素和交互的按钮。但是随着网上的资源越来越丰富,已经很少有人会一行一行的去写轮播了,唉!

现在的工作也是,一直纠结于 curd ,一些css3的属性都不会用了,本文里面用到的景深、3D 转换都不会玩了。

以后有时间再搞一下3D动画方面的小玩意。