一文搞懂卡片、堆叠式所有轮播图原理-JavaScript实现轮播图

        大厂各种花哨轮播图你是措手无策,我的教程你是不屑一顾,相信看完本篇文章,能带你掌握各种花哨的轮播图,先放上之前写好的效果图,代码在文章最后

一、早期常用实现

大家在早期实现轮播图时,千篇一律都是采用绝对定位,通过js不断去调整其left或者translateX属性来实现轮播,但已经过时了,为什么过时?

1、先讲述下其原理

        需要不断调整left或translateX,然后再给出active激活类区别显示主轮播图

2、缺点

2-1、代码繁多,不易维护和阅读,无缝轮播总有小bug(空白区域、走马灯式回到首图、跳转首图时轮播时间变双倍)

2-2、计算式改变left等值进行移动,有1px左右的像素误差,会导致轮播时出现其他图片

2-3、某些时刻缩放网页时出现错乱,计算没有及时得到调整,导致轮播图轮播时错乱
2-4、不仅要直接操作style,且还要操作class

        有很多边界问题需要考虑且难以解决和优化,因此他不是我们本篇文章的主角,只是在这里给大家废话一下

为了杜绝这些迹象,我给大家提供一套通俗易懂的方案,极少的代码极高的质量完成轮播图

二、Class式实现轮播

原理:我们最终都会搭建轮播图的样式,每张图放在该有的位置,就由不同的class去控制,为何还要去操作style?我们直接对其class值进行调换,你就会发现元素位置也会跟着变化,没错,动态有序的对已有元素的class属性进行调换,移动,就可以实现轮播,强大且通俗易懂

class式轮播优点?

1、通俗易懂,代码量少,容易维护

2、天生即可无缝轮播!无需考虑各种繁琐的边界值!

3、完全可替代常规式(操作style去移动)轮播

四、直接开干

先简单搭建一个卡片式轮播图布局

        为了突出显示中间的主元素,我们将两边的元素进行了0.9倍的缩放,页面是搭建好了,但是怎么实现呢

<!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>Document</title>
  <style>
    .carousel {
      position: relative;
      height: 500px;
      background: #ccc
    }
    .item-1, .item-2, .item-3 {
      position: absolute;
      top: 2vw;
      width: 400px;
      height: calc(100% - 4vw);
      background: rgb(106, 175, 255)
    }
    .item-1 {
      left: 3vw;
      transform: scale(.9);
      z-index: 1
    }
    .item-2 {
      left: 50%;
      transform: translateX(-50%);
      z-index: 3
    }
    .item-3 {
      left: calc(100% - 3vw - 400px);
      transform: scale(.9);
      z-index: 2
    }
  </style>
</head>
<body>
  
  <div class="carousel">
    <div class="item-1">ITEM-1</div>
    <div class="item-2">ITEM-2</div>
    <div class="item-3">ITEM-3</div>
  </div>

</body>
</html>
//将写好的样式存入数组
const currentClass = ['item-1', 'item-2', 'item-3']

        现在我们可以编写一个初始函数和一个功能函数实现class属性的调换,先介绍一下稍后需要使用到的API,以下是操作数组的API

前置条件:请务必在item-1、2、3这三个标签上加上item类名,并只保留item类名方便我们统一收集Dom,加入两个按钮绑定我们的事件触发手动轮播

注意:你应该在你的item类加上过渡样式(transition),这样在轮播时才有动画,这是最重要的一步,此步骤不做演示

<div class="item">ITEM-1</div>
<div class="item">ITEM-2</div>
<div class="item">ITEM-3</div>

<div>
    <button onclick="onMove('left')">向左</button>
    <button onclick="onMove('right')">向右</button>
</div>
名称作用示例
push向末尾添加元素,返回当前数组长度push('1')、push(() => (['1', '2', '3']))
pop从末尾移除一个元素,返回被删除的元素pop()
unshift向头部添加元素,返回当前数组长度unshift('1')
shift从头部移除一个元素shift()
//使用数组保存好写好的class样式名
const currentClass = ['item-1', 'item-2', 'item-3']
//收集所有轮播子元素
const items = Array.form(document.querySelectorAll('.item'))

//我们通过一个参数控制数组的移动 减少相似功能的函数
function onMove(move = 'left') {
    if (move == 'right') {
        //数组依次向右移动一位 将尾部元素添加到头部即实现
        currentClass.unShift(currentClass.pop())
    } else {
        //数组依次向左移动一位 将头部元素添加到末尾即实现
        currentClass.push(currentClass.shift())
    }
    //对数组进行移动之后 我们调用编写好的功能函数 动态对子元素的class进行重新赋值
    resolveClass()
}

//收集dom 对其class重新赋值
function resolveClass() {

    items.forEach((el, index) =>  {
        const classWith = el.className.split(' ')[1]
        //说明已经存在class 如果不对其删除 会造成class共存出现bug
        if(currentClass.findIndex(classed => classed == classWith ) != -1) {
            el.classList.remove(classWith)
        }
        //重新添加class
        el.classList.add(currentClass[index])
    })
}

//立即执行一次函数 自动为轮播图子元素赋与相应class
resolveClass()

接着我们把item子元素的文字换成实打实的图片,并付上动画预览

卡片式轮播

从预览图看出我们的实现已经非常丝滑了,我们再来梳理一下核心原理

五、梳理核心原理

1、首先使用html+css搭建好卡片式轮播图布局

2、使用数组保存好不同位置的类名(左、中、右),通过左右移动数组使其元素顺序发生改变,上表已给出API

3、获取轮播子元素的关键类名(除了item外的item-1、item-2...),这才是决定位置的关键类名,然后依次删除位置(item-1、item-2...)类名、重新赋与所有轮播子元素决定位置的类名(也就是将item-1、item-2、item-3从原来的位置进行调换,依次调换给上一个dom元素)

4、将第2步与第3步操作进行绑定,通过第2步对数组改变顺序后调用第3步编写的函数使其同步,达到轮播的效果

六、细节问题

1、图片少于3张时?不用考虑图少的问题,3张图都不够用什么轮播,那图片大于3张呢?这才是问题的考虑

解决方案:

        直接在样式表加一个隐藏类名('.none'),层级设置最低,可以调整位置被主图遮住,看个人喜好,举个例子,假如我有10张图需要轮播,我们只固定了3张图的位置,剩下7张图需要统一添加none类名实现隐藏,那如何动态计算这个none类名的添加?通过合并数组,轮播图正常也是显示前三张图,完美隐藏多余的图

/**

const currentClass = ['item-1', 'item-2', 'item-3']

//收集所有轮播子元素
const items = Array.from(document.querySelectorAll('.item'))

*/
//需要用到上面两个收集好的变量,const不会被向上引用 因此我们要调换两行代码位置

//收集所有轮播子元素
const items = Array.from(document.querySelectorAll('.item'))

//通过items的长度减去固定的三个轮播样式再合并none 刚好得到10个类名供渲染
//通过...运算符合并数组 fill方法填充重复元素
const currentClass = ['item-1', 'item-2', 'item-3'
...Array.form(items.length - 3).fill('none')
]

注意:none实现隐藏请不要使用display: none,虽然使用display控制隐藏会提升性能,但我们悉知,一个元素设置display:none之后,再到显示的过程,任何过渡动画都将失效

2、动态生成小圆点

//生成小圆点
function createDot() {
    const root = document.querySelector('.carousel')

    root.insertAdjacentHTML('beforeend', `
        <ul class="carousel-dot-container">
            ${ items.map((elment, index) => `
                <li class="${ index == currentCss.indexOf('item-2') ? 'carousel-dot dot-active' : 'carousel-dot' }" onclick="onDotMove(${ index })"></li>
            `).join('') }
        </ul>
    `)
}

3、小圆点绑定的点击事件

        点击对应的小圆点切换主图

//小圆点点击事件
function onDotMove(index) {
    const dots = Array.form(document.querySelectorAll('.carousel-dot'))

    //计算主图类名的位置 item-2为主显示图 indexOf返回下标
    const isActive = currentCss.indexOf('item-2')
    
    //排他思想清除当前小圆点样式
    dots.forEach(el => el.classList.remove('dot-active'))

    //当前点击的小圆点得到激活样式
    dots[index].classList.add('dot-active')

    if(index > isActive) {
        //此条件成立 表示向左轮播 数组元素需要往左依次移动 使用splice方法
        currentCss.push(currentCss.splice(0, index - isActive))
    } else {
        //向右轮播 数组元素往右移动

        //应该移动的距离
        const isSteep = isActive - index
        currentCss.unshift(currentCss.splice(items.length - isSteep, items.length))
    }

    //调用之前编写好的class赋值函数
    resolveCss()
}

4、编写定时方法自动轮播

//自动轮播
function autoMove(steep = 3000) {
    setInterval(() => {
        onMove('left')
    }, steep)
}

autoMove()

七、现成代码即粘即用

上述代码比较简陋,要使用的话请自行组合,本人比较懒,下面提供一份已经写好的全自动堆叠轮播图,感兴趣的可以直接复制过去用

index.html

<!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>
    *{
      padding: 0;
      margin: 0
    }
    .carousel-card {
      margin-top: 50px;
      height: 400px
    }
    .carousel-card-item {
      background: blue;
      border-radius: .5vw;
      overflow: hidden
    }
    .carousel-card-item img {
      width: 100%
    }
  </style>
</head>
<body>

  <div class="carousel-card">
    <div class="carousel-card-item">
      <img src="https://img0.baidu.com/it/u=3798955928,4058999457&fm=253&fmt=auto&app=138&f=JPEG?w=889&h=500">
    </div>
    <div class="carousel-card-item">
      <img src="https://img2.baidu.com/it/u=3619185720,2241707230&fm=253&fmt=auto&app=138&f=JPEG?w=889&h=500">
    </div>
    <div class="carousel-card-item">
      <img src="https://img1.baidu.com/it/u=2695548791,2852480012&fm=253&fmt=auto&app=138&f=JPEG?w=750&h=500">
    </div>
    <div class="carousel-card-item">
      <img src="https://img0.baidu.com/it/u=1311709305,4030987719&fm=253&fmt=auto&app=138&f=JPEG?w=888&h=500">
    </div>
    <div class="carousel-card-item">
      <img src="https://img1.baidu.com/it/u=897637806,689703833&fm=253&fmt=auto&app=120&f=JPEG?w=800&h=500">
    </div>
    
    <!-- 多余的图片 需要隐藏起来 -->
    <div class="carousel-card-item">
      <img src="https://img2.baidu.com/it/u=1283117790,2359032005&fm=253&fmt=auto&app=138&f=JPEG?w=812&h=500">
    </div>
    <div class="carousel-card-item">
      <img src="https://img1.baidu.com/it/u=545794128,261263758&fm=253&fmt=auto&app=138&f=JPEG?w=890&h=500">
    </div>
    <div class="carousel-card-item">
      <img src="https://img1.baidu.com/it/u=3043799943,3338472581&fm=253&fmt=auto&app=120&f=JPEG?w=750&h=500">
    </div>
  </div>

  <div>
    <button onclick="cd.onMove('left')">向左</button>
    <button onclick="cd.onMove('right')">向右</button>
  </div>

  <script src="/carousel_card.js"></script>
</body>
</html>

carousel-card.js

//样式
const resolveStyle = () => { 
  const head = document.querySelector('head')

  if(head.querySelector('[carousel-card]')) {
    return
  } else {
    const styleCss = document.createElement('style') ; styleCss.setAttribute('carousel-card', true)
    styleCss.innerHTML = `
    .carousel-card {
      position: relative;
      transform-style: preserve-3d;
      min-height: 50vh;
      perspective: 1000px
    }
    .carousel-card > .carousel-card-item {
      position: absolute;
      width   : 35%;
      height  : calc(100% - 4vw);
      display : flex;
      justify-content : center;
      align-items : center;
      transition : .8s cubic-bezier(0.075, 0.82, 0.165, 1)
    }
    .carousel-card > .carousel-card-item.c-left {
      left: 15vw;
      z-index: 3;
      transform: rotateY(20deg) scale(.85);
      filter: blur(1.5px)
    }
    .carousel-card > .carousel-card-item.c-lefted {
      left: 2vw;
      z-index: 2;
      transform: rotateY(20deg) scale(.7);
      filter: blur(2px)
    }
    .carousel-card > .carousel-card-item.c-active {
      width: 45%;
      left: 50%;
      transform: translateX(-50%) translateZ(50px);
      z-index: 4
    }
    .carousel-card > .carousel-card-item.c-right {
      right: 15vw;
      z-index: 3;
      transform: rotateY(-20deg) scale(.85);
      filter: blur(1.5px)
    }
    .carousel-card > .carousel-card-item.c-righted {
      right: 2vw;
      z-index: 2;
      transform: rotateY(-20deg) scale(.7);
      filter: blur(2px)
    }
    .carousel-card-item.c-more {
      width: 45%;
      left: 50%;
      transform: translateX(-50%)
    }
    .carousel-card ul {
      position: absolute;
      bottom: 5vw;
      left: 50%;
      padding: .05vw;
      list-style: none;
      display: flex;
      justify-content: center;
      align-items: center;
      gap: 2vw;
      transform: translateX(-50%) translateZ(60px)
    }
    .carousel-card ul > li {
      width: .7vw;
      height: .7vw;
      background: black;
      border: solid .05vw white;
      border-radius: 50%;
      cursor: pointer;
      transition: .4s cubic-bezier(0.175, 0.885, 0.32, 1.275)
    }
    .carousel-card ul > li.dot-active, .carousel-card ul > li:hover {
      width: 2vw;
      height: .7vw;
      border-radius: .35vw;
      background: white
    }`
    //遍历其他属性 如浮动布局轮播图容器
    head.insertAdjacentElement('beforeend', styleCss)
  }
}

/** 轮播图类
 * 核心思想: 先使用css布置好堆叠式轮播图布局 使用js依次切换其class 是下一个卡片替换到上一个卡片即可
 * 前提是得附加transition过渡效果 否则很生硬导致没动画
 * **/
class CarouselCard { 
  constructor() {
    //构造时先渲染样式
    resolveStyle()

    //先获取Dom
    this.cardItem = Array.from(document.querySelectorAll('.carousel-card>.carousel-card-item'))

    //初始化排序
    this.cardArray = ['c-lefted', 'c-left', 'c-active', 'c-right', 'c-righted',
      ...new Array(this.cardItem.length - 5).fill('c-more')
    ]

    //执行某些事件
    this.init()
  }
  /**
   * 初始化函数 需要执行的一些前置操作
   */
  init() { 
    //依次赋予class属性
    const length = this.cardArray.length

    this.cardItem.forEach((element, index) => { 
      element.classList.add(this.cardArray[index])
    })

    this.createDot()

    this.clickDot()

    this.clickImg()
  }

  /**
   * 指定向轮播
   */
  onMove(orange = 'right') { 
    //使用数组特性 向末尾添加被移除的头部元素 以此达成轮播原理 再重新赋予class属性
    if (orange == 'left') {
      this.cardArray.push(this.cardArray.shift())
    } else { 
      this.cardArray.unshift(this.cardArray.pop())
    }
    //调用class切换事件重新轮播
    this.checkedClass()
  }

  /**
   * 重新赋予class属性 实现轮播核心方法
   */
  checkedClass() { 
    const length = this.cardArray.length

    this.cardItem.forEach((element, index) => { 
      const className = element.className.split(' ')[1]

      if (this.cardArray.findIndex(cless => cless == className) != -1) {
        element.classList.remove(className)
      }
      element.classList.add(this.cardArray[index])
    })

    //绑定小圆点事件
    checkedDot(this.cardArray.indexOf('c-active'))
  }

  /**
   * 生成小圆点
   */
  createDot() { 
    const cardRoot = document.querySelector('.carousel-card')
    const dotTools = document.createElement('ul')

    dotTools.insertAdjacentHTML('beforeend', `
    ${ this.cardArray.map((className) => 
      `
        <li class="${ className == 'c-active' ? 'card-dot dot-active' : 'card-dot' }"></li>
      `).join('') }
    `)
    
    cardRoot.appendChild(dotTools)
  }

  /**
   * 小圆点 点击事件
   */
  clickDot() { 
    const mirroDot = Array.from(document.querySelectorAll('.card-dot'))
  
    mirroDot.forEach((dot, index) => {
      dot.onclick = () => {
        const beforeSteep = this.cardArray.indexOf('c-active'), length = this.cardArray.length
        //向左轮播 需要记录上一个小圆点位置和当前点击位置 相减得出应该移动的距离 实际上是计算应当从数组第几位开始
        //到末尾的元素往头部添加 再运行class属性重新赋值函数实现向左轮播
        if (index > beforeSteep) {
          const isSteep = index - beforeSteep //应该移动的距离
          this.cardArray.unshift(...this.cardArray.splice(length - isSteep, length))
        } else { 
          const isSteep = beforeSteep - index //应该移动的距离
          this.cardArray.push(...this.cardArray.splice(0, isSteep))
        }

        this.checkedClass()

        checkedDot(index)
      }
    })
  }

  /**
   * 周边图片点击事件 将显示为主图
   */
  clickImg() { 
    const imgs = Array.from(document.querySelectorAll('.carousel-card-item'))
    const dots = Array.from(document.querySelectorAll('.card-dot'))

    imgs.forEach((el, index) => el.onclick = () => { dots[index].click() })
  }

  /**
   * 自动轮播 间隔毫秒 默认5秒换一次
   */
  start(steep = 5000) { 
    setInterval(() => { this.onMove() }, steep)
  }
  stop() { }
}

/**
 * 切换小圆点事件
 */
function checkedDot(index = 2) { 
  const mirroDot = Array.from(document.querySelectorAll('.card-dot'))

  //排它思想
  mirroDot.forEach((dot) => { if (dot.classList.contains('dot-active')) { dot.classList.remove('dot-active') } })

  mirroDot[index].classList.add('dot-active')
}

const cd = new CarouselCard()

cd.start()

结尾:顺带一提的是,使用面向对象(class)方式编写比较容易维护和易懂,下一篇文章有空的话给大家带一个用js实现网页抓包的文章

  • 6
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Unity中可以通过使用UI组件和动画来实现滑动层叠图片轮播展示。 1. 创建UI元素 首先,在场景中创建一个Canvas对象,并添加一个Raw Image组件作为背景图。然后,在Canvas下创建一个Scroll View对象,设置它的大小和位置,并添加一个Viewport作为子对象。 2. 添加图片 在Viewport下,可以添加多个Image对象作为图片集合。可以通过拖拽方添加,也可以通过使用代码动态添加。每个Image对象需要设置好对应的图片和位置,以便在滑动过程中能够正确显示。 3. 编写脚本 可以为Scroll View对象添加一个脚本来实现滑动层叠图片轮播展示。在脚本中,需要实现以下功能: - 获取所有的Image对象,并按照顺序排列。 - 根据滑动的方向(向左或向右),计算出需要显示的图片序号。 - 将需要显示的图片居中显示,并将其前后的图片分别以不同的大小和位置进行显示。 - 在滑动过程中,根据手指的移动距离和速度,计算出需要滑动的距离和时间,并使用动画实现滑动效果。 4. 实现手势控制 可以使用Unity提供的Input系统来实现手势控制。在脚本中,可以通过监听输入事件来获取手指在屏幕上的移动距离和速度,并根据这些信息来计算出需要滑动的距离和时间。 5. 实现自动播放 可以通过使用协程来实现自动播放功能。在脚本中,可以编写一个协程函数,定时切换下一张图片,并使用动画实现滑动效果。可以通过设置定时器来控制自动播放的时间间隔。 6. 完善UI效果 可以通过添加特效和动画来增强UI效果。例如,在图片滑动过程中可以添加渐变效果,以使过渡更加自然。还可以为每个Image对象添加阴影、边框等效果,以增强图片的视觉效果。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值