Skip to content

Commit c2029dc

Browse files
yangxiaolu1993lkjh3214lkjh3214szg2008richard1015
authored
feat(picker): 惯性滚动与性能优化 (#1453)
* fix: marge主分支 (#1) * doc: calendar 文档修改 (#1410) * feat: 添加range组件、calendar组件在线文档 * fix: 文档调整 * fix: 重构calendar组件 * feat: 日历组件重构,文档修改,功能完善 * fix: 格式化 * fix: 代码格式化调整。 * fix: 去除无用代码 * fix: 文档调整 * fix: 文档调整 * fix: taro demo 样式修改 * feat: range组件功能完善,新增 竖向操作,刻度展示。 * fix: 冲突解决 * feat: taro功能新增,兼容处理,文档修改 * feat: 添加range组件,jdt主题色 * fix: 修改组件初始化逻辑 * feat: 新增h5 日期多选功能 * feat: taro版本添加 日期多选功能 * fix: 修复多选,无法选中开头结尾日期问题 * fix: 文档修改,添加en-US 文档 * fix: 文档完善 Co-authored-by: lkjh3214 <[email protected]> Co-authored-by: love_forever <[email protected]> * feat: imagepreview 部分功能补齐 (#1412) * feat: image新增单元测试 * feat: ellipsis添加单元测试 * feat: imagepreview 添加 * fix: popop单元测试修改 * docs: 添加版本号 * feat: support highlight for JetBrains web-types * test(imagepreview): edit snap * fix(image): dts edit import * docs(input): demo和md国际化文案修改 (#1414) * fix: 抽离 input ConfirmTextType * feat: input、switch国际化 * feat: category、address国际化 * feat: taro升级maxlength问题 * fix: 国际化增加默认字段 * fix: blank * fix: input组件国际化文案修改 * style: add ellipsis add sass * docs(elevator): 增加吸顶props * feat: input组件新增input slot插槽 (#1418) * fix: 抽离 input ConfirmTextType * feat: input、switch国际化 * feat: category、address国际化 * feat: taro升级maxlength问题 * fix: 国际化增加默认字段 * fix: blank * fix: input组件国际化文案修改 * feat: input组件新增input slot插槽 * release: v3.1.22 * Update README.md add alipay img * Update README.md * docs: changelog 3.1.22 Co-authored-by: lkjh3214 <[email protected]> Co-authored-by: lkjh3214 <[email protected]> Co-authored-by: love_forever <[email protected]> Co-authored-by: richard1015 <[email protected]> Co-authored-by: ailululu <[email protected]> Co-authored-by: snandy <[email protected]> * feat: imagepreview重复问题修改 * fix: picker 组件重影问题修改 * fix: 科技样式同步 * feat: picker组件惯性滚动优化 * feat: picker组件惯性滚动优化 Co-authored-by: lkjh3214 <[email protected]> Co-authored-by: lkjh3214 <[email protected]> Co-authored-by: love_forever <[email protected]> Co-authored-by: richard1015 <[email protected]> Co-authored-by: ailululu <[email protected]> Co-authored-by: snandy <[email protected]>
1 parent 690f8cb commit c2029dc

File tree

6 files changed

+210
-121
lines changed

6 files changed

+210
-121
lines changed

src/packages/__VUE/picker/Column.vue

Lines changed: 101 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<template>
22
<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">
44
<template v-for="(item, index) in column" :key="item.value ? item.value : index">
55
<view
66
class="nut-picker-roller-item"
@@ -14,23 +14,16 @@
1414
</view>
1515
<view class="nut-picker-roller-mask"></view>
1616

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>
2820
</view>
2921
</template>
3022
<script lang="ts">
3123
import { reactive, ref, watch, computed, toRefs, onMounted, PropType } from 'vue';
3224
import { createComponent } from '@/packages/utils/create';
3325
import { PickerColumnOption, PickerOption, TouchParams } from './types';
26+
import { useTouch } from '@/packages/utils/useTouch';
3427
const { create } = createComponent('picker-column');
3528
3629
export default create({
@@ -54,6 +47,8 @@ export default create({
5447
5548
emits: ['click', 'change'],
5649
setup(props, { emit }) {
50+
const touch: any = useTouch();
51+
5752
const wrapper = ref();
5853
const state = reactive({
5954
touchParams: {
@@ -64,9 +59,10 @@ export default create({
6459
lastY: 0,
6560
lastTime: 0
6661
},
62+
6763
currIndex: 1,
6864
transformY: 0,
69-
scrollDistance: 0,
65+
scrollDistance: 0, // 滚动的距离
7066
lineSpacing: 36,
7167
rotation: 20,
7268
timer: null
@@ -76,62 +72,96 @@ export default create({
7672
const list = ref(null);
7773
const listItem = ref(null);
7874
75+
const moving = ref(false); // 是否处于滚动中
7976
const touchDeg = ref(0);
8077
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(() => {
8386
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})`
8689
};
8790
});
8891
89-
const touchRollerStyle = computed(() => {
92+
const touchListStyle = computed(() => {
9093
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)`
9396
};
9497
});
9598
99+
const setRollerStyle = (index: number) => {
100+
return `transform: rotate3d(1, 0, 0, ${-state.rotation * index}deg) translate3d(0px, 0px, 104px)`;
101+
};
102+
96103
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();
101113
state.transformY = state.scrollDistance;
102114
};
103115
104116
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();
109126
let move = state.touchParams.lastY - state.touchParams.startY;
110127
111128
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+
}
112134
};
113135
114136
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();
120139
let move = state.touchParams.lastY - state.touchParams.startY;
121140
122141
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;
128148
} else {
129149
setMove(move, 'end');
130150
}
151+
152+
setTimeout(() => {
153+
touch.reset();
154+
moving.value = false;
155+
}, 0);
131156
};
132157
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;
135165
};
136166
137167
const isHidden = (index: number) => {
@@ -149,13 +179,12 @@ export default create({
149179
touchTime.value = 0;
150180
}
151181
touchDeg.value = deg as number;
152-
touchTranslateY.value = translateY;
153182
state.scrollDistance = translateY;
154183
};
155184
156185
const setMove = (move: number, type?: string, time?: number) => {
157-
console.log('setMove');
158186
let updateMove = move + state.transformY;
187+
159188
if (type === 'end') {
160189
// 限定滚动距离
161190
if (updateMove > 0) {
@@ -171,33 +200,24 @@ export default create({
171200
172201
setTransform(endMove, type, time, deg);
173202
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);
178207
179208
state.currIndex = Math.abs(Math.round(endMove / state.lineSpacing)) + 1;
180209
} else {
181210
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;
188212
189213
// picker 滚动的最大角度
190214
const maxDeg = (props.column.length + 1) * state.rotation;
191215
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;
201221
}
202222
};
203223
@@ -215,6 +235,24 @@ export default create({
215235
setMove(-move);
216236
};
217237
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+
218256
watch(
219257
() => props.column,
220258
(val) => {
@@ -256,7 +294,8 @@ export default create({
256294
onTouchEnd,
257295
touchRollerStyle,
258296
touchListStyle,
259-
setMove
297+
setMove,
298+
stopMomentum
260299
};
261300
}
262301
});

0 commit comments

Comments
 (0)