You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
// 根据当前的x值驱动每个按钮去做向左滑动动画// 并且如果超出了最大x距离 还要让按钮变长// 让用户有种按钮有弹性拉动的感觉_handleBtns(x){/* istanbul ignore if */if(this.btns.length===0){return}constlen=this.$refs.btns.lengthletdelta=0lettotalWidth=-this.maxScrollXfor(leti=0;i<len;i++){constbtn=this.$refs.btns[i]letrate=(totalWidth-delta)/totalWidthletwidthlettranslate=rate*x-xif(x<this.maxScrollX){width=this.cachedBtns[i].width+rate*(this.maxScrollX-x)}else{width=this.cachedBtns[i].width}delta+=this.cachedBtns[i].widthbtn.style.width=`${width}px`btn.style[transform]=`translate(${translate}px)`btn.style[transitionDuration]='0ms'}},
swipe组件预览地址(手机模式可体验)
作者:黄轶老师
先吹一波黄老,昨天体验swipe组件的时候感受到了什么叫丝滑,这可以说是东半球移动端最好用的swipe组件了吧。
先来一段文档中的用法的简化版:
在cube-ui的项目的src/components/swipe目录下,我们可以看到swipe组件被分为swipe.vue和swipe-item.vue。
其实swipe就是列表的外层容器组件,负责处理一些全列表的事件。
swipe-item就是列表中循环出来的某一项元素的组件,负责处理手势等细节。
我们先从swipe.vue入手:
swipe.vue
我们先从template部分入手, 可以看到结构非常简单,就是一个div中给了一个slot子元素,并且slot有个默认值,
如果用户不传slot的话就默认的带transition-group动效循环出一段cube-swipe-item列表,不使用slot的情况下用户可以传入
这样一段大而全的json数组,渲染出一个列表,不过这种方式比较不灵活。
script的data和methods里提供了很多东西,但是在template里却没有使用到,那么我们猜测这些都是提供给子组件使用的,
provider里把自身实例提供给了子组件
那么我们接下来就去探究swipe-item组件。
swipe-item
可以看到swipe-item的结构也非常简单, 也提供了slot插槽定制子组件的元素
并且在子组件的旁边有个初始隐藏的ul结构 用来循环btns来生成侧滑出来的按钮
.cube-swipe-btn这个类是绝对定位并且left 100% 也就是相对于父relative容器
.cube-swipe-item的宽度偏移 正好隐藏到边缘外。
接下来我们看一下script部分
首先看到inject: ['swipe'], 使得父swipe组件实例自身可以通过this.swipe访问到,
接下来看
组件接受四个props,item是在不使用slot自定义子组件元素的情况下使用的,我们可以先不看。
btns就是描述按钮的数组,形如
index 接受在外层v-for拿到的index传递给swipe-item组件 便于标识这个swipe-item在swipe容器中的序号。
autoShrink用于当点击滑块的按钮后,是否需要自动收缩滑块,如果使用自定义插槽,则直接给 cube-swipe-item 传递此值即可。
看完了props 我们可以按生命周期流程开始看了,先看created周期
this.x用来记录滑动偏移的量,
this.state用来记录状态,默认是缩起,
this.swipe.addItem(this) 调用父组件的addItem方法把自身实例push到父组件的
this.items数组里收集起来。
初始化完了我们来看
首先通过把这个组件的dom节点的style用this.scrollerStyle记录起来 便于后续操作
接着调用了this.refresh
可以看到 我们做了两个初始化工作_initCachedBtns和_calculateBtnsWidth,并且把endTime标识为0
我们先看_initCachedBtns
this.cachedBtns记录按钮宽度大小,
最后生成形如[ {width: 50}, {width: 50 } ] 这样的记录,
再来看_calculateBtnsWidth
其实就是计算出按钮的总长度
然后记录在this.maxScrollX变量上,用于标识向左滑动的最大距离。
mounted的最后this.$on(EVENT_SCROLL, this._handleBtns)
注册了EVENT_SCROLL事件的回调函数为 this._handleBtns, 我们先记下来 等到触发的时候再详细去讲。
初始化的流程到这就结束了, 那么接下来我们就可以看这个组件的核心 touch事件了,touch事件全部注册在最外层的dom节点上
我们顺着流程onTouchStart - onTouchMove - onTouchEnd - onTransitionEnd一步一步来走。
this.swipe.onItemActive(this.index)
首先通知父组件“我被触摸了”, 这里调用父swipe组件的onItemActive方法
如果父元素中有已经被触摸左滑展开的swipe-item记录 并且和这个新的swipe-item不是同一个 就通知上一个子组件shrink() 收起, 并且在swipe组件中记录this.activeIndex = index新的子组件序号
this.pointX = point.pageX
this.pointY = point.pageY
this.distX = 0
this.distY = 0
this.startX = this.x
记录了这个点的xy值 把dist当前手指的触碰距离值置为0,把this.x的值赋值给this.startX
调用this._transitionTime()
把style的transitionDuration置为0 手指触摸的时候不需要transitionDuration来帮我们完成动画过渡效果的,所以先把这个过渡关闭
这段代码做了一个判断 如果当前的状态是展开 并且点击的位置不在btn内部
就设置了一个定时器 如果touchstart过了300ms 就会把这个swipe-item收起
总结起来就是一系列初始化值的设置,接下来看onTouchMove
onTouchMove的方法比较长 也是滑动动画的核心,我们跟着注释一行一行来解读
总结touchmove事件 核心就是根据当前手指的x值和start时的x值 调用_translate让dom去做一些偏移
_translate很简单 把x值写入dom样式里 并且translateZ(0)开启硬件加速
然后更新实例上的this.x 最后还要触发一个EVENT_SCROLL
我们在created里看到了这个EVENT_SCROLL事件注册的回调是_handleBtns
其实就是在touchmove的时候也驱动按钮组做一些动画
_handleBtns
touchend的核心逻辑就是根据记录的一些变量判断是要调用展开还是收起
展开grow
我们来看看scrollTo方法如何让容器偏移到最大滑动距离
其实scrollTo就是给容器设定了一系列的transform的css值,让css帮我们做动画
再看_translateBtns
再来看缩起shrink
先调用了stop
stop中先把this.isInTransition置为false
在touchstart时候也会调用stop 所以要根据state判断目标值
如果状态已经是缩起状态STATE_SHRINK, 则目标值是0
然后_translate过渡到x位置
并且通过EVENT_SCROLL事件通知按钮组也过渡到x位置
最后在nextTick里调用scrollTo和_translateBtns分别把容器dom和按钮组动画移动到缩起状态原位
因为此时state已经是STATE_SHRINK了 所以_translateBtns内部会判定x的目标值为0
至此touch事件三剑客都分析完毕了,内部有些细节实现的很精巧
在动画结束的时候会调用onTransitionEnd,做一些状态的重置。
另外在按钮上点击会触发clickBtn方法,驱动‘btn-click’事件的触发
并且判断autoShrink的情况下自动收缩起按钮组
The text was updated successfully, but these errors were encountered: