卡片滚动

517 阅读4分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第2天

实现的功能

  1. 自动滚动,一个卡片一个卡片的滚动

  2. 无限滚动,无限循环滚动

  3. 左右箭头点击滚动,一个卡片一个卡片的滚动

  4. 鼠标拖拽滚动

  5. 下面的按钮点击切屏

6.鼠标移入停止滚动,移出继续滚动

依赖的第三方库

  1. vue.js
  2. jquery.js

布局

  1. html:左边切换箭头、中间卡片滚动区域(卡片和下面的切屏按钮)、右边切换箭头
<div id="app" class="fd-card-box" ref="cardBox">
  <!-- 左边的三个箭头 可以不要 -->
  <div class="fd-switch-box left" @click="gotToPrev">
    <span class="fd-arrow fd-arrow1"></span>
    <span class="fd-arrow fd-arrow2"></span>
    <span class="fd-arrow fd-arrow3"></span>
  </div>
  <!-- 中间主要部分 -->
  <!-- 需要移动的盒子 -->
  <div class="fd-list-main">
    <ul  id="List"
        ref="List"
        class="fd-list-content">
      <!-- 卡片 -->
      <li v-for="(item,index) in cardList"
          :key="item.code + index"
          :class="['fd-list-item',
                  {'fd-left-model':index<startIndex-1},
                  {'fd-left-model':index===startIndex-1},
                  {'fd-center-model':index===startIndex||index===startIndex+1},
                  {'fd-right-model':index===startIndex+2},
                  {'fd-right-model':index>startIndex+2}
                  ]"
          @click.stop ="changeCenter(index)">
        <p class="fd-title">{{item.name}}</p>
        <div class="fd-content" :class="'fd-code-'+item.code">内容{{index + 1}}</div>
      </li>
    </ul>
    <!-- 翻页 -->
    <ul class="fd-fy-list">
      <li  v-for="item in screens"
          @click.stop="goToPage(item)"
          :class="['fd-fy-list-li', activePage===item ? 'active' : '']"
          :key="item">
      </li>
    </ul>
  </div>
  <!-- 右边的三个箭头 -->
  <div class="fd-switch-box right"  @click="goToNext">
    <span class="fd-arrow fd-arrow1"></span>
    <span class="fd-arrow fd-arrow2"></span>
    <span class="fd-arrow fd-arrow3"></span>
  </div>
</div>
  1. 样式
  • 中间区域的外框需要设置视距
  • 中间内容区域需要3d旋转
  • 左右两边卡片需要旋转
  • 左右两边的卡片样式使用样式实现,不必过多考虑
/* 主要样式 */
.fd-card-box {
    display: flex;
}
  1. 左右两边箭头样式代码
/* 箭头 */
.fd-switch-box {
  position: relative;
  width: 250px;
  height: 170px;
  cursor: pointer;
}

.fd-arrow {
  position: absolute;
  display: inline-block;
  height: 100%;
  vertical-align: top;
  background-position: 0 43px;
  background-repeat: no-repeat;
}

.fd-arrow3 {
  right: -10px;
  width: 88px;
  background-image: url("../img/switchBtn/icon02.png");
}

.fd-arrow2 {
  right: 70px;
  width: 65px;
  background-image: url("../img/switchBtn/icon03.png");
}

.fd-arrow1 {
  right: 133px;
  width: 54px;
  background-image: url("../img/switchBtn/icon04.png");
  background-position: 0 40px;
}

.left {
  text-align: right;
}

.right {
  text-align: left;
  transform: rotateY(180deg);
}

.animate .fd-arrow {
  animation-duration: 3s;
  animation-iteration-count: infinite;
  animation-timing-function: linear;
}

.animate .fd-arrow3 {
  animation-name: opacity3;
}

@keyframes opacity3 {
  0% {
    opacity: 1;
  }
  
  40% {
    opacity: 0;
  }
  
  80% {
    opacity: 1;
  }
}

.animate .fd-arrow2 {
  animation-name: opacity2;
}

@keyframes opacity2 {
  20% {
    opacity: 1;
  }
  
  50% {
    opacity: 0;
  }
  
  90% {
    opacity: 1;
  }
}

.animate .fd-arrow1 {
  animation-name: opacity1;
}

@keyframes opacity1 {
  40% {
    opacity: 1;
  }
  
  60% {
    opacity: 0;
  }
  
  100% {
    opacity: 1;
  }
}
  1. 中间区域样式
/* 中间区域 */
.fd-list-main {
    flex: 1;
    height: 100%;
    overflow: hidden;
    font-size: 0;
    white-space: nowrap;

    /* 设置视距离为1000 */
    perspective: 1000px;
    user-select: none;
}

.fd-list-content {
    position: relative;
    left: 0;
    width: 0;
    height: 198px;

    /* //设置旋转为3d旋转 */
    transform-style: preserve-3d;
}

.fd-list-item {
    display: inline-block;
    margin: 0 6px;
    padding-top: 20px;
    width: 300px;
    height: 100%;
    overflow: hidden;
    color: #fff;
    font-size: 30px;
    text-align: center;
    vertical-align: top;
    background: url("../img/card/card-bg.png") center no-repeat;
    cursor: pointer;
}

.fd-left-model {
    position: relative;
    padding: 0 20px;

    /* //设置y轴的旋转角度 */
    transform: rotateY(336deg) rotateZ(2deg) scale(1.41, 1);

    /* //设置旋转的轴心 */
    transform-origin: right;
}

.fd-right-model {
    position: relative;
    padding: 0 20px;

    /* //设置y轴的旋转角度 */
    transform: rotateY(24deg) rotateZ(-2deg) scale(1.41, 1);

    /* //设置旋转的轴心 */
    transform-origin: left;
}

.fd-center-model {
    position: relative;
    width: 350px;
    height: 100%;
    background-image: url("../img/card/card-bg-big.png");
}

.fd-title {
    color: #fbae03;
    font-size: 16px;
    font-weight: bold;
}

.fd-center-model .fd-title {
    font-size: 18px;
}

.fd-right-model .fd-title,
.fd-left-model .fd-title {
    padding-top: 24px;
}

.fd-fy-list {
    height: 30px;
    font-size: 0;
    text-align: center;
}

.fd-fy-list-li {
    display: inline-block;
    margin: 14px 16px;
    width: 64px;
    height: 8px;
    vertical-align: top;
    border-radius: 4px;
    background-color: #209cff;
    cursor: pointer;
}

.fd-fy-list-li.active {
    background: linear-gradient(to left, #209cff, #68e0cf);
}

功能实现的步骤

  1. 自动滚动
  • 使用定时器setInterval;定义一个记录卡片位置的参数,根据定时器自动加1;然后执行滚动
  1. 无限滚动
  • 无限滚动就需要不停的往后面追加元素(需要删除前面的元素,不然DOM就越来越多),这里有一个技巧就是执行动画使用animate;不然删除元素再移动元素的过程就会表现出来
  1. 左右箭头点击滚动;直接执行滚动方法就好了
  2. 鼠标拖拽滚动;根据鼠标移动的距离,判断是左移还是右移就可以了
  3. 下面的按钮点击切屏,这个将移动的距离设置为一屏的距离就可以了
(function () {
  const vm = new Vue({
    el:'#app',
    data () {
      return {
        cardList:[{
          name:'卡片1',
          code:'card1'
        },{
          name:'卡片2',
          code:'card2'
        },{
          name:'卡片3',
          code:'card3'
        },{
          name:'卡片4',
          code:'card4'
        },{
          name:'卡片5',
          code:'card5'
        },{
          name:'卡片6',
          code:'card6'
        },{
          name:'卡片7',
          code:'card7'
        },{
          name:'卡片8',
          code:'card8'
        }],
        // 最左侧显示的卡片
        startIndex:1,
        // 展示的是第几页
        activePage:1,
        // 一共几屏
        screens:0,
        // 定时器,每隔5秒切换一屏
        timer:null
      }
    },
    created() {
      // 初始化一些数据
      this.initData();
    },
    mounted () {
      // 初始化定时器
      this.initTime();
      // 绑定鼠标移动事件
      this.mouseMove();
    },
    methods: {
      // 初始化一些数据
      initData() {
        // 数据的长度
        this.cardLength = this.cardList.length;
        // 每一个li的宽度
        this.oneLiW = 348;
        // 每屏展示4个
        this.everyScreenNum = 4;
        // 展示几屏
        this.screens = Math.ceil(this.cardLength / this.everyScreenNum);
      },
      // 初始化定时器
      initTime() {
        this.$refs.cardBox.classList.add('animate');
        const time = 3500;
        this.timer = setInterval(() => {
          this.goToNext();
        }, time);
      },
      // 下布局移入事件
      mouseoverBottom() {
        clearInterval(this.timer);
        this.$refs.cardBox.classList.remove('animate');
      },
      // 绑定鼠标移动事件
      mouseMove() {
        const _this = this;
        const dom = this.$refs.List;
        // 是否按下
        let isDown = false;
        // 横向移动的距离
        let x = 0;
        // 鼠标移动的距离
        let nl = 0;
        dom.onmousedown = function (e) {
          isDown = true;
          // 获取x坐标和y坐标
          x = e.clientX;
          nl = 0;
        };
        dom.onmousemove = function (e) {
          if (isDown === false) {
            return;
          }
          const nx = e.clientX;
          nl = nx - x;
        };
        dom.onmouseup = function () {
          isDown = false;
          // 鼠标抬起的时候根据移动的正负数判断是向左移动还是向右移动
          
          // 向右移动
          if (nl>0) {
            _this.gotToPrev();
          } else if (nl<0) {
            _this.goToNext();
          }
        };
      },
      gotToPrev(){
        // 如果最左侧显示的图形不为第一个,直接翻滚
        if (this.startIndex > 1) {
          this.startIndex--;
        } else {
          // 如果显示的是第一个,把最后一个复制到前面
          const lastItem = this.cardList.slice(this.cardLength - 1);
          this.cardList.unshift(lastItem[0]);
          // 设置bottomList的left值为-312
          $('#List').css('left', `-${this.oneLiW}px`);
          this.$nextTick(() => {
            this.cardList = this.cardList.slice(0, this.cardLength);
          });
        }
        this.scrollInto();
      },
      goToNext(){
        this.startIndex++;
        this.toNextLi();
      },
      goToPage(item){
        const _index = (item - 1) * this.everyScreenNum + 1;
        if (_index + this.everyScreenNum> this.cardLength) {
          this.startIndex = this.cardLength - this.everyScreenNum + 1;
        } else {
          this.startIndex = _index;
        }
        this.toNextLi();
      },
      toNextLi(){
        // 如果后面剩不到一屏
        if (this.startIndex === (this.cardList.length - this.everyScreenNum + 1)) {
          // 复制第一个到最后一个
          const firstItem = this.cardList.slice(0, 1);
          this.cardList.push(firstItem[0]);
          // 设置left往前一点这样才有动画
          const left = this.oneLiW * (this.startIndex - 3);
          $('#List').css('left', `-${left}px`);
          this.startIndex--;
          this.$nextTick(() => {
            this.cardList = this.cardList.slice(1, this.cardList.length);
          });
        }
        this.scrollInto();
      },
      scrollInto() {
        // 设置当前高亮的页码
        this.activePage = Math.ceil((this.startIndex + 2) / this.everyScreenNum);
        const dom = $('#List');
        const left = this.oneLiW * (this.startIndex - 1);
        dom.animate({left:`${-left}px`});
      }
    },
    beforeDestroy() {
      this.mouseoverBottom();
    }
  })
})()

总结

  • 实现功能的能力就是将功能拆分的能力
  • 需要无限滚动的都需要注意删除DOM元素的时候要将动画暂停
  • 这里的视觉效果主要使用transform的3d效果(兼容性差)
  • 移动的过程中只需要考虑当前展示的卡片就行,样式用css控制就好