mirror of
				https://gitee.com/vant-contrib/vant.git
				synced 2025-10-27 01:32:10 +08:00 
			
		
		
		
	feat: support picker can scroll on desktop browser (#9713)
* feat: support picker can scroll on desktop browser fix: change mousewheel event and fix pr review opt: optimize wheel on first item * chore: use deltaY directly * fix: direction and unit test Co-authored-by: genffy <genffyl@gmail.com>
This commit is contained in:
		
							parent
							
								
									999f465c97
								
							
						
					
					
						commit
						14303f4ccb
					
				| @ -1,7 +1,7 @@ | |||||||
| import { deepClone } from '../utils/deep-clone'; | import { deepClone } from '../utils/deep-clone'; | ||||||
| import { createNamespace, isObject } from '../utils'; | import { createNamespace, isObject } from '../utils'; | ||||||
| import { range } from '../utils/format/number'; | import { range } from '../utils/format/number'; | ||||||
| import { preventDefault } from '../utils/dom/event'; | import { preventDefault, on, off } from '../utils/dom/event'; | ||||||
| import { TouchMixin } from '../mixins/touch'; | import { TouchMixin } from '../mixins/touch'; | ||||||
| 
 | 
 | ||||||
| const DEFAULT_DURATION = 200; | const DEFAULT_DURATION = 200; | ||||||
| @ -9,8 +9,8 @@ const DEFAULT_DURATION = 200; | |||||||
| // 惯性滑动思路:
 | // 惯性滑动思路:
 | ||||||
| // 在手指离开屏幕时,如果和上一次 move 时的间隔小于 `MOMENTUM_LIMIT_TIME` 且 move
 | // 在手指离开屏幕时,如果和上一次 move 时的间隔小于 `MOMENTUM_LIMIT_TIME` 且 move
 | ||||||
| // 距离大于 `MOMENTUM_LIMIT_DISTANCE` 时,执行惯性滑动
 | // 距离大于 `MOMENTUM_LIMIT_DISTANCE` 时,执行惯性滑动
 | ||||||
| const MOMENTUM_LIMIT_TIME = 300; | export const MOMENTUM_LIMIT_TIME = 300; | ||||||
| const MOMENTUM_LIMIT_DISTANCE = 15; | export const MOMENTUM_LIMIT_DISTANCE = 15; | ||||||
| 
 | 
 | ||||||
| const [createComponent, bem] = createNamespace('picker-column'); | const [createComponent, bem] = createNamespace('picker-column'); | ||||||
| 
 | 
 | ||||||
| @ -25,6 +25,10 @@ function getElementTranslateY(element) { | |||||||
| function isOptionDisabled(option) { | function isOptionDisabled(option) { | ||||||
|   return isObject(option) && option.disabled; |   return isObject(option) && option.disabled; | ||||||
| } | } | ||||||
|  | // use standard WheelEvent:
 | ||||||
|  | // https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent
 | ||||||
|  | const supportMousewheel = 'onwheel' in window; | ||||||
|  | let mousewheelTimer = null; | ||||||
| 
 | 
 | ||||||
| export default createComponent({ | export default createComponent({ | ||||||
|   mixins: [TouchMixin], |   mixins: [TouchMixin], | ||||||
| @ -63,6 +67,9 @@ export default createComponent({ | |||||||
| 
 | 
 | ||||||
|   mounted() { |   mounted() { | ||||||
|     this.bindTouchEvent(this.$el); |     this.bindTouchEvent(this.$el); | ||||||
|  |     if (supportMousewheel) { | ||||||
|  |       on(this.$el, 'wheel', this.onMouseWheel, false); | ||||||
|  |     } | ||||||
|   }, |   }, | ||||||
| 
 | 
 | ||||||
|   destroyed() { |   destroyed() { | ||||||
| @ -71,6 +78,10 @@ export default createComponent({ | |||||||
|     if (children) { |     if (children) { | ||||||
|       children.splice(children.indexOf(this), 1); |       children.splice(children.indexOf(this), 1); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     if (supportMousewheel) { | ||||||
|  |       off(this.$el, 'wheel'); | ||||||
|  |     } | ||||||
|   }, |   }, | ||||||
| 
 | 
 | ||||||
|   watch: { |   watch: { | ||||||
| @ -172,6 +183,44 @@ export default createComponent({ | |||||||
|       }, 0); |       }, 0); | ||||||
|     }, |     }, | ||||||
| 
 | 
 | ||||||
|  |     onMouseWheel(event) { | ||||||
|  |       if (this.readonly) { | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |       preventDefault(event, true); | ||||||
|  |       // simply combine touchstart and touchmove
 | ||||||
|  |       const translateY = getElementTranslateY(this.$refs.wrapper); | ||||||
|  |       this.startOffset = Math.min(0, translateY - this.baseOffset); | ||||||
|  |       this.momentumOffset = this.startOffset; | ||||||
|  |       this.transitionEndTrigger = null; | ||||||
|  | 
 | ||||||
|  |       // directly use deltaY, see https://caniuse.com/?search=deltaY
 | ||||||
|  |       // use deltaY to detect direction for not special setting device
 | ||||||
|  |       // https://developer.mozilla.org/en-US/docs/Web/API/Element/wheel_event
 | ||||||
|  |       const { deltaY } = event; | ||||||
|  |       if (this.startOffset === 0 && deltaY < 0) { | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       // get offset
 | ||||||
|  |       // if necessary, can adjust distance value to make scrolling smoother
 | ||||||
|  |       const distance = -deltaY; | ||||||
|  |       this.offset = range( | ||||||
|  |         this.startOffset + distance, | ||||||
|  |         -(this.count * this.itemHeight), | ||||||
|  |         this.itemHeight | ||||||
|  |       ); | ||||||
|  | 
 | ||||||
|  |       if (mousewheelTimer) { | ||||||
|  |         clearTimeout(mousewheelTimer); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       mousewheelTimer = setTimeout(() => { | ||||||
|  |         this.onTouchEnd(); | ||||||
|  |         this.touchStartTime = 0; | ||||||
|  |       }, MOMENTUM_LIMIT_TIME); | ||||||
|  |     }, | ||||||
|  | 
 | ||||||
|     onTransitionEnd() { |     onTransitionEnd() { | ||||||
|       this.stopMomentum(); |       this.stopMomentum(); | ||||||
|     }, |     }, | ||||||
|  | |||||||
| @ -1,6 +1,7 @@ | |||||||
| import Picker from '..'; | import Picker from '..'; | ||||||
| import PickerColumn from '../PickerColumn'; | import PickerColumn, { MOMENTUM_LIMIT_TIME, MOMENTUM_LIMIT_DISTANCE} from '../PickerColumn'; | ||||||
| import { mount, triggerDrag, later } from '../../../test'; | import { mount, triggerDrag, later } from '../../../test'; | ||||||
|  | import { DEFAULT_ITEM_HEIGHT } from '../shared'; | ||||||
| 
 | 
 | ||||||
| const simpleColumn = ['1990', '1991', '1992', '1993', '1994', '1995']; | const simpleColumn = ['1990', '1991', '1992', '1993', '1994', '1995']; | ||||||
| const columns = [ | const columns = [ | ||||||
| @ -336,3 +337,66 @@ test('readonly prop', () => { | |||||||
| 
 | 
 | ||||||
|   expect(wrapper.emitted('change')).toBeFalsy(); |   expect(wrapper.emitted('change')).toBeFalsy(); | ||||||
| }); | }); | ||||||
|  | 
 | ||||||
|  | test('wheel event on columns is detected', async () => { | ||||||
|  |   const onMouseWheel = jest.spyOn(PickerColumn.methods, 'onMouseWheel'); | ||||||
|  | 
 | ||||||
|  |   const wrapper = mount(PickerColumn, { | ||||||
|  |     propsData: { | ||||||
|  |       initialOptions: [...simpleColumn], | ||||||
|  |     }, | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   await wrapper.trigger('wheel'); | ||||||
|  | 
 | ||||||
|  |   expect(onMouseWheel).toHaveBeenCalled(); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | test('wheel scroll on columns', async () => { | ||||||
|  |   const fakeScroll = (translateY, deltaY)=> { | ||||||
|  |     // mock getComputedStyle
 | ||||||
|  |     // see: https://github.com/jsdom/jsdom/issues/2588
 | ||||||
|  |     const originGetComputedStyle = window.getComputedStyle; | ||||||
|  |     window.getComputedStyle = (ele) => { | ||||||
|  |       const style = originGetComputedStyle(ele); | ||||||
|  |       return { | ||||||
|  |         ...style, | ||||||
|  |         transform: `matrix(1, 0, 0, 1, 0, ${translateY})`, | ||||||
|  |       }; | ||||||
|  |     }; | ||||||
|  |     return new Promise(resolve => { | ||||||
|  |       const wrapper = mount(Picker, { | ||||||
|  |         propsData: { | ||||||
|  |           columns: simpleColumn, | ||||||
|  |         }, | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|  |       wrapper.find('.van-picker-column').trigger('wheel', { | ||||||
|  |         deltaY, | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|  |       return later(MOMENTUM_LIMIT_TIME + 10).then(()=>{ | ||||||
|  |         wrapper.find('.van-picker-column ul').trigger('transitionend'); | ||||||
|  |         resolve(wrapper.emitted('change')); | ||||||
|  |       }).finally(()=>{ | ||||||
|  |         window.getComputedStyle = originGetComputedStyle; | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   const topToDown = await fakeScroll(110, -MOMENTUM_LIMIT_DISTANCE); | ||||||
|  |   expect(topToDown).toEqual(undefined); | ||||||
|  | 
 | ||||||
|  |   const topToUp = await fakeScroll(110, MOMENTUM_LIMIT_DISTANCE + 10); | ||||||
|  |   expect(topToUp[0][1]).toEqual('1991'); | ||||||
|  | 
 | ||||||
|  |   const bottomToUp = await fakeScroll(-110, MOMENTUM_LIMIT_DISTANCE + 5); | ||||||
|  |   expect(bottomToUp[0][1]).toEqual('1995'); | ||||||
|  | 
 | ||||||
|  |   const bottomToDown = await fakeScroll(-110, -(MOMENTUM_LIMIT_DISTANCE - 5)); | ||||||
|  |   expect(bottomToDown[0][1]).toEqual('1995'); | ||||||
|  | 
 | ||||||
|  |   const pos1992 = simpleColumn.indexOf('1992') | ||||||
|  |   const momentum = await fakeScroll(-110 + (pos1992 + 1) * DEFAULT_ITEM_HEIGHT, MOMENTUM_LIMIT_DISTANCE + 10); | ||||||
|  |   expect(momentum[0][1]).toEqual(simpleColumn[pos1992+1]); | ||||||
|  | }); | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user