本文总结了一些和移动端交互体验相关的小技巧,其实并没有很「花」
主要的知识点如下:
- 禁止微信页面及iOS Safari拖动露底效果 (橡皮筋)
- 禁止不必要的选择区域
- 合理规划用户操作区域
- 扩大点击区
- 负Margin实现固定栏
- 频繁操作使用节流函数
禁止微信页面及iOS Safari拖动露底效果 (橡皮筋)
上图就是露底效果 (橡皮筋效果)
其实不止iOS,安卓微信也会有这种露底的效果,为了更接近原生app的体验,建议禁止最外层的橡皮筋效果,防止webview或页面露出底部容器。
仔细使用可以发现,当滑动到顶部,继续往下滑时,容器会暴露;滑动到底部,继续往上滑,容器会暴露。
本质是一个 touchmove
事件不断向上冒泡,尝试拉开自身的容器,如果一直无法拉开冒泡到 webview 时,就出现“露底”。
解决思路很明显:当容器可以滑动时,若已经在顶部,禁止下滑;若在底部,禁止上滑;容器无法滚动时,禁止上下滑。
基本实现方式是通过 document
上监听 touchstart
和 touchmove
事件,判断滑动方向;判断滑动事件的触发 target 祖先元素是否有可滑动元素,无则直接阻止冒泡。
若有这种祖先元素,判断其状态:offsetHeight >= scrollHeight
高度不够发生滚动,阻止冒泡;若可滚动 scrollTop === 0
即在顶部,阻止下滑的冒泡;若 scrollTop + offsetHeight >= scrollHeight
已经滑到底,阻止上滑的冒泡。
还需注意的是,要先判断滑动时 x 轴位移是否大于 y 轴 ,即是否是水平滑动,防止“误伤”一些水平滚动的元素。
下面是一个示例代码(class="scroller"
的元素被标记为可滚动元素)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
const _ = require('src/util') export default function (option) { var scrollSelector = option.scroll || '.scroller' var pos = { x: 0, y: 0 } function stopEvent (e) { e.preventDefault() e.stopPropagation() } function recordPosition (e) { pos.x = e.touches[0].clientX pos.y = e.touches[0].clientY } function watchTouchMove (e) { var target = e.target var parents = _.parents(target, scrollSelector) var el = null if (target.classList.contains(scrollSelector)) el = target else if (parents.length) el = parents[0] else return stopEvent(e) var dx = e.touches[0].clientX - pos.x var dy = e.touches[0].clientY - pos.y var direction = dy > 0 ? '10' : '01' var scrollTop = el.scrollTop var scrollHeight = el.scrollHeight var offsetHeight = el.offsetHeight var isVertical = Math.abs(dx) < Math.abs(dy) var status = '11' if (scrollTop === 0) { status = offsetHeight >= scrollHeight ? '00' : '01' } else if (scrollTop + offsetHeight >= scrollHeight) { status = '10' } if (status !== '11' && isVertical && !(parseInt(status, 2) & parseInt(direction, 2))) return stopEvent(e) } document.addEventListener('touchstart', recordPosition, false) document.addEventListener('touchmove', watchTouchMove, false) } |
禁止不必要的选择区域
如上左图,用户在手指停留过久的时候无意间触发了选择动作,但实际上这块区域没有被选择的必要,这影响了用户正常的操作。
这些标签可以使用 .no-select { user-select: none; }
解决(前缀交给处理器吧)
这个子元素会继承这个属性,所以只需要在稍微顶层的元素写就行,对于一些长文本内容,不应该添加这个属性,用户可能会有选择的需求。
开发中可以用cmd + a
全选页面内容,如上右图,蓝色的即为当前可选区域(截图中的区域应该都禁止选择)
合理规划用户操作区域
用户最方便且单手能操作的区域如上左图蓝色区所示,应该确保用户的大部分点、触、滑、输入操作都集中在这块;更为常用的、高频的操作应该集中在这块。
如上图的输入框,远离了用户操作的输入区,应该只成为一个入口。所以在这里的交互设计上,选择点击后弹出一个真正的输入区,如右图。(模仿Airbnb)
如此引导用户进入舒适区做进一步交互。
不过左上的关闭按钮也是一个槽点、、、
扩大点击区
这个其实在上一篇关于移动端WEB开发的笔记里已经提到过,这里重新说一下。
移动端页面的点触事件是用户通过手指直接触发的,这和PC页面上鼠标触发的按钮不同,需要给予用户一定的点击容错率。
我们扩大按钮的实际触发区域,一般情况下可以使用padding去做,缺点在于可能会影响布局;
另一种方案是使用垫在元素底部的伪类::after / ::before
去扩大,不会影响布局,缺点是会增加复合层数量。
具体使用哪种看取舍。
负Margin实现固定栏
负margin可以代替fixed,用正常流布局实现顶部栏 / 底部栏固定。
频繁操作使用节流函数
大部分同学应该都知道节流是个啥,不赘述;要提的一点是,节流函数可以用requestAnimationFrame
或者 requestIdleCallback
去实现。
一个节流函数的栗子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
function _throttle (fn, time) { let running = false let isFrame = false if (!time) isFrame = true const getCallback = (self, args) => { return () => { fn.apply(self, args) running = false } } return function () { if (running) return running = true if (isFrame) { window.requestAnimationFrame(getCallback(this, arguments)) } else { setTimeout(getCallback(this, arguments), time) } } } |