轮播图、无限轮播图、3d无限轮播图

前言

以前写过移动端的无线轮播图,可是前端的还没有写过,突然想写一个前端的无线轮播图,然后本篇从普通轮播图到无限轮播图,介绍一下实现思路

ps:编写过程中发现,这里的无限轮播图不能像移动端那样使用三张图片无限连播,因为基本很少缓存的缘故,为了避免图片资源频繁销毁重建下载,因此还是要以获取全部资源的前提下,实现无限轮播,只是看着内容多一些一些,实际上也没有太繁琐

ps2:轮播图的实现思路很多,不要被本文限制住思路,本文只是提供一种方案思路罢了

以前都是是用 react 编写,本文就是用 vue3 来编写吧

demo参考地址

基础轮播图

基础轮播图,也就是点击左箭头右箭头实现切换,同时也支持跳转到任意一幅图

先看看实现效果吧,虽抽,但是可用哈😂

如上所示,自己用css编写了两个切换的按钮三角形,中间区域这是用来显示轮播图的,主要实现思路如下

  1. 通过编写基本布局,其中轮播图的父布局(item_container)不固定长度,轮播图平铺父布局不换行即可,这样 item_container的父布局就是一个大长条
  2. 祖布局固定一个轮播图长度,并设置 overflow:hidden 超出隐藏,因此默认只会显示第一个,其他超出布局被隐藏
  3. 根据索引设置 父布局(item_container) left 偏移,每增加一个则向左偏移一个轮播图长度,并且是反向,需要注意的是,需要设置 transition 过渡动画,不然不好看 transition: left 0.4s ease-in-out
  4. 需要注意边界
js 复制代码
//背景
<div class="bkg">
  //轮播图内容父布局,并设置过渡动画 transition
  <div class="item_container" :style="{ left: data.left }">
    //轮播图
    <div v-for="(item, index) in data.list" class="item_view" v-bind:key="index">
      {{ item }}
    </div>
  </div>
  //外面的两个切换按钮
  <div class="left_arrow" @click="onBaseLeft"></div>
  <div class="right_arrow" @click="onBaseRight"></div>
</div>

js 的实现逻辑如下所示,核心逻辑就是 layout 了,主要控制 item_container 的 left 即可,由于是根据索引跳转,因此天然支持跳转到某一个,并且轮播图本身就是平铺有序的,过渡动画也很自然

js 复制代码
import { shallowReactive } from 'vue'

const data = shallowReactive({
  list: [1, 2, 3, 4, 5, 6, 7, 8],
  index: 0,
  left: 'none',
})

//根据索引进行跳转
const Layout = (index: number) => {
  //超出边界的不处理
  if (index < 0 || index > data.list.length - 1) return
  //根据索引设置 left 偏移,记得反向偏移
  data.left = `-${index * 600}px`
  //更新索引
  data.index = index
}

//向左索引 - 1
const onLeft = () => {
  Layout(data.index - 1)
}

//向右索引 + 1
const onRight = () => {
  Layout(data.index + 1)
}

//跳转指定索引
const jumpToIndex = (index: number) => {
  Layout(index)
}

无限轮播图

长得跟基础轮播图一模一样,唯一的区别就是没有边界,可以无限切换,首尾连贯

这个实现逻辑就和基础轮播图不一样了,基础轮播图可以使用父布局取巧,可是这个无线轮播图就行了,只能处理好子图才能更好实现无缝衔接,甚至为了避免连续切换过快还要加上节流函数

布局类似,但由于不能取巧,去掉了父布局,留下了祖布局,过程略复杂,简述一下实现逻辑

  1. 设置父布局超出隐藏,并设置一个 position,避免内部绝对布局相对位置问题
  2. 子布局通过绝对布局,设置好切换箭头位置和轮播图的基础位置,轮播图默认全部平铺重叠到整个父布局,后续通过重新布局来实现平铺
  3. 根据索引将子布局实现平铺,这里直接使用 transform 效果,不使用left了,也减少设置其他属性了,通过设置 translateX 将子图全部平铺完毕,同时设置好 transition 以生成动画
  4. 平铺过程中需要完成无线轮播,因此,中间图的左右两侧必然需要子图,因此我们只需要知道中前后三张图即可,设置调整好好他们的位置,并处理好边界即可
  5. 由于没有取巧,也没有额外增加元素,仅仅通过内部元素调换,因此动画切换过程可能会出现覆盖问题,因此设置一下层级即可,这里通过设置z-index 来改变层级(需要在同一个层叠上下文),因此父布局也需要设置好
  6. 由于这里的变换并不是纯平铺,算是动态变换,因此需要避免切换过快,否则可能会出现动画过渡差,出现白屏等不自然现象,可以通过节流函数 throttle 来解决(为什么不使用防抖,可以自行思考他们的特色)

ps:为何要要给所有元素设置 transition,而不是切换动画的那两三个设置动画,因为从一个跳转到另外一个时,如果中间的没有设置动画,则会出现白屏或者其他不自然的过渡现象(element的轮播图就有这个问题,优化过渡有时候也会出现问题)

js 复制代码
//父布局超出隐藏
<div class="bkg">
  //轮播图
  <div
      v-for="(item, index) in data.list"
      class="item_view"
      :class="{
        is_active: data.activeIndex.includes(index),
      }"
      :style="{ transform: data.transforms[index] }"
      v-bind:key="index">
     <div class="flex w-full h-full justify-center items-center">{{ item }}</div>
  </div>
  //切换箭头
  <div class="left_arrow" @click="onLeft"></div>
  <div class="right_arrow" @click="onRight"></div>
</div>

js实现逻辑如下所示,onLayout 是核心变换函数,throttle 是简易的节流函数(足够使用),onMounted 中调用 onLayout 是为了处理刚开始的重叠布局问题,以便于显示正常和后续切换动画问题

js 复制代码
import { onMounted, ref, shallowReactive } from 'vue'

//声明对象用于渲染内容、更新轮播图
const data = shallowReactive<{
  list: unknown[]
  index: number
  activeIndex: number[]
  transforms: string[]
}>({
  list: [1, 2, 3, 4, 5, 6, 7, 8],
  index: -1,
  activeIndex: [0],
  transforms: [],
})

//节流函数专属
const timeout = ref<number>()

const throttle = (fn: () => void) => {
  if (timeout.value !== undefined) return
  fn()
  timeout.value = setTimeout(() => {
    timeout.value = undefined
  }, 400)
}

//核心布局逻辑,通过传递切换索引,实现切换
const onLayout = (current: number) => {
  //数量为1时无需布局和切换,能够正常动画的最少布局数量则是3
  //数量为2时需要增加非常多的逻辑,但只需要利用面向对象思想,将其转化为大与3的数量即可
  //转化过程为了避免衔接不畅,只需要在后面拼接自己即可,这样相当于保存了同一个指针,内容也指向同一个对象,更新内容对象也会同时变化
  let len = data.list.length
  if (len < 2) {
    return
  } else if (len === 2) {
    data.list = data.list.concat(data.list)
    len = 4
  }
  //获取前中后的索引,能够让当前和下一次切换更为自然
  const pre = (current - 1 + len) % len
  current = (current + len) % len
  const next = (current + 1 + len) % len
  const transform: string[] = []
  //设置好当前三个的transfrom,同时其他的也平铺下去,这样过渡也更加自然
  for (let idx = 0; idx < len; idx++) {
    let index = idx
    if (idx === pre) {
      index = -1
    } else if (idx === current) {
      index = 0
    } else if (idx === next) {
      index = 1
    } else {
      index = idx - current
    }
    //其他的也根据当前索引平铺,这样跳转切换时也更加自然
    transform.push(`translateX(${index * 100}%)`)
  }
  //设置活跃索引用于设置z-index,这样动画切换更加自然,不会出现其他元素从上面飞过的问题
  data.activeIndex = data.index >= 0 ? [current, data.index] : [current]
  data.transforms = transform
  data.index = current
}

const onLeft = () => {
  throttle(() => onLayout(data.index - 1))
}

const onRight = () => {
  throttle(() => onLayout(data.index + 1))
}

//跳转指定索引
const jumpToIndex = (index: number) => {
  throttle(() => onLayout(index))
}

//开始布局,避免重叠和动画问题
onMounted(() => {
  onLayout(0)
})

3d无限轮播图

3d无线轮播图和正常无线轮播图类似,只不过中间的不占满屏幕,两边的略小,当然实际效果可以根据自己需求更新,这里只做一个参考(不好看的换个图,也许就好看了🤣)

实现效果如下所示

看到上面的图可以看到和普通的无线轮播图的区别

  • 多了两张图,需要再额外准备两张图,方便两侧过渡(自己想准备一张自行处理也没事)
  • 中间的图和两边图大小、透明度不一样,因此还需要设置好偏移、大小、透明度

话不多说直接上代码

js 复制代码
<div class="bkg">
  //这里多设置了一个父布局,位的就是子布局好居中,直接铺满,两侧越界展示就行了,祖父节点超出隐藏完事
  <div class="item_container">
    //轮播图子节点仍然是通过设置transfrom、opacity实现动画,同时 is_active 避免动画效果不好
    <div
      v-for="(item, index) in data.list"
      class="item_view"
      :class="{
        is_active: data.activeIndex === index,
      }"
      :style="{ transform: data.transforms[index], opacity: data.opacitys[index] }"
      v-bind:key="index"
    >
      <div class="flex w-full h-full justify-center items-center">{{ item }}</div>
    </div>
  </div>
  <div class="left_arrow" @click="onLeft"></div>
  <div class="right_arrow" @click="onRight"></div>
</div>

js代码仍然是核心的 onLayout 逻辑,其他的和 无线轮播图一样,这里就只介绍 onLayout

js 复制代码
import { onMounted, ref, shallowReactive } from 'vue'

const timeout = ref<number>()

const data = shallowReactive<{
  list: unknown[]
  index: number
  activeIndex: number
  transforms: string[]
  opacitys: string[]
}>({
  list: [1, 2, 3, 4, 5, 6, 7, 8],
  index: -1,
  activeIndex: 0,
  transforms: [],
  opacitys: [],
})

const throttle = (fn: () => void) => {
  if (timeout.value !== undefined) return
  fn()
  timeout.value = setTimeout(() => {
    timeout.value = undefined
  }, 400)
}

const onLayout = (current: number) => {
  //需要注意的是需要的节点从三个增加到了5个,因此也需要额外处理
  let len = data.list.length
  if (len < 2) {
    return
  } else if (len < 5) {
    //小于5个就填充,直到大于等于五个,只能一组一组填充,否则会不连贯(当首尾出现一样的是不是很难受)
    //一般用到了3d轮播图的,应该内容不会太少,无需考虑两个变6个问题🤣
    const list = [...data.list]
    for (let idx = len; idx < 5; idx += len) {
      data.list.push(...list)
    }
    len = data.list.length
  }
  //额外增加两个索引,并处理好越界问题
  const prepre = (current - 2 + len) % len
  const pre = (current - 1 + len) % len
  current = (current + len) % len
  const next = (current + 1 + len) % len
  const nextnext = (current + 2 + len) % len
  //需要同时设置 transform 和 透明度 opacity 
  const transforms: string[] = []
  const opacitys: string[] = []
  for (let idx = 0; idx < len; idx++) {
    const index = idx
    let transform = ''
    let opacity = '0'
    //设置偏移透明度,越近越大不透明度越高
    if (index === prepre) {
      transform = `translateX(-100%) scale(0.6)`
      opacity = '0'
    } else if (idx === pre) {
      transform = `translateX(-60%) scale(0.8)`
      opacity = '0.7'
    } else if (idx === current) {
      transform = `translateX(0) scale(1)`
      opacity = '1'
    } else if (idx === next) {
      transform = `translateX(60%) scale(0.8)`
      opacity = '0.7'
    } else if (idx === nextnext) {
      transform = `translateX(100%) scale(0.6)`
      opacity = '0'
    } else {
      transform = `translateX(${(idx - current) * 100}%)`
      opacity = '0'
    }
    transforms.push(transform)
    opacitys.push(opacity)
  }
  data.activeIndex = current
  data.transforms = transforms
  data.opacitys = opacitys
  data.index = current
}

const onLeft = () => {
  throttle(() => onLayout(data.index - 1))
}

const onRight = () => {
  throttle(() => onLayout(data.index + 1))
}

const jumpToIndex = (index: number) => {
  throttle(() => onLayout(index))
}

onMounted(() => {
  onLayout(0)
})

最后

轮播图就介绍到这里了,如果想作出更好看的 3d 效果,实际上只需要设置好 transform 就行了,不必要的话,甚至不做无限滚动的也行,只需要设置一下旋转阴影,那样 3d 效果就更好看了🤔

相关推荐
Fantasywt3 小时前
THREEJS 片元着色器实现更自然的呼吸灯效果
前端·javascript·着色器
IT、木易4 小时前
大白话JavaScript实现一个函数,将字符串中的每个单词首字母大写。
开发语言·前端·javascript·ecmascript
Mr.NickJJ4 小时前
JavaScript系列06-深入理解 JavaScript 事件系统:从原生事件到 React 合成事件
开发语言·javascript·react.js
张拭心6 小时前
2024 总结,我的停滞与觉醒
android·前端
念九_ysl6 小时前
深入解析Vue3单文件组件:原理、场景与实战
前端·javascript·vue.js
Jenna的海糖6 小时前
vue3如何配置环境和打包
前端·javascript·vue.js
Mr.NickJJ6 小时前
React Native v0.78 更新
javascript·react native·react.js
星之卡比*6 小时前
前端知识点---库和包的概念
前端·harmonyos·鸿蒙
灵感__idea6 小时前
Vuejs技术内幕:数据响应式之3.x版
前端·vue.js·源码阅读
烛阴6 小时前
JavaScript 构造器进阶:掌握 “new” 的底层原理,写出更优雅的代码!
前端·javascript