scroll.vue 16.7 KB
Newer Older
M
init  
miaodian 已提交
1 2
<template>
  <div ref="wrapper" class="cube-scroll-wrapper">
U
ustbhuangyi 已提交
3
    <div class="cube-scroll-content">
4
      <div ref="listWrapper" class="cube-scroll-list-wrapper">
A
AmyFoxFN 已提交
5 6
        <slot>
          <ul class="cube-scroll-list">
A
Amy 已提交
7 8 9 10 11
            <li
              class="cube-scroll-item border-bottom-1px"
              v-for="(item, index) in data"
              :key="index"
              @click="clickItem(item)">{{item}}</li>
A
AmyFoxFN 已提交
12 13 14
          </ul>
        </slot>
      </div>
M
init  
miaodian 已提交
15 16
      <slot name="pullup" :pullUpLoad="pullUpLoad" :isPullUpLoad="isPullUpLoad">
        <div class="cube-pullup-wrapper" v-if="pullUpLoad">
A
AmyFoxFN 已提交
17
          <div class="before-trigger" v-if="!isPullUpLoad">
M
init  
miaodian 已提交
18 19
            <span>{{ pullUpTxt }}</span>
          </div>
A
AmyFoxFN 已提交
20
          <div class="after-trigger" v-else>
M
init  
miaodian 已提交
21 22 23 24 25
            <loading></loading>
          </div>
        </div>
      </slot>
    </div>
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
    <div v-if="pullDownRefresh" class="cube-pulldown" ref="pulldown">
      <slot
        name="pulldown"
        :pullDownRefresh="pullDownRefresh"
        :pullDownStyle="pullDownStyle"
        :beforePullDown="beforePullDown"
        :isPullingDown="isPullingDown"
        :bubbleY="bubbleY">
        <div class="cube-pulldown-wrapper" :style="pullDownStyle">
          <div class="before-trigger" v-show="beforePullDown">
            <bubble :y="bubbleY" class="bubble"></bubble>
          </div>
          <div class="after-trigger" v-show="!beforePullDown">
            <div v-show="isPullingDown" class="loading">
              <loading></loading>
            </div>
            <div v-show="!isPullingDown" class="cube-pulldown-loaded"><span>{{ refreshTxt }}</span></div>
M
init  
miaodian 已提交
43 44
          </div>
        </div>
45 46
      </slot>
    </div>
M
init  
miaodian 已提交
47 48 49 50 51 52 53
  </div>
</template>

<script type="text/ecmascript-6">
  import BScroll from 'better-scroll'
  import Loading from '../loading/loading.vue'
  import Bubble from '../bubble/bubble.vue'
A
Amy 已提交
54
  import scrollMixin from '../../common/mixins/scroll'
T
tank0317 已提交
55
  import deprecatedMixin from '../../common/mixins/deprecated'
U
ustbhuangyi 已提交
56
  import { getRect } from '../../common/helpers/dom'
T
tank0317 已提交
57
  import { camelize } from '../../common/lang/string'
M
init  
miaodian 已提交
58 59 60 61 62

  const COMPONENT_NAME = 'cube-scroll'
  const DIRECTION_H = 'horizontal'
  const DIRECTION_V = 'vertical'
  const DEFAULT_REFRESH_TXT = 'Refresh success'
63
  const DEFAULT_STOP_TIME = 600
M
init  
miaodian 已提交
64 65 66 67 68

  const EVENT_CLICK = 'click'
  const EVENT_PULLING_DOWN = 'pulling-down'
  const EVENT_PULLING_UP = 'pulling-up'

A
Amy 已提交
69 70 71 72
  const EVENT_SCROLL = 'scroll'
  const EVENT_BEFORE_SCROLL_START = 'before-scroll-start'
  const EVENT_SCROLL_END = 'scroll-end'

T
tank0317 已提交
73
  const NEST_MODE_NONE = 'none'
T
tank0317 已提交
74 75
  const NEST_MODE_NATIVE = 'native'

A
Amy 已提交
76 77
  const SCROLL_EVENTS = [EVENT_SCROLL, EVENT_BEFORE_SCROLL_START, EVENT_SCROLL_END]

78
  const DEFAULT_OPTIONS = {
D
dolymood 已提交
79
    observeDOM: true,
80 81 82 83 84 85 86
    click: true,
    probeType: 1,
    scrollbar: false,
    pullDownRefresh: false,
    pullUpLoad: false
  }

M
init  
miaodian 已提交
87 88
  export default {
    name: COMPONENT_NAME,
T
tank0317 已提交
89
    mixins: [scrollMixin, deprecatedMixin],
T
tank0317 已提交
90 91 92 93 94 95 96 97 98 99
    provide() {
      return {
        parentScroll: this
      }
    },
    inject: {
      parentScroll: {
        default: null
      }
    },
M
init  
miaodian 已提交
100 101 102 103 104 105 106
    props: {
      data: {
        type: Array,
        default() {
          return []
        }
      },
A
Amy 已提交
107 108
      scrollEvents: {
        type: Array,
109
        default() {
A
Amy 已提交
110 111 112 113 114 115
          return []
        },
        validator(arr) {
          return arr.every((item) => {
            return SCROLL_EVENTS.indexOf(item) !== -1
          })
116
        }
M
init  
miaodian 已提交
117
      },
A
Amy 已提交
118
      // TODO: plan to remove at 1.10.0
M
init  
miaodian 已提交
119 120
      listenScroll: {
        type: Boolean,
T
tank0317 已提交
121 122
        default: undefined,
        deprecated: {
F
fengweiyao 已提交
123
          replacedBy: 'scroll-events'
T
tank0317 已提交
124
        }
M
init  
miaodian 已提交
125 126 127
      },
      listenBeforeScroll: {
        type: Boolean,
T
tank0317 已提交
128 129
        default: undefined,
        deprecated: {
F
fengweiyao 已提交
130
          replacedBy: 'scroll-events'
T
tank0317 已提交
131
        }
M
init  
miaodian 已提交
132 133 134 135 136 137 138 139
      },
      direction: {
        type: String,
        default: DIRECTION_V
      },
      refreshDelay: {
        type: Number,
        default: 20
T
tank0317 已提交
140 141 142
      },
      nestMode: {
        type: String,
T
tank0317 已提交
143
        default: NEST_MODE_NONE
M
init  
miaodian 已提交
144 145 146 147 148 149 150
      }
    },
    data() {
      return {
        beforePullDown: true,
        isPullingDown: false,
        isPullUpLoad: false,
151
        pullUpNoMore: false,
M
init  
miaodian 已提交
152
        bubbleY: 0,
153 154
        pullDownStyle: '',
        pullDownStop: 40,
155 156
        pullDownHeight: 60,
        pullUpHeight: 0
M
init  
miaodian 已提交
157 158 159
      }
    },
    computed: {
160
      pullDownRefresh() {
161
        let pullDownRefresh = this.options.pullDownRefresh
D
dolymood 已提交
162
        if (!pullDownRefresh) {
163 164 165 166 167 168
          return pullDownRefresh
        }
        if (pullDownRefresh === true) {
          pullDownRefresh = {}
        }
        return Object.assign({stop: this.pullDownStop}, pullDownRefresh)
169
      },
170 171 172
      pullUpLoad() {
        return this.options.pullUpLoad
      },
M
init  
miaodian 已提交
173
      pullUpTxt() {
174
        const pullUpLoad = this.pullUpLoad
D
dolymood 已提交
175
        const txt = pullUpLoad && pullUpLoad.txt
D
doly mood 已提交
176 177
        const moreTxt = (txt && txt.more) || ''
        const noMoreTxt = (txt && txt.noMore) || ''
M
init  
miaodian 已提交
178

179
        return this.pullUpNoMore ? noMoreTxt : moreTxt
M
init  
miaodian 已提交
180 181
      },
      refreshTxt() {
182
        const pullDownRefresh = this.pullDownRefresh
D
doly mood 已提交
183
        return (pullDownRefresh && pullDownRefresh.txt) || DEFAULT_REFRESH_TXT
A
Amy 已提交
184 185 186 187 188 189 190 191 192
      },
      finalScrollEvents() {
        const finalScrollEvents = this.scrollEvents.slice()

        if (!finalScrollEvents.length) {
          this.listenScroll && finalScrollEvents.push(EVENT_SCROLL)
          this.listenBeforeScroll && finalScrollEvents.push(EVENT_BEFORE_SCROLL_START)
        }
        return finalScrollEvents
T
tank0317 已提交
193 194 195
      },
      needListenScroll() {
        return this.finalScrollEvents.indexOf(EVENT_SCROLL) !== -1 || this.parentScroll
M
init  
miaodian 已提交
196 197
      }
    },
198 199 200 201 202 203 204 205 206 207 208 209
    watch: {
      data() {
        setTimeout(() => {
          this.forceUpdate(true)
        }, this.refreshDelay)
      },
      pullDownRefresh: {
        handler(newVal, oldVal) {
          if (newVal) {
            this.scroll.openPullDown(newVal)
            if (!oldVal) {
              this._onPullDownRefresh()
210
              this._pullDownRefreshChangeHandler()
211 212 213 214 215 216
            }
          }

          if (!newVal && oldVal) {
            this.scroll.closePullDown()
            this._offPullDownRefresh()
217
            this._pullDownRefreshChangeHandler()
218 219 220 221 222 223 224 225 226 227
          }
        },
        deep: true
      },
      pullUpLoad: {
        handler(newVal, oldVal) {
          if (newVal) {
            this.scroll.openPullUp(newVal)
            if (!oldVal) {
              this._onPullUpLoad()
228
              this._pullUpLoadChangeHandler()
229 230 231 232 233 234
            }
          }

          if (!newVal && oldVal) {
            this.scroll.closePullUp()
            this._offPullUpLoad()
235
            this._pullUpLoadChangeHandler()
236 237 238 239 240 241 242 243 244 245 246 247
          }
        },
        deep: true
      }
    },
    activated() {
      /* istanbul ignore next */
      this.enable()
    },
    deactivated() {
      /* istanbul ignore next */
      this.disable()
M
init  
miaodian 已提交
248 249
    },
    mounted() {
250
      this.$nextTick(() => {
F
funanamy 已提交
251
        this.initScroll()
252
      })
M
init  
miaodian 已提交
253
    },
D
doly mood 已提交
254 255 256
    beforeDestroy() {
      this.destroy()
    },
M
init  
miaodian 已提交
257
    methods: {
F
funanamy 已提交
258
      initScroll() {
M
init  
miaodian 已提交
259 260 261
        if (!this.$refs.wrapper) {
          return
        }
262
        this._calculateMinHeight()
M
init  
miaodian 已提交
263

264
        let options = Object.assign({}, DEFAULT_OPTIONS, {
M
init  
miaodian 已提交
265
          scrollY: this.direction === DIRECTION_V,
A
Amy 已提交
266
          scrollX: this.direction === DIRECTION_H,
T
tank0317 已提交
267
          probeType: this.needListenScroll ? 3 : 1
268
        }, this.options)
M
init  
miaodian 已提交
269 270 271

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

T
tank0317 已提交
272
        this.parentScroll && this.nestMode !== NEST_MODE_NONE && this._handleNestScroll()
T
tank0317 已提交
273

A
Amy 已提交
274
        this._listenScrollEvents()
M
init  
miaodian 已提交
275 276

        if (this.pullDownRefresh) {
277
          this._onPullDownRefresh()
278
          this._pullDownRefreshChangeHandler()
M
init  
miaodian 已提交
279 280 281
        }

        if (this.pullUpLoad) {
282
          this._onPullUpLoad()
283
          this._pullUpLoadChangeHandler()
M
init  
miaodian 已提交
284 285 286 287 288 289 290 291 292
        }
      },
      disable() {
        this.scroll && this.scroll.disable()
      },
      enable() {
        this.scroll && this.scroll.enable()
      },
      refresh() {
293
        this._calculateMinHeight()
M
init  
miaodian 已提交
294 295
        this.scroll && this.scroll.refresh()
      },
F
funanamy 已提交
296
      destroy() {
D
dolymood 已提交
297
        this.scroll && this.scroll.destroy()
D
doly mood 已提交
298
        this.scroll = null
F
funanamy 已提交
299
      },
M
init  
miaodian 已提交
300 301 302 303 304 305 306 307 308
      scrollTo() {
        this.scroll && this.scroll.scrollTo.apply(this.scroll, arguments)
      },
      scrollToElement() {
        this.scroll && this.scroll.scrollToElement.apply(this.scroll, arguments)
      },
      clickItem(item) {
        this.$emit(EVENT_CLICK, item)
      },
T
tank0317 已提交
309 310 311 312 313
      async forceUpdate(dirty = false, nomore = false) {
        if (this.isPullDownUpdating) {
          return
        }

M
init  
miaodian 已提交
314
        if (this.pullDownRefresh && this.isPullingDown) {
U
ustbhuangyi 已提交
315
          this.isPullingDown = false
T
tank0317 已提交
316 317 318
          this.isPullDownUpdating = true
          await this._waitFinishPullDown()
          this.isPullDownUpdating = false
319
          await this._waitResetPullDown(dirty)
M
init  
miaodian 已提交
320 321 322
        } else if (this.pullUpLoad && this.isPullUpLoad) {
          this.isPullUpLoad = false
          this.scroll.finishPullUp()
323
          this.pullUpNoMore = !dirty || nomore
M
init  
miaodian 已提交
324
        }
T
tank0317 已提交
325 326

        dirty && this.refresh()
M
init  
miaodian 已提交
327
      },
A
Amy 已提交
328
      resetPullUpTxt() {
329
        this.pullUpNoMore = false
A
Amy 已提交
330
      },
A
Amy 已提交
331 332
      _listenScrollEvents() {
        this.finalScrollEvents.forEach((event) => {
D
dolymood 已提交
333
          this.scroll.on(camelize(event), (...args) => {
A
Amy 已提交
334
            this.$emit(event, ...args)
D
dolymood 已提交
335
          })
A
Amy 已提交
336 337
        })
      },
T
tank0317 已提交
338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364
      _handleNestScroll() {
        // waiting scroll initial
        this.$nextTick(() => {
          const innerScroll = this.scroll
          const outerScroll = this.parentScroll.scroll
          const scrolls = [innerScroll, outerScroll]
          scrolls.forEach((scroll, index, arr) => {
            // scroll ended always enable them
            scroll.on('touchEnd', () => {
              outerScroll.enable()
              innerScroll.enable()
              // when inner scroll reaching boundary, we will disable inner scroll, so when 'touchend' event fire,
              // the inner scroll will not reset initiated within '_end' method in better-scroll.
              // then lead to inner and outer scrolls together when we touch and move on the outer scroll element,
              // so here we reset inner scroll's 'initiated' manually.
              innerScroll.initiated = false
            })

            scroll.on('beforeScrollStart', () => {
              this.touchStartMoment = true
              const anotherScroll = arr[(index + 1) % 2]
              anotherScroll.stop()
              anotherScroll.resetPosition()
            })
          })

          innerScroll.on('scroll', (pos) => {
T
tank0317 已提交
365
            // if scroll event triggered not by touch event, such as by 'scrollTo' method
T
tank0317 已提交
366
            if (!innerScroll.initiated || innerScroll.isInTransition) {
T
tank0317 已提交
367 368 369
              return
            }

T
tank0317 已提交
370
            if (this.nestMode === NEST_MODE_NATIVE && !this.touchStartMoment) {
T
tank0317 已提交
371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394
              return
            }

            const reachBoundary = this._checkReachBoundary(pos)
            if (reachBoundary) {
              innerScroll.resetPosition()
              innerScroll.disable()
              // Prevent outer scroll have a bouncing step when enabled in 'free' nestMode.
              outerScroll.pointX = innerScroll.pointX
              outerScroll.pointY = innerScroll.pointY
              outerScroll.enable()
            } else {
              outerScroll.disable()
            }
            this.touchStartMoment = false
          })
        })
      },
      _checkReachBoundary(pos) {
        const distX = this.scroll.distX
        const distY = this.scroll.distY
        const reachBoundaryX = distX > 0 ? pos.x >= this.scroll.minScrollX : distX < 0 ? pos.x <= this.scroll.maxScrollX : false
        const reachBoundaryY = distY > 0 ? pos.y >= this.scroll.minScrollY : distY < 0 ? pos.y <= this.scroll.maxScrollY : false
        const freeScroll = this.scroll.freeScroll
F
fengweiyao 已提交
395 396 397 398 399 400
        const hasHorizontalScroll = this.scroll.hasHorizontalScroll
        const hasVerticalScroll = this.scroll.hasVerticalScroll

        if (!hasHorizontalScroll && !hasVerticalScroll) {
          return true
        }
T
tank0317 已提交
401 402

        if (freeScroll) {
T
tank0317 已提交
403 404 405
          return reachBoundaryX || reachBoundaryY
        }

F
fengweiyao 已提交
406
        let reachBoundary
T
tank0317 已提交
407
        if (this.scroll.movingDirectionX) {
T
tank0317 已提交
408
          reachBoundary = reachBoundaryX
T
tank0317 已提交
409 410
        } else if (this.scroll.movingDirectionY) {
          reachBoundary = reachBoundaryY
T
tank0317 已提交
411 412 413
        }
        return reachBoundary
      },
414
      _calculateMinHeight() {
415 416 417 418 419 420 421 422 423 424 425 426 427
        const { wrapper, listWrapper } = this.$refs
        const pullUpLoad = this.pullUpLoad
        const pullDownRefresh = this.pullDownRefresh
        let minHeight = 0

        if (pullDownRefresh || pullUpLoad) {
          const wrapperHeight = getRect(wrapper).height
          minHeight = wrapperHeight + 1
          if (pullUpLoad && pullUpLoad.visible) {
            // minHeight = wrapperHeight + 1 - pullUpHeight, then pullUpLoad text is visible
            // when content's height is not larger than wrapper height
            minHeight -= this.pullUpHeight
          }
428
        }
429 430

        listWrapper.style.minHeight = `${minHeight}px`
431
      },
432 433 434
      _onPullDownRefresh() {
        this.scroll.on('pullingDown', this._pullDownHandle)
        this.scroll.on('scroll', this._pullDownScrollHandle)
M
init  
miaodian 已提交
435
      },
436 437 438 439
      _offPullDownRefresh() {
        this.scroll.off('pullingDown', this._pullDownHandle)
        this.scroll.off('scroll', this._pullDownScrollHandle)
      },
440 441 442 443 444 445
      _pullDownRefreshChangeHandler() {
        this.$nextTick(() => {
          this._getPullDownEleHeight()
          this._calculateMinHeight()
        })
      },
446
      _pullDownHandle() {
447 448 449
        if (this.resetPullDownTimer) {
          clearTimeout(this.resetPullDownTimer)
        }
450 451 452 453 454 455
        this.beforePullDown = false
        this.isPullingDown = true
        this.$emit(EVENT_PULLING_DOWN)
      },
      _pullDownScrollHandle(pos) {
        if (this.beforePullDown) {
456 457
          this.bubbleY = Math.max(0, pos.y - this.pullDownHeight)
          this.pullDownStyle = `top:${Math.min(pos.y - this.pullDownHeight, 0)}px`
458 459
        } else {
          this.bubbleY = 0
460
          this.pullDownStyle = `top:${Math.min(pos.y - this.pullDownStop, 0)}px`
461 462
        }
      },
463 464 465 466 467 468
      _pullUpLoadChangeHandler() {
        this.$nextTick(() => {
          this._getPullUpEleHeight()
          this._calculateMinHeight()
        })
      },
469 470 471 472 473 474 475 476 477
      _onPullUpLoad() {
        this.scroll.on('pullingUp', this._pullUpHandle)
      },
      _offPullUpLoad() {
        this.scroll.off('pullingUp', this._pullUpHandle)
      },
      _pullUpHandle() {
        this.isPullUpLoad = true
        this.$emit(EVENT_PULLING_UP)
M
init  
miaodian 已提交
478
      },
T
tank0317 已提交
479
      _waitFinishPullDown(next) {
480
        const {stopTime = DEFAULT_STOP_TIME} = this.pullDownRefresh
T
tank0317 已提交
481 482 483 484 485 486
        return new Promise(resolve => {
          setTimeout(() => {
            this.scroll.finishPullDown()
            resolve()
          }, stopTime)
        })
M
init  
miaodian 已提交
487
      },
T
tank0317 已提交
488 489 490 491 492 493 494 495
      _waitResetPullDown(dirty) {
        return new Promise(resolve => {
          this.resetPullDownTimer = setTimeout(() => {
            this.pullDownStyle = `top: -${this.pullDownHeight}px`
            this.beforePullDown = true
            resolve()
          }, this.scroll.options.bounceTime)
        })
A
Amy 已提交
496
      },
497
      _getPullDownEleHeight() {
498 499 500 501 502
        let pulldown = this.$refs.pulldown
        if (!pulldown) {
          return
        }
        pulldown = pulldown.firstChild
503 504 505 506 507 508 509 510 511 512
        this.pullDownHeight = getRect(pulldown).height

        this.beforePullDown = false
        this.isPullingDown = true
        this.$nextTick(() => {
          this.pullDownStop = getRect(pulldown).height

          this.beforePullDown = true
          this.isPullingDown = false
        })
513 514 515 516 517 518 519 520 521
      },
      _getPullUpEleHeight() {
        const listWrapper = this.$refs.listWrapper
        const pullup = listWrapper.nextElementSibling
        if (!pullup) {
          this.pullUpHeight = 0
          return
        }
        this.pullUpHeight = getRect(pullup).height
M
init  
miaodian 已提交
522 523 524 525 526 527 528 529 530 531
      }
    },
    components: {
      Loading,
      Bubble
    }
  }
</script>

<style lang="stylus" rel="stylesheet/stylus">
532 533 534
  @require "../../common/stylus/variable.styl"

  .cube-scroll-wrapper
535
    position: relative
536
    height: 100%
537
    overflow: hidden
538

539 540 541
  .cube-scroll-list-wrapper
    overflow: hidden

M
init  
miaodian 已提交
542 543 544 545 546 547 548 549
  .cube-pulldown-wrapper
    position: absolute
    width: 100%
    left: 0
    display: flex
    justify-content: center
    align-items: center
    transition: all
550 551 552 553
    .before-trigger
      height: 54px
      line-height: 0
      padding-top: 6px
M
init  
miaodian 已提交
554
    .after-trigger
555 556 557 558
      .loading
        padding: 8px 0
      .cube-pulldown-loaded
        padding: 12px 0
M
init  
miaodian 已提交
559 560 561 562 563 564

  .cube-pullup-wrapper
    width: 100%
    display: flex
    justify-content: center
    align-items: center
565 566
    .before-trigger
      padding: 22px 0
A
AmyFoxFN 已提交
567
      min-height: 1em
568
    .after-trigger
A
AmyFoxFN 已提交
569
      padding: 19px 0
U
ustbhuangyi 已提交
570 571 572 573

  .cube-scroll-content
    position: relative
    z-index: 1
574

575 576 577 578 579
  .cube-scroll-item
    height: 60px
    line-height: 60px
    font-size: $fontsize-large-x
    padding-left: 20px
M
init  
miaodian 已提交
580
</style>