轮播图、无限轮播图、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 效果就更好看了🤔

相关推荐
WeiXiao_Hyy11 分钟前
成为 Top 1% 的工程师
java·开发语言·javascript·经验分享·后端
吃杠碰小鸡27 分钟前
高中数学-数列-导数证明
前端·数学·算法
kingwebo'sZone33 分钟前
C#使用Aspose.Words把 word转成图片
前端·c#·word
xjt_09011 小时前
基于 Vue 3 构建企业级 Web Components 组件库
前端·javascript·vue.js
我是伪码农1 小时前
Vue 2.3
前端·javascript·vue.js
夜郎king1 小时前
HTML5 SVG 实现日出日落动画与实时天气可视化
前端·html5·svg 日出日落
辰风沐阳2 小时前
JavaScript 的宏任务和微任务
javascript
夏幻灵2 小时前
HTML5里最常用的十大标签
前端·html·html5
冰暮流星3 小时前
javascript之二重循环练习
开发语言·javascript·数据库
Mr Xu_3 小时前
Vue 3 中 watch 的使用详解:监听响应式数据变化的利器
前端·javascript·vue.js