vue + any-touch实现一个iscroll ? - (1) 实现拖拽和滑动动画

https://github.com/383514580/any-touch先看demodemo说点湿的本文代码非常简单, 请不要被系统预估的阅读时间误导.iscroll其实代码量挺大的(近2100行, 还有另一个类似的库betterScroll他的代码量和iscroll差不多, 因为原理都是一样的), 阅读他们的代码发现里面很多逻辑其实都是在做手势判断, 比如拖拽(pan), 和划(swipe), 还有部分元素(表单元素等)需要单独判断点击(tap), 这部分代码接近1/3, 所以我决定用自己开发的手势库(any-touch)实现一个iscroll, 同时配合文字让大家最终都可以以最少的代码实现一个iscroll.vue观察了一段时间推荐排行, 发现大家都对vue感兴趣, 所以本次的"iscroll"将以vue组件的形式实现, 同时我也希望借助vue强大的抽象能力, 让最终代码控制在500行以内, 希望大家喜欢.本文是个系列文章本文先实现拖拽和滑动动画, 因为这2部分都依赖手势, 借此用最少的代码先实现最核心的功能, 也让大家对后续的内容有信心.简单说下iscroll原理添加2个div, 最内的div(子div)通过设置css的transform的translate的值来模拟系统滚动效果.说完逻辑再说代码拖拽的时候通过panstart/panmove手势返回的位移增量(deltaX/Y)进行位置变化, 同时关闭动画效果.发生快速划(swipe)的时候, 开启动画, 同时通过计算目标位置和动画时间来触发滑动动画.代码<div class=“any-scroll-view”> <div ref=“body” :style=“bodyStyle” class=“any-scroll-view__body”><slot></slot></div></div>.any-scroll-view { position: relative; width: 100%; height: 90vh; overflow: hidden; &__body { transition-timing-function: cubic-bezier(0.1, 0.57, 0.1, 1); background: #eee; position: absolute; width: 100%; height: 100%; }}import AnyTouch from ‘any-touch’;export default { name: ‘any-scroll-view’, props: { // 减速度, 单位px/s² acceleration: { type: Number, default: 3600 } }, data() { return { scrollTop: 0, scrollLeft: 0, transitionDuration: 300 }; }, computed: { bodyStyle() { return { transitionDuration: ${this.transitionDuration}ms, transform: translate(${this.scrollLeft}px, ${ this.scrollTop }px) }; } }, mounted() { const at = new AnyTouch(this.$el); // 第一次触碰 at.on(‘inputstart’, (ev) => { this.stopRoll(); }); // 拖拽开始 at.on(‘panstart’, (ev) => { this.move(ev); }); // 拖拽中 at.on(‘panmove’, (ev) => { this.move(ev); }); // 快速滑动 at.on(‘swipe’, (ev) => { this.decelerate(ev); }); this.$on(‘hook:destroy’, () => { at.destroy(); }); }, methods: { // https://github.com/nolimits4web/swiper/blob/master/dist/js/swiper.esm.js#L87 // https://github.com/nolimits4web/Swiper/blob/master/src/utils/utils.js#L25 getCurrentTranslate() { const style = getComputedStyle(this.$refs.body, null); const { transform } = style; const array = transform.match(/(-?)(\d)+(.\d{0,})?/g); return { x: Math.round(array[4]), y: Math.round(array[5]) }; }, stopRoll() { const { x, y } = this.getCurrentTranslate(); this.moveTo({ scrollTop: y, scrollLeft: x }); }, /** * 移动body * @param {Object} 拖拽产生的数据 * @param {Number} deltaX: x轴位移变化 * @param {Number} deltaY: y轴位移变化 / move({ deltaX, deltaY }, transitionDuration = 0) { this.transitionDuration = transitionDuration; this.scrollLeft += deltaX; this.scrollTop += deltaY; }, /* * 移动到 / moveTo({ scrollTop, scrollLeft }, transitionDuration = 0) { this.transitionDuration = transitionDuration; this.scrollLeft = scrollLeft; this.scrollTop = scrollTop; }, /* * 拖拽松手后减速移动至停止 * velocityX/Y的单位是px/ms */ decelerate(ev) { const directionSign = { up: -1, right: 1, down: 1, left: -1 }[ ev.direction ]; // Top? | Left? let SCROLL_SUFFIX = ‘Top’; // x ? | y? let AXIS_SUFFIX = ‘Y’; if (ev.velocityX > ev.velocityY) { SCROLL_SUFFIX = ‘Left’; AXIS_SUFFIX = ‘X’; } // 减速时间, 单位ms // t = (v - v) / a const velocity = ev[velocity${AXIS_SUFFIX}]; this.transitionDuration = Math.round( ((velocity * 1000) / this.acceleration) * 1000 ); // 滑动距离 // s = (v² - v²) / (2 * a) const scrollAxis = scroll${SCROLL_SUFFIX}; this[scrollAxis] += directionSign * Math.round( Math.pow(velocity * 1000, 2) / (2 * this.acceleration) ); } }};下一期大家也发现了, 只有页面在滚动, 没有滚动条, 所以下期我们讲如何给scroll-view加上滚动条.有不明白的地方请留言, 知无不言, 言无不尽. 如觉得本文对您有帮助, 就请给any-touch一个star吧, 谢谢. ...

April 7, 2019 · 2 min · jiezi

用typescript开发手势库 - (1)web开发常用手势有哪些?

这只是个开头说在最前面,本文是一个系列文章的开头, 这个系列里我会讲如何用typescript开发一款支持pc和手机端的手势库any-touch, 以及通过jest让你的代码测试覆盖率100%.今天我们先不写代码今天的文章里我们先不讲代码, 我们讲一下都有哪些手势以及手势的逻辑, 理解了逻辑再写代码就简单了.(即便后面的文章不看,因为理解了不同手势的区别, 在移动端开发中也可以规避很多touch事件产生bug, 请务必看完本文)感谢一定要写在最前面感谢开源的hammer.js, 是他让我学会了很多手势方面的知识.还要感谢写这篇文章的作者,文章地址, 文章详细介绍了关于旋转和缩放手势变化的计算.那么常用手势有哪些?回到正题, 常见的手势有: 点击(tap) press(按住) 拖拽(pan) 划过(swipe) 捏合缩放(pinch) 旋转(rotate), 所有的手势在移动端其实都是通过touch事件的不同触发时机而区分出的(移动端是mouseup/mousemove/mousedown).这里有个demo, 我把所有的手势识别放在了里面.tap(点击)众所周知, 移动端的click有300ms延迟(浏览器延迟300ms为了识别双击操作, 因为移动端浏览器默认双击可以缩放页面), 为了避免"点击穿透"我们创建了tap事件, 同时通过preventDefault来禁止click触发. tap在touchend阶段触发, 说下识别tap的必要条件:触发一次touchstart和touchend.touchstart和touchend的坐标之间的距离不能超过2px.touchstart触发后,250ms内必须触发touchend, 不然会被识别成press.doubletap(双击)2次连续的tap(单击)触发双击, 双击的原理如下: 每次tap后并不触发tap, 而是等待300ms看是否有双击触发, 如果没有那么2次tap依次触发, 否则触发双击,单击不触发. (关于多击更细的原理, 后面我会单独通过源码解析), 必要条件:2次点击之间不能超过300ms.2次点击的距离不能超过9px.press(按)按住屏幕不放, 一段时间后触发press时间,触发press后, 离开触点, 触发pressup事件, 识别的必要条件如下:touchstart和touchmove之间的距离不能超过9px.即便触点不离开屏幕, 251ms后也会触发press. 这个251ms对应tap的必要条件第3条.pan(拖拽)(1指或多指)按住屏幕不离开, 每一次移动都会触发pan. 举几个例子:轮播效果的拖拽切换, 这个拖拽动作就是pan.抽屉(draw)组件的拖拽显示更多内容.选项卡(tabs)组件的拖拽显示更多选项卡.pan是组件开发中最常用的手势.swipe(划)(1指或多指)按住屏幕快速滑动, 当手指离开屏幕的一瞬间, 触发swipe. 必要条件如下:滑动超过一定距离(如10px).滑动速度足够快(大于0.3px/ms).轮播组件的用swipe来表示"切换下一幕".pinch(啮合)2指及以上按住屏幕, 让2个手指之间的距离发生变化, 通过距离的变化来表示pinch是放大开始缩小.常见于gallery组件, 用来放大/缩小图片.rotate(旋转)2指及以上按住屏幕, 通过2指形成的直线和坐标系的x轴的夹角的变化而出发rotate. 常用语图片处理, 用来旋转图片.源码上面关于手势识别的具体逻辑可以看我的仓库, 地址: https://github.com/383514580/…未完待续本次先讲这么多, 后面的文章具体要讲什么看大家的回复想听什么, 期待大家的回复, 本人热爱前端, 但能力有限, 有讲的不对的请大家多多指点.相关概念解释点击穿透当A/B两个层上下z轴重叠,上层的A点击后消失或移开,并且B元素本身有默认click事件(如a标签)或绑定了click事件。在这种情况下,点击A/B重叠的部分,就会出现点透的现象.

March 19, 2019 · 1 min · jiezi