mirror of
https://gitee.com/vant-contrib/vant.git
synced 2025-04-06 03:57:59 +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