首页 > 其他 > 详细

better-scroll

时间:2020-08-26 19:19:24      阅读:76      评论:0      收藏:0      [点我收藏+]

betterScroll.vue

<template>
  <div ref="wrapper" class="list-wrapper">
    <div class="scroll-content">
      <div ref="listWrapper">
        <slot>
          <ul class="list-content">
            <li @click="clickItem($event,item)" class="list-item" v-for="item in data">{{item}}</li>
          </ul>
        </slot>
      </div>
      <slot name="pullup"
            :pullUpLoad="pullUpLoad"
            :isPullUpLoad="isPullUpLoad"
      >
        <div class="pullup-wrapper" v-if="pullUpLoad">
          <div class="before-trigger" v-if="!isPullUpLoad">
            <span>{{pullUpTxt}}</span>
          </div>
          <div class="after-trigger" v-else>
            <loading></loading>
          </div>
        </div>
      </slot>
    </div>
    <slot name="pulldown"
          :pullDownRefresh="pullDownRefresh"
          :pullDownStyle="pullDownStyle"
          :beforePullDown="beforePullDown"
          :isPullingDown="isPullingDown"
          :bubbleY="bubbleY"
    >
      <div ref="pulldown" class="pulldown-wrapper" :style="pullDownStyle" v-if="pullDownRefresh">
        <div class="before-trigger" v-if="beforePullDown">
          <bubble :y="bubbleY"></bubble>
        </div>
        <div class="after-trigger" v-else>
          <div v-if="isPullingDown" class="loading">
            <loading></loading>
          </div>
          <div v-else><span>{{refreshTxt}}</span></div>
        </div>
      </div>
    </slot>
  </div>
</template>

<script>
  import BScroll from ‘better-scroll‘
  import Loading from ‘../loading/loading.vue‘
  import Bubble from ‘./bubble.vue‘
  import { getRect } from ‘@/common/js/dom‘

  const COMPONENT_NAME = ‘scroll‘
  const DIRECTION_H = ‘horizontal‘
  const DIRECTION_V = ‘vertical‘

  export default {
    name: COMPONENT_NAME,
    props: {
      data: {
        type: Array,
        default: function () {
          return []
        }
      },
      probeType: {
        type: Number,
        default: 1
      },
      click: {
        type: Boolean,
        default: true
      },
      listenScroll: {
        type: Boolean,
        default: false
      },
      listenBeforeScroll: {
        type: Boolean,
        default: false
      },
      direction: {
        type: String,
        default: DIRECTION_V
      },
      scrollbar: {
        type: null,
        default: false
      },
      pullDownRefresh: {
        type: null,
        default: false
      },
      pullUpLoad: {
        type: null,
        default: false
      },
      startY: {
        type: Number,
        default: 0
      },
      refreshDelay: {
        type: Number,
        default: 20
      },
      freeScroll: {
        type: Boolean,
        default: false
      },
      mouseWheel: {
        type: Boolean,
        default: false
      },
      bounce: {
        default: true
      }
    },
    data() {
      return {
        beforePullDown: true,
        isRebounding: false,
        isPullingDown: false,
        isPullUpLoad: false,
        pullUpDirty: true,
        pullDownStyle: ‘‘,
        bubbleY: 0
      }
    },
    computed: {
      pullUpTxt() {
        const moreTxt = this.pullUpLoad && this.pullUpLoad.txt && this.pullUpLoad.txt.more || ‘加载更多‘

        const noMoreTxt = this.pullUpLoad && this.pullUpLoad.txt && this.pullUpLoad.txt.noMore || ‘没有更多数据了‘

        return this.pullUpDirty ? moreTxt : noMoreTxt
      },
      refreshTxt() {
        return this.pullDownRefresh && this.pullDownRefresh.txt || ‘刷新成功‘
      }
    },
    created() {
      this.pullDownInitTop = -50
    },
    mounted() {
      setTimeout(() => {
        this.initScroll()
      }, 20)
    },
    methods: {
      initScroll() {
        if (!this.$refs.wrapper) {
          return
        }
        if (this.$refs.listWrapper && (this.pullDownRefresh || this.pullUpLoad)) {
          this.$refs.listWrapper.style.minHeight = `${getRect(this.$refs.wrapper).height + 0.1}px`
        }

        let options = {
          probeType: this.probeType,
          click: this.click,
          scrollY: this.freeScroll || this.direction === DIRECTION_V,
          scrollX: this.freeScroll || this.direction === DIRECTION_H,
          scrollbar: this.scrollbar,
          pullDownRefresh: this.pullDownRefresh,
          pullUpLoad: this.pullUpLoad,
          startY: this.startY,
          freeScroll: this.freeScroll,
          mouseWheel: this.mouseWheel,
          bounce: this.bounce
        }

        this.scroll = new BScroll(this.$refs.wrapper, options)

        if (this.listenScroll) {
          this.scroll.on(‘scroll‘, (pos) => {
            this.$emit(‘scroll‘, pos)
          })
        }

        if (this.listenBeforeScroll) {
          this.scroll.on(‘beforeScrollStart‘, () => {
            this.$emit(‘beforeScrollStart‘)
          })
        }

        if (this.pullDownRefresh) {
          this._initPullDownRefresh()
        }

        if (this.pullUpLoad) {
          this._initPullUpLoad()
        }
      },
      disable() {
        this.scroll && this.scroll.disable()
      },
      enable() {
        this.scroll && this.scroll.enable()
      },
      refresh() {
        this.scroll && this.scroll.refresh()
      },
      scrollTo() {
        this.scroll && this.scroll.scrollTo.apply(this.scroll, arguments)
      },
      scrollToElement() {
        this.scroll && this.scroll.scrollToElement.apply(this.scroll, arguments)
      },
      clickItem(e, item) {
        // console.log(e)
        this.$emit(‘click‘, item)
      },
      destroy() {
        //   销毁 better-scroll,解绑事件
        this.scroll.destroy()
      },
      forceUpdate(dirty) {
        if (this.pullDownRefresh && this.isPullingDown) {
          this.isPullingDown = false
          this._reboundPullDown().then(() => {
            this._afterPullDown()
          })
        } else if (this.pullUpLoad && this.isPullUpLoad) {
          this.isPullUpLoad = false
          this.scroll.finishPullUp()
          this.pullUpDirty = dirty
          this.refresh()
        } else {
          this.refresh()
        }
      },
      _initPullDownRefresh() {
        this.scroll.on(‘pullingDown‘, () => {
          this.beforePullDown = false
          this.isPullingDown = true
          this.$emit(‘pullingDown‘)
        })

        this.scroll.on(‘scroll‘, (pos) => {
          if (!this.pullDownRefresh) {
            return
          }
          if (this.beforePullDown) {
            this.bubbleY = Math.max(0, pos.y + this.pullDownInitTop)
            this.pullDownStyle = `top:${Math.min(pos.y + this.pullDownInitTop, 10)}px`
          } else {
            this.bubbleY = 0
          }

          if (this.isRebounding) {
            this.pullDownStyle = `top:${10 - (this.pullDownRefresh.stop - pos.y)}px`
          }
        })
      },
      _initPullUpLoad() {
        this.scroll.on(‘pullingUp‘, () => {
          this.isPullUpLoad = true
          this.$emit(‘pullingUp‘)
        })
      },
      _reboundPullDown() {
        const {stopTime = 600} = this.pullDownRefresh
        return new Promise((resolve) => {
          setTimeout(() => {
            this.isRebounding = true
            this.scroll.finishPullDown()
            resolve()
          }, stopTime)
        })
      },
      _afterPullDown() {
        setTimeout(() => {
          this.pullDownStyle = `top:${this.pullDownInitTop}px`
          this.beforePullDown = true
          this.isRebounding = false
          this.refresh()
        }, this.scroll.options.bounceTime)
      }
    },
    watch: {
      data() {
        setTimeout(() => {
          this.forceUpdate(true)
        }, this.refreshDelay)
      }
    },
    components: {
      Loading,
      Bubble
    }
  }

</script>

<style scoped>
  .list-wrapper{
      position: relative;
    height: 100%;
    overflow: hidden;
    /* background: #fff; */
  }
    
    .scroll-content{
        position: relative;
      z-index: 1;
    }
      
    .list-content{
        position: relative;
      z-index: 10;
      background: #fff;
    }
      
      .list-item{
          height: 60px;
        line-height: 60px;
        font-size: 18px;
        padding-left: 20px;
        border-bottom: 1px solid #e5e5e5;
      }
        

  .pulldown-wrapper{
      position: absolute;
    width: 100%;
    left: 0;
    display: flex;
    justify-content:center;
    align-items: center;
    transition: all;
  }
    
    .after-trigger{
        /* margin-top: 10px; */
        font-size: 16px;
    }
      

  .pullup-wrapper{
      width: 100%;
    display: flex;
    justify-content: center;
    align-items: center;
    padding: 16px 0;
  }
    .before-trigger{
        font-size: 16px;
    }
</style>

bubble.vue

<template>
  <canvas ref="bubble" :width="width" :height="height" :style="style"></canvas>
</template>

<script>
  export default {
    props: {
      y: {
        type: Number,
        default: 0
      }
    },
    data() {
      return {
        width: 50,
        height: 80
      }
    },
    computed: {
      distance() {
        return Math.max(0, Math.min(this.y * this.ratio, this.maxDistance))
      },
      style() {
        return `width:${this.width / this.ratio}px;height:${this.height / this.ratio}px`
      }
    },
    created() {
      this.ratio = window.devicePixelRatio
      this.width *= this.ratio
      this.height *= this.ratio
      this.initRadius = 18 * this.ratio
      this.minHeadRadius = 12 * this.ratio
      this.minTailRadius = 5 * this.ratio
      this.initArrowRadius = 10 * this.ratio
      this.minArrowRadius = 6 * this.ratio
      this.arrowWidth = 3 * this.ratio
      this.maxDistance = 40 * this.ratio
      this.initCenterX = 25 * this.ratio
      this.initCenterY = 25 * this.ratio
      this.headCenter = {
        x: this.initCenterX,
        y: this.initCenterY
      }
    },
    mounted() {
      this._draw()
    },
    methods: {
      _draw() {
        const bubble = this.$refs.bubble
        let ctx = bubble.getContext(‘2d‘)
        ctx.clearRect(0, 0, bubble.width, bubble.height)

        this._drawBubble(ctx)

        this._drawArrow(ctx)
      },
      _drawBubble(ctx) {
        ctx.save()
        ctx.beginPath()

        const rate = this.distance / this.maxDistance
        const headRadius = this.initRadius - (this.initRadius - this.minHeadRadius) * rate

        this.headCenter.y = this.initCenterY - (this.initRadius - this.minHeadRadius) * rate

        // 画上半弧线
        ctx.arc(this.headCenter.x, this.headCenter.y, headRadius, 0, Math.PI, true)

        // 画左侧贝塞尔
        const tailRadius = this.initRadius - (this.initRadius - this.minTailRadius) * rate
        const tailCenter = {
          x: this.headCenter.x,
          y: this.headCenter.y + this.distance
        }

        const tailPointL = {
          x: tailCenter.x - tailRadius,
          y: tailCenter.y
        }
        const controlPointL = {
          x: tailPointL.x,
          y: tailPointL.y - this.distance / 2
        }

        ctx.quadraticCurveTo(controlPointL.x, controlPointL.y, tailPointL.x, tailPointL.y)

        // 画下半弧线
        ctx.arc(tailCenter.x, tailCenter.y, tailRadius, Math.PI, 0, true)

        // 画右侧贝塞尔
        const headPointR = {
          x: this.headCenter.x + headRadius,
          y: this.headCenter.y
        }
        const controlPointR = {
          x: tailCenter.x + tailRadius,
          y: headPointR.y + this.distance / 2
        }
        ctx.quadraticCurveTo(controlPointR.x, controlPointR.y, headPointR.x, headPointR.y)

        ctx.fillStyle = ‘rgb(170,170,170)‘
        ctx.fill()
        ctx.strokeStyle = ‘rgb(153,153,153)‘
        ctx.stroke()
        ctx.restore()
      },
      _drawArrow(ctx) {
        ctx.save()
        ctx.beginPath()

        const rate = this.distance / this.maxDistance
        const arrowRadius = this.initArrowRadius - (this.initArrowRadius - this.minArrowRadius) * rate

        // 画内圆
        ctx.arc(this.headCenter.x, this.headCenter.y, arrowRadius - (this.arrowWidth - rate), -Math.PI / 2, 0, true)

        // 画外圆
        ctx.arc(this.headCenter.x, this.headCenter.y, arrowRadius, 0, Math.PI * 3 / 2, false)

        ctx.lineTo(this.headCenter.x, this.headCenter.y - arrowRadius - this.arrowWidth / 2 + rate)
        ctx.lineTo(this.headCenter.x + this.arrowWidth * 2 - rate * 2, this.headCenter.y - arrowRadius + this.arrowWidth / 2)

        ctx.lineTo(this.headCenter.x, this.headCenter.y - arrowRadius + this.arrowWidth * 3 / 2 - rate)

        ctx.fillStyle = ‘rgb(255,255,255)‘
        ctx.fill()
        ctx.strokeStyle = ‘rgb(170,170,170)‘
        ctx.stroke()
        ctx.restore()
      }
    },
    watch: {
      y() {
        this._draw()
      }
    }
  }
</script>

loading.vue

<template>
  <div class="mf-loading-container">
    <img src="./loading.gif">
  </div>
</template>
<script>
  const COMPONENT_NAME = ‘loading‘

  export default {
    name: COMPONENT_NAME
  }
</script>
<style>
  .mf-loading-container img{
      width: 20px;
      height: 20px;
      display: block;
  }
</style>

dom.js

export function hasClass(el, className) {
    let reg = new RegExp(‘(^|\\s)‘ + className + ‘(\\s|$)‘)
    return reg.test(el.className)
  }
  
  export function addClass(el, className) {
    if (hasClass(el, className)) {
      return
    }
  
    let newClass = el.className.split(‘ ‘)
    newClass.push(className)
    el.className = newClass.join(‘ ‘)
  }
  
  export function removeClass(el, className) {
    if (!hasClass(el, className)) {
      return
    }
  
    let reg = new RegExp(‘(^|\\s)‘ + className + ‘(\\s|$)‘, ‘g‘)
    el.className = el.className.replace(reg, ‘ ‘)
  }
  
  export function getData(el, name, val) {
    let prefix = ‘data-‘
    if (val) {
      return el.setAttribute(prefix + name, val)
    }
    return el.getAttribute(prefix + name)
  }
  
  export function getRect(el) {
    if (el instanceof window.SVGElement) {
      let rect = el.getBoundingClientRect()
      return {
        top: rect.top,
        left: rect.left,
        width: rect.width,
        height: rect.height
      }
    } else {
      return {
        top: el.offsetTop,
        left: el.offsetLeft,
        width: el.offsetWidth,
        height: el.offsetHeight
      }
    }
  }
  

封装 mixin/scroll.js

import Vue from ‘vue‘
import Scroll from ‘@/components/betterScroll/betterScroll‘
// better-scroll官网--https://ustbhuangyi.github.io/better-scroll/doc/zh-hans/api-specific.html#finishpullup
export const Bscroll = {
    data() {
        return {
          scrollbar: true,
          // scrollbar 控制是否开启滚动条
          scrollbarFade: true,
          // scrollbarFade 控制滚动条显示隐藏
          pullDownRefresh: true,
          // pullDownRefresh 控制是否开启下拉刷新
          pullDownRefreshThreshold: 90,
          pullDownRefreshStop: 40,
          pullUpLoad: true,
          // pullUpLoad 控制是否开启上拉加载
          pullUpLoadThreshold: -1,
          // 在上拉到超过底部 1px 时,触发 上拉加载 事件
          pullUpLoadMoreTxt: ‘加载更多‘,
          pullUpLoadNoMoreTxt: ‘没有更多数据了‘,
          startY: 0,
          itemIndex: 0,
          noData:false,
          pageNo: 1,//当前页
          pageSize: 20,//每页显示数据条数
        }
    },
    created() {},
    components: {
        Scroll
    },
    activated(){
        let _self = this
        this.$nextTick(() => {
            if(_self.$refs.scroll && _self.$refs.scroll.scroll) {
                _self.$refs.scroll.scroll.refresh();
            }
        })
    },
    watch: {
        scrollbarObj: {
            //   显示滚动条
            handler() {
                this.rebuildScroll()
            },
            deep: true
        },
        pullDownRefreshObj: {
            // 深度 watcher
            handler(val) {
                const scroll = this.$refs.scroll.scroll
                if (val) {
                    scroll.openPullDown()
                } else {
                    scroll.closePullDown()
                }
            },
            deep: true
        },
        pullUpLoadObj: {
            handler(val) {
                const scroll = this.$refs.scroll.scroll
                if (val) {
                    scroll.openPullUp()
                } else {
                    scroll.closePullUp()
                }
            },
            deep: true
        },
        startY() {
          this.rebuildScroll()
        }
    },
    computed: {
        scrollbarObj: function () {
          return this.scrollbar ? {fade: this.scrollbarFade} : false
        },
        pullDownRefreshObj: function () {
            // 下拉刷新---下拉距离与回弹距离
            return this.pullDownRefresh ? {
                threshold: parseInt(this.pullDownRefreshThreshold),
                stop: parseInt(this.pullDownRefreshStop)
            } : false
        },
        pullUpLoadObj: function () {
          // 上拉加载---显示提示
          return this.pullUpLoad ? {
                threshold: parseInt(this.pullUpLoadThreshold),
                txt: {more: this.pullUpLoadMoreTxt, noMore: this.pullUpLoadNoMoreTxt}
            } : false
        }
    },
    methods: {
        /*onPullingDown() {
            // 模拟更新数据
            console.log(‘下拉刷新‘)
            setTimeout(() => {
                if (Math.random() > 1) {
                    // 如果有新数据
                    this.massifList.unshift(‘我是新数据‘ + +new Date())
                } else {
                    // 如果没有新数据
                    this.$refs.scroll.forceUpdate()
                }
            }, 1500)
        },
        onPullingUp() {
            // 更新数据
            console.log(‘上拉加载‘)
            if (this.noData) {
                this.$refs.scroll.forceUpdate()
                return
            }
            setTimeout(() => {
                if (Math.random() > 1) {
                    // 如果有新数据
                    let newPage = []
                    for (let i = 0; i < 10; i++) {
                        newPage.push(‘第 ‘ + ++this.itemIndex + ‘ 行‘)
                    }
                } else {
                    // 如果没有新数据
                    this.noData = true
                    this.$refs.scroll.forceUpdate()
                }
            }, 1500)
        },*/
        onPullingDown() {
            // console.log(‘下拉刷新‘)
            setTimeout(() => {
                this._scrollList()
            }, 100)
        },
        onPullingUp() {
            // console.log(‘上拉加载‘)
            if (this.noData) {
                this.$refs.scroll.forceUpdate()
                return
            }
            this.pageNo ++ 
            setTimeout(() => {
                this._scrollList(‘onPullingUp‘);
            }, 500)
        },
        clickItem() {
            this.$router.back()
        },
        rebuildScroll() {
            Vue.nextTick(() => {
                this.$refs.scroll.destroy()
                this.$refs.scroll.initScroll()
            })
        }
    }
}

页面引用

<template>
    <div id="specialTask">
        <div class="top-new-add-task dis-flex-center">
            <div class="add-txt" @click="newAdd"><div class="new-add-icon"></div></div>
        </div>
        <div class="problem-box">
            <scroll ref="scroll"
            :data="itemList" :scrollbar="scrollbarObj"
            :pullDownRefresh="pullDownRefreshObj" :pullUpLoad="pullUpLoadObj"
            :startY="parseInt(startY)" @pullingDown="onPullingDown"
            @pullingUp="onPullingUp" @click="clickItem">
                <div class="scroll-list">
                    <div class="problem-li dis-flex-between" v-for="(item,i) in itemList" @click.stop="toUrlEdit(item,true)">
                        <div class="massif-num"></div>
                        <div class="scoring">{{ i + 1}}</div>
                        <div class="department over_elips">{{item.name}}</div>
                        <div class="bg-state" @click.stop="toUrlEdit(item)">编辑</div>
                        <div class="arrow-character-rg"></div>
                    </div>
                    <div class="no-data dis-flex-center" v-if="!itemList.length">暂无数据</div>
                </div>
            </scroll>
        </div>
    </div>
</template>

<script>
import { Bscroll } from ‘@/mixin/scroll‘
import { specialList } from ‘@/http/api‘
import { mapActions } from ‘vuex‘
export default {
    name: ‘specialTask‘,
    mixins:[Bscroll],
    data(){
        return {
            itemList: []
        }
    },
    created(){
        isBack = false;
        this.addKeepAlive("index");
        this._scrollList();
    },
    activated(){
        isBack = false;
    },
    methods: {
        _scrollList(directionPas){
            this.pageNo = directionPas == ‘onPullingUp‘ ? this.pageNo : 1;
            let opt = {
                pageData: {
                    pageNo: this.pageNo,//当前页
                    pageSize: this.pageSize,//每页显示数据条数
                }
            }
            specialList(opt).then(res => {
                if(res.returnResult == 200){
                    if(directionPas == ‘onPullingUp‘){
                        this.itemList = this.itemList.concat(res.returnData.data.map(e=>{
                            return {
                                name: e.name,
                                id: e.id
                            }
                        }));
                    } else {
                        if(this.noData){
                            this.noData = false;
                            this.$refs.scroll.scroll.openPullUp();
                        }
                        this.itemList = res.returnData.data.map(e=>{
                            return {
                                name: e.name,
                                id: e.id
                            }
                        });
                    }
                    if (this.itemList.length >= res.returnData.recordCount) {
                        this.noData = true
                        this.$refs.scroll.forceUpdate()
                    }
                }
            })
        },
        newAdd(){
            this.$router.push({
                path: ‘/specialList‘
            })
        },
        toUrlEdit(item,stateSee){
            this.$router.push({
                path: ‘/specialList‘,
                query: {
                    id: item.id,
                    stateSee
                }
            })
        },
        ...mapActions({
            "cleanKeepAlive": "cleanKeepAlive",
            "addKeepAlive": "addKeepAlive"
        })
    },
    mounted() {
        //给当前页面顶层一个初始高度
        document.getElementById("specialTask").style.minHeight = document.body.clientHeight + ‘px‘;
    }
}
</script>

<style lang="less" scoped>
    #specialTask{
        background-color: #F7F8FA;
        overflow: hidden;
        width:100%;
        .problem-box{
            position: fixed;
            overflow-y: scroll;
            top: 0.70rem;
            left: 0;
            right: 0;
            bottom: 1.02rem;
            .scroll-list{
                padding: 0.20rem;
                padding-top: 0;
                .problem-li{
                    margin-top: 0.20rem;
                    background:rgba(255,255,255,1);
                    box-shadow:0px 0.04rem 0.12rem rgba(61,180,248,0.25);
                    border-radius: 0.08rem;
                    color: #000;
                    height: 1.00rem;
                    padding-right: 0.36rem;
                    &:first-child{
                        margin-top: 0;
                    }
                    .massif-num{
                        width: 0.10rem;
                        height:100%;
                        background:rgba(61,180,248,1);
                        border-radius:0.08rem 0px 0px 0.08rem;
                    }
                    .scoring{
                        font-size: 0.36rem;
                        width: 0.60rem;
                        text-align: center;
                    }
                    .department{
                        flex: 1;
                        margin: 0 0.15rem;
                        line-height: 0.48rem;
                        font-size: 0.30rem;
                    }
                    .bg-state{
                        background: url(../../assets/images/jdgl_bi@2x.png) 0 0 no-repeat;
                        background-size: 0.29rem 0.30rem;
                        padding: 0 0.20rem 0 0.40rem;
                        color: #606060;
                        font-size: 0.24rem;
                        line-height: 0.30rem;
                    }
                }
            }
        }
    }
</style>

 

better-scroll

原文:https://www.cnblogs.com/lijh03/p/13566505.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!