Browse Source

refactor: swiper组件

suzigang 4 years ago
parent
commit
2166981eef

+ 31 - 0
src/packages/swiper-item/demo.vue

@@ -0,0 +1,31 @@
+<template>
+  <div class="demo">
+    <h2>基本用法</h2>
+    <view class="demo-box">
+      <nut-swiper> </nut-swiper>
+    </view>
+    <h2>直播轮播样式</h2>
+    <view class="demo-box">
+      <nut-swiper> </nut-swiper>
+    </view>
+    <h2>商品轮播样式</h2>
+    <view class="demo-box">
+      <nut-swiper> </nut-swiper>
+    </view>
+  </div>
+</template>
+
+<script lang="ts">
+import { reactive } from 'vue';
+import { createComponent } from '@/utils/create';
+const { createDemo } = createComponent('swiper');
+export default createDemo({
+  props: {},
+  setup() {
+    const data = reactive({});
+    return {};
+  }
+});
+</script>
+
+<style lang="scss" scoped></style>

+ 0 - 0
src/packages/swiper-item/doc.md


+ 3 - 0
src/packages/swiper-item/index.scss

@@ -0,0 +1,3 @@
+.swiper-item {
+  height: 100%;
+}

+ 53 - 0
src/packages/swiper-item/index.vue

@@ -0,0 +1,53 @@
+<template>
+  <view class="swiper-item" :style="style">
+    <slot></slot>
+  </view>
+</template>
+
+<script lang="ts">
+import { computed, reactive, inject, getCurrentInstance } from 'vue';
+import { createComponent } from '@/utils/create';
+import { useExpose } from '@/packages/swiper/use-expose';
+const { create } = createComponent('swiper-item');
+export default create({
+  props: {},
+  setup(props, { slots }) {
+    const parent = inject('parent') as any;
+    parent['relation'](getCurrentInstance());
+    const state = reactive({
+      offset: 0
+    });
+    const style = computed(() => {
+      const style = {};
+      const direction = parent?.props.direction;
+      if (parent?.size.value) {
+        style[
+          direction === 'horizontal' ? 'width' : 'height'
+        ] = `${parent?.size.value}px`;
+      }
+
+      if (state.offset) {
+        style['transform'] = `translate${
+          direction === 'horizontal' ? 'X' : 'Y'
+        }(${state.offset}px)`;
+      }
+
+      return style;
+    });
+
+    const setOffset = (offset: number) => {
+      state.offset = offset;
+    };
+
+    useExpose({ setOffset });
+
+    return {
+      style
+    };
+  }
+});
+</script>
+
+<style scoped lang="scss">
+@import 'index.scss';
+</style>

+ 146 - 186
src/packages/swiper/demo.vue

@@ -3,148 +3,158 @@
     <h2>基本用法</h2>
     <view class="demo-box">
       <nut-swiper
-        class="swiper1"
-        swipeid="swiper1"
-        ref="swiper1"
-        pagination="true"
-        paginationClass="swiper1-pagination"
+        :init-page="state.page"
+        :pagination-visible="true"
+        pagination-color="#426543"
+        auto-play="3000"
       >
-        <template v-slot:swiper-con>
-          <view
-            v-for="(item, index) in swiperData1"
-            :key="index"
-            class="swiper-slide"
-          >
-            <img :src="item.image" />
-          </view>
-        </template>
+        <nut-swiper-item>
+          <img
+            src="//m.360buyimg.com/mobilecms/s700x280_jfs/t1/152158/32/19170/280589/6035429fE08208901/024578cf04ce3b47.jpg!cr_1125x445_0_171"
+            alt=""
+          />
+        </nut-swiper-item>
+        <nut-swiper-item>
+          <img
+            src="//m.360buyimg.com/mobilecms/s700x280_jfs/t1/159602/22/7999/126515/6035f371E6f95bfee/559cb1ee48c962c9.jpg"
+            alt=""
+          />
+        </nut-swiper-item>
+        <nut-swiper-item>
+          <img
+            src="//m.360buyimg.com/mobilecms/s700x280_jfs/t1/166846/13/7534/136440/60338b5cEd491d8b3/9e5527429136ab86.jpg"
+            alt=""
+          />
+        </nut-swiper-item>
+        <nut-swiper-item>
+          <img
+            src="//m.360buyimg.com/mobilecms/s700x280_jfs/t1/145433/27/14059/141680/5facf066Ec402354c/530d5a316aed55fc.jpg!cr_1125x445_0_171"
+            alt=""
+          />
+        </nut-swiper-item>
       </nut-swiper>
     </view>
-    <h2>直播轮播样式</h2>
+    <h2>自定义大小</h2>
     <view class="demo-box">
-      <nut-swiper
-        class="swiper2"
-        swipeid="swiper2"
-        ref="swiper2"
-        loop="true"
-        pagination="true"
-        paginationClass="swiper2-pagination"
-        slidesPerView="auto"
-        paginationPosiiton="outside"
-      >
-        <template v-slot:swiper-con>
-          <view
-            v-for="(item, index) in swiperData2"
-            :key="index"
-            class="swiper-slide"
-          >
-            <img :src="item.image" />
-          </view>
+      <nut-swiper :init-page="state.page2" :loop="false" width="300">
+        <nut-swiper-item>
+          <img
+            src="//m.360buyimg.com/mobilecms/s700x280_jfs/t1/152158/32/19170/280589/6035429fE08208901/024578cf04ce3b47.jpg!cr_1125x445_0_171"
+            alt=""
+          />
+        </nut-swiper-item>
+        <nut-swiper-item>
+          <img
+            src="//m.360buyimg.com/mobilecms/s700x280_jfs/t1/159602/22/7999/126515/6035f371E6f95bfee/559cb1ee48c962c9.jpg"
+            alt=""
+          />
+        </nut-swiper-item>
+        <nut-swiper-item>
+          <img
+            src="//m.360buyimg.com/mobilecms/s700x280_jfs/t1/166846/13/7534/136440/60338b5cEd491d8b3/9e5527429136ab86.jpg"
+            alt=""
+          />
+        </nut-swiper-item>
+        <nut-swiper-item>
+          <img
+            src="//m.360buyimg.com/mobilecms/s700x280_jfs/t1/145433/27/14059/141680/5facf066Ec402354c/530d5a316aed55fc.jpg!cr_1125x445_0_171"
+            alt=""
+          />
+        </nut-swiper-item>
+      </nut-swiper>
+    </view>
+    <h2>自定义指示器</h2>
+    <view class="demo-box">
+      <nut-swiper :init-page="state.page3" :loop="true" @change="change">
+        <nut-swiper-item>
+          <img
+            src="//m.360buyimg.com/mobilecms/s700x280_jfs/t1/152158/32/19170/280589/6035429fE08208901/024578cf04ce3b47.jpg!cr_1125x445_0_171"
+            alt=""
+          />
+        </nut-swiper-item>
+        <nut-swiper-item>
+          <img
+            src="//m.360buyimg.com/mobilecms/s700x280_jfs/t1/159602/22/7999/126515/6035f371E6f95bfee/559cb1ee48c962c9.jpg"
+            alt=""
+          />
+        </nut-swiper-item>
+        <nut-swiper-item>
+          <img
+            src="//m.360buyimg.com/mobilecms/s700x280_jfs/t1/166846/13/7534/136440/60338b5cEd491d8b3/9e5527429136ab86.jpg"
+            alt=""
+          />
+        </nut-swiper-item>
+        <nut-swiper-item>
+          <img
+            src="//m.360buyimg.com/mobilecms/s700x280_jfs/t1/145433/27/14059/141680/5facf066Ec402354c/530d5a316aed55fc.jpg!cr_1125x445_0_171"
+            alt=""
+          />
+        </nut-swiper-item>
+        <template v-slot:page>
+          <div class="page"> {{ state.current }}/4 </div>
         </template>
       </nut-swiper>
     </view>
-    <h2>商品轮播样式</h2>
+    <h2>垂直方向</h2>
     <view class="demo-box">
       <nut-swiper
-        class="swiper3"
-        swipeid="swiper3"
-        ref="swiper3"
-        pagination="true"
-        paginationClass="swiper3-pagination"
-        paginationType="fraction"
+        :init-page="state.page4"
+        :loop="true"
+        auto-play="3000"
+        direction="vertical"
+        height="150"
+        :pagination-visible="true"
+        style="height: 150px"
       >
-        <template v-slot:swiper-con>
-          <view
-            v-for="(item, index) in swiperData3"
-            :key="index"
-            class="swiper-slide"
-          >
-            <img :src="item.image" />
-          </view>
-        </template>
+        <nut-swiper-item>
+          <img
+            src="//m.360buyimg.com/mobilecms/s700x280_jfs/t1/152158/32/19170/280589/6035429fE08208901/024578cf04ce3b47.jpg!cr_1125x445_0_171"
+            alt=""
+          />
+        </nut-swiper-item>
+        <nut-swiper-item>
+          <img
+            src="//m.360buyimg.com/mobilecms/s700x280_jfs/t1/159602/22/7999/126515/6035f371E6f95bfee/559cb1ee48c962c9.jpg"
+            alt=""
+          />
+        </nut-swiper-item>
+        <nut-swiper-item>
+          <img
+            src="//m.360buyimg.com/mobilecms/s700x280_jfs/t1/166846/13/7534/136440/60338b5cEd491d8b3/9e5527429136ab86.jpg"
+            alt=""
+          />
+        </nut-swiper-item>
+        <nut-swiper-item>
+          <img
+            src="//m.360buyimg.com/mobilecms/s700x280_jfs/t1/145433/27/14059/141680/5facf066Ec402354c/530d5a316aed55fc.jpg!cr_1125x445_0_171"
+            alt=""
+          />
+        </nut-swiper-item>
       </nut-swiper>
     </view>
   </div>
 </template>
 
 <script lang="ts">
-import { reactive, toRefs } from 'vue';
+import { reactive } from 'vue';
 import { createComponent } from '@/utils/create';
 const { createDemo } = createComponent('swiper');
-// import mSwipe from "./components/HelloWorld";
 export default createDemo({
   props: {},
   setup() {
-    const data = reactive({
-      swiperData1: [
-        {
-          title: '第1个',
-          image:
-            'https://img10.360buyimg.com/imagetools/jfs/t1/152371/17/7059/268929/5fbcb34cE2276dc0d/bb22de1b6f838ae4.png'
-        },
-        {
-          title: '第2个',
-          image:
-            'https://img11.360buyimg.com/imagetools/jfs/t1/152491/29/7102/178474/5fbcb34cE0713a25c/4cb9bbf205a4fca0.png'
-        },
-        {
-          title: '第3个',
-          image:
-            'https://img10.360buyimg.com/imagetools/jfs/t1/152371/17/7059/268929/5fbcb34cE2276dc0d/bb22de1b6f838ae4.png'
-        },
-        {
-          title: '第4个',
-          image:
-            'https://img11.360buyimg.com/imagetools/jfs/t1/152491/29/7102/178474/5fbcb34cE0713a25c/4cb9bbf205a4fca0.png'
-        }
-      ],
-      swiperData2: [
-        {
-          image:
-            'https://img14.360buyimg.com/imagetools/jfs/t1/136318/2/17914/253972/5fbcb355E73dd171e/7afebc86dec494e4.png'
-        },
-        {
-          image:
-            'https://img14.360buyimg.com/imagetools/jfs/t1/136318/2/17914/253972/5fbcb355E73dd171e/7afebc86dec494e4.png'
-        },
-        {
-          image:
-            'https://img14.360buyimg.com/imagetools/jfs/t1/136318/2/17914/253972/5fbcb355E73dd171e/7afebc86dec494e4.png'
-        }
-      ],
-      swiperData3: [
-        {
-          image:
-            'https://img10.360buyimg.com/imagetools/jfs/t1/145583/3/15916/517992/5fbf4eebEc3f84d1d/060f933b48cec59f.png'
-        },
-        {
-          image:
-            'https://img10.360buyimg.com/imagetools/jfs/t1/145583/3/15916/517992/5fbf4eebEc3f84d1d/060f933b48cec59f.png'
-        },
-        {
-          image:
-            'https://img10.360buyimg.com/imagetools/jfs/t1/145583/3/15916/517992/5fbf4eebEc3f84d1d/060f933b48cec59f.png'
-        },
-        {
-          image:
-            'https://img10.360buyimg.com/imagetools/jfs/t1/145583/3/15916/517992/5fbf4eebEc3f84d1d/060f933b48cec59f.png'
-        },
-        {
-          image:
-            'https://img10.360buyimg.com/imagetools/jfs/t1/145583/3/15916/517992/5fbf4eebEc3f84d1d/060f933b48cec59f.png'
-        },
-        {
-          image:
-            'https://img10.360buyimg.com/imagetools/jfs/t1/145583/3/15916/517992/5fbf4eebEc3f84d1d/060f933b48cec59f.png'
-        },
-        {
-          image:
-            'https://img10.360buyimg.com/imagetools/jfs/t1/145583/3/15916/517992/5fbf4eebEc3f84d1d/060f933b48cec59f.png'
-        }
-      ]
+    const state = reactive({
+      page: 2,
+      page2: 0,
+      page3: 0,
+      page4: 0,
+      current: 1
     });
+    const change = index => {
+      state.current = index + 1;
+    };
     return {
-      ...toRefs(data)
+      state,
+      change
     };
   }
 });
@@ -152,74 +162,24 @@ export default createDemo({
 
 <style lang="scss" scoped>
 .demo-box {
-  position: relative;
-  border-radius: 7px;
-  img {
-    width: 100%;
-  }
-}
-/* demo2 */
-.swiper2 {
-  .swiper-slide {
-    width: 210px !important;
-    transform: scale(0.8);
+  .swiper-item {
+    line-height: 150px;
+    img {
+      width: 100%;
+      height: 100%;
+    }
   }
-  .swiper-slide-active,
-  .swiper-slide-duplicate-active {
-    transform: scale(1);
+  .page {
+    position: absolute;
+    bottom: 0;
+    right: 0;
+    width: 46px;
+    height: 22px;
+    background: rgba(0, 0, 0, 0.33);
+    border-radius: 22px;
+    text-align: center;
+    color: #fff;
+    font-size: 14px;
   }
 }
-/* demo3 */
-.swiper3 {
-}
-</style>
-<style>
-.swiper-container /deep/ .swiper-pagination-bullet-active {
-  background: linear-gradient(
-    90deg,
-    rgba(250, 32, 12, 1) 0%,
-    rgba(250, 32, 12, 0.65) 100%
-  );
-}
-/* demo1 */
-.swiper1 .swiper-pagination-bullet {
-  width: 8px !important;
-  height: 3px !important;
-  background: rgba(255, 255, 255, 0.51) !important;
-  border-radius: 2px !important;
-}
-.swiper1 /deep/ .swiper-pagination-bullet-active {
-  background: rgba(255, 255, 255, 1) !important;
-}
-/* demo2 */
-.swiper-pagination {
-  width: 100%;
-  left: 0;
-  position: relative;
-}
-.swiper-pagination-bullet {
-  margin: 0 4px;
-}
-.swiper2-pagination {
-  position: relative !important;
-}
-/* demo3 */
-.swiper3 .swiper-pagination {
-  display: inline-block;
-  width: auto !important;
-  left: auto !important;
-  right: 0;
-  height: 22px;
-  line-height: 22px;
-  padding: 0 15px;
-  color: #fff;
-  font-size: 12px;
-  letter-spacing: -2px;
-  background: rgba(0, 0, 0, 0.33);
-  border-radius: 11px;
-}
-.swiper-pagination-current {
-  font-size: 16px;
-  font-weight: bold;
-}
 </style>

+ 146 - 0
src/packages/swiper/doc.md

@@ -0,0 +1,146 @@
+# Swiper 轮播图 组件
+
+### 介绍
+
+常用于一组图片或卡片轮播,当内容空间不足时,可以用走马灯的形式进行收纳,进行轮播展现。
+
+### 安装
+
+```javascript
+import { createApp } from 'vue';
+import { Swiper } from '@nutui/nutui';
+
+const app = createApp();
+app.use(Swiper);
+```
+
+## 代码演示
+
+### 基础用法
+
+`auto-play` 自动轮播的时长
+`init-page` 初始索引值
+`pagination-visible` 是否显示分页指示器
+`pagination-color` 指示器颜色自定义
+
+```html
+<nut-swiper :init-page="state.page" :pagination-visible="true" pagination-color="#426543" auto-play="3000">
+  <nut-swiper-item>
+    <img src="//m.360buyimg.com/mobilecms/s700x280_jfs/t1/152158/32/19170/280589/6035429fE08208901/024578cf04ce3b47.jpg!cr_1125x445_0_171" alt="" />
+  </nut-swiper-item>
+  <nut-swiper-item>
+    <img src="//m.360buyimg.com/mobilecms/s700x280_jfs/t1/159602/22/7999/126515/6035f371E6f95bfee/559cb1ee48c962c9.jpg" alt="" />
+  </nut-swiper-item>
+  <nut-swiper-item>
+    <img src="//m.360buyimg.com/mobilecms/s700x280_jfs/t1/166846/13/7534/136440/60338b5cEd491d8b3/9e5527429136ab86.jpg" alt="" />
+  </nut-swiper-item>
+  <nut-swiper-item>
+    <img src="//m.360buyimg.com/mobilecms/s700x280_jfs/t1/145433/27/14059/141680/5facf066Ec402354c/530d5a316aed55fc.jpg!cr_1125x445_0_171" alt="" />
+  </nut-swiper-item>
+</nut-swiper>
+```
+
+### 自定义大小
+
+`width` 自定义轮播大小
+
+```html
+<nut-swiper :init-page="state.page2" :loop="false" width="300">
+  <nut-swiper-item>
+    <img src="//m.360buyimg.com/mobilecms/s700x280_jfs/t1/152158/32/19170/280589/6035429fE08208901/024578cf04ce3b47.jpg!cr_1125x445_0_171" alt="" />
+  </nut-swiper-item>
+  <nut-swiper-item>
+    <img src="//m.360buyimg.com/mobilecms/s700x280_jfs/t1/159602/22/7999/126515/6035f371E6f95bfee/559cb1ee48c962c9.jpg" alt="" />
+  </nut-swiper-item>
+  <nut-swiper-item>
+    <img src="//m.360buyimg.com/mobilecms/s700x280_jfs/t1/166846/13/7534/136440/60338b5cEd491d8b3/9e5527429136ab86.jpg" alt="" />
+  </nut-swiper-item>
+  <nut-swiper-item>
+    <img src="//m.360buyimg.com/mobilecms/s700x280_jfs/t1/145433/27/14059/141680/5facf066Ec402354c/530d5a316aed55fc.jpg!cr_1125x445_0_171" alt="" />
+  </nut-swiper-item>
+</nut-swiper>
+```
+
+### 自定义分页指示器
+
+`v-slot:page` 表示自定义指示器
+
+```html
+<nut-swiper :init-page="state.page3" :loop="true" @change="change">
+  <nut-swiper-item>
+    <img src="//m.360buyimg.com/mobilecms/s700x280_jfs/t1/152158/32/19170/280589/6035429fE08208901/024578cf04ce3b47.jpg!cr_1125x445_0_171" alt="" />
+  </nut-swiper-item>
+  <nut-swiper-item>
+    <img src="//m.360buyimg.com/mobilecms/s700x280_jfs/t1/159602/22/7999/126515/6035f371E6f95bfee/559cb1ee48c962c9.jpg" alt="" />
+  </nut-swiper-item>
+  <nut-swiper-item>
+    <img src="//m.360buyimg.com/mobilecms/s700x280_jfs/t1/166846/13/7534/136440/60338b5cEd491d8b3/9e5527429136ab86.jpg" alt="" />
+  </nut-swiper-item>
+  <nut-swiper-item>
+    <img src="//m.360buyimg.com/mobilecms/s700x280_jfs/t1/145433/27/14059/141680/5facf066Ec402354c/530d5a316aed55fc.jpg!cr_1125x445_0_171" alt="" />
+  </nut-swiper-item>
+  <template v-slot:page>
+    <div class="page"></div>
+  </template>
+</nut-swiper>
+```
+
+### 垂直方向
+
+`direction` 自定义轮播方向
+
+
+```html
+<nut-swiper :init-page="state.page4" :loop="true" auto-play="3000" direction="vertical" height="150" :pagination-visible="true" style="height: 150px">
+  <nut-swiper-item>
+    <img src="//m.360buyimg.com/mobilecms/s700x280_jfs/t1/152158/32/19170/280589/6035429fE08208901/024578cf04ce3b47.jpg!cr_1125x445_0_171" alt="" />
+  </nut-swiper-item>
+  <nut-swiper-item>
+    <img src="//m.360buyimg.com/mobilecms/s700x280_jfs/t1/159602/22/7999/126515/6035f371E6f95bfee/559cb1ee48c962c9.jpg" alt="" />
+  </nut-swiper-item>
+  <nut-swiper-item>
+    <img src="//m.360buyimg.com/mobilecms/s700x280_jfs/t1/166846/13/7534/136440/60338b5cEd491d8b3/9e5527429136ab86.jpg" alt="" />
+  </nut-swiper-item>
+  <nut-swiper-item>
+    <img src="//m.360buyimg.com/mobilecms/s700x280_jfs/t1/145433/27/14059/141680/5facf066Ec402354c/530d5a316aed55fc.jpg!cr_1125x445_0_171" alt="" />
+  </nut-swiper-item>
+</nut-swiper>
+```
+
+
+## API
+
+### Props
+
+| 参数                   | 说明                                                        | 类型           | 默认值      |
+| ---------------------- | ----------------------------------------------------------- | -------------- | ----------- |
+| width                   | 轮播卡片的宽度                                            | Number、String        | 0       |
+| height                | 轮播卡片的高度                                                    | String、Number | 0        |
+| direction               | 轮播方向                                            | String | 'horizontal'         |
+| pagination-visible          | 分页指示器是否展示                                              | Boolean         | false           |
+| pagination-color         | 分页指示器选中的颜色                                              | String  | '#fff'           |
+| loop           | 是否循环轮播                                                | Boolean        | true       |
+| duration                | 动画时长(单位是ms)                                                | Number、String        | 500        |
+| auto-play | 自动轮播时长,0表示不会自动轮播                                          | Number、String        | 0        |
+| init-page               | 初始化索引值                   | Number、String         | 0    |
+| touchable             | 是否可触摸滑动                                                      | Boolean         | true          |
+| is-preventDefault                  | 滑动过程中是否禁用默认事件                                              | Boolean  | true           |
+| is-stopPropagation               | 滑动过程中是否禁止冒泡                    | Boolean         | true    |
+
+
+
+### Events
+
+| 事件名           | 说明                   | 回调参数     |
+| ---------------- | ---------------------- | ------------ |
+| change            | 滑动之后的回调         | 当前索引值index |
+
+
+
+### 向外暴露的方法
+
+| 事件名           | 说明                   | 参数     |
+| ---------------- | ---------------------- | ------------ |
+| prev            | 切换到上一页         | - |
+| next            | 切换到下一页         | - |
+| to            | 切换到指定轮播         | index:number |

File diff suppressed because it is too large
+ 24 - 670
src/packages/swiper/index.scss


+ 412 - 106
src/packages/swiper/index.vue

@@ -1,145 +1,451 @@
 <template>
-  <view :class="swipeid" class="nut-swiper-container swiper-container">
-    <view class="swiper-wrapper">
-      <!-- 存放具体的轮播内容 -->
-      <slot name="swiper-con"></slot>
+  <view
+    ref="container"
+    class="swiper-cont"
+    @touchstart="onTouchStart"
+    @touchmove="onTouchMove"
+    @touchend="onTouchEnd"
+    @touchcancel="onTouchEnd"
+  >
+    <view
+      :class="{ 'swiper-inner': true, 'swiper-vertical': isVertical }"
+      :style="state.style"
+    >
+      <slot></slot>
+    </view>
+    <slot name="page"></slot>
+    <view class="swiper-pagination" v-if="paginationVisible && !slots.page">
+      <i
+        :style="{
+          backgroundColor: activePagination === index ? paginationColor : '#ddd'
+        }"
+        v-for="(item, index) in state.children.length"
+        v-bind:key="index"
+      />
     </view>
-    <!-- 分页器 -->
-    <div
-      v-if="pagination && paginationPosiiton === 'inside'"
-      :class="paginationClass"
-      class="swiper-pagination"
-    ></div>
   </view>
-  <div
-    v-if="pagination && paginationPosiiton === 'outside'"
-    :class="paginationClass"
-    class="swiper-pagination"
-  ></div>
 </template>
 
 <script lang="ts">
-import Swiper from 'swiper';
+import {
+  onMounted,
+  onActivated,
+  onDeactivated,
+  onBeforeUnmount,
+  provide,
+  ComponentInternalInstance,
+  ComponentPublicInstance,
+  reactive,
+  computed,
+  nextTick,
+  ref,
+  watch
+} from 'vue';
 import { createComponent } from '@/utils/create';
+import { useTouch } from './use-touch';
+import { useExpose } from './use-expose';
 const { create } = createComponent('swiper');
-// import 'swiper/dist/css/swiper.min.css';
-// import { reactive, onMounted } from 'vue';
-import { onMounted, PropType } from 'vue';
-type PaginationType = 'bullets' | 'fraction' | 'progressbar' | 'custom';
 export default create({
   props: {
-    variableClass: {
-      type: String,
-      default: ''
+    width: {
+      type: [Number, String],
+      default: 0
+    },
+    height: {
+      type: [Number, String],
+      default: 0
     },
-    swipeid: {
+    direction: {
+      type: [String],
+      default: 'horizontal' //horizontal and vertical
+    },
+    paginationVisible: {
+      type: Boolean,
+      default: false
+    },
+    paginationColor: {
       type: String,
-      default: ''
+      default: '#fff'
     },
     loop: {
       type: Boolean,
-      default: false
+      default: true
+    },
+    duration: {
+      type: [Number, String],
+      default: 500
     },
-    slidesPerView: {
+    autoPlay: {
       type: [Number, String],
-      default: 1
+      default: 0
     },
-    spaceBetween: {
+    initPage: {
       type: [Number, String],
       default: 0
     },
-    pagination: {
+    touchable: {
       type: Boolean,
       default: true
     },
-    paginationPosiiton: {
-      type: String,
-      default: 'inside'
-    },
-    paginationClass: {
-      type: String,
-      default: ''
+    isPreventDefault: {
+      type: Boolean,
+      default: true
     },
-    paginationType: {
-      type: String as PropType<PaginationType>,
-      default: 'bullets'
+    isStopPropagation: {
+      type: Boolean,
+      default: true
     }
   },
-  data() {
-    return {};
-  },
-  setup(props) {
-    // console.log('props', props);
-    // let mySwiper: any = reactive({});
-    // const { swipeid, loop: boolean, direction } = toRefs(props);
-    // mounted
-    function initSwiper() {
-      console.log('swipeid', props.swipeid);
-      // new Swiper(
-      // "." + (props.variableClass ? props.variableClass : "swiper-container"),
-      //   {
-      //     pagination: {
-      //     el: '.swiper-pagination'
-      //   },
-      //     observer: true   //当修改swiper的样式或者子元素时,swiper自动刷新
-      //   }
-      // );
-      new Swiper('.' + props.swipeid, {
-        loop: props.loop,
-        slidesPerView: props.slidesPerView as number | 'auto',
-        spaceBetween: props.spaceBetween as number,
-        //分页器
-        pagination: {
-          el: '.' + props.paginationClass,
-          type: props.paginationType
-        },
-        // 分页类型
-        // paginationType: paginationType,
-        // //自动播放
-        // autoPlay: prop.autoPlay,
-        // 用户操作swiper之后,不禁止autoplay
-        observer: true,
-        observeParents: true
+  emits: ['change'],
+
+  setup(props, { emit, slots }) {
+    const container = ref();
+    const state = reactive({
+      active: 0,
+      num: 0,
+      rect: null as DOMRect | null,
+      width: 0,
+      height: 0,
+      moving: false,
+      offset: 0,
+      touchTime: 0,
+      autoplayTimer: 0 as number | undefined,
+      children: [] as ComponentPublicInstance[],
+      style: {}
+    });
+
+    const touch = useTouch();
+
+    const isVertical = computed(() => props.direction === 'vertical');
+
+    const delTa = computed(() => {
+      return isVertical.value ? touch.state.deltaY : touch.state.deltaX;
+    });
+
+    const isCorrectDirection = computed(() => {
+      return touch.state.direction === props.direction;
+    });
+
+    const childCount = computed(() => state.children.length);
+
+    const size = computed(() => state[isVertical.value ? 'height' : 'width']);
+
+    const trackSize = computed(() => childCount.value * size.value);
+
+    const minOffset = computed(() => {
+      if (state.rect) {
+        const base = isVertical.value ? state.rect.height : state.rect.width;
+        return base - size.value * childCount.value;
+      }
+      return 0;
+    });
+
+    const activePagination = computed(
+      () => (state.active + childCount.value) % childCount.value
+    );
+
+    const getStyle = () => {
+      state.style = {
+        transitionDuration: `${state.moving ? 0 : props.duration}ms`,
+        transform: `translate${isVertical.value ? 'Y' : 'X'}(${
+          state.offset
+        }px)`,
+        [isVertical.value ? 'height' : 'width']: `${size.value *
+          childCount.value}px`,
+        [isVertical.value ? 'width' : 'height']: `${
+          isVertical.value ? state.width : state.height
+        }px`
+      };
+    };
+
+    const relation = (child: ComponentInternalInstance) => {
+      if (child.proxy) {
+        state.children.push(child.proxy);
+      }
+    };
+
+    const range = (num: number, min: number, max: number) => {
+      return Math.min(Math.max(num, min), max);
+    };
+
+    const requestFrame = fn => {
+      window.requestAnimationFrame.call(window, fn);
+    };
+
+    const getOffset = (active, offset = 0) => {
+      let currentPosition = active * size.value;
+      if (!props.loop) {
+        currentPosition = Math.min(currentPosition, -minOffset.value);
+      }
+
+      let targetOffset = offset - currentPosition;
+      if (!props.loop) {
+        targetOffset = range(targetOffset, minOffset.value, 0);
+      }
+
+      return targetOffset;
+    };
+
+    const getActive = pace => {
+      const { active } = state;
+      if (pace) {
+        if (props.loop) {
+          return range(active + pace, -1, childCount.value);
+        }
+        return range(active + pace, 0, childCount.value - 1);
+      }
+      return active;
+    };
+
+    const move = ({ pace = 0, offset = 0, isEmit = false }) => {
+      if (childCount.value <= 1) return;
+
+      const { active } = state;
+
+      const targetActive = getActive(pace);
+      const targetOffset = getOffset(targetActive, offset);
+
+      if (props.loop) {
+        if (state.children[0] && targetOffset !== minOffset.value) {
+          const rightBound = targetOffset < minOffset.value;
+          (state.children[0] as any).setOffset(
+            rightBound ? trackSize.value : 0
+          );
+        }
+        if (state.children[childCount.value - 1] && targetOffset !== 0) {
+          const leftBound = targetOffset > 0;
+          (state.children[childCount.value - 1] as any).setOffset(
+            leftBound ? -trackSize.value : 0
+          );
+        }
+      }
+
+      state.active = targetActive;
+      state.offset = targetOffset;
+
+      if (isEmit && active !== state.active) {
+        emit('change', activePagination.value);
+      }
+
+      getStyle();
+    };
+
+    const resettPosition = () => {
+      state.moving = true;
+
+      if (state.active <= -1) {
+        move({ pace: childCount.value });
+      }
+      if (state.active >= childCount.value) {
+        move({ pace: -childCount.value });
+      }
+    };
+
+    const stopAutoPlay = () => {
+      clearTimeout(state.autoplayTimer);
+    };
+
+    const prev = () => {
+      resettPosition();
+      touch.reset();
+
+      requestFrame(() => {
+        requestFrame(() => {
+          state.moving = false;
+          move({
+            pace: -1,
+            isEmit: true
+          });
+        });
       });
-    }
+    };
+
+    const next = () => {
+      resettPosition();
+      touch.reset();
+
+      requestFrame(() => {
+        requestFrame(() => {
+          state.moving = false;
+          move({
+            pace: 1,
+            isEmit: true
+          });
+        });
+      });
+    };
+
+    const to = (index: number) => {
+      resettPosition();
+
+      touch.reset();
+
+      requestFrame(() => {
+        requestFrame(() => {
+          state.moving = false;
+          let targetIndex;
+          if (props.loop && childCount.value === index) {
+            targetIndex = state.active === 0 ? 0 : index;
+          } else {
+            targetIndex = index % childCount.value;
+          }
+          move({
+            pace: targetIndex - state.active,
+            isEmit: true
+          });
+        });
+      });
+    };
+
+    const autoplay = () => {
+      if (props.autoPlay <= 0 || childCount.value <= 1) return;
+      stopAutoPlay();
+
+      state.autoplayTimer = setTimeout(() => {
+        next();
+        autoplay();
+      }, Number(props.autoPlay));
+    };
+
+    const init = (active: number = +props.initPage) => {
+      stopAutoPlay();
+      state.rect = container.value.getBoundingClientRect();
+      active = Math.min(childCount.value - 1, active);
+      state.width = props.width ? +props.width : (state.rect as DOMRect).width;
+      state.height = props.height
+        ? +props.height
+        : (state.rect as DOMRect).height;
+      state.active = active;
+      state.offset = getOffset(state.active);
+      state.moving = true;
+      getStyle();
+
+      autoplay();
+    };
+
+    const onTouchStart = (e: TouchEvent) => {
+      if (props.isPreventDefault) e.preventDefault();
+      if (props.isStopPropagation) e.stopPropagation();
+      if (!props.touchable) return;
+      touch.start(e);
+      state.touchTime = Date.now();
+      stopAutoPlay();
+      resettPosition();
+    };
+
+    const onTouchMove = (e: TouchEvent) => {
+      if (props.touchable && state.moving) {
+        touch.move(e);
+        if (isCorrectDirection.value) {
+          move({
+            offset: delTa.value
+          });
+        }
+      }
+    };
+
+    const onTouchEnd = (e: TouchEvent) => {
+      if (!props.touchable || !state.moving) return;
+      const speed = delTa.value / (Date.now() - state.touchTime);
+      const isShouldMove =
+        Math.abs(speed) > 0.3 ||
+        Math.abs(delTa.value) > +(size.value / 2).toFixed(2);
+
+      if (isShouldMove && isCorrectDirection.value) {
+        let pace = 0;
+        const offset = isVertical.value
+          ? touch.state.offsetY
+          : touch.state.offsetX;
+        if (props.loop) {
+          pace = offset > 0 ? (delTa.value > 0 ? -1 : 1) : 0;
+        } else {
+          pace = -Math[delTa.value > 0 ? 'ceil' : 'floor'](
+            delTa.value / size.value
+          );
+        }
+        move({
+          pace,
+          isEmit: true
+        });
+      } else if (delTa.value) {
+        move({ pace: 0 });
+      }
+      state.moving = false;
+      getStyle();
+      autoplay();
+    };
+
+    provide('parent', {
+      props,
+      size,
+      relation
+    });
+
+    useExpose({
+      prev,
+      next,
+      to
+    });
+
     onMounted(() => {
-      initSwiper();
-      // new Swiper("."+ props.swipeid ,this.variableData);
-      // new Swiper(
-      // "." + (props.variableClass ? props.variableClass : "swiper-container"),
-      //   {
-      //     pagination: {
-      //       el: '.swiper-pagination'
-      //     },
-      //     observer: true   //当修改swiper的样式或者子元素时,swiper自动刷新
-      //   }
-      // );
+      nextTick(() => {
+        init();
+      });
+    });
+
+    onActivated(() => {
+      nextTick(() => {
+        init();
+      });
     });
+
+    onDeactivated(() => {
+      stopAutoPlay();
+    });
+
+    onBeforeUnmount(() => {
+      stopAutoPlay();
+    });
+
+    watch(
+      () => props.initPage,
+      val => {
+        nextTick(() => {
+          init(Number(val));
+        });
+      }
+    );
+
+    watch(
+      () => state.children.length,
+      () => {
+        nextTick(() => {
+          init(state.active);
+        });
+      }
+    );
+
+    watch(
+      () => props.autoPlay,
+      val => {
+        val > 0 ? autoplay() : stopAutoPlay();
+      }
+    );
+
     return {
-      // mySwiper
-      // swiper
+      state,
+      container,
+      isVertical,
+      slots,
+      activePagination,
+      onTouchStart,
+      onTouchMove,
+      onTouchEnd
     };
   }
 });
 </script>
 
-<!-- Add "scoped" attribute to limit CSS to this component only -->
 <style scoped lang="scss">
 @import 'index.scss';
 </style>
-<style>
-.swiper-pagination .swiper-pagination-bullet {
-  width: 4px;
-  height: 4px;
-  opacity: 1;
-  background: linear-gradient(
-    90deg,
-    rgba(250, 32, 12, 1) 0%,
-    rgba(250, 32, 12, 0.65) 100%
-  );
-}
-.swiper-pagination /deep/ .swiper-pagination-bullet-active {
-  width: 10px;
-  border-radius: 3px;
-}
-</style>

+ 8 - 0
src/packages/swiper/use-expose.ts

@@ -0,0 +1,8 @@
+import { getCurrentInstance } from 'vue';
+
+export function useExpose(apis: Record<string, any>) {
+  const instance = getCurrentInstance();
+  if (instance) {
+    Object.assign(instance.proxy, apis);
+  }
+}

+ 55 - 0
src/packages/swiper/use-touch.ts

@@ -0,0 +1,55 @@
+import { reactive } from 'vue';
+
+const DISTANCE = 5;
+
+type Direction = 'horizontal' | 'vertical' | '';
+
+export function useTouch() {
+  const state = reactive({
+    startX: 0,
+    startY: 0,
+    deltaX: 0,
+    deltaY: 0,
+    offsetX: 0,
+    offsetY: 0,
+    direction: '' as Direction
+  });
+  const getDirection = (x: number, y: number) => {
+    if (x > y && x > DISTANCE) return 'horizontal';
+    if (y > x && y > DISTANCE) return 'vertical';
+    return '';
+  };
+  const reset = () => {
+    state.startX = 0;
+    state.startY = 0;
+    state.deltaX = 0;
+    state.deltaY = 0;
+    state.offsetX = 0;
+    state.offsetY = 0;
+    state.direction = '';
+  };
+
+  const start = ((e: TouchEvent) => {
+    reset();
+    state.startX = e.touches[0].clientX;
+    state.startY = e.touches[0].clientY;
+  }) as EventListener;
+
+  const move = ((e: TouchEvent) => {
+    state.deltaX = e.touches[0].clientX - state.startX;
+    state.deltaY = e.touches[0].clientY - state.startY;
+    state.offsetX = Math.abs(state.deltaX);
+    state.offsetY = Math.abs(state.deltaY);
+
+    if (!state.direction) {
+      state.direction = getDirection(state.offsetX, state.offsetY);
+    }
+  }) as EventListener;
+
+  return {
+    state,
+    start,
+    reset,
+    move
+  };
+}