Browse Source

feat: pullrefresh 纵向

yangxiaolu3 5 years ago
parent
commit
caa646625d

+ 10 - 0
src/config.js

@@ -313,6 +313,16 @@ module.exports = {
           sort: 15,
           show: true,
           author: 'yangxiaolu'
+        },
+        {
+          version: '3.0.0',
+          name: 'PullRefresh',
+          type: 'component',
+          cName: '下拉刷新',
+          desc: '下拉刷新',
+          sort: 16,
+          show: true,
+          author: 'yangxiaolu3'
         }
       ]
     },

+ 61 - 0
src/packages/pullrefresh/demo.vue

@@ -0,0 +1,61 @@
+<template>
+  <div class="demo">
+    <nut-pullrefresh @refresh="refresh" :useWindow="false" containerId="pull">
+      <div class="content" id="pull">
+        <div class="main">
+          <div class="text-data">我是测试数据1</div>
+          <div class="text-data">我是测试数据2</div>
+          <div class="text-data">我是测试数据3</div>
+          <div class="text-data">我是测试数据4</div>
+          <div class="text-data">我是测试数据5</div>
+          <div class="text-data">我是测试数据6</div>
+          <div class="text-data">我是测试数据7</div>
+          <div class="text-data">我是测试数据8</div>
+          <div class="text-data">我是测试数据9</div>
+          <div class="text-data">我是测试数据10</div>
+          <div class="text-data">我是测试数据11</div>
+          <div class="text-data">我是测试数据12</div>
+          <div class="text-data">我是测试数据13</div>
+          <div class="text-data">我是测试数据14</div>
+          <div class="text-data">我是测试数据15</div>
+          <div class="text-data">我是测试数据16</div>
+          <div class="text-data">我是测试数据17</div>
+          <div class="text-data">我是测试数据18</div>
+          <div class="text-data">我是测试数据19</div>
+          <div class="text-data">我是测试数据20</div>
+          <div class="text-data">我是测试数据21</div>
+          <div class="text-data">我是测试数据22</div>
+          <div class="text-data">我是测试数据23</div>
+          <div class="text-data">我是测试数据24</div>
+        </div>
+      </div>
+    </nut-pullrefresh>
+  </div>
+</template>
+
+<script lang="ts">
+import { createComponent } from '@/utils/create';
+const { createDemo } = createComponent('pullrefresh');
+export default createDemo({
+  props: {},
+  setup() {
+    const refresh = done => {
+      setTimeout(() => {
+        done();
+      }, 1000);
+    };
+    return { refresh };
+  }
+});
+</script>
+
+<style lang="scss" scoped>
+.content {
+  height: 100%;
+  overflow: auto;
+  .main {
+    padding: 10px 0;
+    background: #f00;
+  }
+}
+</style>

+ 34 - 0
src/packages/pullrefresh/doc.md

@@ -0,0 +1,34 @@
+#  pullrefresh组件
+
+    ### 介绍
+    
+    基于 xxxxxxx
+    
+    ### 安装
+    
+    
+    
+    ## 代码演示
+    
+    ### 基础用法1
+    
+
+    
+    ## API
+    
+    ### Props
+    
+    | 参数         | 说明                             | 类型   | 默认值           |
+    |--------------|----------------------------------|--------|------------------|
+    | name         | 图标名称或图片链接               | String | -                |
+    | color        | 图标颜色                         | String | -                |
+    | size         | 图标大小,如 '20px' '2em' '2rem' | String | -                |
+    | class-prefix | 类名前缀,用于使用自定义图标     | String | 'nutui-iconfont' |
+    | tag          | HTML 标签                        | String | 'i'              |
+    
+    ### Events
+    
+    | 事件名 | 说明           | 回调参数     |
+    |--------|----------------|--------------|
+    | click  | 点击图标时触发 | event: Event |
+    

+ 43 - 0
src/packages/pullrefresh/index.css

@@ -0,0 +1,43 @@
+view {
+  display: block;
+}
+
+.nut-pullrefresh {
+  position: relative;
+  height: 100%;
+}
+
+.nut-pullrefresh .pullrefresh-top {
+  position: absolute;
+  left: 0;
+  width: 100%;
+  height: 50px;
+  overflow: hidden;
+  color: #969799;
+  font-size: 14px;
+  line-height: 50px;
+  text-align: center;
+  -webkit-transform: translateY(-100%);
+  transform: translateY(-100%);
+}
+
+.nut-pullrefresh .pullrefresh-content {
+  height: 100%;
+  overflow: auto;
+  background: #fff;
+}
+
+.nut-pullrefresh .pullrefresh-bottom {
+  position: absolute;
+  left: 0;
+  bottom: 0;
+  width: 100%;
+  height: 0px;
+  overflow: hidden;
+  color: #969799;
+  font-size: 14px;
+  line-height: 50px;
+  text-align: center;
+  -webkit-transform: translateY(100%);
+  transform: translateY(100%);
+}

File diff suppressed because it is too large
+ 1 - 0
src/packages/pullrefresh/index.min.css


+ 43 - 0
src/packages/pullrefresh/index.scss

@@ -0,0 +1,43 @@
+view {
+  display: block;
+}
+
+.nut-pullrefresh {
+  position: relative;
+  height: 100%;
+
+  .pullrefresh-top {
+    position: absolute;
+    left: 0;
+    width: 100%;
+    height: 50px;
+    overflow: hidden;
+    color: #969799;
+    font-size: 14px;
+    line-height: 50px;
+    text-align: center;
+    -webkit-transform: translateY(-100%);
+    transform: translateY(-100%);
+  }
+
+  .pullrefresh-content {
+    height: 100%;
+    overflow: auto;
+    background: #fff;
+  }
+
+  .pullrefresh-bottom {
+    position: absolute;
+    left: 0;
+    bottom: 0;
+    width: 100%;
+    height: 0px;
+    overflow: hidden;
+    color: #969799;
+    font-size: 14px;
+    line-height: 50px;
+    text-align: center;
+    -webkit-transform: translateY(100%);
+    transform: translateY(100%);
+  }
+}

+ 231 - 0
src/packages/pullrefresh/index.vue

@@ -0,0 +1,231 @@
+<template>
+  <view
+    class="nut-pullrefresh"
+    ref="scroller"
+    :style="getStyle"
+    @touchstart="touchStart"
+    @touchmove="touchMove"
+    @touchend="touchEnd"
+  >
+    <view class="pullrefresh-top">
+      <template v-if="status == 'loading' && reachTop && distance > 0">
+        加载中...
+      </template>
+      <template v-if="status == 'pulling' && reachTop && distance > 0">
+        下拉刷新...
+      </template>
+      <template v-if="status == 'loosing' && reachTop && distance > 0">
+        释放刷新...
+      </template>
+    </view>
+    <view class="pullrefresh-content" ref="pull">
+      <slot></slot>
+    </view>
+
+    <view class="pullrefresh-bottom" :style="getBottomStyle">
+      <template v-if="status == 'loading' && reachBottom && distance < 0">
+        加载中...
+      </template>
+      <template v-if="status == 'pulling' && reachBottom && distance < 0">
+        下拉刷新...
+      </template>
+      <template v-if="status == 'loosing' && reachBottom && distance < 0">
+        释放刷新...
+      </template>
+    </view>
+  </view>
+</template>
+
+<script lang="ts">
+import { ref, toRefs, reactive, onMounted, computed, CSSProperties } from 'vue';
+import { createComponent } from '@/utils/create';
+import { useTouch } from './use-touch';
+import { preventDefault } from './util';
+const { componentName, create } = createComponent('pullrefresh');
+
+export default create({
+  props: {
+    useWindow: {
+      type: Boolean,
+      default: true
+    },
+    containerId: {
+      type: String,
+      default: ''
+    }
+  },
+  components: {},
+  emits: ['refresh'],
+
+  setup(props, { emit }) {
+    console.log('componentName', componentName);
+
+    const { containerId, useWindow } = toRefs(props);
+
+    const reachTop = ref(false);
+    const reachBottom = ref(false);
+
+    const state = reactive({
+      status: 'normal',
+      distance: 0,
+      duration: 0
+    });
+
+    let scrollEl: HTMLElement = document.documentElement || document.body;
+
+    const scroller = ref<null | HTMLElement>(null);
+    const touch = useTouch();
+
+    const getStyle = computed(() => {
+      let style: CSSProperties = {};
+
+      if (
+        (reachTop.value && touch.deltaY.value > 0 && touch.isVertical()) ||
+        (reachBottom.value && touch.deltaY.value < 0 && touch.isVertical())
+      ) {
+        style = {
+          transitionDuration: `${state.duration}ms`,
+          transform: state.distance
+            ? `translate3d(0,${state.distance}px, 0)`
+            : `translate3d(0,0,0)`
+        };
+      }
+      return style;
+    });
+
+    const getBottomStyle = computed(() => {
+      let style: CSSProperties = {};
+      if (reachBottom.value && touch.deltaY.value < 0 && touch.isVertical()) {
+        const dis = Math.abs(state.distance) < 50 ? -state.distance : 50;
+        style = {
+          height: dis + 'px'
+        };
+      }
+      return style;
+    });
+
+    const setStatus = (distance: number, isLoading?: boolean) => {
+      state.distance = distance;
+
+      if (isLoading) {
+        state.status = 'loading';
+      } else if (distance === 0) {
+        state.status = 'normal';
+      } else if (Math.abs(distance) < 50) {
+        state.status = 'pulling';
+      } else {
+        state.status = 'loosing';
+      }
+    };
+
+    /** 获取监听自定义滚动节点 */
+    const getParentElement = el => {
+      if (containerId.value != '') {
+        return document.querySelector(`#${containerId.value}`);
+      }
+      return el && el.parentNode;
+    };
+
+    /** 生命周期 首次加载 */
+    onMounted(() => {
+      const parentElement = getParentElement(scroller);
+
+      let scrollElCopy = document.documentElement || document.body;
+      if (useWindow.value === false) {
+        scrollElCopy = parentElement;
+      }
+
+      scrollEl = scrollElCopy;
+    });
+
+    const ease = (distance: number) => {
+      const headHeight = 50;
+
+      if (distance > headHeight) {
+        if (distance < headHeight * 2) {
+          distance = headHeight + (distance - headHeight) / 2;
+        } else {
+          distance = headHeight * 1.5 + (distance - headHeight * 2) / 4;
+        }
+      }
+
+      return Math.round(distance);
+    };
+
+    const refreshDone = () => {
+      setStatus(0);
+    };
+
+    const touchStart = event => {
+      /** 判断滚动条是否在顶部 */
+      const top = 'scrollTop' in scrollEl ? scrollEl.scrollTop : 0;
+      reachTop.value = Math.max(top, 0) == 0 ? true : false;
+
+      if (reachTop.value) {
+        state.duration = 0;
+        touch.start(event);
+      }
+
+      const { scrollHeight, clientHeight, scrollTop } = scrollEl;
+
+      /** 判断滚动条是否在底部*/
+      reachBottom.value =
+        clientHeight + scrollTop == scrollHeight ? true : false;
+
+      if (reachBottom.value) {
+        state.duration = 0;
+        touch.start(event);
+      }
+    };
+
+    const touchMove = event => {
+      const { deltaY } = touch;
+
+      touch.move(event);
+      if (reachTop.value && deltaY.value >= 0 && touch.isVertical()) {
+        preventDefault(event);
+        setStatus(ease(deltaY.value));
+      }
+
+      if (reachBottom.value && deltaY.value < 0 && touch.isVertical()) {
+        preventDefault(event);
+        setStatus(ease(deltaY.value));
+      }
+    };
+    const touchEnd = () => {
+      if (reachTop.value && touch.deltaY.value > 0) {
+        if (state.status === 'loosing') {
+          setStatus(50, true);
+          emit('refresh', refreshDone);
+        } else {
+          setStatus(0);
+        }
+      }
+
+      if (reachBottom.value && touch.deltaY.value < 0) {
+        if (state.status === 'loosing') {
+          setStatus(-50, true);
+          emit('refresh', refreshDone);
+        } else {
+          setStatus(0);
+        }
+      }
+    };
+    return {
+      scroller,
+      touchStart,
+      touchMove,
+      touchEnd,
+      getStyle,
+      reachBottom,
+      reachTop,
+      getBottomStyle,
+      ...toRefs(state)
+    };
+  }
+});
+</script>
+
+<style lang="scss">
+@import 'index.scss';
+</style>

+ 69 - 0
src/packages/pullrefresh/use-touch.ts

@@ -0,0 +1,69 @@
+import { ref } from 'vue';
+
+const MIN_DISTANCE = 10;
+
+type Direction = '' | 'vertical' | 'horizontal';
+
+function getDirection(x: number, y: number) {
+  if (x > y && x > MIN_DISTANCE) {
+    return 'horizontal';
+  }
+  if (y > x && y > MIN_DISTANCE) {
+    return 'vertical';
+  }
+  return '';
+}
+
+export function useTouch() {
+  const startX = ref(0);
+  const startY = ref(0);
+  const deltaX = ref(0);
+  const deltaY = ref(0);
+  const offsetX = ref(0);
+  const offsetY = ref(0);
+  const direction = ref<Direction>('');
+
+  const isVertical = () => direction.value === 'vertical';
+  const isHorizontal = () => direction.value === 'horizontal';
+
+  const reset = () => {
+    deltaX.value = 0;
+    deltaY.value = 0;
+    offsetX.value = 0;
+    offsetY.value = 0;
+    direction.value = '';
+  };
+
+  const start = ((event: TouchEvent) => {
+    reset();
+    startX.value = event.touches[0].clientX;
+    startY.value = event.touches[0].clientY;
+  }) as EventListener;
+
+  const move = ((event: TouchEvent) => {
+    const touch = event.touches[0];
+    deltaX.value = touch.clientX - startX.value;
+    deltaY.value = touch.clientY - startY.value;
+    offsetX.value = Math.abs(deltaX.value);
+    offsetY.value = Math.abs(deltaY.value);
+
+    if (!direction.value) {
+      direction.value = getDirection(offsetX.value, offsetY.value);
+    }
+  }) as EventListener;
+
+  return {
+    move,
+    start,
+    reset,
+    startX,
+    startY,
+    deltaX,
+    deltaY,
+    offsetX,
+    offsetY,
+    direction,
+    isVertical,
+    isHorizontal
+  };
+}

+ 14 - 0
src/packages/pullrefresh/util.ts

@@ -0,0 +1,14 @@
+export function preventDefault(event: Event, isStopPropagation?: boolean) {
+  /* istanbul ignore else */
+  if (typeof event.cancelable !== 'boolean' || event.cancelable) {
+    event.preventDefault();
+  }
+
+  if (isStopPropagation) event.stopPropagation();
+}
+
+export function trigger(target: Element, type: string) {
+  const inputEvent = document.createEvent('HTMLEvents');
+  inputEvent.initEvent(type, true, true);
+  target.dispatchEvent(inputEvent);
+}