jsonp+bscroll制作轮播

源码地址:

https://github.com/CiroMzy/jsonp-bscroll

首先介绍一下统一封装的几个部分。

config.js => 这里我封装了一些jsonp调用时的通用配置信息,

commonParams是配置参数,options是回调,ERR_OK对应成功状态码

export const commonParams = {
  g_tk: 5381,
  format: 'json',
  inCharset: 'utf-8',
  outCharset: 'utf-8',
  notice: 0,
  needNewCode: 1
}
export const options = {
  param: 'jsonpCallback'
}
export const ERR_OK = 0

jsonp.js => 这里封装jsonp方法,

涉及到url拼接,需要注意的是,data[k]可能为unifined,需要用substring截掉第一个&,还要判断拼接前是否有?,有?就用&,没有就用?。

然后用promise,判断是否成功。

import orignJsonp from 'jsonp'
export default function jsonp (url, data, option) {
  url += (url.indexOf('?') < 0 ? '?' : '&') + params(data)
  return new Promise((resolve, reject) => {
    orignJsonp(url, option, (err, data) => {
      if (!err) {
        resolve(data)
      } else {
        reject(err)
      }
    })
  })
}

function params (data) {
  let url = ''
  for (let k in data) {
    let value = data[k] === undefined ? '' : data[k]
    url += '&' + k + '=' + encodeURIComponent(value)
  }
  return url ? url.substring(1) : ''
}

recommend.js => 通过jsonp获取数据列表

这里用了一下Object.assign()这个函数只进行一层的深拷贝。

import jsonp from '../common/js/jsonp'
import {commonParams, options} from './config'

export function getSliderList () {
  let url = 'https://c.y.qq.com/musichall/fcgi-bin/fcg_yqqhomepagerecommend.fcg'
  let data = Object.assign({}, commonParams, {
    platform: 'h5',
    uin: 0,
    needNewCode: 1
  })
  return jsonp(url, data, options)
}

recommend.vue => 父组件创建,并通过内容分发把轮播图传给子组件

这里需要注意的是,由于是异步获取的,如果直接渲染子组件,会出现子组件内容为空的问题。所以这里在外层用v-if判断一下,等有数据返回了,再去渲染slider组件。这里就体现出了v-if和v-show的不同,v-if是控制渲染,而v-show只是display。所以这里用v-show显然不靠谱。

<template>
  <div class="recommend">
    <div v-if="sliderList.length">
      <slider >
        <div v-for="item in sliderList" >
          <img class="needsclick" :src="item.picUrl">
        </div>
      </slider>
    </div>
  </div>
</template>
<script>
  import {getSliderList} from '../../api/recommend'
  import {ERR_OK} from '../../api/config'
  import Slider from '../slider/slider.vue'
  export default {
    name: 'recommend',
    data () {
      return {
        sliderList: []
      }
    },
    created () {
      this._initSlider()
    },
    methods: {
      _initSlider () {
        getSliderList().then((res) => {
          if (res.code === ERR_OK) {
            this.sliderList = res.data.slider
          }
        })
      }
    },
    components: {
      Slider
    }
  }
</script>

slider.vue => 轮播图组件

首先是html部分,通过slot接受分发的内容,然后创建dots,这里不作赘述。

js部分,首先是通过props接受父组件的配置控制。

触发渲染的时机要选择mounted时,并且以setTimeout的20ms处理,因为页面渲染大概需要18ms。

在这里需要初始化宽度,初始化bscroll等等,这里可以直接看代码。

需要注意的是,dots的控制。在loop模式下,bscroll会自动多生成两个slider,以达成无缝滚动的目的。所以当前dot需要根据模式进行加减处理。

<template>
  <div class="slider" ref="sliderContainer">
    <div class="slider-container" ref="sliderGroup">
      <slot></slot>
    </div>
    <div class="dots">
      <span v-for="(item, index) in dots" class="dot" :class="{active: currentPageIndex === index}"></span>
    </div>
  </div>
</template>
<script>
  import BScroll from 'better-scroll'
  export default {
    name: 'slider',
    props: {
      loop: {
        type: Boolean,
        default: true
      },
      autoPlay: {
        type: Boolean,
        default: true
      },
      interval: {
        type: Number,
        default: 4000
      }
    },
    data () {
      return {
        dots: [],
        currentPageIndex: 0
      }
    },
    mounted () {
      setTimeout(() => {
        this._initWidth()
        this._initDots()
        this._initBScroll()
      }, 20)
    },
    methods: {
      _initWidth () {
        this.sliderGroup = this.$refs.sliderGroup
        this.singleWidth = this.sliderGroup.clientWidth
        this.slider = this.sliderGroup.children
        this.fullWidth = 0
        for (let i = 0; i < this.slider.length; i++) {
          this.slider[i].style.width = this.singleWidth + 'px'
          this.slider[i].className = this._addClass(this.slider[i], 'slider-item')
          this.fullWidth += this.singleWidth
        }
        if (this.loop) {
          this.fullWidth += 2 * this.singleWidth
        }
        this.sliderGroup.style.width = this.fullWidth + 'px'
      },
      _initBScroll () {
        this.scroll = new BScroll(this.$refs.sliderContainer, {
          scrollX: true,
          scrollY: false,
          momentum: false,
          snap: true,
          snapLoop: this.loop,
          snapThreshold: 0.3,
          snapSpeed: 400
        })
        this.scroll.on('scrollEnd', () => {
          let pageIndex = this.scroll.getCurrentPage().pageX
          this.currentPageIndex = pageIndex
          if (this.loop) {
            this.currentPageIndex --
          }
          this._play()
        })
        this.scroll.on('beforeScrollStart', () => {
          clearTimeout(this.timer)
        })
        this._play()
      },
      _play () {
        this.timer = setTimeout(() => {
          let pageIndex = this.currentPageIndex + 1
          pageIndex = this.loop ? (pageIndex + 1) : pageIndex
          this.scroll.goToPage(pageIndex, 0, 400)
        }, this.interval)
      },
      _initDots () {
        this.dots = new Array(this.slider.length)
      },
      _addClass (el, addName) {
        if (this._hasClass(el, addName)) {
          return
        }
        let str = el.className.split('')
        str.push(addName)
        str.join('')
        return str
      },
      _hasClass (el, addName) {
        let reg = new RegExp('(^|\\s)' + addName + '($|\\s)')
        return reg.test(el.className)
      }
    }
  }
</script>
<style lang="less" scoped>
.slider{
  position: relative;
  overflow: hidden;
  width:100%;
  font-size:0;
  img{
    width:100%;
  }
  .slider-item{
    float: left;
  }
  .slider-container:after{
    display: block;
    content: '';
    visibility: hidden;
    clear: both;
  }
}
  .dots{
    position: absolute;
    bottom:5px;
    width:100%;
    height:10px;
    left:0;
    text-align: center;
    .dot{
      display: inline-block;
      width:10px;
      height:10px;
      border-radius: 5px;
      background: #333;
      margin:0 2px;
      &.active{
        background: #990000;
      }
    }
  }
</style>
需要补充完善的,有window的resize事件绑定,组件销毁时对timeout事件的清除,等。这些就自己加上吧。


随机浏览