1
1
<template >
2
2
<view class =" nut-picker__list" @touchstart =" onTouchStart" @touchmove =" onTouchMove" @touchend =" onTouchEnd" >
3
- <view class =" nut-picker-roller" ref =" roller" :style =" touchRollerStyle" >
3
+ <view class =" nut-picker-roller" ref =" roller" :style =" touchRollerStyle" @transitionend = " stopMomentum " >
4
4
<template v-for =" (item , index ) in column " :key =" item .value ? item .value : index " >
5
5
<view
6
6
class =" nut-picker-roller-item"
14
14
</view >
15
15
<view class =" nut-picker-roller-mask" ></view >
16
16
17
- <!-- <view class="nut-picker-content">
18
- <view class="nut-picker-list-panel" ref="list" :style="touchListStyle">
19
- <view
20
- :class="['nut-picker-item', 'nut-picker-item-ref', item.className]"
21
- v-for="(item, index) in column"
22
- :key="item.value ? item.value : index"
23
- >{{ item.text }}
24
- </view>
25
- <view class="nut-picker-placeholder" v-if="column && column.length === 1"></view>
26
- </view>
27
- </view> -->
17
+ <view class =" nut-picker-content"
18
+ ><view class =" nut-picker-list-panel" ref =" list" :style =" touchListStyle" ></view
19
+ ></view >
28
20
</view >
29
21
</template >
30
22
<script lang="ts">
31
23
import { reactive , ref , watch , computed , toRefs , onMounted , PropType } from ' vue' ;
32
24
import { createComponent } from ' @/packages/utils/create' ;
33
25
import { PickerColumnOption , PickerOption , TouchParams } from ' ./types' ;
26
+ import { useTouch } from ' @/packages/utils/useTouch' ;
34
27
const { create } = createComponent (' picker-column' );
35
28
36
29
export default create ({
@@ -54,6 +47,8 @@ export default create({
54
47
55
48
emits: [' click' , ' change' ],
56
49
setup(props , { emit }) {
50
+ const touch: any = useTouch ();
51
+
57
52
const wrapper = ref ();
58
53
const state = reactive ({
59
54
touchParams: {
@@ -64,9 +59,10 @@ export default create({
64
59
lastY: 0 ,
65
60
lastTime: 0
66
61
},
62
+
67
63
currIndex: 1 ,
68
64
transformY: 0 ,
69
- scrollDistance: 0 ,
65
+ scrollDistance: 0 , // 滚动的距离
70
66
lineSpacing: 36 ,
71
67
rotation: 20 ,
72
68
timer: null
@@ -76,62 +72,96 @@ export default create({
76
72
const list = ref (null );
77
73
const listItem = ref (null );
78
74
75
+ const moving = ref (false ); // 是否处于滚动中
79
76
const touchDeg = ref (0 );
80
77
const touchTime = ref (0 );
81
- const touchTranslateY = ref (0 );
82
- const touchListStyle = computed (() => {
78
+
79
+ // 触发惯性滑动条件:
80
+ // 在手指离开屏幕时,如果和上一次 move 时的间隔小于 `MOMENTUM_TIME` 且 move
81
+ // 距离大于 `MOMENTUM_DISTANCE` 时,执行惯性滑动
82
+ const INERTIA_TIME = 300 ;
83
+ const INERTIA_DISTANCE = 15 ;
84
+
85
+ const touchRollerStyle = computed (() => {
83
86
return {
84
- transition: ` transform ${touchTime .value }ms cubic-bezier(0.19, 1 , 0.22 , 1) ` ,
85
- transform: ` translate3d( 0, ${ state . scrollDistance }px, 0 )`
87
+ transition: ` transform ${touchTime .value }ms cubic-bezier(0.17, 0.89 , 0.45 , 1) ` ,
88
+ transform: ` rotate3d(1, 0, 0, ${ touchDeg . value } )`
86
89
};
87
90
});
88
91
89
- const touchRollerStyle = computed (() => {
92
+ const touchListStyle = computed (() => {
90
93
return {
91
- transition: ` transform ${touchTime .value }ms cubic-bezier(0.19, 1 , 0.22 , 1) ` ,
92
- transform: ` rotate3d(1, 0, 0, ${ touchDeg . value } )`
94
+ transition: ` transform ${touchTime .value }ms cubic-bezier(0.17, 0.89 , 0.45 , 1) ` ,
95
+ transform: ` translate3d( 0, ${ state . scrollDistance }px, 0 )`
93
96
};
94
97
});
95
98
99
+ const setRollerStyle = (index : number ) => {
100
+ return ` transform: rotate3d(1, 0, 0, ${- state .rotation * index }deg) translate3d(0px, 0px, 104px) ` ;
101
+ };
102
+
96
103
const onTouchStart = (event : TouchEvent ) => {
97
- event .preventDefault ();
98
- let changedTouches = event .changedTouches [0 ];
99
- state .touchParams .startY = changedTouches .pageY ;
100
- state .touchParams .startTime = event .timeStamp || Date .now ();
104
+ touch .start (event );
105
+
106
+ if (moving .value ) {
107
+ const { transform } = window .getComputedStyle (list .value as any );
108
+ state .scrollDistance = + transform .slice (7 , transform .length - 1 ).split (' , ' )[5 ];
109
+ }
110
+
111
+ state .touchParams .startY = touch .deltaY .value ;
112
+ state .touchParams .startTime = Date .now ();
101
113
state .transformY = state .scrollDistance ;
102
114
};
103
115
104
116
const onTouchMove = (event : TouchEvent ) => {
105
- event .preventDefault ();
106
- let changedTouches = event .changedTouches [0 ];
107
- (state .touchParams as TouchParams ).lastY = changedTouches .pageY ;
108
- (state .touchParams as TouchParams ).lastTime = event .timeStamp || Date .now ();
117
+ touch .move (event );
118
+
119
+ if ((touch as any ).isVertical ) {
120
+ moving .value = true ;
121
+ preventDefault (event , true );
122
+ }
123
+
124
+ (state .touchParams as TouchParams ).lastY = touch .deltaY .value ;
125
+ const now = Date .now ();
109
126
let move = state .touchParams .lastY - state .touchParams .startY ;
110
127
111
128
setMove (move );
129
+
130
+ if (now - (state .touchParams as TouchParams ).startTime > INERTIA_TIME ) {
131
+ (state .touchParams as TouchParams ).startTime = now ;
132
+ state .touchParams .startY = (state .touchParams as TouchParams ).lastY ;
133
+ }
112
134
};
113
135
114
136
const onTouchEnd = (event : TouchEvent ) => {
115
- event .preventDefault ();
116
-
117
- let changedTouches = event .changedTouches [0 ];
118
- state .touchParams .lastY = changedTouches .pageY ;
119
- state .touchParams .lastTime = event .timeStamp || Date .now ();
137
+ state .touchParams .lastY = touch .deltaY .value ;
138
+ state .touchParams .lastTime = Date .now ();
120
139
let move = state .touchParams .lastY - state .touchParams .startY ;
121
140
122
141
let moveTime = state .touchParams .lastTime - state .touchParams .startTime ;
123
- console .log (' touchEnd' , move , moveTime );
124
- if (moveTime <= 300 ) {
125
- move = move * 2 ;
126
- moveTime = moveTime + 1000 ;
127
- setMove (move , ' end' , moveTime );
142
+
143
+ if (moveTime <= INERTIA_TIME && Math .abs (move ) > INERTIA_DISTANCE ) {
144
+ // 惯性滚动
145
+ const distance = momentum (move , moveTime );
146
+ setMove (distance , ' end' , moveTime + 1000 );
147
+ return ;
128
148
} else {
129
149
setMove (move , ' end' );
130
150
}
151
+
152
+ setTimeout (() => {
153
+ touch .reset ();
154
+ moving .value = false ;
155
+ }, 0 );
131
156
};
132
157
133
- const setRollerStyle = (index : number ) => {
134
- return ` transform: rotate3d(1, 0, 0, ${- state .rotation * index }deg) translate3d(0px, 0px, 104px) ` ;
158
+ // 惯性滚动 距离
159
+ const momentum = (distance : number , duration : number ) => {
160
+ // 惯性滚动的速度
161
+ const speed = Math .abs (distance / duration );
162
+ // 惯性滚动的距离
163
+ distance = (speed / 0.003 ) * (distance < 0 ? - 1 : 1 );
164
+ return distance ;
135
165
};
136
166
137
167
const isHidden = (index : number ) => {
@@ -149,13 +179,12 @@ export default create({
149
179
touchTime .value = 0 ;
150
180
}
151
181
touchDeg .value = deg as number ;
152
- touchTranslateY .value = translateY ;
153
182
state .scrollDistance = translateY ;
154
183
};
155
184
156
185
const setMove = (move : number , type ? : string , time ? : number ) => {
157
- console .log (' setMove' );
158
186
let updateMove = move + state .transformY ;
187
+
159
188
if (type === ' end' ) {
160
189
// 限定滚动距离
161
190
if (updateMove > 0 ) {
@@ -171,33 +200,24 @@ export default create({
171
200
172
201
setTransform (endMove , type , time , deg );
173
202
174
- let t = time ? time / 2 : 0 ;
175
- (state .timer as any ) = setTimeout (() => {
176
- setChooseValue ();
177
- }, t );
203
+ // let t = time ? time / 2 : 0;
204
+ // (state.timer as any) = setTimeout(() => {
205
+ // setChooseValue();
206
+ // }, t);
178
207
179
208
state .currIndex = Math .abs (Math .round (endMove / state .lineSpacing )) + 1 ;
180
209
} else {
181
210
let deg = ' 0deg' ;
182
- let degNum = 0 ;
183
- if (updateMove < 0 ) {
184
- degNum = (Math .abs (updateMove / state .lineSpacing ) + 1 ) * state .rotation ;
185
- } else {
186
- degNum = (- updateMove / state .lineSpacing + 1 ) * state .rotation ;
187
- }
211
+ let currentDeg = (- updateMove / state .lineSpacing + 1 ) * state .rotation ;
188
212
189
213
// picker 滚动的最大角度
190
214
const maxDeg = (props .column .length + 1 ) * state .rotation ;
191
215
const minDeg = 0 ;
192
- if (degNum > maxDeg ) {
193
- deg = ` ${maxDeg }deg ` ;
194
- } else if (degNum < minDeg ) {
195
- deg = ` ${minDeg }deg ` ;
196
- } else {
197
- deg = ` ${degNum }deg ` ;
198
- setTransform (updateMove , null , undefined , deg );
199
- state .currIndex = Math .abs (Math .round (updateMove / state .lineSpacing )) + 1 ;
200
- }
216
+
217
+ deg = Math .min (Math .max (currentDeg , minDeg ), maxDeg ) + ' deg' ;
218
+
219
+ setTransform (updateMove , null , undefined , deg );
220
+ state .currIndex = Math .abs (Math .round (updateMove / state .lineSpacing )) + 1 ;
201
221
}
202
222
};
203
223
@@ -215,6 +235,24 @@ export default create({
215
235
setMove (- move );
216
236
};
217
237
238
+ const preventDefault = (event : Event , isStopPropagation ? : boolean ) => {
239
+ /* istanbul ignore else */
240
+ if (typeof event .cancelable !== ' boolean' || event .cancelable ) {
241
+ event .preventDefault ();
242
+ }
243
+
244
+ if (isStopPropagation ) {
245
+ event .stopPropagation ();
246
+ }
247
+ };
248
+
249
+ // 惯性滚动结束
250
+ const stopMomentum = () => {
251
+ moving .value = false ;
252
+
253
+ setChooseValue ();
254
+ };
255
+
218
256
watch (
219
257
() => props .column ,
220
258
(val ) => {
@@ -256,7 +294,8 @@ export default create({
256
294
onTouchEnd ,
257
295
touchRollerStyle ,
258
296
touchListStyle ,
259
- setMove
297
+ setMove ,
298
+ stopMomentum
260
299
};
261
300
}
262
301
});
0 commit comments