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:
genffy 2021-11-02 19:15:17 +08:00 committed by GitHub
parent 999f465c97
commit 14303f4ccb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 117 additions and 4 deletions

View File

@ -1,7 +1,7 @@
import { deepClone } from '../utils/deep-clone';
import { createNamespace, isObject } from '../utils';
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';
const DEFAULT_DURATION = 200;
@ -9,8 +9,8 @@ const DEFAULT_DURATION = 200;
// 惯性滑动思路:
// 在手指离开屏幕时,如果和上一次 move 时的间隔小于 `MOMENTUM_LIMIT_TIME` 且 move
// 距离大于 `MOMENTUM_LIMIT_DISTANCE` 时,执行惯性滑动
const MOMENTUM_LIMIT_TIME = 300;
const MOMENTUM_LIMIT_DISTANCE = 15;
export const MOMENTUM_LIMIT_TIME = 300;
export const MOMENTUM_LIMIT_DISTANCE = 15;
const [createComponent, bem] = createNamespace('picker-column');
@ -25,6 +25,10 @@ function getElementTranslateY(element) {
function isOptionDisabled(option) {
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({
mixins: [TouchMixin],
@ -63,6 +67,9 @@ export default createComponent({
mounted() {
this.bindTouchEvent(this.$el);
if (supportMousewheel) {
on(this.$el, 'wheel', this.onMouseWheel, false);
}
},
destroyed() {
@ -71,6 +78,10 @@ export default createComponent({
if (children) {
children.splice(children.indexOf(this), 1);
}
if (supportMousewheel) {
off(this.$el, 'wheel');
}
},
watch: {
@ -172,6 +183,44 @@ export default createComponent({
}, 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() {
this.stopMomentum();
},

View File

@ -1,6 +1,7 @@
import Picker from '..';
import PickerColumn from '../PickerColumn';
import PickerColumn, { MOMENTUM_LIMIT_TIME, MOMENTUM_LIMIT_DISTANCE} from '../PickerColumn';
import { mount, triggerDrag, later } from '../../../test';
import { DEFAULT_ITEM_HEIGHT } from '../shared';
const simpleColumn = ['1990', '1991', '1992', '1993', '1994', '1995'];
const columns = [
@ -336,3 +337,66 @@ test('readonly prop', () => {
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]);
});