ColumnTaro.vue 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. <template>
  2. <view
  3. class="nut-picker__list"
  4. @touchstart="onTouchStart"
  5. @touchmove="onTouchMove"
  6. @touchend="onTouchEnd"
  7. v-if="itemShow"
  8. >
  9. <view class="nut-picker-roller" ref="roller" :style="touchRollerStyle">
  10. <view
  11. class="nut-picker-roller-item"
  12. :class="{ 'nut-picker-roller-item-hidden': isHidden(index + 1) }"
  13. v-for="(item, index) in listData.values"
  14. :style="setRollerStyle(index + 1)"
  15. :key="item.label ? item.label : index"
  16. >
  17. {{ dataType === 'cascade' ? item.text : item }}
  18. </view>
  19. </view>
  20. <view class="nut-picker-content">
  21. <view class="nut-picker-list-panel" ref="list" :style="touchListStyle">
  22. <view
  23. class="nut-picker-item nut-picker-item-ref"
  24. v-for="(item, index) in listData.values"
  25. :key="item.label ? item.label : index"
  26. >{{ dataType === 'cascade' ? item.text : item }}
  27. </view>
  28. <view class="nut-picker-placeholder" v-if="listData && listData.length === 1"></view>
  29. </view>
  30. </view>
  31. </view>
  32. </template>
  33. <script lang="ts">
  34. import { reactive, ref, watch, computed, toRefs, onMounted } from 'vue';
  35. import { createComponent } from '../../utils/create';
  36. import { commonProps } from './commonProps';
  37. const { create } = createComponent('picker-column');
  38. import { TouchParams } from './types';
  39. import Taro from '@tarojs/taro';
  40. export default create({
  41. props: {
  42. dataType: String,
  43. itemShow: {
  44. type: Boolean,
  45. default: false
  46. },
  47. ...commonProps
  48. },
  49. emits: ['click', 'change'],
  50. setup(props, { emit }) {
  51. const wrapper = ref();
  52. const state = reactive({
  53. touchParams: {
  54. startY: 0,
  55. endY: 0,
  56. startTime: 0,
  57. endTime: 0,
  58. lastY: 0,
  59. lastTime: 0
  60. },
  61. currIndex: 1,
  62. transformY: 0,
  63. scrollDistance: 0,
  64. lineSpacing: 36,
  65. rotation: 20,
  66. timer: null
  67. });
  68. const roller = ref(null);
  69. const list = ref(null);
  70. const listItem = ref(null);
  71. const touchDeg = ref(0);
  72. const touchTime = ref(0);
  73. const touchTranslateY = ref(0);
  74. const touchListStyle = computed(() => {
  75. return {
  76. transition: `transform ${touchTime.value}ms cubic-bezier(0.19, 1, 0.22, 1)`,
  77. transform: `translate3d(0, ${state.scrollDistance}px, 0)`
  78. };
  79. });
  80. const touchRollerStyle = computed(() => {
  81. return {
  82. transition: `transform ${touchTime.value}ms cubic-bezier(0.19, 1, 0.22, 1)`,
  83. transform: `rotate3d(1, 0, 0, ${touchDeg.value})`
  84. };
  85. });
  86. const onTouchStart = (event: TouchEvent) => {
  87. console.log(event);
  88. event.preventDefault();
  89. let changedTouches = event.changedTouches[0];
  90. state.touchParams.startY = changedTouches.pageY;
  91. state.touchParams.startTime = event.timeStamp || Date.now();
  92. state.transformY = state.scrollDistance;
  93. };
  94. const onTouchMove = (event: TouchEvent) => {
  95. event.preventDefault();
  96. let changedTouches = event.changedTouches[0];
  97. (state.touchParams as TouchParams).lastY = changedTouches.pageY;
  98. (state.touchParams as TouchParams).lastTime = event.timeStamp || Date.now();
  99. let move = state.touchParams.lastY - state.touchParams.startY;
  100. setMove(move);
  101. };
  102. const onTouchEnd = (event: TouchEvent) => {
  103. event.preventDefault();
  104. let changedTouches = event.changedTouches[0];
  105. state.touchParams.lastY = changedTouches.pageY;
  106. state.touchParams.lastTime = event.timeStamp || Date.now();
  107. let move = state.touchParams.lastY - state.touchParams.startY;
  108. let moveTime = state.touchParams.lastTime - state.touchParams.startTime;
  109. if (moveTime <= 300) {
  110. move = move * 2;
  111. moveTime = moveTime + 1000;
  112. setMove(move, 'end', moveTime);
  113. } else {
  114. setMove(move, 'end');
  115. }
  116. };
  117. const setRollerStyle = (index: number) => {
  118. return `transform: rotate3d(1, 0, 0, ${-state.rotation * index}deg) translate3d(0px, 0px, 104px)`;
  119. };
  120. const isHidden = (index: number) => {
  121. if (index >= state.currIndex + 8 || index <= state.currIndex - 8) {
  122. return true;
  123. } else {
  124. return false;
  125. }
  126. };
  127. const setTransform = (translateY = 0, type: string | null, time = 1000, deg: string | number) => {
  128. if (type === 'end') {
  129. touchTime.value = time;
  130. } else {
  131. touchTime.value = 0;
  132. }
  133. touchDeg.value = deg as number;
  134. touchTranslateY.value = translateY;
  135. state.scrollDistance = translateY;
  136. };
  137. const setMove = (move: number, type?: string, time?: number) => {
  138. let updateMove = move + state.transformY;
  139. if (type === 'end') {
  140. // 限定滚动距离
  141. if (updateMove > 0) {
  142. updateMove = 0;
  143. }
  144. if (updateMove < -(props.listData.values.length - 1) * state.lineSpacing) {
  145. updateMove = -(props.listData.values.length - 1) * state.lineSpacing;
  146. }
  147. // 设置滚动距离为lineSpacing的倍数值
  148. let endMove = Math.round(updateMove / state.lineSpacing) * state.lineSpacing;
  149. let deg = `${(Math.abs(Math.round(endMove / state.lineSpacing)) + 1) * state.rotation}deg`;
  150. setTransform(endMove, type, time, deg);
  151. let t = time ? time / 2 : 0;
  152. (state.timer as any) = setTimeout(() => {
  153. setChooseValue();
  154. }, t);
  155. state.currIndex = Math.abs(Math.round(endMove / state.lineSpacing)) + 1;
  156. } else {
  157. let deg = '0deg';
  158. if (updateMove < 0) {
  159. deg = `${(Math.abs(updateMove / state.lineSpacing) + 1) * state.rotation}deg`;
  160. } else {
  161. deg = `${(-updateMove / state.lineSpacing + 1) * state.rotation}deg`;
  162. }
  163. setTransform(updateMove, null, undefined, deg);
  164. state.currIndex = Math.abs(Math.round(updateMove / state.lineSpacing)) + 1;
  165. }
  166. };
  167. const setChooseValue = () => {
  168. emit('change', state.currIndex - 1);
  169. };
  170. const modifyStatus = (type: boolean) => {
  171. let index = props.defaultIndex;
  172. state.currIndex = index === -1 ? 1 : (index as number) + 1;
  173. let move = index === -1 ? 0 : (index as number) * state.lineSpacing;
  174. type && setChooseValue();
  175. setMove(-move);
  176. };
  177. watch(
  178. () => props.listData,
  179. (val) => {
  180. state.transformY = 0;
  181. modifyStatus(false);
  182. },
  183. {
  184. deep: true
  185. }
  186. );
  187. watch(
  188. () => props.itemShow,
  189. (val) => {
  190. if (val) {
  191. setTimeout(() => {
  192. Taro.createSelectorQuery()
  193. .selectAll('.nut-picker-item-ref')
  194. .boundingClientRect((rects) => {
  195. state.lineSpacing = (rects as any)[0].height;
  196. })
  197. .exec();
  198. }, 100);
  199. }
  200. },
  201. {
  202. deep: true
  203. }
  204. );
  205. watch(
  206. () => props.defaultIndex,
  207. (val) => {
  208. state.transformY = 0;
  209. modifyStatus(false);
  210. }
  211. );
  212. onMounted(() => {
  213. modifyStatus(true);
  214. });
  215. return {
  216. ...toRefs(state),
  217. ...toRefs(props),
  218. wrapper,
  219. setRollerStyle,
  220. isHidden,
  221. roller,
  222. list,
  223. listItem,
  224. onTouchStart,
  225. onTouchMove,
  226. onTouchEnd,
  227. touchRollerStyle,
  228. touchListStyle
  229. };
  230. }
  231. });
  232. </script>