index.vue 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  1. <template>
  2. <view
  3. class="nut-pullrefresh"
  4. ref="scroller"
  5. :style="getStyle"
  6. @touchstart="touchStart"
  7. @touchmove="touchMove"
  8. @touchend="touchEnd"
  9. >
  10. <view class="pullrefresh-top" :class="direction == 'horizontal' ? 'pullrefresh-top-h' : 'pullrefresh-top-v'">{{
  11. refreshTopTem
  12. }}</view>
  13. <view class="pullrefresh-content" ref="pull">
  14. <slot></slot>
  15. </view>
  16. <view
  17. class="pullrefresh-bottom"
  18. :class="direction == 'horizontal' ? 'pullrefresh-bottom-h' : 'pullrefresh-bottom-v'"
  19. :style="getBottomStyle"
  20. >{{ refreshBottomTem }}</view
  21. >
  22. </view>
  23. </template>
  24. <script lang="ts">
  25. import { ref, toRefs, reactive, onMounted, computed, CSSProperties } from 'vue';
  26. import { createComponent } from '../../utils/create';
  27. import { useTouch } from './use-touch';
  28. import { preventDefault } from './util';
  29. const { componentName, create } = createComponent('pullrefresh');
  30. export default create({
  31. props: {
  32. useWindow: {
  33. type: Boolean,
  34. default: true
  35. },
  36. containerId: {
  37. type: String,
  38. default: ''
  39. },
  40. disabled: {
  41. type: Boolean,
  42. default: false
  43. },
  44. /** 方向 Horizontal */
  45. direction: {
  46. type: String,
  47. default: 'vertical'
  48. },
  49. pullingText: {
  50. type: Object,
  51. default: {
  52. top: '下拉刷新',
  53. bottom: '上拉加载',
  54. left: '左滑刷新',
  55. right: '右滑加载'
  56. }
  57. },
  58. loosingText: {
  59. type: Object,
  60. default: {
  61. top: '松手释放刷新',
  62. bottom: '松手释放刷新',
  63. left: '释放刷新',
  64. right: '加载更多'
  65. }
  66. },
  67. loadingText: {
  68. type: Object,
  69. default: {
  70. top: '加载中...',
  71. bottom: '加载中...',
  72. left: '加载中...',
  73. right: '加载中...'
  74. }
  75. }
  76. },
  77. emits: ['refresh', 'downRefresh'], // refresh 上拉加载、右滑加载更多 downRefresh 下拉刷新、左滑刷新
  78. setup(props, { emit }) {
  79. // console.log('componentName', componentName);
  80. const { containerId, useWindow, direction, disabled } = toRefs(props);
  81. const reachTop = ref(false);
  82. const reachBottom = ref(false);
  83. const reachLeft = ref(false);
  84. const reachRight = ref(false);
  85. const state = reactive({
  86. status: 'normal',
  87. distance: 0,
  88. duration: 0
  89. });
  90. let scrollEl: HTMLElement = document.documentElement || document.body;
  91. const scroller = ref<null | HTMLElement>(null);
  92. const touch = useTouch();
  93. const getStyle = computed(() => {
  94. let style: CSSProperties = {};
  95. const { deltaY, deltaX } = touch;
  96. if (
  97. direction.value == 'vertical' &&
  98. ((reachTop.value && deltaY.value > 0) || (reachBottom.value && deltaY.value < 0)) &&
  99. touch.isVertical()
  100. ) {
  101. style = {
  102. transitionDuration: `${state.duration}ms`,
  103. transform: state.distance ? `translate3d(0,${state.distance}px, 0)` : `translate3d(0,0,0)`
  104. };
  105. }
  106. if (
  107. direction.value == 'horizontal' &&
  108. ((reachLeft.value && deltaX.value > 0) || (reachRight.value && deltaX.value < 0)) &&
  109. touch.isHorizontal()
  110. ) {
  111. style = {
  112. transitionDuration: `${state.duration}ms`,
  113. transform: state.distance ? `translate3d(${state.distance}px, 0,0)` : `translate3d(0,0,0)`
  114. };
  115. }
  116. return style;
  117. });
  118. const getBottomStyle = computed(() => {
  119. let style: CSSProperties = {};
  120. if (direction.value == 'vertical' && reachBottom.value && touch.deltaY.value < 0 && touch.isVertical()) {
  121. const dis = Math.abs(state.distance) < 50 ? -state.distance : 50;
  122. style = {
  123. height: dis + 'px'
  124. };
  125. }
  126. if (direction.value == 'horizontal' && reachRight.value && touch.deltaX.value < 0 && touch.isVertical()) {
  127. const dis = Math.abs(state.distance) < 50 ? -state.distance : 50;
  128. style = {
  129. width: dis + 'px'
  130. };
  131. }
  132. return style;
  133. });
  134. /** 刷新 顶部或左侧 */
  135. const refreshTopTem = computed(() => {
  136. const { status, distance } = state;
  137. const tag = direction.value == 'vertical' ? 'top' : 'left';
  138. if (status == 'loading' && (reachTop.value || reachLeft.value) && distance > 0) {
  139. return props.loadingText[tag];
  140. }
  141. if (status == 'pulling' && (reachTop.value || reachLeft.value) && distance > 0) {
  142. return props.pullingText[tag];
  143. }
  144. if (status == 'loosing' && (reachTop.value || reachLeft.value) && distance > 0) {
  145. return props.loosingText[tag];
  146. }
  147. return '';
  148. });
  149. /** 刷新 底部或右侧 */
  150. const refreshBottomTem = computed(() => {
  151. const { status, distance } = state;
  152. const tag = direction.value == 'vertical' ? 'bottom' : 'right';
  153. if (status == 'loading' && (reachBottom.value || reachRight.value) && distance < 0) {
  154. return props.loadingText[tag];
  155. }
  156. if (status == 'pulling' && (reachBottom.value || reachRight.value) && distance < 0) {
  157. return props.pullingText[tag];
  158. }
  159. if (status == 'loosing' && (reachBottom.value || reachRight.value) && distance < 0) {
  160. return props.loosingText[tag];
  161. }
  162. return '';
  163. });
  164. const isTouchable = () => state.status !== 'loading' && !disabled.value;
  165. const setStatus = (distance: number, isLoading?: boolean) => {
  166. state.distance = distance;
  167. if (isLoading) {
  168. state.status = 'loading';
  169. } else if (distance === 0) {
  170. state.status = 'normal';
  171. } else if (Math.abs(distance) < 50) {
  172. state.status = 'pulling';
  173. } else {
  174. state.status = 'loosing';
  175. }
  176. };
  177. /** 获取监听自定义滚动节点 */
  178. const getParentElement = (el) => {
  179. if (containerId.value != '') {
  180. return document.querySelector(`#${containerId.value}`);
  181. }
  182. return el && el.parentNode;
  183. };
  184. /** 生命周期 首次加载 */
  185. onMounted(() => {
  186. const parentElement = getParentElement(scroller);
  187. let scrollElCopy = document.documentElement || document.body;
  188. if (useWindow.value === false) {
  189. scrollElCopy = parentElement;
  190. }
  191. scrollEl = scrollElCopy;
  192. });
  193. const ease = (distance: number) => {
  194. const headHeight = 50;
  195. if (distance > headHeight) {
  196. if (distance < headHeight * 2) {
  197. distance = headHeight + (distance - headHeight) / 2;
  198. } else {
  199. distance = headHeight * 1.5 + (distance - headHeight * 2) / 4;
  200. }
  201. }
  202. return Math.round(distance);
  203. };
  204. const refreshDone = () => {
  205. setStatus(0);
  206. };
  207. const touchStart = (event) => {
  208. if (isTouchable()) {
  209. if (direction.value == 'vertical') {
  210. /** 判断滚动条是否在顶部 */
  211. const top = 'scrollTop' in scrollEl ? scrollEl.scrollTop : 0;
  212. reachTop.value = Math.max(top, 0) == 0 ? true : false;
  213. /** 判断滚动条是否在底部*/
  214. const { scrollHeight, clientHeight, scrollTop } = scrollEl;
  215. reachBottom.value = clientHeight + scrollTop == scrollHeight ? true : false;
  216. if (reachTop.value || reachBottom.value) {
  217. state.duration = 0;
  218. touch.start(event);
  219. }
  220. } else {
  221. const { scrollWidth, clientWidth, scrollLeft } = scrollEl;
  222. /** 判断滚动条是否在左边 */
  223. const left = 'scrollLeft' in scrollEl ? scrollEl.scrollLeft : 0;
  224. reachLeft.value = Math.max(left, 0) == 0 ? true : false;
  225. /** 判断滚动条是否在右边 */
  226. reachRight.value = clientWidth + scrollLeft == scrollWidth ? true : false;
  227. if (reachLeft.value || reachRight.value) {
  228. state.duration = 0;
  229. touch.start(event);
  230. }
  231. }
  232. } else {
  233. preventDefault(event);
  234. }
  235. };
  236. const touchMove = (event) => {
  237. if (isTouchable()) {
  238. const { deltaY, deltaX } = touch;
  239. touch.move(event);
  240. if (
  241. direction.value == 'vertical' &&
  242. ((reachBottom.value && deltaY.value < 0) || (reachTop.value && deltaY.value >= 0)) &&
  243. touch.isVertical()
  244. ) {
  245. preventDefault(event);
  246. setStatus(ease(deltaY.value));
  247. }
  248. if (
  249. direction.value == 'horizontal' &&
  250. ((reachLeft.value && deltaX.value >= 0) || (reachRight.value && deltaX.value < 0)) &&
  251. touch.isHorizontal()
  252. ) {
  253. preventDefault(event);
  254. setStatus(ease(deltaX.value));
  255. }
  256. } else {
  257. preventDefault(event);
  258. }
  259. };
  260. const touchEnd = () => {
  261. if (isTouchable()) {
  262. const { deltaY, deltaX } = touch;
  263. if (state.status === 'loosing') {
  264. let dis = 0;
  265. if (direction.value == 'vertical' && reachTop.value && deltaY.value > 0) {
  266. dis = 50;
  267. emit('downRefresh', refreshDone);
  268. }
  269. if (direction.value == 'vertical' && reachBottom.value && deltaY.value < 0) {
  270. dis = -50;
  271. emit('refresh', refreshDone);
  272. }
  273. if (direction.value == 'horizontal' && reachLeft.value && deltaX.value > 0) {
  274. dis = 50;
  275. emit('downRefresh', refreshDone);
  276. }
  277. if (direction.value == 'horizontal' && reachRight.value && deltaX.value < 0) {
  278. dis = -50;
  279. emit('refresh', refreshDone);
  280. }
  281. setStatus(dis, true);
  282. } else {
  283. setStatus(0);
  284. }
  285. }
  286. };
  287. return {
  288. scroller,
  289. touchStart,
  290. touchMove,
  291. touchEnd,
  292. getStyle,
  293. reachBottom,
  294. reachTop,
  295. reachRight,
  296. reachLeft,
  297. getBottomStyle,
  298. refreshTopTem,
  299. refreshBottomTem,
  300. ...toRefs(state)
  301. };
  302. }
  303. });
  304. </script>