Browse Source

refactor: backtop

suzigang 4 years ago
parent
commit
98e7468de3

+ 13 - 4
src/packages/backtop/demo.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="demo" id="elId" ref="scroll">
+  <div class="demo" id="elId">
     <div class="text-data">我是测试数据1</div>
     <div class="text-data">我是测试数据2</div>
     <div class="text-data">我是测试数据3</div>
@@ -24,10 +24,19 @@
     <div class="text-data">我是测试数据22</div>
     <div class="text-data">我是测试数据23</div>
     <div class="text-data">我是测试数据24</div>
-    <nut-backtop @click="handleClick" elId="elId" :distance="100" :bottom="90"
-      ><view>无</view></nut-backtop
+    <nut-backtop
+      @click="handleClick"
+      :el-id="elId"
+      :distance="100"
+      :bottom="90"
     >
-    <nut-backtop @click="handleClick" elId="elId" :distance="200"></nut-backtop>
+      <view>无</view>
+    </nut-backtop>
+    <nut-backtop
+      @click="handleClick"
+      :el-id="elId"
+      :distance="200"
+    ></nut-backtop>
   </div>
 </template>
 

+ 12 - 15
src/packages/backtop/doc.md

@@ -21,7 +21,7 @@ app.use(BackTop);
 ### 基本用法
 
 ```html
-<nut-backtop  elId="elId" ></nut-backtop>
+<nut-backtop  :el-id="elId" ></nut-backtop>
 ```
 
 ### 设置出现位置
@@ -33,14 +33,9 @@ app.use(BackTop);
 ### 自定义样式
 
 ```html
-<nut-backtop @click="handleClick" elId="elId" :distance="100" :bottom="90" ><div>无</div></nut-backtop>
+<nut-backtop @click="handleClick" :el-id="elId" :distance="100" :bottom="90" ><div>无</div></nut-backtop>
 ```
 
-### 设置样式
-<nut-backtop>
-</nut-backtop>
-
-
 ### click事件
 
 ```html
@@ -68,15 +63,17 @@ export default createDemo({
 
 ### Prop  
 
-| 字段            | 说明                                                                                           | 类型    | 默认值  |
-|-----------------|------------------------------------------------------------------------------------------------|---------|---------|
-| elId           | 获取监听元素的父级元素                                                                           | String  | -       |
-| bottom         | 距离页面底部距离                                                                                | Number  | -       |
-| right        | 距离页面右侧距离                                                                                  | Number |    |
-| distance     | 页面垂直滚动多高后出现                                                            | Number  | -       |
-| zIndex         | 设置组件页面层级                                                         | Number  | -       |                                          
+| 字段            | 说明                 | 类型    | 默认值  |
+|-----------------|------------------------------------------|---------|---------|
+| el-id           | 获取监听元素的父级元素         | String | -       |
+| bottom         | 距离页面底部距离    | Number  | `20`       |
+| right        | 距离页面右侧距离      | Number |  `10`  |
+| distance     | 页面垂直滚动多高后出现   | Number  | `200`      |
+| z-index         | 设置组件页面层级   | Number  | `10`       |  
+| is-animation         | 是否有动画,和duration参数互斥   | Boolean  | `true`       |  
+| duration         | 设置动画持续时间   | Number  | `1000`       |                                          
 
 ### Event
 | 名称  | 说明     | 回调参数    |
 |-------|----------|-------------|
-| click | 按钮点击时触发事件 | event:Event |
+| click | 按钮点击时触发事件 | event: MouseEvent |

+ 2 - 6
src/packages/backtop/index.scss

@@ -1,15 +1,11 @@
 .nut-backtop {
   display: none;
   position: fixed;
-  bottom: 20px;
-  right: 20px;
-  z-index: 111;
   &.show {
-    display: block;
     width: 40px;
     height: 40px;
-    background: rgba(255, 255, 255, 1);
-    border: 1px solid rgba(224, 224, 224, 1);
+    background: $white;
+    border: 1px solid $backtop-border-color;
     border-radius: 50%;
     display: flex;
     align-items: center;

+ 108 - 69
src/packages/backtop/index.vue

@@ -1,13 +1,5 @@
 <template>
-  <div
-    :class="['nut-backtop', { show: backTop }]"
-    :style="{
-      right: styleRight,
-      bottom: styleBottom,
-      'z-index': zIndex
-    }"
-    @click="click"
-  >
+  <div :class="classes" :style="style" @click.stop="click">
     <slot>
       <nut-icon size="19px" class="nut-backtop-main" name="top"></nut-icon>
     </slot>
@@ -15,121 +7,168 @@
 </template>
 
 <script lang="ts">
-import { computed, ref, onMounted, onUnmounted } from 'vue';
+import {
+  computed,
+  onMounted,
+  onUnmounted,
+  onActivated,
+  onDeactivated,
+  reactive
+} from 'vue';
 import { createComponent } from '@/utils/create';
-const { create } = createComponent('backtop');
+const { componentName, create } = createComponent('backtop');
 export default create({
   props: {
-    //距离页面底部
     bottom: {
       type: Number,
       default: 20
     },
-    //距离页面右侧
     right: {
       type: Number,
       default: 10
     },
-    ///获取容器ID
     elId: {
       type: String,
       default: ''
     },
-    //页面垂直滚动多高后出现
     distance: {
       type: Number,
       default: 200
     },
-    //设置层级
     zIndex: {
       type: Number,
-      default: 1111
+      default: 10
+    },
+    isAnimation: {
+      type: Boolean,
+      default: true
+    },
+    duration: {
+      type: Number,
+      default: 1000
     }
   },
   emits: ['click'],
   setup(props, { emit }) {
-    const styleBottom = computed(() => `${props.bottom}px`);
-    const styleRight = computed(() => `${props.right}px`);
-    // const styleDistance = computed(() => `${props.distance}px`);
-    const zIndex = computed(() => `${props.zIndex}px`);
-
-    //默认图标不出现
-    const backTop = ref(false);
-
+    const state = reactive({
+      backTop: false,
+      scrollTop: 0,
+      scrollEl: window as HTMLElement | Window,
+      startTime: 0,
+      keepAlive: false
+    });
     let scrollEl: Window | HTMLElement = window;
-    const elId = ref(props.elId);
+    const classes = computed(() => {
+      const prefixCls = componentName;
+      return {
+        [prefixCls]: true,
+        show: state.backTop
+      };
+    });
+    const style = computed(() => {
+      return {
+        right: `${props.bottom}px`,
+        bottom: `${props.right}px`,
+        zIndex: props.zIndex
+      };
+    });
 
     function scrollListener() {
-      //滚动条偏移量
-      //Window
-      if (scrollEl instanceof Window) {
-        const scrollTop =
-          scrollEl.pageYOffset !== undefined ? scrollEl.pageYOffset : '';
-        backTop.value = scrollTop >= props.distance;
-        //DOM
-      } else if (scrollEl instanceof HTMLElement) {
-        const scrollTop = scrollEl.scrollTop;
-        backTop.value = scrollTop >= props.distance;
+      if (state.scrollEl instanceof Window) {
+        state.scrollTop = state.scrollEl.pageYOffset;
+      } else {
+        state.scrollTop = state.scrollEl.scrollTop;
       }
+      state.backTop = state.scrollTop >= props.distance;
     }
 
-    //点击按钮返回顶部
     function scroll(y = 0) {
-      if (scrollEl instanceof Window) {
+      if (state.scrollEl instanceof Window) {
         window.scrollTo(0, y);
-      } else if (scrollEl instanceof HTMLElement) {
-        scrollEl.scrollTop = y;
+      } else {
+        state.scrollEl.scrollTop = y;
       }
     }
 
-    //监听页面滚动事件
+    function scrollAnimation() {
+      let cid = requestAniFrame()(function fn() {
+        var t =
+          props.duration -
+          Math.max(0, state.startTime - +new Date() + props.duration);
+        var y = (t * -state.scrollTop) / props.duration + state.scrollTop;
+        scroll(y);
+        cid = requestAniFrame()(fn);
+        if (t == props.duration || y == 0) {
+          window.cancelAnimationFrame(cid);
+        }
+      });
+    }
+
     function addEventListener() {
-      scrollEl.addEventListener('scroll', scrollListener, true);
+      state.scrollEl.addEventListener('scroll', scrollListener, false);
+      state.scrollEl.addEventListener('resize', scrollListener, false);
     }
 
-    //解绑监听页面滚动事件
     function removeEventListener() {
-      scrollEl.removeEventListener('scroll', scrollListener, true);
+      state.scrollEl.removeEventListener('scroll', scrollListener, false);
+      state.scrollEl.removeEventListener('resize', scrollListener, false);
     }
-    // function deactivated() {
-    //   keepAlive.value = true;
-    //   removeEventListener();
-    // }
 
-    onUnmounted(() => {
-      removeEventListener();
-    });
+    function initCancelAniFrame() {
+      window.cancelAnimationFrame = window.webkitCancelAnimationFrame;
+    }
 
-    //点击回到顶部
-    function click() {
-      scroll();
-      emit('click');
+    function requestAniFrame() {
+      return (
+        window.requestAnimationFrame ||
+        window.webkitRequestAnimationFrame ||
+        function(callback) {
+          window.setTimeout(callback, 1000 / 60);
+        }
+      );
     }
 
-    function init() {
-      //重新获取容器id
-      const _scrollEl = document.getElementById(elId.value);
+    function click(e: MouseEvent) {
+      state.startTime = +new Date();
+      props.isAnimation && props.duration > 0 ? scrollAnimation() : scroll();
+      emit('click', e);
+    }
 
-      if (elId.value && _scrollEl) {
-        scrollEl = _scrollEl;
-        _scrollEl.style.scrollBehavior = 'smooth';
+    function init() {
+      if (props.elId && document.getElementById(props.elId)) {
+        state.scrollEl = document.getElementById(props.elId) as
+          | HTMLElement
+          | Window;
       }
-
       addEventListener();
-      scrollListener();
+      initCancelAniFrame();
     }
 
     onMounted(() => {
       init();
     });
 
+    onUnmounted(() => {
+      removeEventListener();
+    });
+
+    onActivated(() => {
+      if (state.keepAlive) {
+        state.keepAlive = false;
+        init();
+      }
+    });
+
+    onDeactivated(() => {
+      state.keepAlive = true;
+      removeEventListener();
+    });
+
     return {
-      backTop,
-      scrollEl,
-      click,
-      styleBottom,
-      styleRight,
-      zIndex
+      state,
+      classes,
+      style,
+      click
     };
   }
 });

+ 3 - 0
src/styles/variables.scss

@@ -145,6 +145,9 @@ $avatar-normal-height: 40px;
 $switch-close-bg-color: #ebebeb;
 $switch-close--cline-bg-color: #f0f0f0;
 
+//backtop
+$backtop-border-color: #e0e0e0;
+
 // calendar
 $calendar-primary-color: $primary-color;
 $calendar-choose-color: #fef6f6;