博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
原生js系列之无限循环轮播组件
阅读量:6094 次
发布时间:2019-06-20

本文共 11032 字,大约阅读时间需要 36 分钟。

前情回顾

在上一篇文章中,我们封装了一个DOM库(qnode),为了让大家直观地感受到其方便友好的自定义工厂模式,于是给大家带来了这篇文章。

没有看过上一篇文章的话,可以在这里找到:。

那么这篇文章,我们将基于上述的qnode,从头开始写一个无限循环轮播图的组件。

思路讲解

先看一张轮播布局图:

滑动的时候,整个轮播容器整体前进或后退一格,通过css3过渡效果的设置,来达到滑动的效果。也许你会疑惑,头尾怎么会多出两张图呢?

其实无限循环轮播的核心就在于头尾多出的两张图,从图三再向后滑动,会滑到红色图一(我称之为占位图一),这个时候给用户的感觉就是无缝从最后一张滑动到第一张的,当他滑到占位图一时,我们再瞬间切换到粉色图一(即真正的图一),由于是瞬间变换,用户是感知不到的。同理,从图一滑到图三也一样。由此,周而复始,无穷无尽,给人的感觉是永远也不会到尽头,当然个中奥妙只有我们知道哈哈。

目录结构

swiper├── README.md├── index.js├── qnode│   ├── index.js│   ├── method.js│   └── store.js├── render│   ├── index.js│   ├── indicator.js│   └── list.js└── styles    ├── indicator.mcss    ├── list.mcss    └── wrap.mcss

说明:mcss文件是通过css-modules来编译的,给class名称生成唯一标识,防止命名冲突。这里有我配置好的一套脚手架,觉得webpack配置麻烦的话,可以clone我这个项目来编译代码:。

代码编写

index.js

import qnode from './qnode'import render from './render'const defaults = {  initIndex: 1,  autoplay: {    use: true,    delay: 3000  },  slide: {    use: true,    scale: 1 / 3,    speed: 0.2  },  indicator: {    use: true,    bottom: '',    dotClass: '',    dotActiveClass: ''  }}export default function swiper (node, {  datas,  initIndex,  slide,  autoplay,  indicator}) {  if (!node || !datas || !datas.length) return  // 储存数据的前后顺序很重要,一定要在调用前设置  qnode.setStore('datas', datas)  qnode.setStore('index', (initIndex || defaults.initIndex) - 1)  qnode.setStore('slide', Object.assign({}, defaults.slide, slide))  qnode.setStore('autoplay', Object.assign({}, defaults.autoplay, autoplay))  qnode.setStore('indicator', Object.assign({}, defaults.indicator, indicator))  // 渲染dom并储存在qnode,以便后续的获取和操作  render()  // 自动轮播  qnode.execMethod('autoplay')  // 滑动翻页  qnode.execMethod('slide')  // 挂载到真实的节点上  qnode.getNode('wrap').appendTo(node)}

render/index.js

import qnode from '../qnode'import renderList from './list'import renderIndicator from './indicator'import mcss from '../styles/wrap.mcss'export default function () {  renderList() // 渲染列表  renderIndicator() // 渲染指示器,若没有开启则不会渲染  qnode.setNode('wrap', '$div')    .addClass(mcss.wrap)    .append([      qnode.getNode('list'),      qnode.getNode('indicator') // 有可能没有值,这一层我们的qnode会过滤调,所以放心大胆地写    ])}

render/list.js

import { isElement, isString } from '@m/utils/is'import qnode from '../qnode'import mcss from '../styles/list.mcss'function getItemNode (data) {  const qItem = qnode.q('$div').addClass(mcss.item)  if (isElement(data)) {    return qItem.append(data)  }  if (isString(data)) {    return qItem.html(data)  }  return qItem.html(`          img      `)}export default function () {  const datas = qnode.getStore('datas')  const tdTime = qnode.getStore('tdTime')  const posIndex = qnode.getStore('index') + 1  const qItems = datas.map(item => getItemNode(item))  // 首位多插入一个节点,用于视觉感知,交互完成后瞬间替换到相应的节点  qItems.unshift(getItemNode(datas[datas.length - 1]))  qItems.push(getItemNode(datas[0]))  qnode.setNode('list', '$div')    .addClass(mcss.list)    .style({      transitionDuration: tdTime + 'ms',      transform: `translateX(${posIndex * -100}%)`    })    .append(qItems)}

render/indicator.js

import qnode from '../qnode'import mcss from '../styles/indicator.mcss'export default function () {  const indicator = qnode.getStore('indicator')  const last = qnode.getStore('datas').length - 1  const index = qnode.getStore('index')  const dotClass = indicator.dotClass || mcss.dot  const dotActiveClass = indicator.dotActiveClass || mcss.dotActive  if (indicator.use) {    let qDots = []    for (let i = 0; i <= last; i++) {      qDots.push(        qnode.q('$div').addClass(dotClass, (i === index) && dotActiveClass)      )    }    qnode.setNode('dots', qDots)    qnode.setStore('dotActiveClass', dotActiveClass)    qnode.setNode('indicator', '$div')      .addClass(mcss.indicator)      .style('bottom', indicator.bottom)      .append(qDots)  }}

qnode/index.js

import { QNode } from '@m/qnode'import { tdTime } from './store'import { change, autoplay, slide, indicator } from './method'const qnode = new QNode()qnode.setStore('tdTime', tdTime)qnode.setMethod('change', change)qnode.setMethod('autoplay', autoplay)qnode.setMethod('slide', slide)qnode.setMethod('indicator', indicator)export default qnode

qnode/store.js

// 静态数据可以放在这里export const tdTime = 500

qnode/method.js

import touchSlide from './touchSlide'// 翻页处理export function change (isNext) {  let index = this.getStore('index')  let cacheIndex = index // 用于记录上一次的索引,移除指示器激活样式时使用  let last = this.getStore('datas').length - 1  let tdTime = this.getStore('tdTime')  let qList = this.getNode('list')  let isNextContinue = isNext && (index === last)  let isPrevContinue = !isNext && (index === 0)  let posIndex = index + (isNext ? 2 : 0)  if (isNextContinue || isPrevContinue) {    // 滑到占位图    qList.style('transform', `translateX(${posIndex * -100}%)`)    index = isNextContinue ? 0 : last    setTimeout(() => {      qList.style({        transitionDuration: '0ms',        transform: `translateX(${(index + 1) * -100}%)`      })    }, tdTime)  } else {    qList.style({      transitionDuration: tdTime + 'ms',      transform: `translateX(${posIndex * -100}%)`    })    index += isNext ? 1 : -1  }  this.setStore('index', index)  this.execMethod('indicator', cacheIndex, index)}// 自动轮播export function autoplay () {  let opt = this.getStore('autoplay')  if (!opt.use) return  let timer = setInterval(() => {    this.execMethod('change', true)  }, opt.delay)  this.setStore('timer', timer)}// 滑动处理export function slide () {  let qWrap = this.getNode('wrap')  let qList = this.getNode('list')  let tdTime = this.getStore('tdTime')  let slideData = this.getStore('slide')  let self = this  if (!slideData.use) return  touchSlide(qWrap.current(), {    delay: 0,    start () {      // 清除轮播定时器和css3过渡效果      clearTimeout(self.getStore('timer'))      qList.style('transitionDuration', '0ms')    },    move (info) {      let posIndex = self.getStore('index') + 1      let move = info.disX / qWrap.width() * 100      let total = posIndex * -100 + move      qList.style('transform', `translateX(${total}%)`)    },    end (info) {      // 开启轮播和css3过渡效果      self.execMethod('autoplay')      qList.style('transitionDuration', tdTime + 'ms')      let posIndex = self.getStore('index') + 1      let scale = Math.abs(info.disX) / qWrap.width()      let speed = Math.abs(info.speedX)      if (scale >= slideData.scale || speed >= slideData.speed) {        self.execMethod('change', info.disX < 0) // 翻页      } else {        qList.style('transform', `translateX(${posIndex * -100}%)`)      }    }  })}// 修改指示器索引export function indicator (lastIndex, currIndex) {  const qDots = this.getNode('dots')  const dotActiveClass = this.getStore('dotActiveClass')  if (qDots && dotActiveClass) {    qDots[lastIndex].removeClass(dotActiveClass)    qDots[currIndex].addClass(dotActiveClass)  }}

touchSlide.js

// 截流function throttle (fn, delay = 100) {  let wait = false  return function () {    if (!wait) {      fn && fn.apply(this, arguments)      wait = true      setTimeout(() => {        wait = false      }, delay)    }  }}/** * * 滑动 * @param {HTMLElement} node * @param {Object} { *   delay = 100, // move截流时间 *   start, // 滑动开始 *      参数: pageX, pageY *   move, // 滑动中,会不断地触发,可以通过截流来限制触发频率 *      参数:            time, // 总时间:ms            disX, // 总路程:px            disY,            addX, // 路程增量:px            addY,            speedX: disX / time, // 平均速度:px/ms            speedY: disY / time *   end, // 滑动结束,参数同move * } */export default function (node, {  delay = 100,  start,  move,  end}) {  if (!node) return  let sTouch, eTouch, sTime  let touch, time, disX, disY, addX, addY  node.addEventListener('touchstart', e => {    e.preventDefault()    sTime = e.timeStamp    sTouch = eTouch = e.targetTouches[0]    start && start({      pageX: sTouch.pageX,      pageY: sTouch.pageY    })  }, false)  node.addEventListener('touchmove', throttle(e => {    touch = e.targetTouches[0]    time = e.timeStamp - sTime    disX = touch.pageX - sTouch.pageX    disY = touch.pageY - sTouch.pageY    addX = touch.pageX - eTouch.pageX    addY = touch.pageY - eTouch.pageY    move && move({      time, // 总时间:ms      disX, // 总路程:px      disY,      addX, // 路程增量:px      addY,      speedX: disX / time, // 平均速度:px/ms      speedY: disY / time    })    // 记录上一次touch    eTouch = touch  }, delay), false)  node.addEventListener('touchend', e => {    touch = e.changedTouches[0]    time = e.timeStamp - sTime    disX = touch.pageX - sTouch.pageX    disY = touch.pageY - sTouch.pageY    addX = touch.pageX - eTouch.pageX    addY = touch.pageY - eTouch.pageY    end && end({      time,      disX,      disY,      addX,      addY,      speedX: disX / time,      speedY: disY / time    })  }, false)}

styles/wrap.mcss

.wrap {  position: relative;  overflow: hidden;  transform: translate3d(0, 0, 0);}

styles/list.mcss

.list {  display: flex;  flex-direction: row;  transform: translateX(0);  transition: transform 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94);}.item {  flex-basis: 100%;  flex-shrink: 0;  box-sizing: border-box;  a {    display: block;    font-size: 0;    img {      width: 100%;      height: auto;    }  }}

styles/indicator.mcss

.indicator {  position: absolute;  bottom: 1em;  left: 0;  right: 0;  display: flex;  justify-content: center;}.dot {  width: 1em;  height: 0.12em;  margin: 0 0.12em;  background-color: rgba(255, 255, 255, 0.5);  &-active {    background-color: #fff;  }}

README

参数

  • node: 要挂载的dom节点,必须
  • options: 如下(其中datas是必要的)
{  initIndex: 1, // 初始化展示的索引  autoplay: { // 自动轮播设置    use: true, // 开关    delay: 3000 // 间隔3s  },  slide: { // 手指滑动设置    use: true, // 开关    scale: 1/3, // 划过总共宽度的1/3则翻页    speed: 0.2 // 滑动的速度超过0.2px/ms则翻页,即快速滑动也可以翻页  },  indicator: { // 索引指示器设置    use: true, // 开关    bottom: '', // 底部的距离    dotClass: '', // 自定义圆点样式    dotActiveClass: '' // 自定义激活样式  },  datas: [ // 图片数据    {      src: 'xxx', // 图片URL      href: '/', // 图片锚点,可以不设置      target: '_blank' // 点击锚点的跳转处理(是在当前页打开还是新建窗口)    }  ]}

示例

import swiper from '@c/swiper'import img1 from './images/1.jpg'import img2 from './images/2.jpg'import img3 from './images/3.jpg'import img4 from './images/4.jpg'import img5 from './images/5.jpg'import img6 from './images/6.jpg'const rootNode = document.getElementById('root')swiper(rootNode, {  // initIndex: 1,  // autoplay: {  //   use: true,  //   delay: 3000  // },  // slide: {  //   use: true,  //   scale: 1/3,  //   speed: 0.2  // },  // indicator: {  //   use: true,  //   bottom: '',  //   dotClass: '',  //   dotActiveClass: ''  // },  datas: [    {      src: img1,      href: '/',      target: '_blank'    },    {      src: img2,      href: '/',      target: '_blank'    },    {      src: img3,      href: '/',      target: '_blank'    },    {      src: img4,      href: '/',      target: '_blank'    },    {      src: img5,      href: '/',      target: '_blank'    },    {      src: img6,      href: '/',      target: '_blank'    }  ]})

使用心得

总体来说使用qnode来开发的话还是比较方便的,文件拆分以及数据共享都可以做到,唯一有一点瑕疵的话,就是对于js执行的顺序要慎重考虑。想一想为什么render文件暴露出来的是函数,原因就是因为此时数据还未储存到qnode,因此通过函数来进行惰性加载,在合适的地方执行。

对于qnode,目前还没有错误提醒,调用方式不对的话没有信息吐出,后续可以考虑补上这个功能,毕竟其他开发者用的话,可能并不熟悉API,调用姿势不对也是有可能发生的。

以上就是本文的全部内容了。

附:

转载地址:http://vjwza.baihongyu.com/

你可能感兴趣的文章
SQL Server表分区详解
查看>>
使用FMDB最新v2.3版本教程
查看>>
SSIS从理论到实战,再到应用(3)----SSIS包的变量,约束,常用容器
查看>>
STM32启动过程--启动文件--分析
查看>>
垂死挣扎还是涅槃重生 -- Delphi XE5 公布会归来感想
查看>>
淘宝的几个架构图
查看>>
Android扩展 - 拍照篇(Camera)
查看>>
JAVA数组的定义及用法
查看>>
充分利用HTML标签元素 – 简单的xtyle前端框架
查看>>
设计模式(十一):FACADE外观模式 -- 结构型模式
查看>>
iOS xcodebuile 自动编译打包ipa
查看>>
程序员眼中的 SQL Server-执行计划教会我如何创建索引?
查看>>
【BZOJ】1624: [Usaco2008 Open] Clear And Present Danger 寻宝之路(floyd)
查看>>
cmake总结
查看>>
数据加密插件
查看>>
linux后台运行程序
查看>>
win7 vs2012/2013 编译boost 1.55
查看>>
IIS7如何显示详细错误信息
查看>>
ViewPager切换动画PageTransformer使用
查看>>
coco2d-x 基于视口的地图设计
查看>>