Browse Source

upd: swiper

suzigang 4 years ago
parent
commit
bbce35bf6d

+ 1 - 0
src/packages/__VUE/address/index.scss

@@ -51,6 +51,7 @@
         width: 26px;
         height: 3px;
         background: $address-region-tab-line;
+        transition: 0.2s all linear;
       }
     }
 

File diff suppressed because it is too large
+ 0 - 3082
src/sites/mobile-taro/vue/dist/app.js


File diff suppressed because it is too large
+ 0 - 1
src/sites/mobile-taro/vue/dist/app.js.map


+ 0 - 1
src/sites/mobile-taro/vue/dist/app.json

@@ -1 +0,0 @@
-{"pages":["pages/swiper/demo"],"window":{"backgroundTextStyle":"light","navigationBarBackgroundColor":"#fff","navigationBarTitleText":"WeChat","navigationBarTextStyle":"black"}}

+ 0 - 86
src/sites/mobile-taro/vue/dist/common.js

@@ -1,86 +0,0 @@
-(wx['webpackJsonp'] = wx['webpackJsonp'] || []).push([
-  ['common'],
-  {
-    /***/ '../../../packages/utils/create/component.ts':
-      /*!***********************************************************************************************!*\
-  !*** /Users/suzigang/Downloads/project/nutui3.0/nutui/src/packages/utils/create/component.ts ***!
-  \***********************************************************************************************/
-      /*! exports provided: createComponent */
-      /*! exports used: createComponent */
-      /***/ function(module, __webpack_exports__, __webpack_require__) {
-        'use strict';
-        /* harmony export (binding) */ __webpack_require__.d(
-          __webpack_exports__,
-          'a',
-          function() {
-            return createComponent;
-          }
-        );
-        /* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(
-          /*! vue */ '../../../../node_modules/@vue/runtime-core/dist/runtime-core.esm-bundler.js'
-        );
-
-        function createComponent(name) {
-          var componentName = 'nut-' + name;
-          return {
-            componentName: componentName,
-            create: function(_component) {
-              _component.baseName = name;
-              _component.name = componentName;
-
-              _component.install = function(vue) {
-                var _component$children;
-
-                vue.component(_component.name, _component);
-                (_component === null || _component === void 0
-                  ? void 0
-                  : (_component$children = _component.children) === null ||
-                    _component$children === void 0
-                  ? void 0
-                  : _component$children.length) &&
-                  _component.children.forEach(function(item) {
-                    vue.component(item.name, item);
-                  });
-              };
-
-              return Object(
-                vue__WEBPACK_IMPORTED_MODULE_0__[/* defineComponent */ 'e']
-              )(_component);
-            },
-            createDemo: function(_component) {
-              _component.baseName = name;
-              _component.name = 'demo-' + name;
-              return Object(
-                vue__WEBPACK_IMPORTED_MODULE_0__[/* defineComponent */ 'e']
-              )(_component);
-            }
-          };
-        }
-
-        /***/
-      },
-
-    /***/ '../../../packages/utils/create/index.ts':
-      /*!*******************************************************************************************!*\
-  !*** /Users/suzigang/Downloads/project/nutui3.0/nutui/src/packages/utils/create/index.ts ***!
-  \*******************************************************************************************/
-      /*! exports provided: createComponent */
-      /*! exports used: createComponent */
-      /***/ function(module, __webpack_exports__, __webpack_require__) {
-        'use strict';
-        /* harmony import */ var _component__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(
-          /*! ./component */ '../../../packages/utils/create/component.ts'
-        );
-        /* harmony reexport (safe) */ __webpack_require__.d(
-          __webpack_exports__,
-          'a',
-          function() {
-            return _component__WEBPACK_IMPORTED_MODULE_0__['a'];
-          }
-        );
-
-        /***/
-      }
-  }
-]);
-//# sourceMappingURL=common.js.map

File diff suppressed because it is too large
+ 0 - 1
src/sites/mobile-taro/vue/dist/common.js.map


File diff suppressed because it is too large
+ 0 - 13550
src/sites/mobile-taro/vue/dist/taro.js


File diff suppressed because it is too large
+ 0 - 1
src/sites/mobile-taro/vue/dist/taro.js.map


File diff suppressed because it is too large
+ 0 - 36398
src/sites/mobile-taro/vue/dist/vendors.js


File diff suppressed because it is too large
+ 0 - 1
src/sites/mobile-taro/vue/dist/vendors.js.map


+ 2 - 2
src/sites/mobile-taro/vue/project.config.json

@@ -6,10 +6,10 @@
   "setting": {
     "urlCheck": true,
     "es6": false,
-    "enhance": false,
+    "enhance": true,
     "postcss": false,
     "preloadBackgroundData": false,
-    "minified": false,
+    "minified": true,
     "newFeature": false,
     "coverView": true,
     "nodeModules": false,

+ 6 - 0
src/sites/mobile-taro/vue/project.private.config.json

@@ -23,6 +23,12 @@
           "pathName": "pages/infiniteloading/demo",
           "query": "",
           "scene": null
+        },
+        {
+          "name": "pages/button/demo",
+          "pathName": "pages/button/demo",
+          "query": "",
+          "scene": null
         }
       ]
     }

+ 7 - 7
src/sites/mobile-taro/vue/src/app.config.ts

@@ -1,12 +1,12 @@
 export default {
   pages: [
-    'pages/swiper/demo'
-    // 'pages/calendar/demo',
-    // 'pages/input/demo',
-    // 'pages/popup/demo',
-    // 'pages/inputnumber/demo',
-    // 'pages/textarea/demo',
-    // 'pages/button/demo'
+    'pages/calendar/demo',
+    'pages/input/demo',
+    'pages/popup/demo',
+    'pages/inputnumber/demo',
+    'pages/textarea/demo',
+    'pages/button/demo',
+    'pages/infiniteloading/demo'
   ],
   window: {
     backgroundTextStyle: 'light',

+ 0 - 12
src/sites/mobile-taro/vue/src/pages/address/demo.vue

@@ -323,15 +323,3 @@ export default createDemo({
   }
 });
 </script>
-
-<style lang="scss" scoped>
-.demo {
-  .nut-cell {
-    align-items: center;
-
-    .nut-cell__value {
-      margin-right: 8px;
-    }
-  }
-}
-</style>

+ 20 - 9
src/sites/mobile-taro/vue/src/pages/address/index.taro.vue

@@ -36,7 +36,7 @@
         <view-block class="region-tab">
           <view-block
             class="tab-item"
-            :class="[index == tabIndex ? 'active' : '']"
+            :class="[index == tabIndex ? 'active' : '', key]"
             v-for="(item, key, index) in selectedRegion"
             :key="index"
             :ref="key"
@@ -45,7 +45,11 @@
             <view>{{ getTabName(item, index) }}</view>
           </view-block>
 
-          <view-block class="region-tab-line" ref="regionLine"></view-block>
+          <view-block
+            class="region-tab-line"
+            ref="regionLine"
+            :style="{ left: lineDistance + 'px' }"
+          ></view-block>
         </view-block>
 
         <view-block class="region-con">
@@ -119,6 +123,7 @@ import { reactive, ref, toRefs, watch, nextTick, computed } from 'vue';
 import { createComponent } from './../../../../../../packages/utils/create';
 import Icon from './../icon/index.taro.vue';
 import Popup from './../popup/index.taro.vue';
+import Taro from '@tarojs/taro';
 
 const { create, componentName } = createComponent('address');
 
@@ -264,6 +269,8 @@ export default create({
 
     const closeWay = ref('self');
 
+    const lineDistance = ref(20);
+
     //获取已选地区列表名称
     const getTabName = (item: RegionData, index: number) => {
       if (item.name) return item.name;
@@ -288,13 +295,16 @@ export default create({
     };
     // 移动下面的红线
     const lineAnimation = () => {
-      const name = (tabItemRef as any)[tabName.value[tabIndex.value]];
-      nextTick(() => {
-        if (name) {
-          // const distance = name.offsetLeft;
-          // TweenMax.to(regionLine.value, 0.5, { left: distance });
-        }
-      });
+      setTimeout(() => {
+        Taro.createSelectorQuery()
+          .selectAll(`.${tabName.value[tabIndex.value]}`)
+          .boundingClientRect(rects => {
+            (rects as any).forEach(rect => {
+              if (rect.width > 0) lineDistance.value = rect.left;
+            });
+          })
+          .exec();
+      }, 100);
     };
     // 切换下一级列表
     const nextAreaList = (item: RegionData | string) => {
@@ -491,6 +501,7 @@ export default create({
       getTabName,
       nextAreaList,
       regionLine,
+      lineDistance,
       changeRegionTab,
       selectedExist,
       clickOverlay,

+ 23 - 88
src/sites/mobile-taro/vue/src/pages/infiniteloading/demo.vue

@@ -1,65 +1,26 @@
 <template>
   <view class="demo">
-    <h2>基础用法</h2>
+    <h2>基础演示</h2>
     <nut-cell>
-      <ul class="infiniteUl" id="scroll">
-        <nut-infiniteloading
-          container-id="scroll"
-          :use-window="false"
-          :has-more="hasMore"
-          @load-more="loadMore"
-        >
-          <li
-            class="infiniteLi"
-            v-for="(item, index) in defultList"
-            :key="index"
-            >{{ item }}</li
-          >
-        </nut-infiniteloading>
-      </ul>
-    </nut-cell>
-
-    <h2>下拉刷新</h2>
-    <nut-cell>
-      <ul class="infiniteUl" id="refreshScroll">
+      <view-block class="infiniteUl" id="scrollDemo">
         <nut-infiniteloading
           pull-icon="JD"
-          container-id="refreshScroll"
-          :use-window="false"
-          :is-open-refresh="true"
-          :has-more="refreshHasMore"
-          @load-more="refreshLoadMore"
-          @refresh="refresh"
-        >
-          <li
-            class="infiniteLi"
-            v-for="(item, index) in refreshList"
-            :key="index"
-            >{{ item }}</li
-          >
-        </nut-infiniteloading>
-      </ul>
-    </nut-cell>
-
-    <h2>自定义加载文案</h2>
-    <nut-cell>
-      <ul class="infiniteUl" id="customScroll">
-        <nut-infiniteloading
           load-txt="loading"
           load-more-txt="没有啦~"
-          container-id="customScroll"
-          :use-window="false"
-          :has-more="customHasMore"
-          @load-more="customLoadMore"
+          :is-open-refresh="true"
+          container-id="scrollDemo"
+          :has-more="hasMore"
+          @load-more="loadMore"
+          @refresh="refresh"
         >
-          <li
+          <view-block
             class="infiniteLi"
-            v-for="(item, index) in customList"
+            v-for="(item, index) in defultList"
             :key="index"
-            >{{ item }}</li
+            >{{ item }}</view-block
           >
         </nut-infiniteloading>
-      </ul>
+      </view-block>
     </nut-cell>
   </view>
 </template>
@@ -68,18 +29,20 @@
 import { onMounted, ref, reactive, toRefs } from 'vue';
 import { createComponent } from './../../../../../../packages/utils/create';
 const { createDemo } = createComponent('infiniteloading');
-import Toast from './../toast/index.taro.vue';
+import Infiniteloading from './index.taro.vue';
+import Cell from './../cell/index.taro.vue';
+
 export default createDemo({
   props: {},
+  components: {
+    'nut-infiniteloading': Infiniteloading,
+    'nut-cell': Cell
+  },
   setup() {
     const hasMore = ref(true);
-    const customHasMore = ref(true);
-    const refreshHasMore = ref(true);
 
     const data = reactive({
-      defultList: [''],
-      customList: [''],
-      refreshList: ['']
+      defultList: ['']
     });
 
     const loadMore = done => {
@@ -96,40 +59,16 @@ export default createDemo({
       }, 500);
     };
 
-    const customLoadMore = done => {
-      setTimeout(() => {
-        const curLen = data.customList.length;
-        for (let i = curLen; i < curLen + 10; i++) {
-          data.customList.push(`${i}`);
-        }
-        if (data.customList.length > 30) customHasMore.value = false;
-        done();
-      }, 500);
-    };
-
-    const refreshLoadMore = done => {
-      setTimeout(() => {
-        const curLen = data.refreshList.length;
-        for (let i = curLen; i < curLen + 10; i++) {
-          data.refreshList.push(`${i}`);
-        }
-        if (data.refreshList.length > 30) refreshHasMore.value = false;
-        done();
-      }, 500);
-    };
-
     const refresh = done => {
       setTimeout(() => {
-        Toast.success('刷新成功');
+        console.log('刷新成功');
         done();
       }, 1000);
     };
 
     const init = () => {
-      for (let i = 0; i < 10; i++) {
+      for (let i = 0; i < 20; i++) {
         data.defultList.push(`${i}`);
-        data.customList.push(`${i}`);
-        data.refreshList.push(`${i}`);
       }
     };
     onMounted(() => {
@@ -139,10 +78,6 @@ export default createDemo({
     return {
       loadMore,
       hasMore,
-      customHasMore,
-      customLoadMore,
-      refreshHasMore,
-      refreshLoadMore,
       refresh,
       ...toRefs(data)
     };
@@ -150,9 +85,9 @@ export default createDemo({
 });
 </script>
 
-<style lang="scss" scoped>
+<style>
 .infiniteUl {
-  height: 300px;
+  height: 500px;
   width: 100%;
   overflow-y: auto;
   overflow-x: hidden;

+ 67 - 120
src/sites/mobile-taro/vue/src/pages/infiniteloading/index.taro.vue

@@ -1,13 +1,17 @@
 <template>
-  <view-block
+  <scroll-view
     :class="classes"
-    ref="scroller"
+    scrollY="true"
+    style="height: 100%"
+    id="scroller"
+    @scrolltolower="lower"
+    @scroll="scroll"
     @touchstart="touchStart"
     @touchmove="touchMove"
     @touchend="touchEnd"
   >
-    <view-block class="nut-infinite-top" ref="refreshTop" :style="getStyle">
-      <view-block class="top-box">
+    <view-block class="nut-infinite-top" :style="getStyle">
+      <view-block class="top-box" id="refreshTop">
         <nut-icon class="top-img" :name="pullIcon"></nut-icon>
         <view-block class="top-text">{{ pullTxt }}</view-block>
       </view-block>
@@ -28,20 +32,14 @@
         <view-block class="tips">{{ loadMoreTxt }}</view-block>
       </template>
     </view-block>
-  </view-block>
+  </scroll-view>
 </template>
 <script lang="ts">
-import {
-  toRefs,
-  onMounted,
-  onUnmounted,
-  reactive,
-  computed,
-  CSSProperties
-} from 'vue';
+import { toRefs, onMounted, reactive, computed, CSSProperties } from 'vue';
 import { createComponent } from './../../../../../../packages/utils/create';
 const { componentName, create } = createComponent('infiniteloading');
 import Icon from './../icon/index.taro.vue';
+import Taro from '@tarojs/taro';
 export default create({
   props: {
     hasMore: {
@@ -97,12 +95,11 @@ export default create({
   },
   setup(props, { emit, slots }) {
     const state = reactive({
-      scrollEl: window as Window | HTMLElement | (Node & ParentNode),
-      scroller: null as null | HTMLElement,
-      refreshTop: null as null | HTMLElement,
-      beforeScrollTop: 0,
-      isTouching: false,
+      scrollHeight: 0,
+      scrollTop: 0,
       isInfiniting: false,
+      direction: 'down',
+      isTouching: false,
       refreshMaxH: 0,
       y: 0,
       x: 0,
@@ -125,113 +122,67 @@ export default create({
           : `height 0.2s cubic-bezier(0.25,0.1,0.25,1)`
       };
     });
-
-    const getParentElement = (el: HTMLElement) => {
-      return !!props.containerId
-        ? document.querySelector(`#${props.containerId}`)
-        : el && el.parentNode;
-    };
-
-    const requestAniFrame = () => {
-      return (
-        window.requestAnimationFrame ||
-        window.webkitRequestAnimationFrame ||
-        function(callback) {
-          window.setTimeout(callback, 1000 / 60);
-        }
+    const getParentElement = el => {
+      return Taro.createSelectorQuery().select(
+        !!props.containerId ? `#${props.containerId} #${el}` : `#${el}`
       );
     };
-
-    const getWindowScrollTop = () => {
-      return window.pageYOffset !== undefined
-        ? window.pageYOffset
-        : (
-            document.documentElement ||
-            document.body.parentNode ||
-            document.body
-          ).scrollTop;
-    };
-
-    const calculateTopPosition = (el: HTMLElement): number => {
-      return !el
-        ? 0
-        : el.offsetTop + calculateTopPosition(el.offsetParent as HTMLElement);
+    /** 获取需要滚动的距离 */
+    const getScrollHeight = () => {
+      const parentElement = getParentElement('scroller');
+
+      parentElement
+        .boundingClientRect(rect => {
+          state.scrollHeight = rect.height;
+        })
+        .exec();
     };
 
-    const isScrollAtBottom = () => {
-      let offsetDistance = 0;
-      let resScrollTop = 0;
-      let direction = 'down';
-      const windowScrollTop = getWindowScrollTop();
-      if (props.useWindow) {
-        if (state.scroller) {
-          offsetDistance =
-            calculateTopPosition(state.scroller) +
-            state.scroller.offsetHeight -
-            windowScrollTop -
-            window.innerHeight;
-        }
-        resScrollTop = windowScrollTop;
+    /** 滚动到底部 */
+    const lower = () => {
+      if (state.direction == 'up' || !props.hasMore || state.isInfiniting) {
+        return false;
       } else {
-        const {
-          scrollHeight,
-          clientHeight,
-          scrollTop
-        } = state.scrollEl as HTMLElement;
-
-        offsetDistance = scrollHeight - clientHeight - scrollTop;
-        resScrollTop = scrollTop;
+        state.isInfiniting = true;
+        emit('load-more', infiniteDone);
       }
+    };
 
-      if (state.beforeScrollTop > resScrollTop) {
-        direction = 'up';
+    const scroll = e => {
+      // 滚动方向
+      if (e.detail.scrollTop <= 0) {
+        // 滚动到最顶部
+        e.detail.scrollTop = 0;
+      } else if (e.detail.scrollTop >= state.scrollHeight) {
+        // 滚动到最底部
+        e.detail.scrollTop = state.scrollHeight;
+      }
+      if (
+        e.detail.scrollTop > state.scrollTop ||
+        e.detail.scrollTop >= state.scrollHeight
+      ) {
+        state.direction = 'down';
       } else {
-        direction = 'down';
+        state.direction = 'up';
       }
+      state.scrollTop = e.detail.scrollTop;
 
-      state.beforeScrollTop = resScrollTop;
-
-      emit('scroll-change', resScrollTop);
-
-      return offsetDistance <= props.threshold && direction == 'down';
+      emit('scroll-change', e.detail.scrollTop);
     };
 
     const infiniteDone = () => {
       state.isInfiniting = false;
     };
 
-    const handleScroll = () => {
-      requestAniFrame()(() => {
-        if (!isScrollAtBottom() || !props.hasMore || state.isInfiniting) {
-          return false;
-        } else {
-          state.isInfiniting = true;
-          emit('load-more', infiniteDone);
-        }
-      });
-    };
-
-    const scrollListener = () => {
-      state.scrollEl.addEventListener('scroll', handleScroll, props.useCapture);
-    };
-
-    const refreshDone = () => {
-      state.distance = 0;
-      state.isTouching = false;
-    };
-
     const touchStart = (event: TouchEvent) => {
-      if (
-        state.beforeScrollTop == 0 &&
-        !state.isTouching &&
-        props.isOpenRefresh
-      ) {
+      if (state.scrollTop == 0 && !state.isTouching && props.isOpenRefresh) {
         state.y = event.touches[0].pageY;
         state.isTouching = true;
-
-        const childHeight = ((state.refreshTop as HTMLElement)
-          .firstElementChild as HTMLElement).offsetHeight;
-        state.refreshMaxH = Math.floor(childHeight * 1 + 10);
+        getParentElement('refreshTop')
+          .boundingClientRect(rect => {
+            state.refreshMaxH = Math.floor(rect.height * 1 + 10);
+          })
+          .exec();
       }
     };
 
@@ -256,26 +207,22 @@ export default create({
       }
     };
 
-    onMounted(() => {
-      const parentElement = getParentElement(
-        state.scroller as HTMLElement
-      ) as Node & ParentNode;
-      state.scrollEl = props.useWindow ? window : parentElement;
-
-      scrollListener();
-    });
+    const refreshDone = () => {
+      state.distance = 0;
+      state.isTouching = false;
+    };
 
-    onUnmounted(() => {
-      state.scrollEl.removeEventListener(
-        'scroll',
-        handleScroll,
-        props.useCapture
-      );
+    onMounted(() => {
+      setTimeout(() => {
+        getScrollHeight();
+      }, 200);
     });
 
     return {
       classes,
       ...toRefs(state),
+      lower,
+      scroll,
       touchStart,
       touchMove,
       touchEnd,

+ 287 - 0
src/sites/mobile-taro/vue/src/pages/infiniteloading/index.taroC.vue

@@ -0,0 +1,287 @@
+<template>
+  <view-block
+    :class="classes"
+    ref="scroller"
+    @touchstart="touchStart"
+    @touchmove="touchMove"
+    @touchend="touchEnd"
+  >
+    <view-block class="nut-infinite-top" ref="refreshTop" :style="getStyle">
+      <view-block class="top-box">
+        <nut-icon class="top-img" :name="pullIcon"></nut-icon>
+        <view-block class="top-text">{{ pullTxt }}</view-block>
+      </view-block>
+    </view-block>
+
+    <view-block class="nut-infinite-container">
+      <slot></slot>
+    </view-block>
+
+    <view-block class="nut-infinite-bottom">
+      <template v-if="isInfiniting">
+        <view-block class="bottom-box">
+          <nut-icon class="bottom-img" :name="loadIcon"></nut-icon>
+          <view-block class="bottom-text">{{ loadTxt }}</view-block>
+        </view-block>
+      </template>
+      <template v-else-if="!hasMore">
+        <view-block class="tips">{{ loadMoreTxt }}</view-block>
+      </template>
+    </view-block>
+  </view-block>
+</template>
+<script lang="ts">
+import {
+  toRefs,
+  onMounted,
+  onUnmounted,
+  reactive,
+  computed,
+  CSSProperties
+} from 'vue';
+import { createComponent } from '@/packages/utils/create';
+const { componentName, create } = createComponent('infiniteloading');
+export default create({
+  props: {
+    hasMore: {
+      type: Boolean,
+      default: true
+    },
+    threshold: {
+      type: Number,
+      default: 200
+    },
+    pullIcon: {
+      type: String,
+      default:
+        'https://img10.360buyimg.com/imagetools/jfs/t1/169863/6/4565/6306/60125948E7e92774e/40b3a0cf42852bcb.png'
+    },
+    pullTxt: {
+      type: String,
+      default: '松开刷新'
+    },
+    loadIcon: {
+      type: String,
+      default:
+        'https://img10.360buyimg.com/imagetools/jfs/t1/169863/6/4565/6306/60125948E7e92774e/40b3a0cf42852bcb.png'
+    },
+    loadTxt: {
+      type: String,
+      default: '加载中···'
+    },
+    loadMoreTxt: {
+      type: String,
+      default: '哎呀,这里是底部了啦'
+    },
+    useWindow: {
+      type: Boolean,
+      default: true
+    },
+    containerId: {
+      type: String,
+      default: ''
+    },
+    useCapture: {
+      type: Boolean,
+      default: false
+    },
+    isOpenRefresh: {
+      type: Boolean,
+      default: false
+    }
+  },
+  emits: ['scroll-change', 'load-more', 'refresh'],
+
+  setup(props, { emit, slots }) {
+    const state = reactive({
+      scrollEl: window as Window | HTMLElement | (Node & ParentNode),
+      scroller: null as null | HTMLElement,
+      refreshTop: null as null | HTMLElement,
+      beforeScrollTop: 0,
+      isTouching: false,
+      isInfiniting: false,
+      refreshMaxH: 0,
+      y: 0,
+      x: 0,
+      distance: 0
+    });
+
+    const classes = computed(() => {
+      const prefixCls = componentName;
+      return {
+        [prefixCls]: true
+      };
+    });
+
+    const getStyle = computed(() => {
+      const style: CSSProperties = {};
+      return {
+        height: state.distance < 0 ? `0px` : `${state.distance}px`,
+        transition: state.isTouching
+          ? `height 0s cubic-bezier(0.25,0.1,0.25,1)`
+          : `height 0.2s cubic-bezier(0.25,0.1,0.25,1)`
+      };
+    });
+
+    const getParentElement = (el: HTMLElement) => {
+      return !!props.containerId
+        ? document.querySelector(`#${props.containerId}`)
+        : el && el.parentNode;
+    };
+
+    const requestAniFrame = () => {
+      return (
+        window.requestAnimationFrame ||
+        window.webkitRequestAnimationFrame ||
+        function(callback) {
+          window.setTimeout(callback, 1000 / 60);
+        }
+      );
+    };
+
+    const getWindowScrollTop = () => {
+      return window.pageYOffset !== undefined
+        ? window.pageYOffset
+        : (
+            document.documentElement ||
+            document.body.parentNode ||
+            document.body
+          ).scrollTop;
+    };
+
+    const calculateTopPosition = (el: HTMLElement): number => {
+      return !el
+        ? 0
+        : el.offsetTop + calculateTopPosition(el.offsetParent as HTMLElement);
+    };
+
+    const isScrollAtBottom = () => {
+      let offsetDistance = 0;
+      let resScrollTop = 0;
+      let direction = 'down';
+      const windowScrollTop = getWindowScrollTop();
+      if (props.useWindow) {
+        if (state.scroller) {
+          offsetDistance =
+            calculateTopPosition(state.scroller) +
+            state.scroller.offsetHeight -
+            windowScrollTop -
+            window.innerHeight;
+        }
+        resScrollTop = windowScrollTop;
+      } else {
+        const {
+          scrollHeight,
+          clientHeight,
+          scrollTop
+        } = state.scrollEl as HTMLElement;
+
+        offsetDistance = scrollHeight - clientHeight - scrollTop;
+        resScrollTop = scrollTop;
+      }
+
+      if (state.beforeScrollTop > resScrollTop) {
+        direction = 'up';
+      } else {
+        direction = 'down';
+      }
+
+      state.beforeScrollTop = resScrollTop;
+
+      emit('scroll-change', resScrollTop);
+
+      return offsetDistance <= props.threshold && direction == 'down';
+    };
+
+    const infiniteDone = () => {
+      state.isInfiniting = false;
+    };
+
+    const handleScroll = () => {
+      requestAniFrame()(() => {
+        if (!isScrollAtBottom() || !props.hasMore || state.isInfiniting) {
+          return false;
+        } else {
+          state.isInfiniting = true;
+          emit('load-more', infiniteDone);
+        }
+      });
+    };
+
+    const scrollListener = () => {
+      state.scrollEl.addEventListener('scroll', handleScroll, props.useCapture);
+    };
+
+    const refreshDone = () => {
+      state.distance = 0;
+      state.isTouching = false;
+    };
+
+    const touchStart = (event: TouchEvent) => {
+      if (
+        state.beforeScrollTop == 0 &&
+        !state.isTouching &&
+        props.isOpenRefresh
+      ) {
+        state.y = event.touches[0].pageY;
+        state.isTouching = true;
+
+        const childHeight = ((state.refreshTop as HTMLElement)
+          .firstElementChild as HTMLElement).offsetHeight;
+        state.refreshMaxH = Math.floor(childHeight * 1 + 10);
+      }
+    };
+
+    const touchMove = (event: TouchEvent) => {
+      state.distance = event.touches[0].pageY - state.y;
+
+      if (state.distance > 0 && state.isTouching) {
+        event.preventDefault();
+        if (state.distance >= state.refreshMaxH)
+          state.distance = state.refreshMaxH;
+      } else {
+        state.distance = 0;
+        state.isTouching = false;
+      }
+    };
+
+    const touchEnd = () => {
+      if (state.distance < state.refreshMaxH) {
+        state.distance = 0;
+      } else {
+        emit('refresh', refreshDone);
+      }
+    };
+
+    onMounted(() => {
+      const parentElement = getParentElement(
+        state.scroller as HTMLElement
+      ) as Node & ParentNode;
+      state.scrollEl = props.useWindow ? window : parentElement;
+
+      scrollListener();
+    });
+
+    onUnmounted(() => {
+      state.scrollEl.removeEventListener(
+        'scroll',
+        handleScroll,
+        props.useCapture
+      );
+    });
+
+    return {
+      classes,
+      ...toRefs(state),
+      touchStart,
+      touchMove,
+      touchEnd,
+      getStyle
+    };
+  }
+});
+</script>
+
+<style lang="scss">
+@import 'index.scss';
+</style>