Open libin1991 opened 5 years ago
Demo gist地址 👈
做web开发经常会遇到列表操作, 如果不涉及移动端, 那么在列表上放几个按钮, 用户点击就完事了, 如果是移动端, 受限于屏幕宽度, 操作按钮太多会影响布局, 所以在移动端列表的滑动操作比较常见.
做原生开发, 系统可能给列表提供了基本的删除等功能, 那么网页应该如何实现呢?
本文以地址管理为demo, 用react实现, 其实不管是什么框架, 涉及到的大部分都是 web 的接口.
demo用到了coroutine, 使用协程方便管理一系列事件 (event flow).
2件事要处理: 滑动 和 布局
滑动事件需要被监听, 应该在列表的每一个item上设置监听, 每个item处理滑动事件.
如果是 mobile 监听这三个事件:
否则监听这几个:
在生命周期开始时候监听这几个事件:
startupTouchEvent() { const current = ReactDOM.findDOMNode(this); current.addEventListener('touchstart', this.moveLoop); current.addEventListener('touchend', this.moveLoop); current.addEventListener('touchmove', this.moveLoop); } 复制代码
其中 this.moveLoop 是:
this.moveLoop
this.moveLoop = coroutine(function*() { let e = {}; while (e = yield) { if (e.type === 'touchstart') { // trace position const startX = e.touches[0].clientX; while (e = yield) { if (e.type === 'touchmove') { // trace position // console.log('touchmove', e); const movedX = e.changedTouches[0].clientX; const deltaX = movedX - startX; // console.log('moved', deltaX); if (deltaX <= 0) { that.moveMask(deltaX); } } if (e.type === 'touchend') { const endX = e.changedTouches[0].clientX; const deltaX = endX - startX; // console.log('end', deltaX); if (deltaX >= -40) { that.closeMaskIfNeeded(); } else { that.openMask(); } break; } } } } }) 复制代码
这里用到了 corutine.
首先当手指放到 item 上时, 记录位置 startX = e.touches[0].clientX;.
startX = e.touches[0].clientX;
当手指滑动时获取此时的位置 e.changedTouches[0].clientX, 减去初始位置 deltaX = movedX - startX;, 如果 deltaX 小于0, 那么此时是左滑, 进行 UI 上的操作, 将上层 div 左移 deltaX.
e.changedTouches[0].clientX
deltaX = movedX - startX;
deltaX
div
当手指离开屏幕时候, 记录此时位置并获取与初始位置的差值 deltaX = endX - startX, 判断 deltaX, 如果滑动距离太小(40px)或者向右滑, 那么就关掉展开的 div, 如果滑动距离够长, 那么就完全展开 div.
deltaX = endX - startX
<div className="address-swipe-wrapper"> <div className="swiper-operation-btns"> <button style={{ backgroundColor: '#7EA1D6' }} onClick={onEdit}> 编辑 </button> <button style={{ backgroundColor: 'red' }} onClick={onDelete}> 删除 </button> </div> <div className="address-item" onClick={onClick} style={{ left, position: 'relative', transition: 'all 250ms', }}> {selected && <img className="address-item-selected-icon" src={require('../img/check.png')} alt="选中" /> } <div className="address-content"> <div>{`${name} ${mobile}`}</div> <div>{provinceName+cityName+districtName+detailedAddress}</div> </div> </div> </div> 复制代码
几个操作按钮是绝对布局被盖在 address-item 内容的下面, 当滑动或者展开时候 address-item 会左移 left 距离, 它是 relative 布局.
left
为了让滑动有动效, 可以添加 transition: 'all 250ms'.
transition: 'all 250ms'
openMask() { this.setState({ left: -160 }); } moveMask(deltaX) { this.setState({ left: deltaX }); } closeMaskIfNeeded() { this.setState({ left: 0 }); } 复制代码
不会, 经过pc和手机(ios/android)尝试, 滑动时候不会触发 address-item 的选中, 并没发现会冲突, 除非你写 evt.preventDefault().
evt.preventDefault()
给 window 添加监听事件:
window
window.addEventListener('touchstart', this.closeMaskIfNeeded); 复制代码
在 PC 上表现良好, 但是在 mobile 上表现异常. 所以移到 TODO 里待解决.
预览
前言
Demo gist地址 👈
做web开发经常会遇到列表操作, 如果不涉及移动端, 那么在列表上放几个按钮, 用户点击就完事了, 如果是移动端, 受限于屏幕宽度, 操作按钮太多会影响布局, 所以在移动端列表的滑动操作比较常见.
做原生开发, 系统可能给列表提供了基本的删除等功能, 那么网页应该如何实现呢?
本文以地址管理为demo, 用react实现, 其实不管是什么框架, 涉及到的大部分都是 web 的接口.
demo用到了coroutine, 使用协程方便管理一系列事件 (event flow).
原理
2件事要处理: 滑动 和 布局
滑动
滑动事件需要被监听, 应该在列表的每一个item上设置监听, 每个item处理滑动事件.
如果是 mobile 监听这三个事件:
否则监听这几个:
在生命周期开始时候监听这几个事件:
startupTouchEvent() { const current = ReactDOM.findDOMNode(this); current.addEventListener('touchstart', this.moveLoop); current.addEventListener('touchend', this.moveLoop); current.addEventListener('touchmove', this.moveLoop); } 复制代码
其中
this.moveLoop
是:this.moveLoop = coroutine(function*() { let e = {}; while (e = yield) { if (e.type === 'touchstart') { // trace position const startX = e.touches[0].clientX; while (e = yield) { if (e.type === 'touchmove') { // trace position // console.log('touchmove', e); const movedX = e.changedTouches[0].clientX; const deltaX = movedX - startX; // console.log('moved', deltaX); if (deltaX <= 0) { that.moveMask(deltaX); } } if (e.type === 'touchend') { const endX = e.changedTouches[0].clientX; const deltaX = endX - startX; // console.log('end', deltaX); if (deltaX >= -40) { that.closeMaskIfNeeded(); } else { that.openMask(); } break; } } } } }) 复制代码
这里用到了 corutine.
首先当手指放到 item 上时, 记录位置
startX = e.touches[0].clientX;
.当手指滑动时获取此时的位置
e.changedTouches[0].clientX
, 减去初始位置deltaX = movedX - startX;
, 如果deltaX
小于0, 那么此时是左滑, 进行 UI 上的操作, 将上层div
左移deltaX
.当手指离开屏幕时候, 记录此时位置并获取与初始位置的差值
deltaX = endX - startX
, 判断deltaX
, 如果滑动距离太小(40px)或者向右滑, 那么就关掉展开的div
, 如果滑动距离够长, 那么就完全展开div
.布局
<div className="address-swipe-wrapper"> <div className="swiper-operation-btns"> <button style={{ backgroundColor: '#7EA1D6' }} onClick={onEdit}> 编辑 </button> <button style={{ backgroundColor: 'red' }} onClick={onDelete}> 删除 </button> </div> <div className="address-item" onClick={onClick} style={{ left, position: 'relative', transition: 'all 250ms', }}> {selected && <img className="address-item-selected-icon" src={require('../img/check.png')} alt="选中" /> } <div className="address-content"> <div>{`${name} ${mobile}`}</div> <div>{provinceName+cityName+districtName+detailedAddress}</div> </div> </div> </div> 复制代码
几个操作按钮是绝对布局被盖在 address-item 内容的下面, 当滑动或者展开时候 address-item 会左移
left
距离, 它是 relative 布局.为了让滑动有动效, 可以添加
transition: 'all 250ms'
.其他几个方法
openMask() { this.setState({ left: -160 }); } moveMask(deltaX) { this.setState({ left: deltaX }); } closeMaskIfNeeded() { this.setState({ left: 0 }); } 复制代码
小结
会不会手势滑动与点击冲突?
不会, 经过pc和手机(ios/android)尝试, 滑动时候不会触发 address-item 的选中, 并没发现会冲突, 除非你写
evt.preventDefault()
.如何实现点击空白关掉?
给
window
添加监听事件:window.addEventListener('touchstart', this.closeMaskIfNeeded); 复制代码
在 PC 上表现良好, 但是在 mobile 上表现异常. 所以移到 TODO 里待解决.
TODO
参考