{"action":"opened","issue":{"url":"https://api.github.com/repos/sl1673495/blogs/issues/10","repository_url":"https://api.github.com/repos/sl1673495/blogs","labels_url":"https://api.github.com/repos/sl1673495/blogs/issues/10/labels{/name}","comments_url":"https://api.github.com/repos/sl1673495/blogs/issues/10/comments","events_url":"https://api.github.com/repos/sl1673495/blogs/issues/10/events","html_url":"https://github.com/sl1673495/blogs/issues/10","id":381488634,"node_id":"MDU6SXNzdWUzODE0ODg2MzQ=","number":10,"title":"cube-ui源码学习 swipe组件","user":{"login":"sl1673495","id":23615778,"node_id":"MDQ6VXNlcjIzNjE1Nzc4","avatar_url":"https://avatars1.githubusercontent.com/u/23615778?v=4","gravatar_id":"","url":"https://api.github.com/users/sl1673495","html_url":"https://github.com/sl1673495","followers_url":"https://api.github.com/users/sl1673495/followers","following_url":"https://api.github.com/users/sl1673495/following{/other_user}","gists_url":"https://api.github.com/users/sl1673495/gists{/gist_id}","starred_url":"https://api.github.com/users/sl1673495/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sl1673495/subscriptions","organizations_url":"https://api.github.com/users/sl1673495/orgs","repos_url":"https://api.github.com/users/sl1673495/repos","events_url":"https://api.github.com/users/sl1673495/events{/privacy}","received_events_url":"https://api.github.com/users/sl1673495/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":0,"created_at":"2018-11-16T08:08:18Z","updated_at":"2018-11-16T08:08:18Z","closed_at":null,"author_association":"OWNER","body":"[swipe组件预览地址（手机模式可体验）](https://didi.github.io/cube-ui/#/zh-CN/docs/swipe)\r\n[作者：黄轶老师](https://github.com/ustbhuangyi)\r\n\r\n先吹一波黄老，昨天体验swipe组件的时候感受到了什么叫丝滑，这可以说是东半球移动端最好用的swipe组件了吧。\r\n\r\n先来一段文档中的用法的简化版：\r\n```\r\n<cube-swipe>\r\n    <li class=\"swipe-item-wrapper\" v-for=\"(data,index) in swipeData\" :key=\"data.item.id\">\r\n      <cube-swipe-item\r\n          ref=\"swipeItem\"\r\n          :btns=\"data.btns\"\r\n          :index=\"index\"\r\n          @btn-click=\"onBtnClick\">\r\n           <div></div>\r\n      </cube-swipe-item>\r\n   </li>      \r\n </cube-swipe>\r\n```\r\n在cube-ui的项目的src/components/swipe目录下，我们可以看到swipe组件被分为swipe.vue和swipe-item.vue。\r\n其实swipe就是列表的外层容器组件，负责处理一些全列表的事件。\r\nswipe-item就是列表中循环出来的某一项元素的组件，负责处理手势等细节。\r\n我们先从swipe.vue入手：\r\n\r\n### swipe.vue\r\n```\r\n<template>\r\n  <div class=\"cube-swipe\">\r\n    <slot>\r\n      <transition-group name=\"cube-swipe\" tag=\"ul\">\r\n        <li v-for=\"(item, index) in data\" :key=\"item.item.value\">\r\n          <cube-swipe-item\r\n            :btns=\"item.btns\"\r\n            :item=\"item.item\"\r\n            :index=\"index\"\r\n            :auto-shrink=\"autoShrink\" />\r\n        </li>\r\n      </transition-group>\r\n    </slot>\r\n  </div>\r\n</template>\r\n```\r\n我们先从template部分入手， 可以看到结构非常简单，就是一个div中给了一个slot子元素，并且slot有个默认值，\r\n如果用户不传slot的话就默认的带transition-group动效循环出一段cube-swipe-item列表，不使用slot的情况下用户可以传入\r\n```\r\nswipeData: [{\r\n        item: {\r\n          text: '测试1',\r\n          value: 1\r\n        },\r\n        btns: [\r\n          {\r\n            action: 'clear',\r\n            text: '不再关注',\r\n            color: '#c8c7cd'\r\n          },\r\n          {\r\n            action: 'delete',\r\n            text: '删除',\r\n            color: '#ff3a32'\r\n          }\r\n        ]\r\n      }, {\r\n        item: {\r\n          text: '测试2',\r\n          value: 2\r\n        },\r\n        btns: [\r\n          {\r\n            action: 'clear',\r\n            text: '不再关注',\r\n            color: '#c8c7cd'\r\n          },\r\n          {\r\n            action: 'delete',\r\n            text: '删除',\r\n            color: '#ff3a32'\r\n          }\r\n        ]\r\n      }, {\r\n        item: {\r\n          text: '测试3',\r\n          value: 3\r\n        },\r\n        btns: [\r\n          {\r\n            action: 'clear',\r\n            text: '不再关注',\r\n            color: '#c8c7cd'\r\n          },\r\n          {\r\n            action: 'delete',\r\n            text: '删除',\r\n            color: '#ff3a32'\r\n          }\r\n        ]\r\n      }]\r\n```\r\n这样一段大而全的json数组，渲染出一个列表，不过这种方式比较不灵活。\r\n\r\n```\r\n<script type=\"text/ecmascript-6\">\r\n  import CubeSwipeItem from './swipe-item.vue'\r\n  const COMPONENT_NAME = 'cube-swipe'\r\n  const EVENT_ITEM_CLICK = 'item-click'\r\n  const EVENT_BTN_CLICK = 'btn-click'\r\n  export default {\r\n    name: COMPONENT_NAME,\r\n    provide() {\r\n      return {\r\n        swipe: this\r\n      }\r\n    },\r\n    props: {\r\n      data: {\r\n        type: Array,\r\n        default() {\r\n          return []\r\n        }\r\n      },\r\n      autoShrink: {\r\n        type: Boolean,\r\n        default: false\r\n      }\r\n    },\r\n    created() {\r\n      this.activeIndex = -1\r\n      this.items = []\r\n    },\r\n    methods: {\r\n      addItem(item) {\r\n        this.items.push(item)\r\n      },\r\n      removeItem(item) {\r\n        const index = this.items.indexOf(item)\r\n        this.items.splice(index, 1)\r\n        if (index <= this.activeIndex) {\r\n          this.activeIndex -= 1\r\n        }\r\n      },\r\n      onItemClick(item, index) {\r\n        this.$emit(EVENT_ITEM_CLICK, item, index)\r\n      },\r\n      onBtnClick(btn, index) {\r\n        const item = this.data[index]\r\n        this.$emit(EVENT_BTN_CLICK, btn, index, item)\r\n      },\r\n      onItemActive(index) {\r\n        if (index === this.activeIndex) {\r\n          return\r\n        }\r\n        if (this.activeIndex !== -1) {\r\n          const activeItem = this.items[this.activeIndex]\r\n          activeItem.shrink()\r\n        }\r\n        this.activeIndex = index\r\n      }\r\n    },\r\n    components: {\r\n      CubeSwipeItem\r\n    }\r\n  }\r\n</script>\r\n```\r\n\r\nscript的data和methods里提供了很多东西，但是在template里却没有使用到，那么我们猜测这些都是提供给子组件使用的，\r\nprovider里把自身实例提供给了子组件\r\n```\r\n provide() {\r\n      return {\r\n        swipe: this\r\n      }\r\n    },\r\n```\r\n那么我们接下来就去探究swipe-item组件。\r\n\r\n### swipe-item\r\n```\r\n<template>\r\n  <div ref=\"swipeItem\"\r\n       @transitionend=\"onTransitionEnd\"\r\n       @touchstart=\"onTouchStart\"\r\n       @touchmove=\"onTouchMove\"\r\n       @touchend=\"onTouchEnd\"\r\n       class=\"cube-swipe-item\">\r\n    <slot>\r\n      <div @click=\"clickItem\" class=\"cube-swipe-item-inner border-bottom-1px\">\r\n        <span>{{item.text}}</span>\r\n      </div>\r\n    </slot>\r\n    <ul class=\"cube-swipe-btns\">\r\n      <li ref=\"btns\"\r\n          v-for=\"btn in btns\"\r\n          class=\"cube-swipe-btn\"\r\n          :style=\"genBtnStyl(btn)\"\r\n          @click.prevent=\"clickBtn(btn)\">\r\n        <span class=\"text\">{{btn.text}}</span>\r\n      </li>\r\n    </ul>\r\n  </div>\r\n</template>\r\n\r\n<style lang=\"stylus\" rel=\"stylesheet/stylus\">\r\n  @require \"../../common/stylus/variable.styl\"\r\n  .cube-swipe-item\r\n    position: relative\r\n  .cube-swipe-item-inner\r\n    height: 60px\r\n    line-height: 60px\r\n    font-size: $fontsize-large\r\n    padding-left: 20px\r\n  .cube-swipe-btn\r\n    display: flex\r\n    align-items: center\r\n    position: absolute\r\n    top: 0\r\n    left: 100%\r\n    height: 100%\r\n    text-align: left\r\n    font-size: $fontsize-large\r\n    .text\r\n      flex: 1\r\n      padding: 0 20px\r\n      white-space: nowrap\r\n      color: $swipe-btn-color\r\n</style>\r\n```\r\n可以看到swipe-item的结构也非常简单， 也提供了slot插槽定制子组件的元素\r\n并且在子组件的旁边有个初始隐藏的ul结构 用来循环btns来生成侧滑出来的按钮\r\n.cube-swipe-btn这个类是绝对定位并且left 100% 也就是相对于父relative容器\r\n  .cube-swipe-item的宽度偏移 正好隐藏到边缘外。\r\n\r\n接下来我们看一下script部分\r\n```\r\n<script type=\"text/ecmascript-6\">\r\n  import {\r\n    getRect,\r\n    prefixStyle\r\n  } from '../../common/helpers/dom'\r\n  import { easeOutQuart, easeOutCubic } from '../../common/helpers/ease'\r\n  import { getNow } from '../../common/lang/date'\r\n  const COMPONENT_NAME = 'cube-swipe-item'\r\n  const EVENT_ITEM_CLICK = 'item-click'\r\n  const EVENT_BTN_CLICK = 'btn-click'\r\n  const EVENT_SCROLL = 'scroll'\r\n  const EVENT_ACTIVE = 'active'\r\n  const DIRECTION_LEFT = 1\r\n  const DIRECTION_RIGHT = -1\r\n  const STATE_SHRINK = 0\r\n  const STATE_GROW = 1\r\n  const easingTime = 600\r\n  const momentumLimitTime = 300\r\n  const momentumLimitDistance = 15\r\n  const directionLockThreshold = 5\r\n  const transform = prefixStyle('transform')\r\n  const transitionProperty = prefixStyle('transitionProperty')\r\n  const transitionDuration = prefixStyle('transitionDuration')\r\n  const transitionTimingFunction = prefixStyle('transitionTimingFunction')\r\n  export default {\r\n    name: COMPONENT_NAME,\r\n    inject: ['swipe'],\r\n    props: {\r\n      item: {\r\n        type: Object,\r\n        default() {\r\n          return {}\r\n        }\r\n      },\r\n      btns: {\r\n        type: Array,\r\n        default() {\r\n          return []\r\n        }\r\n      },\r\n      index: {\r\n        type: Number,\r\n        index: -1\r\n      },\r\n      autoShrink: {\r\n        type: Boolean,\r\n        default: false\r\n      }\r\n    },\r\n    watch: {\r\n      btns() {\r\n        this.$nextTick(() => {\r\n          this.refresh()\r\n        })\r\n      }\r\n    },\r\n    created() {\r\n      this.x = 0\r\n      this.state = STATE_SHRINK\r\n      this.swipe.addItem(this)\r\n    },\r\n    mounted() {\r\n      this.scrollerStyle = this.$refs.swipeItem.style\r\n      this.$nextTick(() => {\r\n        this.refresh()\r\n      })\r\n      this.$on(EVENT_SCROLL, this._handleBtns)\r\n    },\r\n    methods: {\r\n      _initCachedBtns() {\r\n        this.cachedBtns = []\r\n        const len = this.$refs.btns.length\r\n        for (let i = 0; i < len; i++) {\r\n          this.cachedBtns.push({\r\n            width: getRect(this.$refs.btns[i]).width\r\n          })\r\n        }\r\n      },\r\n      _handleBtns(x) {\r\n        /* istanbul ignore if */\r\n        if (this.btns.length === 0) {\r\n          return\r\n        }\r\n        const len = this.$refs.btns.length\r\n        let delta = 0\r\n        let totalWidth = -this.maxScrollX\r\n        for (let i = 0; i < len; i++) {\r\n          const btn = this.$refs.btns[i]\r\n          let rate = (totalWidth - delta) / totalWidth\r\n          let width\r\n          let translate = rate * x - x\r\n          if (x < this.maxScrollX) {\r\n            width = this.cachedBtns[i].width + rate * (this.maxScrollX - x)\r\n          } else {\r\n            width = this.cachedBtns[i].width\r\n          }\r\n          delta += this.cachedBtns[i].width\r\n          btn.style.width = `${width}px`\r\n          btn.style[transform] = `translate(${translate}px)`\r\n          btn.style[transitionDuration] = '0ms'\r\n        }\r\n      },\r\n      _isInBtns(target) {\r\n        let parent = target\r\n        let flag = false\r\n        while (parent && parent.className.indexOf('cube-swipe-item') < 0) {\r\n          if (parent.className.indexOf('cube-swipe-btns') >= 0) {\r\n            flag = true\r\n            break\r\n          }\r\n          parent = parent.parentNode\r\n        }\r\n        return flag\r\n      },\r\n      _calculateBtnsWidth() {\r\n        let width = 0\r\n        const len = this.cachedBtns.length\r\n        for (let i = 0; i < len; i++) {\r\n          width += this.cachedBtns[i].width\r\n        }\r\n        this.maxScrollX = -width\r\n      },\r\n      _translate(x, useZ) {\r\n        let translateZ = useZ ? ' translateZ(0)' : ''\r\n        this.scrollerStyle[transform] = `translate(${x}px,0)${translateZ}`\r\n        this.x = x\r\n      },\r\n      _transitionProperty(property = 'transform') {\r\n        this.scrollerStyle[transitionProperty] = property\r\n      },\r\n      _transitionTimingFunction(easing) {\r\n        this.scrollerStyle[transitionTimingFunction] = easing\r\n      },\r\n      _transitionTime(time = 0) {\r\n        this.scrollerStyle[transitionDuration] = `${time}ms`\r\n      },\r\n      _getComputedPositionX() {\r\n        let matrix = window.getComputedStyle(this.$refs.swipeItem, null)\r\n        matrix = matrix[transform].split(')')[0].split(', ')\r\n        let x = +(matrix[12] || matrix[4])\r\n        return x\r\n      },\r\n      _translateBtns(time, easing, extend) {\r\n        /* istanbul ignore if */\r\n        if (this.btns.length === 0) {\r\n          return\r\n        }\r\n        const len = this.$refs.btns.length\r\n        let delta = 0\r\n        let translate = 0\r\n        for (let i = 0; i < len; i++) {\r\n          const btn = this.$refs.btns[i]\r\n          if (this.state === STATE_GROW) {\r\n            translate = delta\r\n          } else {\r\n            translate = 0\r\n          }\r\n          delta += this.cachedBtns[i].width\r\n          btn.style[transform] = `translate(${translate}px,0) translateZ(0)`\r\n          btn.style[transitionProperty] = 'all'\r\n          btn.style[transitionTimingFunction] = easing\r\n          btn.style[transitionDuration] = `${time}ms`\r\n          if (extend) {\r\n            btn.style.width = `${this.cachedBtns[i].width}px`\r\n          }\r\n        }\r\n      },\r\n      refresh() {\r\n        if (this.btns.length > 0) {\r\n          this._initCachedBtns()\r\n          this._calculateBtnsWidth()\r\n        }\r\n        this.endTime = 0\r\n      },\r\n      shrink() {\r\n        this.stop()\r\n        this.state = STATE_SHRINK\r\n        this.$nextTick(() => {\r\n          this.scrollTo(0, easingTime, easeOutQuart)\r\n          this._translateBtns(easingTime, easeOutQuart)\r\n        })\r\n      },\r\n      grow() {\r\n        this.state = STATE_GROW\r\n        const extend = this.x < this.maxScrollX\r\n        let easing = easeOutCubic\r\n        this.scrollTo(this.maxScrollX, easingTime, easing)\r\n        this._translateBtns(easingTime, easing, extend)\r\n      },\r\n      scrollTo(x, time, easing) {\r\n        this._transitionProperty()\r\n        this._transitionTimingFunction(easing)\r\n        this._transitionTime(time)\r\n        this._translate(x, true)\r\n        if (time) {\r\n          this.isInTransition = true\r\n        }\r\n      },\r\n      genBtnStyl(btn) {\r\n        return `background: ${btn.color}`\r\n      },\r\n      clickItem() {\r\n        this.swipe.onItemClick(this.item, this.index)\r\n        this.$emit(EVENT_ITEM_CLICK, this.item, this.index)\r\n      },\r\n      clickBtn(btn) {\r\n        this.swipe.onBtnClick(btn, this.index)\r\n        this.$emit(EVENT_BTN_CLICK, btn, this.index)\r\n        if (this.autoShrink) {\r\n          this.shrink()\r\n        }\r\n      },\r\n      stop() {\r\n        if (this.isInTransition) {\r\n          this.isInTransition = false\r\n          let x = this.state === STATE_SHRINK ? 0 : this._getComputedPositionX()\r\n          this._translate(x)\r\n          this.$emit(EVENT_SCROLL, this.x)\r\n        }\r\n      },\r\n      onTouchStart(e) {\r\n        this.swipe.onItemActive(this.index)\r\n        this.$emit(EVENT_ACTIVE, this.index)\r\n        this.stop()\r\n        this.moved = false\r\n        this.movingDirectionX = 0\r\n        const point = e.touches[0]\r\n        this.pointX = point.pageX\r\n        this.pointY = point.pageY\r\n        this.distX = 0\r\n        this.distY = 0\r\n        this.startX = this.x\r\n        this._transitionTime()\r\n        this.startTime = getNow()\r\n        if (this.state === STATE_GROW && !this._isInBtns(e.target)) {\r\n          this.shrinkTimer = setTimeout(() => {\r\n            this.shrink()\r\n          }, 300)\r\n        }\r\n      },\r\n      onTouchMove(e) {\r\n        if (this.moved) {\r\n          clearTimeout(this.shrinkTimer)\r\n          e.stopPropagation()\r\n        }\r\n        /* istanbul ignore if */\r\n        if (this.isInTransition) {\r\n          return\r\n        }\r\n        e.preventDefault()\r\n        const point = e.touches[0]\r\n        let deltaX = point.pageX - this.pointX\r\n        let deltaY = point.pageY - this.pointY\r\n        this.pointX = point.pageX\r\n        this.pointY = point.pageY\r\n        this.distX += deltaX\r\n        this.distY += deltaY\r\n        let absDistX = Math.abs(this.distX)\r\n        let absDistY = Math.abs(this.distY)\r\n        if (absDistX + directionLockThreshold <= absDistY) {\r\n          return\r\n        }\r\n        let timestamp = getNow()\r\n        if (timestamp - this.endTime > momentumLimitTime && absDistX < momentumLimitDistance) {\r\n          return\r\n        }\r\n        this.movingDirectionX = deltaX > 0 ? DIRECTION_RIGHT : deltaX < 0 ? DIRECTION_LEFT : 0\r\n        let newX = this.x + deltaX\r\n        if (newX > 0) {\r\n          newX = 0\r\n        }\r\n        if (newX < this.maxScrollX) {\r\n          newX = this.x + deltaX / 3\r\n        }\r\n        if (!this.moved) {\r\n          this.moved = true\r\n        }\r\n        this._translate(newX, true)\r\n        if (timestamp - this.startTime > momentumLimitTime) {\r\n          this.startTime = timestamp\r\n          this.startX = this.x\r\n        }\r\n        this.$emit(EVENT_SCROLL, this.x)\r\n      },\r\n      onTouchEnd() {\r\n        if (!this.moved) {\r\n          return\r\n        }\r\n        if (this.movingDirectionX === DIRECTION_RIGHT) {\r\n          this.shrink()\r\n          return\r\n        }\r\n        this.endTime = getNow()\r\n        let duration = this.endTime - this.startTime\r\n        let absDistX = Math.abs(this.x - this.startX)\r\n        if ((duration < momentumLimitTime && absDistX > momentumLimitDistance) || this.x < this.maxScrollX / 2) {\r\n          this.grow()\r\n        } else {\r\n          this.shrink()\r\n        }\r\n      },\r\n      onTransitionEnd() {\r\n        this.isInTransition = false\r\n        this._transitionTime()\r\n        this._translate(this.x)\r\n      }\r\n    },\r\n    beforeDestroy() {\r\n      this.swipe.removeItem(this)\r\n    }\r\n  }\r\n</script>\r\n```\r\n首先看到inject: ['swipe']， 使得父swipe组件实例自身可以通过this.swipe访问到，\r\n接下来看\r\n```\r\n\r\n  props: {\r\n      item: {\r\n        type: Object,\r\n        default() {\r\n          return {}\r\n        }\r\n      },\r\n      btns: {\r\n        type: Array,\r\n        default() {\r\n          return []\r\n        }\r\n      },\r\n      index: {\r\n        type: Number,\r\n        index: -1\r\n      },\r\n      autoShrink: {\r\n        type: Boolean,\r\n        default: false\r\n      }\r\n    },\r\n```\r\n组件接受四个props，item是在不使用slot自定义子组件元素的情况下使用的，我们可以先不看。\r\nbtns就是描述按钮的数组，形如\r\n```\r\nbtns: [\r\n            {\r\n              action: 'clear',\r\n              text: '不再关注',\r\n              color: '#c8c7cd'\r\n            },\r\n            {\r\n              action: 'delete',\r\n              text: '删除',\r\n              color: '#ff3a32'\r\n            }\r\n          ]\r\n```\r\nindex 接受在外层v-for拿到的index传递给swipe-item组件 便于标识这个swipe-item在swipe容器中的序号。\r\nautoShrink用于当点击滑块的按钮后，是否需要自动收缩滑块，如果使用自定义插槽，则直接给 cube-swipe-item 传递此值即可。\r\n\r\n看完了props 我们可以按生命周期流程开始看了，先看created周期\r\n```\r\n    created() {\r\n      this.x = 0\r\n      this.state = STATE_SHRINK\r\n      this.swipe.addItem(this)\r\n    },\r\n```\r\n\r\nthis.x用来记录滑动偏移的量，\r\nthis.state用来记录状态，默认是缩起，\r\nthis.swipe.addItem(this) 调用父组件的addItem方法把自身实例push到父组件的\r\nthis.items数组里收集起来。\r\n\r\n初始化完了我们来看\r\n```\r\nmounted() {\r\n      this.scrollerStyle = this.$refs.swipeItem.style\r\n      this.$nextTick(() => {\r\n        this.refresh()\r\n      })\r\n      this.$on(EVENT_SCROLL, this._handleBtns)\r\n    },\r\n```\r\n首先通过把这个组件的dom节点的style用this.scrollerStyle记录起来 便于后续操作\r\n接着调用了this.refresh\r\n\r\n```\r\nrefresh() {\r\n        if (this.btns.length > 0) {\r\n          this._initCachedBtns()\r\n          this._calculateBtnsWidth()\r\n        }\r\n        this.endTime = 0\r\n      },\r\n\r\n```\r\n可以看到 我们做了两个初始化工作_initCachedBtns和_calculateBtnsWidth，并且把endTime标识为0\r\n我们先看_initCachedBtns\r\n```\r\n_initCachedBtns() {\r\n        this.cachedBtns = []\r\n        const len = this.$refs.btns.length\r\n        for (let i = 0; i < len; i++) {\r\n          this.cachedBtns.push({\r\n            width: getRect(this.$refs.btns[i]).width\r\n          })\r\n        }\r\n      },\r\n\r\n```\r\nthis.cachedBtns记录按钮宽度大小，\r\n最后生成形如[ {width: 50}, {width: 50 } ] 这样的记录，\r\n再来看_calculateBtnsWidth\r\n```\r\n_calculateBtnsWidth() {\r\n        let width = 0\r\n        const len = this.cachedBtns.length\r\n        for (let i = 0; i < len; i++) {\r\n          width += this.cachedBtns[i].width\r\n        }\r\n        this.maxScrollX = -width\r\n      },\r\n```\r\n其实就是计算出按钮的总长度 \r\n然后记录在this.maxScrollX变量上，用于标识向左滑动的最大距离。\r\n\r\nmounted的最后this.$on(EVENT_SCROLL, this._handleBtns)\r\n注册了EVENT_SCROLL事件的回调函数为 this._handleBtns， 我们先记下来 等到触发的时候再详细去讲。\r\n\r\n初始化的流程到这就结束了， 那么接下来我们就可以看这个组件的核心 touch事件了，touch事件全部注册在最外层的dom节点上\r\n```\r\n <div ref=\"swipeItem\"\r\n       @transitionend=\"onTransitionEnd\"\r\n       @touchstart=\"onTouchStart\"\r\n       @touchmove=\"onTouchMove\"\r\n       @touchend=\"onTouchEnd\"\r\n       class=\"cube-swipe-item\">\r\n```\r\n\r\n我们顺着流程onTouchStart - onTouchMove - onTouchEnd - onTransitionEnd一步一步来走。\r\n\r\n```\r\nonTouchStart(e) {\r\n        this.swipe.onItemActive(this.index)\r\n        this.$emit(EVENT_ACTIVE, this.index)\r\n        this.stop()\r\n        this.moved = false\r\n        this.movingDirectionX = 0\r\n        const point = e.touches[0]\r\n        this.pointX = point.pageX\r\n        this.pointY = point.pageY\r\n        this.distX = 0\r\n        this.distY = 0\r\n        this.startX = this.x\r\n        this._transitionTime()\r\n        this.startTime = getNow()\r\n        if (this.state === STATE_GROW && !this._isInBtns(e.target)) {\r\n          this.shrinkTimer = setTimeout(() => {\r\n            this.shrink()\r\n          }, 300)\r\n        }\r\n      },\r\n```\r\n this.swipe.onItemActive(this.index)\r\n首先通知父组件“我被触摸了”， 这里调用父swipe组件的onItemActive方法\r\n```\r\n onItemActive(index) {\r\n        if (index === this.activeIndex) {\r\n          return\r\n        }\r\n        if (this.activeIndex !== -1) {\r\n          const activeItem = this.items[this.activeIndex]\r\n          activeItem.shrink()\r\n        }\r\n        this.activeIndex = index\r\n      }\r\n```\r\n如果父元素中有已经被触摸左滑展开的swipe-item记录 并且和这个新的swipe-item不是同一个 就通知上一个子组件shrink() 收起， 并且在swipe组件中记录this.activeIndex = index新的子组件序号\r\n        this.pointX = point.pageX\r\n        this.pointY = point.pageY\r\n        this.distX = 0\r\n        this.distY = 0\r\n        this.startX = this.x\r\n记录了这个点的xy值 把dist当前手指的触碰距离值置为0，把this.x的值赋值给this.startX\r\n调用this._transitionTime()\r\n```\r\n      _transitionTime(time = 0) {\r\n        this.scrollerStyle[transitionDuration] = `${time}ms`\r\n      },\r\n```\r\n把style的transitionDuration置为0 手指触摸的时候不需要transitionDuration来帮我们完成动画过渡效果的，所以先把这个过渡关闭\r\nthis.startTime = getNow() 记录触摸开始的时间\r\nif (this.state === STATE_GROW && !this._isInBtns(e.target)) {\r\n          this.shrinkTimer = setTimeout(() => {\r\n            this.shrink()\r\n          }, 300)\r\n        }\r\n这段代码做了一个判断 如果当前的状态是展开 并且点击的位置不在btn内部\r\n就设置了一个定时器 如果touchstart过了300ms 就会把这个swipe-item收起\r\n总结起来就是一系列初始化值的设置，接下来看onTouchMove\r\n```\r\nonTouchMove(e) {\r\n        if (this.moved) {\r\n          // 如果moved变量为true 也就是正在移动中， 就把300ms后自动缩进的定时器清空掉\r\n          clearTimeout(this.shrinkTimer)\r\n         // 并且阻止事件冒泡\r\n          e.stopPropagation()\r\n        }\r\n        /* istanbul ignore if */\r\n\r\n       // 如果已经在进行动画 就直接return \r\n       // 展开动画和缩起动画的过程中这个值都是true\r\n        if (this.isInTransition) {\r\n          return\r\n        }\r\n\r\n        // 阻止浏览器默认touch行为，比如页面滚动\r\n        e.preventDefault()\r\n        const point = e.touches[0]\r\n\r\n        // 相对于上次触发touchmove时候横向的偏移量deltaX\r\n        let deltaX = point.pageX - this.pointX\r\n        // 相对于上次触发touchmove时候竖直方向的偏移量deltaY\r\n        let deltaY = point.pageY - this.pointY\r\n\r\n        // 记录最新的pointX和Y\r\n        this.pointX = point.pageX\r\n        this.pointY = point.pageY\r\n\r\n        // 本次从touchstart事件开始移动的横向总距离\r\n        this.distX += deltaX\r\n\r\n         // 本次从touchstart事件开始移动的纵向总距离\r\n        this.distY += deltaY\r\n\r\n       // distX和distY的绝对值\r\n        let absDistX = Math.abs(this.distX)\r\n        let absDistY = Math.abs(this.distY)\r\n\r\n        // 如果横向距离 加directionLockThreshold(被设置成了5) \r\n        // 小与纵向移动的距离 就判定成上下滑动 不做任何行为\r\n        //这其实就是稍微大于45度角的角度以内的滑动会被识别为侧滑\r\n        if (absDistX + directionLockThreshold <= absDistY) {\r\n          return\r\n        }\r\n\r\n        let timestamp = getNow()\r\n        // momentumLimitTime和momentumLimitDistance\r\n        // 定义两次动画的最小间隔事件和最小间隔移动距离\r\n        // 距离上次touchend 300ms内并且 横向移动小于15的move事件会被无视\r\n        if (timestamp - this.endTime > momentumLimitTime && absDistX < momentumLimitDistance) {\r\n          return\r\n        }\r\n\r\n        // movingDirectionX 滑动的方向， 如果deltaX大于0 则是向右滑动-1 \r\n        // 如果deltaX小于0则是左滑-1 如果等于0 则记录为0\r\n        this.movingDirectionX = deltaX > 0 ? DIRECTION_RIGHT : deltaX < 0 ? DIRECTION_LEFT : 0\r\n        // this.x在执行_translate动画的之后会被更新成当前的translateX值， \r\n\r\n       // newX拿到了到上次move为止偏移的x值 \r\n       // 加上本次move偏移的deltaX值\r\n       // 计算出newX也就是下一次应该_translate到x位置值，\r\n       // 这个值一定是负数，因为我们的按钮组一定是向左做偏移translateX(-x)\r\n       //  当然这个值不能直接交给_translate方法 我们要做一些边界值处理\r\n        let newX = this.x + deltaX\r\n        // 不能大于0的边界限制， 保证向右滑动不能超出边缘\r\n        if (newX > 0) {\r\n          newX = 0\r\n        }\r\n        // 如果X的值比最大的maxScrollX值还小\r\n        // maxScrollX的值在refresh中\r\n        // 被设置成了按钮组的总width的负值\r\n        // 用比较好理解的方法 就是向左拉到了极限值\r\n        // 那么你下次再拉30px 只会向左做10px的动画\r\n        // 给你一种有阻力的感觉\r\n        if (newX < this.maxScrollX) {\r\n          newX = this.x + deltaX / 3\r\n        }\r\n\r\n       // 如果moved是false 记录为true\r\n        if (!this.moved) {\r\n          this.moved = true\r\n        }\r\n       // 调用_translate 真正去操作dom左偏移的行为\r\n        this._translate(newX, true)\r\n\r\n       // 如果这次move的事件减去开始事件小于momentumLimitTime边界值300ms\r\n        if (timestamp - this.startTime > momentumLimitTime) {\r\n          // 重置startTime为当前时间\r\n          this.startTime = timestamp\r\n          // 重置startX为当前的偏移值x\r\n          this.startX = this.x\r\n        }\r\n        // 触发EVENT_SCROLL事件 带出当前的x值。\r\n        this.$emit(EVENT_SCROLL, this.x)\r\n      },\r\n```"}}
{"actor":{"display_login":"sl1673495"}}