|
|
@@ -1,6 +1,6 @@
|
|
|
<template>
|
|
|
<view class="nut-picker__list" @touchstart="onTouchStart" @touchmove="onTouchMove" @touchend="onTouchEnd">
|
|
|
- <view class="nut-picker-roller" ref="roller" :style="touchRollerStyle">
|
|
|
+ <view class="nut-picker-roller" ref="roller" :style="touchRollerStyle" @transitionend="stopMomentum">
|
|
|
<template v-for="(item, index) in column" :key="item.value ? item.value : index">
|
|
|
<view
|
|
|
class="nut-picker-roller-item"
|
|
|
@@ -14,23 +14,16 @@
|
|
|
</view>
|
|
|
<view class="nut-picker-roller-mask"></view>
|
|
|
|
|
|
- <!-- <view class="nut-picker-content">
|
|
|
- <view class="nut-picker-list-panel" ref="list" :style="touchListStyle">
|
|
|
- <view
|
|
|
- :class="['nut-picker-item', 'nut-picker-item-ref', item.className]"
|
|
|
- v-for="(item, index) in column"
|
|
|
- :key="item.value ? item.value : index"
|
|
|
- >{{ item.text }}
|
|
|
- </view>
|
|
|
- <view class="nut-picker-placeholder" v-if="column && column.length === 1"></view>
|
|
|
- </view>
|
|
|
- </view> -->
|
|
|
+ <view class="nut-picker-content"
|
|
|
+ ><view class="nut-picker-list-panel" ref="list" :style="touchListStyle"></view
|
|
|
+ ></view>
|
|
|
</view>
|
|
|
</template>
|
|
|
<script lang="ts">
|
|
|
import { reactive, ref, watch, computed, toRefs, onMounted, PropType } from 'vue';
|
|
|
import { createComponent } from '@/packages/utils/create';
|
|
|
import { PickerColumnOption, PickerOption, TouchParams } from './types';
|
|
|
+import { useTouch } from '@/packages/utils/useTouch';
|
|
|
const { create } = createComponent('picker-column');
|
|
|
|
|
|
export default create({
|
|
|
@@ -54,6 +47,8 @@ export default create({
|
|
|
|
|
|
emits: ['click', 'change'],
|
|
|
setup(props, { emit }) {
|
|
|
+ const touch: any = useTouch();
|
|
|
+
|
|
|
const wrapper = ref();
|
|
|
const state = reactive({
|
|
|
touchParams: {
|
|
|
@@ -64,9 +59,10 @@ export default create({
|
|
|
lastY: 0,
|
|
|
lastTime: 0
|
|
|
},
|
|
|
+
|
|
|
currIndex: 1,
|
|
|
transformY: 0,
|
|
|
- scrollDistance: 0,
|
|
|
+ scrollDistance: 0, // 滚动的距离
|
|
|
lineSpacing: 36,
|
|
|
rotation: 20,
|
|
|
timer: null
|
|
|
@@ -76,62 +72,96 @@ export default create({
|
|
|
const list = ref(null);
|
|
|
const listItem = ref(null);
|
|
|
|
|
|
+ const moving = ref(false); // 是否处于滚动中
|
|
|
const touchDeg = ref(0);
|
|
|
const touchTime = ref(0);
|
|
|
- const touchTranslateY = ref(0);
|
|
|
- const touchListStyle = computed(() => {
|
|
|
+
|
|
|
+ // 触发惯性滑动条件:
|
|
|
+ // 在手指离开屏幕时,如果和上一次 move 时的间隔小于 `MOMENTUM_TIME` 且 move
|
|
|
+ // 距离大于 `MOMENTUM_DISTANCE` 时,执行惯性滑动
|
|
|
+ const INERTIA_TIME = 300;
|
|
|
+ const INERTIA_DISTANCE = 15;
|
|
|
+
|
|
|
+ const touchRollerStyle = computed(() => {
|
|
|
return {
|
|
|
- transition: `transform ${touchTime.value}ms cubic-bezier(0.19, 1, 0.22, 1)`,
|
|
|
- transform: `translate3d(0, ${state.scrollDistance}px, 0)`
|
|
|
+ transition: `transform ${touchTime.value}ms cubic-bezier(0.17, 0.89, 0.45, 1)`,
|
|
|
+ transform: `rotate3d(1, 0, 0, ${touchDeg.value})`
|
|
|
};
|
|
|
});
|
|
|
|
|
|
- const touchRollerStyle = computed(() => {
|
|
|
+ const touchListStyle = computed(() => {
|
|
|
return {
|
|
|
- transition: `transform ${touchTime.value}ms cubic-bezier(0.19, 1, 0.22, 1)`,
|
|
|
- transform: `rotate3d(1, 0, 0, ${touchDeg.value})`
|
|
|
+ transition: `transform ${touchTime.value}ms cubic-bezier(0.17, 0.89, 0.45, 1)`,
|
|
|
+ transform: `translate3d(0, ${state.scrollDistance}px, 0)`
|
|
|
};
|
|
|
});
|
|
|
|
|
|
+ const setRollerStyle = (index: number) => {
|
|
|
+ return `transform: rotate3d(1, 0, 0, ${-state.rotation * index}deg) translate3d(0px, 0px, 104px)`;
|
|
|
+ };
|
|
|
+
|
|
|
const onTouchStart = (event: TouchEvent) => {
|
|
|
- event.preventDefault();
|
|
|
- let changedTouches = event.changedTouches[0];
|
|
|
- state.touchParams.startY = changedTouches.pageY;
|
|
|
- state.touchParams.startTime = event.timeStamp || Date.now();
|
|
|
+ touch.start(event);
|
|
|
+
|
|
|
+ if (moving.value) {
|
|
|
+ const { transform } = window.getComputedStyle(list.value as any);
|
|
|
+ state.scrollDistance = +transform.slice(7, transform.length - 1).split(', ')[5];
|
|
|
+ }
|
|
|
+
|
|
|
+ state.touchParams.startY = touch.deltaY.value;
|
|
|
+ state.touchParams.startTime = Date.now();
|
|
|
state.transformY = state.scrollDistance;
|
|
|
};
|
|
|
|
|
|
const onTouchMove = (event: TouchEvent) => {
|
|
|
- event.preventDefault();
|
|
|
- let changedTouches = event.changedTouches[0];
|
|
|
- (state.touchParams as TouchParams).lastY = changedTouches.pageY;
|
|
|
- (state.touchParams as TouchParams).lastTime = event.timeStamp || Date.now();
|
|
|
+ touch.move(event);
|
|
|
+
|
|
|
+ if ((touch as any).isVertical) {
|
|
|
+ moving.value = true;
|
|
|
+ preventDefault(event, true);
|
|
|
+ }
|
|
|
+
|
|
|
+ (state.touchParams as TouchParams).lastY = touch.deltaY.value;
|
|
|
+ const now = Date.now();
|
|
|
let move = state.touchParams.lastY - state.touchParams.startY;
|
|
|
|
|
|
setMove(move);
|
|
|
+
|
|
|
+ if (now - (state.touchParams as TouchParams).startTime > INERTIA_TIME) {
|
|
|
+ (state.touchParams as TouchParams).startTime = now;
|
|
|
+ state.touchParams.startY = (state.touchParams as TouchParams).lastY;
|
|
|
+ }
|
|
|
};
|
|
|
|
|
|
const onTouchEnd = (event: TouchEvent) => {
|
|
|
- event.preventDefault();
|
|
|
-
|
|
|
- let changedTouches = event.changedTouches[0];
|
|
|
- state.touchParams.lastY = changedTouches.pageY;
|
|
|
- state.touchParams.lastTime = event.timeStamp || Date.now();
|
|
|
+ state.touchParams.lastY = touch.deltaY.value;
|
|
|
+ state.touchParams.lastTime = Date.now();
|
|
|
let move = state.touchParams.lastY - state.touchParams.startY;
|
|
|
|
|
|
let moveTime = state.touchParams.lastTime - state.touchParams.startTime;
|
|
|
- console.log('touchEnd', move, moveTime);
|
|
|
- if (moveTime <= 300) {
|
|
|
- move = move * 2;
|
|
|
- moveTime = moveTime + 1000;
|
|
|
- setMove(move, 'end', moveTime);
|
|
|
+
|
|
|
+ if (moveTime <= INERTIA_TIME && Math.abs(move) > INERTIA_DISTANCE) {
|
|
|
+ // 惯性滚动
|
|
|
+ const distance = momentum(move, moveTime);
|
|
|
+ setMove(distance, 'end', moveTime + 1000);
|
|
|
+ return;
|
|
|
} else {
|
|
|
setMove(move, 'end');
|
|
|
}
|
|
|
+
|
|
|
+ setTimeout(() => {
|
|
|
+ touch.reset();
|
|
|
+ moving.value = false;
|
|
|
+ }, 0);
|
|
|
};
|
|
|
|
|
|
- const setRollerStyle = (index: number) => {
|
|
|
- return `transform: rotate3d(1, 0, 0, ${-state.rotation * index}deg) translate3d(0px, 0px, 104px)`;
|
|
|
+ // 惯性滚动 距离
|
|
|
+ const momentum = (distance: number, duration: number) => {
|
|
|
+ // 惯性滚动的速度
|
|
|
+ const speed = Math.abs(distance / duration);
|
|
|
+ // 惯性滚动的距离
|
|
|
+ distance = (speed / 0.003) * (distance < 0 ? -1 : 1);
|
|
|
+ return distance;
|
|
|
};
|
|
|
|
|
|
const isHidden = (index: number) => {
|
|
|
@@ -149,13 +179,12 @@ export default create({
|
|
|
touchTime.value = 0;
|
|
|
}
|
|
|
touchDeg.value = deg as number;
|
|
|
- touchTranslateY.value = translateY;
|
|
|
state.scrollDistance = translateY;
|
|
|
};
|
|
|
|
|
|
const setMove = (move: number, type?: string, time?: number) => {
|
|
|
- console.log('setMove');
|
|
|
let updateMove = move + state.transformY;
|
|
|
+
|
|
|
if (type === 'end') {
|
|
|
// 限定滚动距离
|
|
|
if (updateMove > 0) {
|
|
|
@@ -171,33 +200,24 @@ export default create({
|
|
|
|
|
|
setTransform(endMove, type, time, deg);
|
|
|
|
|
|
- let t = time ? time / 2 : 0;
|
|
|
- (state.timer as any) = setTimeout(() => {
|
|
|
- setChooseValue();
|
|
|
- }, t);
|
|
|
+ // let t = time ? time / 2 : 0;
|
|
|
+ // (state.timer as any) = setTimeout(() => {
|
|
|
+ // setChooseValue();
|
|
|
+ // }, t);
|
|
|
|
|
|
state.currIndex = Math.abs(Math.round(endMove / state.lineSpacing)) + 1;
|
|
|
} else {
|
|
|
let deg = '0deg';
|
|
|
- let degNum = 0;
|
|
|
- if (updateMove < 0) {
|
|
|
- degNum = (Math.abs(updateMove / state.lineSpacing) + 1) * state.rotation;
|
|
|
- } else {
|
|
|
- degNum = (-updateMove / state.lineSpacing + 1) * state.rotation;
|
|
|
- }
|
|
|
+ let currentDeg = (-updateMove / state.lineSpacing + 1) * state.rotation;
|
|
|
|
|
|
// picker 滚动的最大角度
|
|
|
const maxDeg = (props.column.length + 1) * state.rotation;
|
|
|
const minDeg = 0;
|
|
|
- if (degNum > maxDeg) {
|
|
|
- deg = `${maxDeg}deg`;
|
|
|
- } else if (degNum < minDeg) {
|
|
|
- deg = `${minDeg}deg`;
|
|
|
- } else {
|
|
|
- deg = `${degNum}deg`;
|
|
|
- setTransform(updateMove, null, undefined, deg);
|
|
|
- state.currIndex = Math.abs(Math.round(updateMove / state.lineSpacing)) + 1;
|
|
|
- }
|
|
|
+
|
|
|
+ deg = Math.min(Math.max(currentDeg, minDeg), maxDeg) + 'deg';
|
|
|
+
|
|
|
+ setTransform(updateMove, null, undefined, deg);
|
|
|
+ state.currIndex = Math.abs(Math.round(updateMove / state.lineSpacing)) + 1;
|
|
|
}
|
|
|
};
|
|
|
|
|
|
@@ -215,6 +235,24 @@ export default create({
|
|
|
setMove(-move);
|
|
|
};
|
|
|
|
|
|
+ const preventDefault = (event: Event, isStopPropagation?: boolean) => {
|
|
|
+ /* istanbul ignore else */
|
|
|
+ if (typeof event.cancelable !== 'boolean' || event.cancelable) {
|
|
|
+ event.preventDefault();
|
|
|
+ }
|
|
|
+
|
|
|
+ if (isStopPropagation) {
|
|
|
+ event.stopPropagation();
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 惯性滚动结束
|
|
|
+ const stopMomentum = () => {
|
|
|
+ moving.value = false;
|
|
|
+
|
|
|
+ setChooseValue();
|
|
|
+ };
|
|
|
+
|
|
|
watch(
|
|
|
() => props.column,
|
|
|
(val) => {
|
|
|
@@ -256,7 +294,8 @@ export default create({
|
|
|
onTouchEnd,
|
|
|
touchRollerStyle,
|
|
|
touchListStyle,
|
|
|
- setMove
|
|
|
+ setMove,
|
|
|
+ stopMomentum
|
|
|
};
|
|
|
}
|
|
|
});
|