# mouse-wheel
mouseWheel 扩展 BetterScroll 鼠标滚轮的能力。
# 安装
npm install @better-scroll/mouse-wheel --save
// or
yarn add @better-scroll/mouse-wheel
TIP
目前支持鼠标滚轮有:core、slide、wheel、pullup、pulldown
# 基础使用
为了开启鼠标滚动功能,你需要首先引入 mouseWheel 插件,通过静态方法 BScroll.use()
注册插件,最后传入正确的 mouseWheel 选项对象
import BScroll from '@better-scroll/core'
import MouseWheel from '@better-scroll/mouse-wheel'
BScroll.use(MouseWheel)
new BScroll('.bs-wrapper', {
//...
mouseWheel: {
speed: 20,
invert: false,
easeTime: 300
}
})
纵向普通滚动示例
<template> <div class="mouse-wheel-vertical-scroll"> <div class="mouse-wheel-wrapper" ref="scroll"> <div class="mouse-wheel-content"> <div class="mouse-wheel-item" v-for="n in 100" :key="n">{{n}}</div> </div> </div> </div> </template>
<script type="text/ecmascript-6"> import BScroll from '@better-scroll/core' import MouseWheel from '@better-scroll/mouse-wheel' BScroll.use(MouseWheel) export default { mounted() { this.init() }, methods: { init() { this.scroll = new BScroll(this.$refs.scroll, { mouseWheel: true }) } } } </script>
<style lang="stylus" rel="stylesheet/stylus" scoped> .mouse-wheel-vertical-scroll .mouse-wheel-wrapper height 400px overflow hidden .mouse-wheel-item height 50px line-height 50px font-size 20px font-weight bold text-align center &:nth-child(2n) background-color #C3D899 &:nth-child(2n+1) background-color #F2D4A7 </style>
横向普通滚动示例
<template> <div class="mouse-wheel-horizontal-scroll"> <div class="mouse-wheel-wrapper" ref="scroll"> <div class="mouse-wheel-content"> <div class="mouse-wheel-item" v-for="n in 100" :key="n">{{n}}</div> </div> </div> </div> </template>
<script type="text/ecmascript-6"> import BScroll from '@better-scroll/core' import MouseWheel from '@better-scroll/mouse-wheel' BScroll.use(MouseWheel) export default { mounted() { this.init() }, methods: { init() { this.bs = new BScroll(this.$refs.scroll, { scrollX: true, scrollY: false, mouseWheel: true }) } } } </script>
<style lang="stylus" scoped> .mouse-wheel-horizontal-scroll .mouse-wheel-wrapper width 90% margin 80px auto white-space nowrap border 3px solid #42b983 border-radius 5px overflow hidden .mouse-wheel-content display inline-block .mouse-wheel-item height 50px line-height 50px font-size 24px display inline-block text-align center padding 0 20px &:nth-child(2n) background-color #C3D899 &:nth-child(2n+1) background-color #F2D4A7 </style>
# 进阶使用
mouseWheel 插件还可以搭配其他的插件,为其增加鼠标滚轮的操作。
mouseWheel & slide
通过鼠标滚轮操作 slide。
横向 slide 示例
<template> <div class="mouse-wheel-horizontal-slide"> <div class="slide-container"> <div class="slide-wrapper" ref="slide"> <div class="slide-content"> <div class="slide-page page1">page 1</div> <div class="slide-page page2">page 2</div> <div class="slide-page page3">page 3</div> <div class="slide-page page4">page 4</div> </div> </div> <div class="dots-wrapper"> <span class="dot" v-for="(item, index) in 4" :key="index" :class="{'active': currentPageIndex === index}"></span> </div> </div> </div> </template>
<script type="text/ecmascript-6"> import BScroll from '@better-scroll/core' import Slide from '@better-scroll/slide' import MouseWheel from '@better-scroll/mouse-wheel' BScroll.use(Slide) BScroll.use(MouseWheel) export default { data() { return { currentPageIndex: 0 } }, mounted() { this.init() }, beforeDestroy() { this.slide.destroy() }, methods: { init() { this.slide = new BScroll(this.$refs.slide, { scrollX: true, scrollY: false, slide: { loop: true, threshold: 100 }, useTransition: false, momentum: false, bounce: false, stopPropagation: true, mouseWheel: { speed: 2, invert: false, easeTime: 300 } }) this.slide.on('scrollEnd', this._onScrollEnd) }, _onScrollEnd() { let pageIndex = this.slide.getCurrentPage().pageX this.currentPageIndex = pageIndex } } } </script>
<style lang="stylus" rel="stylesheet/stylus"> .mouse-wheel-horizontal-slide .slide-container position relative .slide-wrapper min-height 1px overflow hidden .slide-content height 200px white-space nowrap font-size 0 .slide-page display inline-block height 200px width 100% line-height 200px text-align center font-size 26px &.page1 background-color #95B8D1 &.page2 background-color #DDA789 &.page3 background-color #C3D899 &.page4 background-color #F2D4A7 .dots-wrapper position absolute bottom 4px left 50% transform translateX(-50%) .dot display inline-block margin 0 4px width 8px height 8px border-radius 50% background #eee &.active width 20px border-radius 5px </style>
纵向 slide 示例
<template> <div class="mouse-wheel-slide-vertical"> <div class="slide-container"> <div class="slide-wrapper" ref="slide"> <div class="slide-content"> <div class="slide-page page1">page 1</div> <div class="slide-page page2">page 2</div> <div class="slide-page page3">page 3</div> <div class="slide-page page4">page 4</div> </div> </div> <div class="dots-wrapper"> <span class="dot" v-for="(item, index) in 4" :key="index" :class="{'active': currentPageIndex === index}"></span> </div> </div> </div> </template>
<script type="text/ecmascript-6"> import BScroll from '@better-scroll/core' import Slide from '@better-scroll/slide' import MouseWheel from '@better-scroll/mouse-wheel' BScroll.use(MouseWheel) BScroll.use(Slide) export default { data() { return { currentPageIndex: 0 } }, mounted() { this.init() }, beforeDestroy() { this.slide.destroy() }, methods: { init() { this.slide = new BScroll(this.$refs.slide, { scrollX: false, scrollY: true, slide: { loop: true, threshold: 100 }, mouseWheel: true, momentum: false, bounce: false, stopPropagation: true }) this.slide.on('scrollEnd', this._onScrollEnd) }, _onScrollEnd() { let pageIndex = this.slide.getCurrentPage().pageY this.currentPageIndex = pageIndex } } } </script>
<style lang="stylus" rel="stylesheet/stylus"> .mouse-wheel-slide-vertical height 100% &.view padding 0 height 100% .slide-container position relative height 100% font-size 0 .slide-wrapper height 100% overflow hidden .slide-page display inline-block width 100% line-height 200px text-align center font-size 26px transform translate3d(0,0,0) backface-visibility hidden &.page1 background-color #D6EADF &.page2 background-color #DDA789 &.page3 background-color #C3D899 &.page4 background-color #F2D4A7 .dots-wrapper position absolute right 4px top 50% transform translateY(-50%) .dot display block margin 4px 0 width 8px height 8px border-radius 50% background #eee &.active height 20px border-radius 5px </style>
mouseWheel & pullup
通过鼠标触发上拉加载 pullup。
<template> <div class="mouse-wheel-pullup"> <div ref="scroll" class="pullup-wrapper"> <div class="pullup-content"> <ul class="pullup-list"> <li v-for="i of data" :key="i" class="pullup-list-item"> {{ i % 5 === 0 ? 'use your mousewheel please 👆🏻' : `I am item ${i} `}} </li> </ul> <div class="pullup-tips"> <div v-if="!isPullUpLoad" class="before-trigger"> <span class="pullup-txt">mousewheel trigger pullingup and load more</span> </div> <div v-else class="after-trigger"> <span class="pullup-txt">Loading...</span> </div> </div> </div> </div> </div> </template>
<script> import BScroll from '@better-scroll/core' import Pullup from '@better-scroll/pull-up' import MouseWheel from '@better-scroll/mouse-wheel' BScroll.use(Pullup) BScroll.use(MouseWheel) export default { data() { return { isPullUpLoad: false, data: 30 } }, mounted() { this.initBscroll() }, methods: { initBscroll() { this.scroll = new BScroll(this.$refs.scroll, { probeType: 3, pullUpLoad: true, mouseWheel: true }) this.scroll.on('pullingUp', this.pullingUpHandler) }, async pullingUpHandler() { this.isPullUpLoad = true await this.requestData() this.scroll.finishPullUp() this.scroll.refresh() this.isPullUpLoad = false }, async requestData() { try { const newData = await this.ajaxGet(/* url */) this.data += newData } catch (err) { // handle err console.log(err) } }, ajaxGet(/* url */) { return new Promise(resolve => { setTimeout(() => { resolve(20) }, 1000) }) } } } </script>
<style lang="stylus" scoped> .mouse-wheel-pullup height: 100% .pullup-wrapper height: 100% padding: 0 10px border: 1px solid #ccc overflow: hidden .pullup-list padding: 0 .pullup-list-item padding: 10px 0 list-style: none border-bottom: 1px solid #ccc .pullup-tips padding: 20px text-align: center color: #999 </style>
mouseWheel & pulldown
通过鼠标触发下拉加载 pulldown。
<template> <div class="mouse-wheel-pulldown"> <div ref="scroll" class="pulldown-wrapper"> <div class="pulldown-content"> <div class="pulldown-tips"> <div v-show="beforePullDown"> <span>Pull Down and refresh</span> </div> <div v-show="!beforePullDown"> <div v-show="isPullingDown"> <span>Loading...</span> </div> <div v-show="!isPullingDown"><span>Refresh success</span></div> </div> </div> <ul class="pulldown-list"> <li v-for="i of dataList" :key="i" class="pulldown-list-item"> {{ `I am item ${i} ` }} </li> </ul> </div> </div> </div> </template>
<script> import BScroll from '@better-scroll/core' import PullDown from '@better-scroll/pull-down' import MouseWheel from '@better-scroll/mouse-wheel' BScroll.use(PullDown) BScroll.use(MouseWheel) function generateData() { const BASE = 30 const begin = BASE * STEP const end = BASE * (STEP + 1) let ret = [] for(let i = end; i > begin; i--) { ret.push(i) } return ret } const TIME_BOUNCE = 800 const TIME_STOP = 600 const REQUEST_TIME = 1000 const THRESHOLD = 70 const STOP = 56 let STEP = 0 export default { data() { return { beforePullDown: true, isPullingDown: false, dataList: generateData() } }, mounted() { this.initBscroll() }, methods: { initBscroll() { this.bscroll = new BScroll(this.$refs.scroll, { scrollY: true, bounceTime: TIME_BOUNCE, mouseWheel: true, pullDownRefresh: { threshold: THRESHOLD, stop: STOP } }) this.bscroll.on('pullingDown', this.pullingDownHandler) this.bscroll.on('scroll', this.scrollHandler) this.bscroll.on('scrollEnd', (e) => { console.log('scrollEnd') }) }, scrollHandler(pos) { console.log(pos.y) }, async pullingDownHandler() { console.log('trigger pullDown') this.beforePullDown = false this.isPullingDown = true STEP += 1 await this.requestData() this.isPullingDown = false this.finishPullDown() }, async finishPullDown() { const stopTime = TIME_STOP await new Promise(resolve => { setTimeout(() => { this.bscroll.finishPullDown() resolve() }, stopTime) }) setTimeout(() => { this.beforePullDown = true this.bscroll.refresh() }, TIME_BOUNCE) }, async requestData() { try { const newData = await this.ajaxGet(/* url */) this.dataList = newData.concat(this.dataList) } catch (err) { // handle err console.log(err) } }, ajaxGet(/* url */) { return new Promise(resolve => { setTimeout(() => { const dataList = generateData() resolve(dataList) }, REQUEST_TIME) }) } } } </script>
<style lang="stylus" scoped> .mouse-wheel-pulldown height: 100% .pulldown-wrapper position: relative height: 100% padding: 0 10px border: 1px solid #ccc overflow: hidden .pulldown-list padding: 0 .pulldown-list-item padding: 10px 0 list-style: none border-bottom: 1px solid #ccc .pulldown-tips position: absolute width: 100% padding: 20px box-sizing: border-box transform: translateY(-100%) translateZ(0) text-align: center color: #999 </style>
mouseWheel & wheel
通过鼠标触发 wheel。
<template> <div class="mouse-wheel-picker"> <ul class="example-list"> <li class="example-item" @click="show"> <span class="open">{{selectedText}}</span> </li> </ul> <transition name="picker-fade"> <div class="picker" v-show="state===1" @touchmove.prevent @click="_cancel"> <transition name="picker-move"> <div class="picker-panel" v-show="state===1" @click.stop> <div class="picker-choose border-bottom-1px"> <span class="cancel" @click="_cancel">Cancel</span> <span class="confirm" @click="_confirm">Confirm</span> <h1 class="picker-title">Title</h1> </div> <div class="picker-content"> <div class="mask-top border-bottom-1px"></div> <div class="mask-bottom border-top-1px"></div> <div class="wheel-wrapper" ref="wheelWrapper"> <div class="wheel"> <ul class="wheel-scroll"> <li v-for="(item, index) in pickerData" :key="index" :class="{'wheel-disabled-item':item.disabled}" class="wheel-item">{{item.text}}</li> </ul> </div> </div> </div> <div class="picker-footer"></div> </div> </transition> </div> </transition> </div> </template>
<script type="text/ecmascript-6"> import BScroll from '@better-scroll/core' import Wheel from '@better-scroll/wheel' import MouseWheel from '@better-scroll/mouse-wheel' BScroll.use(Wheel) BScroll.use(MouseWheel) const STATE_HIDE = 0 const STATE_SHOW = 1 const COMPONENT_NAME = 'picker' const EVENT_SELECT = 'select' const EVENT_CANCEL = 'cancel' const EVENT_CHANGE = 'change' const DATA = [ { text: 'Venomancer', value: 1, disabled: 'wheel-disabled-item' }, { text: 'Nerubian Weaver', value: 2 }, { text: 'Spectre', value: 3 }, { text: 'Juggernaut', value: 4 }, { text: 'Karl', value: 5 }, { text: 'Zeus', value: 6 }, { text: 'Witch Doctor', value: 7 }, { text: 'Lich', value: 8 }, { text: 'Oracle', value: 9 }, { text: 'Earthshaker', value: 10 } ] export default { name: COMPONENT_NAME, data() { return { state: STATE_HIDE, selectedIndex: 2, selectedText: 'open', pickerData: DATA } }, methods: { _confirm() { if (this._isMoving()) { return } this.hide() const currentSelectedIndex = this.wheel.getSelectedIndex() this.selectedIndex = currentSelectedIndex this.selectedText = this.pickerData[this.selectedIndex].text this.$emit(EVENT_SELECT, currentSelectedIndex) }, _cancel() { this.hide() this.$emit(EVENT_CANCEL) }, _isMoving() { return this.wheel.pending }, show() { if (this.state === STATE_SHOW) { return } this.state = STATE_SHOW if (!this.wheel) { // waiting for DOM rendered this.$nextTick(() => { const wrapper = this.$refs.wheelWrapper.children[0] this._createWheel(wrapper) }) } else { this.wheel.enable() this.wheel.wheelTo(this.selectedIndex) } }, hide() { this.state = STATE_HIDE // if wheel is in animation, clear timer in it this.wheel.disable() }, refresh() { this.$nextTick(() => { this.wheel.refresh() }) }, _createWheel(wheelWrapper) { if (!this.wheel) { this.wheel = new BScroll(wheelWrapper, { mouseWheel: true, wheel: { selectedIndex: this.selectedIndex, wheelWrapperClass: 'wheel-scroll', wheelItemClass: 'wheel-item', wheelDisabledItemClass: 'wheel-disabled-item' }, probeType: 3, useTransition: true, }) this.wheel.on('scrollEnd', () => { this.$emit(EVENT_CHANGE, this.wheel.getSelectedIndex()) }) } else { this.wheel.refresh() } return this.wheel } } } </script>
<style scoped lang="stylus" rel="stylesheet/stylus"> /* reset */ ul list-style none padding 0 .example-list display: flex justify-content: space-between flex-wrap: wrap margin: 2rem .example-item background-color white padding: 0.8rem border: 1px solid rgba(0, 0, 0, .1) box-shadow: 0 1px 2px 0 rgba(0,0,0,0.1) text-align: center margin-bottom: 1rem flex: 1 &.placeholder visibility: hidden height: 0 margin: 0 padding: 0 .picker position: fixed left: 0 top: 0 z-index: 100 width: 100% height: 100% overflow: hidden text-align: center font-size: 14px background-color: rgba(37, 38, 45, .4) &.picker-fade-enter, &.picker-fade-leave-active opacity: 0 &.picker-fade-enter-active, &.picker-fade-leave-active transition: all .3s ease-in-out .picker-panel position: absolute z-index: 600 bottom: 0 width: 100% height: 273px background: white &.picker-move-enter, &.picker-move-leave-active transform: translate3d(0, 273px, 0) &.picker-move-enter-active, &.picker-move-leave-active transition: all .3s ease-in-out .picker-choose position: relative height: 60px color: #999 .picker-title margin: 0 line-height: 60px font-weight: normal text-align: center font-size: 18px color: #333 .confirm, .cancel position: absolute top: 6px padding: 16px font-size: 14px .confirm right: 0 color: #007bff &:active color: #5aaaff .cancel left: 0 &:active color: #c2c2c2 .picker-content position: relative top: 20px .mask-top, .mask-bottom z-index: 10 width: 100% height: 68px pointer-events: none transform: translateZ(0) .mask-top position: absolute top: 0 background: linear-gradient(to top, rgba(255, 255, 255, 0.4), rgba(255, 255, 255, 0.8)) .mask-bottom position: absolute bottom: 1px background: linear-gradient(to bottom, rgba(255, 255, 255, 0.4), rgba(255, 255, 255, 0.8)) .wheel-wrapper display: flex padding: 0 16px .wheel -ms-flex: 1 1 0.000000001px -webkit-box-flex: 1 -webkit-flex: 1 flex: 1 -webkit-flex-basis: 0.000000001px flex-basis: 0.000000001px width: 1% height: 173px overflow: hidden font-size: 18px .wheel-scroll padding: 0 margin-top: 68px line-height: 36px list-style: none .wheel-item list-style: none height: 36px overflow: hidden white-space: nowrap color: #333 &.wheel-disabled-item opacity: .2; .picker-footer height: 20px </style>
# mouseWheel 选项对象
# speed
- 类型:
number
- 默认值:
20
鼠标滚轮滚动的速度。
# invert
- 类型:
boolean
- 默认值:
false
当该值为 true 时,表示滚轮滚动和 BetterScroll 滚动的方向相反。
# easeTime
- 类型:
number
- 默认值:
300
(ms)
滚动动画的缓动时长。
# discreteTime
- 类型:
number
- 默认值:
400
(ms)
由于滚轮滚动是一种离散的运动,并没有 start、move、end 的事件类型,因此只要在 discreteTime 时间内没有探测到滚动,那么一次的滚轮动作就结束了。
注意
当搭配 pulldown 插件的时候,easeTime
和 discreteTime
会被内部修改成合理的固定值,以便触发 pullingDown
钩子
# throttleTime
- 类型:
number
- 默认值:
0
(ms)
由于滚轮滚动是高频率的动作,因此可以通过 throttleTime 来限制触发频率,mouseWheel 内部会缓存滚动的距离,并且每隔 throttleTime 会计算缓存的距离并且滚动。
修改 throttleTime 可能会造成滚动动画不连贯,请根据实际场景进行调整。
# dampingFactor
- 类型:
number
- 默认值:
0.1
阻尼因子,值的范围是[0, 1],当 BetterScroll 滚出边界的时候,需要施加阻力,防止滚动幅度过大,值越小,阻力越大。
提示
当 mouseWheel 配置为 true 的时候,插件内部使用的是默认的插件选项对象。
const bs = new BScroll('.wrapper', {
mouseWheel: true
})
// 相当于
const bs = new BScroll('.wrapper', {
mouseWheel: {
speed: 20,
invert: false,
easeTime: 300,
discreteTime: 400,
throttleTime: 0,
dampingFactor: 0.1
}
})
# 事件
# alterOptions
- 参数:
MouseWheelConfig
export interface MouseWheelConfig { speed: number invert: boolean easeTime: number discreteTime: number throttleTime: number, dampingFactor: number }
- 触发时机:滚轮滚动开始
允许修改 options 来控制滚动中的某些行为。
# mousewheelStart
- 参数:无
- 触发时机:滚轮滚动开始。
# mousewheelMove
- 参数:
{ x, y }
{ number } x
:当前 BetterScroll 的横向滚动位置{ number } y
:当前 BetterScroll 的纵向滚动位置- 类型:
{ x: number, y: number }
- 触发时机:滚轮滚动中
# mousewheelEnd
- 参数:
delta
- 类型:
WheelDelta
interface WheelDelta {
x: number
y: number
directionX: Direction
directionY: Direction
}
- 触发时机:discreteTime 之后如果还没有触发 mousewheel 事件,那么便结算一次滚轮滚动行为。
警告
由于 mousewheel 事件的特殊性,mousewheelEnd 派发并不代表滚动动画结束。
提示
在绝大多数的场景下,如果你想要精确的知道当前 BetterScroll 的滚动位置,请监听 scroll、scrollEnd 钩子,而不是 mouseXXX
钩子。
← 如何写一个插件 observe-dom →