scroll.vue 14.9 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 74
  const NEST_MODE_NATIVE = 'native'

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

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

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

        return this.pullUpDirty ? moreTxt : noMoreTxt
      },
      refreshTxt() {
180
        const pullDownRefresh = this.pullDownRefresh
D
doly mood 已提交
181
        return (pullDownRefresh && pullDownRefresh.txt) || DEFAULT_REFRESH_TXT
A
Amy 已提交
182 183 184 185 186 187 188 189 190
      },
      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 已提交
191 192 193
      },
      needListenScroll() {
        return this.finalScrollEvents.indexOf(EVENT_SCROLL) !== -1 || this.parentScroll
M
init  
miaodian 已提交
194 195
      }
    },
196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245
    watch: {
      data() {
        setTimeout(() => {
          this.forceUpdate(true)
        }, this.refreshDelay)
      },
      pullDownRefresh: {
        handler(newVal, oldVal) {
          if (newVal) {
            this.scroll.openPullDown(newVal)
            if (!oldVal) {
              this._onPullDownRefresh()
              this._calculateMinHeight()
            }
          }

          if (!newVal && oldVal) {
            this.scroll.closePullDown()
            this._offPullDownRefresh()
            this._calculateMinHeight()
          }
        },
        deep: true
      },
      pullUpLoad: {
        handler(newVal, oldVal) {
          if (newVal) {
            this.scroll.openPullUp(newVal)
            if (!oldVal) {
              this._onPullUpLoad()
              this._calculateMinHeight()
            }
          }

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

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

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

T
tank0317 已提交
270
        this.parentScroll && this.nestMode !== 'none' && this._handleNestScroll()
T
tank0317 已提交
271

A
Amy 已提交
272
        this._listenScrollEvents()
M
init  
miaodian 已提交
273 274

        if (this.pullDownRefresh) {
275
          this._getPullDownEleHeight()
276
          this._onPullDownRefresh()
M
init  
miaodian 已提交
277 278 279
        }

        if (this.pullUpLoad) {
280
          this._onPullUpLoad()
M
init  
miaodian 已提交
281 282 283 284 285 286 287 288 289
        }
      },
      disable() {
        this.scroll && this.scroll.disable()
      },
      enable() {
        this.scroll && this.scroll.enable()
      },
      refresh() {
290
        this._calculateMinHeight()
M
init  
miaodian 已提交
291 292
        this.scroll && this.scroll.refresh()
      },
F
funanamy 已提交
293
      destroy() {
D
dolymood 已提交
294
        this.scroll && this.scroll.destroy()
D
doly mood 已提交
295
        this.scroll = null
F
funanamy 已提交
296
      },
M
init  
miaodian 已提交
297 298 299 300 301 302 303 304 305
      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)
      },
306
      forceUpdate(dirty = false) {
M
init  
miaodian 已提交
307
        if (this.pullDownRefresh && this.isPullingDown) {
U
ustbhuangyi 已提交
308
          this.isPullingDown = false
309
          this._reboundPullDown(() => {
A
AmyFoxFN 已提交
310
            this._afterPullDown(dirty)
M
init  
miaodian 已提交
311 312 313 314 315
          })
        } else if (this.pullUpLoad && this.isPullUpLoad) {
          this.isPullUpLoad = false
          this.scroll.finishPullUp()
          this.pullUpDirty = dirty
A
AmyFoxFN 已提交
316
          dirty && this.refresh()
M
init  
miaodian 已提交
317
        } else {
A
AmyFoxFN 已提交
318
          dirty && this.refresh()
M
init  
miaodian 已提交
319 320
        }
      },
A
Amy 已提交
321 322 323
      resetPullUpTxt() {
        this.pullUpDirty = true
      },
A
Amy 已提交
324 325
      _listenScrollEvents() {
        this.finalScrollEvents.forEach((event) => {
D
dolymood 已提交
326
          this.scroll.on(camelize(event), (...args) => {
A
Amy 已提交
327
            this.$emit(event, ...args)
D
dolymood 已提交
328
          })
A
Amy 已提交
329 330
        })
      },
T
tank0317 已提交
331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357
      _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 已提交
358
            if (!innerScroll.initiated || innerScroll.isInTransition) {
T
tank0317 已提交
359 360 361
              return
            }

T
tank0317 已提交
362
            if (this.nestMode === NEST_MODE_NATIVE && !this.touchStartMoment) {
T
tank0317 已提交
363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397
              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

        let reachBoundary
        if (freeScroll) {
          reachBoundary = reachBoundaryX || reachBoundaryY
        } else if (this.direction === DIRECTION_V) {
          reachBoundary = reachBoundaryY
        } else if (this.direction === DIRECTION_H) {
          reachBoundary = reachBoundaryX
        }
        return reachBoundary
      },
398
      _calculateMinHeight() {
399 400
        if (this.$refs.listWrapper) {
          this.$refs.listWrapper.style.minHeight = this.pullDownRefresh || this.pullUpLoad ? `${getRect(this.$refs.wrapper).height + 1}px` : 0
401 402
        }
      },
403 404 405
      _onPullDownRefresh() {
        this.scroll.on('pullingDown', this._pullDownHandle)
        this.scroll.on('scroll', this._pullDownScrollHandle)
M
init  
miaodian 已提交
406
      },
407 408 409 410 411
      _offPullDownRefresh() {
        this.scroll.off('pullingDown', this._pullDownHandle)
        this.scroll.off('scroll', this._pullDownScrollHandle)
      },
      _pullDownHandle() {
412 413 414
        if (this.resetPullDownTimer) {
          clearTimeout(this.resetPullDownTimer)
        }
415 416 417 418 419 420
        this.beforePullDown = false
        this.isPullingDown = true
        this.$emit(EVENT_PULLING_DOWN)
      },
      _pullDownScrollHandle(pos) {
        if (this.beforePullDown) {
421 422
          this.bubbleY = Math.max(0, pos.y - this.pullDownHeight)
          this.pullDownStyle = `top:${Math.min(pos.y - this.pullDownHeight, 0)}px`
423 424
        } else {
          this.bubbleY = 0
425
          this.pullDownStyle = `top:${Math.min(pos.y - this.pullDownStop, 0)}px`
426 427 428 429 430 431 432 433 434 435 436
        }
      },
      _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 已提交
437
      },
438 439 440 441 442 443
      _reboundPullDown(next) {
        const {stopTime = DEFAULT_STOP_TIME} = this.pullDownRefresh
        setTimeout(() => {
          this.scroll.finishPullDown()
          next()
        }, stopTime)
M
init  
miaodian 已提交
444
      },
A
AmyFoxFN 已提交
445
      _afterPullDown(dirty) {
446
        this.resetPullDownTimer = setTimeout(() => {
447
          this.pullDownStyle = `top: -${this.pullDownHeight}px`
M
init  
miaodian 已提交
448
          this.beforePullDown = true
A
AmyFoxFN 已提交
449
          dirty && this.refresh()
M
init  
miaodian 已提交
450
        }, this.scroll.options.bounceTime)
A
Amy 已提交
451
      },
452 453 454 455 456 457 458 459 460 461 462 463
      _getPullDownEleHeight() {
        const pulldown = this.$refs.pulldown.firstChild
        this.pullDownHeight = getRect(pulldown).height

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

          this.beforePullDown = true
          this.isPullingDown = false
        })
M
init  
miaodian 已提交
464 465 466 467 468 469 470 471 472 473
      }
    },
    components: {
      Loading,
      Bubble
    }
  }
</script>

<style lang="stylus" rel="stylesheet/stylus">
474 475 476
  @require "../../common/stylus/variable.styl"

  .cube-scroll-wrapper
477
    position: relative
478
    height: 100%
479
    overflow: hidden
480

481 482 483
  .cube-scroll-list-wrapper
    overflow: hidden

M
init  
miaodian 已提交
484 485 486 487 488 489 490 491
  .cube-pulldown-wrapper
    position: absolute
    width: 100%
    left: 0
    display: flex
    justify-content: center
    align-items: center
    transition: all
492 493 494 495
    .before-trigger
      height: 54px
      line-height: 0
      padding-top: 6px
M
init  
miaodian 已提交
496
    .after-trigger
497 498 499 500
      .loading
        padding: 8px 0
      .cube-pulldown-loaded
        padding: 12px 0
M
init  
miaodian 已提交
501 502 503 504 505 506

  .cube-pullup-wrapper
    width: 100%
    display: flex
    justify-content: center
    align-items: center
507 508
    .before-trigger
      padding: 22px 0
A
AmyFoxFN 已提交
509
      min-height: 1em
510
    .after-trigger
A
AmyFoxFN 已提交
511
      padding: 19px 0
U
ustbhuangyi 已提交
512 513 514 515

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

517 518 519 520 521
  .cube-scroll-item
    height: 60px
    line-height: 60px
    font-size: $fontsize-large-x
    padding-left: 20px
M
init  
miaodian 已提交
522
</style>