JS如何让你的移动端交互体验更加优秀

1. 即时反馈

我们在玩游戏的过程中,通常都会遇到一个词:“打击感”,通俗的理解就是我们做出的每一个操作,都有很强烈的反馈,比如视觉上的动画变化,听觉上产生的声音,或者移动设备的震动感等。

1.1 按钮的即时反馈

在前端页面中,也应当像游戏中的打击感一样,用户任何的操作都应当予以即时的反馈,告诉用户他的操作是有效的,系统已收到他的操作,内部正在处理中。

例如用户在点击页面中的按钮时,按钮最好有一种被按下的效果:

button:active {
  transform: translateY(4px);
}

若按钮被下压的效果不太适合页面整体的风格,您也可以做一个背景颜色上的变化。

1.2 持续性的反馈

每个用户的设备型号、网络状态等情况都不一样,我们不能要求每个用户都在良好的 WiFi 下操作我们的页面。

若用户的某个行为产生了网络请求,并要根据请求返回的结果,反馈给用户。这种情况,页面都应当给用户一种持续性的反馈,表示一个动作正在后台执行。如果没有这种效果,即使已经在请求接口了,用户也会认为点击没有反应,会多次的去点击按钮,以期望得到响应。

我们可以在这里给自己定下一条规则:

凡是有网络请求的情形,均要有 loading 效果的持续性反馈。

我们通常可以在用户触发的按钮上展示 loading 效果,也可以在全局页面上展示 loading 效果,这个根据每个页面的风格自行选择即可。

例如页面上有个红包需要点击按钮开启,当用户点击按钮后,按钮就可以展示出一个旋转的 loading 效果,待接口返回结果再打开红包,展示具体的金额,或者其他的结果。

1.3 页面初始化

在现在大部分前后端分离的场景下(同时没有使用同构直出方案),都是先展示出一个没有数据的前端页面,然后请求数据,待数据返回后再渲染页面。

这种情形和上面 1.2 中是一样的,不过这个是在刚进入页面就触发的!这里我们也是要展示出 loading 效果的,只不过是 loading 展示的时机的问题。

1.先一个全局 loading 的开启页,在数据没有返回回来时,看不到任何相关活动元素;

2.先用初始化的假数据或者兜底数据,渲染一个基本框架,然后在某个位置展示 loading 效果,并请求数据,数据返回后再替换假数据进行渲染。

这两种方式也是各有不同的使用场景,就我个人而言,我更喜欢第 2 种方式,能够第一时间将页面中的元素展示给用户;但如果页面布局因接口的数据改变较大,建议还是采用第 1 种方式,这样 loading 结束时,不会出现页面大幅度闪动的感觉。

1.4 数据的展示

我们拿到接口的数据后,通常会有两种展示状态:

1.无数据,进行“暂无数据”之类的提示;

2.有数据,正常展示数据;

比如一个展示奖品列表中数据中,这里我们通常会初始化一个 list 变量来接收接口返回的数据:

const List = () => {
  const [list, setList] = useState([]);

  useEffect(() => {
    // 设置数据
    // setList([]);
  }, []);

  return (
    <div className="list">
      {list.length ? (
        <div className="container">
          {list.map((item) => (
            <div key={item.key}>{item.title}</div>
          ))}
        </div>
      ) : (
        <div className="nothing">暂无数据</div>
      )}
    </div>
  );
};

在请求接口的过程中,页面会展示什么?“暂无数据”,给用户的第一视觉感受就是:我的奖品丢了。等过一会儿接口返回数据了,然后又重新将数据展示出来。

这里,我们就忽略了一个很重要的状态:loading状态。因为“暂无数据”,也是一种结果,不是过程,是要告诉用户,您当前是没有数据的。因此,不能把“暂无数据”作为 loading 状态来展示。

const List = () => {
  const [loading, setLoading] = useState(true);
  const [list, setList] = useState([]);

  useEffect(() => {
    // 设置数据
    // setList([]);
    setLoading(false); // 请求完接口,再把loading状态取消,该展示什么结果就展示什么
  }, []);

  if (loading) {
    return (
      <div className="list">
        <div className="loading">请求数据中...</div>
      </div>
    );
  }

  return (
    <div className="list">
      {list.length ? (
        <div className="container">
          {list.map((item) => (
            <div key={item.key}>{item.title}</div>
          ))}
        </div>
      ) : (
        <div className="nothing">暂无数据</div>
      )}
    </div>
  );
};

2. 行为跟随

这里我也不太想好用个什么名字,概况来说,告诉用户刚才发生了什么,将用户操作可视化, 来增强用户对操作行为的感知度, 同时也能对元素内容的认知。

因用户行为产生的新交互,应当与当前用户的行为相关。

2.1 点击按钮后呼起弹窗

用户点击按钮后,会弹出一个弹窗,弹窗可以从按钮所在的方向或者位置,弹出到整个页面的中心。

给到用户的感受就是该弹窗与按钮是相关的。

2.2 列表中有对象变动时

例如在一个表格或者列表中,有新增、修改或者删除一行(一列)的行为,可以用一个动画和背景色来区分该元素, 过一段时间再恢复正常。

2.3 丝滑的滑动跟随

在不添加任何 CSS 属性时,滑动有滚动条区域时,总感觉有一种卡顿感,就是手指滑动时页面就跟着滑动,手指离开则页面停止滑动。

这里我们添加上一个属性即可:

body {
  -webkit-overflow-scrolling: touch;
}

3. 考虑移动设备的握持姿势

在现在手机屏幕越来越大的趋势下,单手握持手机时,大模板只能在以左下角或者右下角为中心的区域活动。因此,在底部区域操作的情况越来越多,例如底部区域的导航,弹窗中点击空白区域即可关闭等等。

3.1 避免滚动穿透

在一个可滚动的页面中,呼起一个弹窗,这个弹窗中的内容也比较多,也需要滚动,如果不加处理的话,可能会造成两个区域同时滚动,体验不好。也就是避免滚动穿透。

这里我们就要把底层的滚动锁住,只可以滚动处在最上层的区域。这里的原理我就不多讲解,推荐一个我一直在使用的组件tua-body-scroll-lock,该组件导出了 2 个方法:

1.lock: 锁定区域,传入 dom 元素,则表示该 dom 区域内是可以滚动的;

2.unlock: 解除锁定,当弹窗消除时,需要解除被锁定的区域;

在 react 中的使用方式:

useEffect(() => {
  // 锁定body的滚动,只在弹窗内部滚动
  // 只有需要设置可以滚动区域时,才使用该方法
  if (props.scrollContainer) {
    lock(props.scrollContainer);
  }

  return () => {
    if (props.scrollContainer) {
      unlock(props.scrollContainer);
    }
  };
}, [props.scrollContainer]);

同时的,我们最好在遮罩区域添加可以关闭弹窗的操作,避免用户伸手够弹窗右上角的关闭按钮。

3.2 原生 select 标签的使用

在移动端开发中,下拉框我们使用原生 select 标签时,iOS 和 Android 的表现是不一样的,iOS 会出现在屏幕的底部,滚动选择某个选项;而 Android 中,则是屏幕中间弹出一个弹层,然后可以进行选择。

如果图方便的话,其实可以使用原生的 select 标签。但这种方式,总感觉与页面元素之间产生了割裂,因此如果可以的话,尽量模拟出一个 select 标签。

4. 良好的兜底策略

每个用户的设备型号、网络状态等情况都不一样。总会因为各种各样的原因,导致页面展示异常。因此,我们应当做好提示和一些兜底策略。

4.1 全屏沉浸式页面应当保持关闭操作

通常情况下,在移动端 APP 中打开的页面,顶部都会有一个白色的标题栏。但有些活动页面为了更好地沉浸式体验,会把白色标题栏去掉,同时还去掉了右划退出的操作,只能点击自定义的返回按钮才能退出。

例如这个页面,左上角的返回按钮是页面本身自定义的。而这个页面必须是接口正常返回数据后才展示出来,在最开始时,如果有异常时,会展示错误信息,但没有返回按钮。这就导致用户无法退出该活动,只能杀掉 APP 再重新进入。

体验非常不好,这里我们就要保证:全屏沉浸式页面不管是哪种状态,应当全程保持关闭操作!

当然,现在已经没有这个问题了。

4.2 永远不要相信后台一直很稳定

后台经常说的一句话是“不要相信任何从前端传过来的数据”,我们也一样:

永远不要相信后台一直很稳定。

我们要做好接口服务可能会挂掉的预案:

1.设置请求接口的超时时间,不要让用户无限制等待;

2.良好的提示;

3.有条件时,可以自动重试,或者让用户手动尝试重试请求接口;

4.采用兜底策略遮盖;

前 3 种我们都可以理解,当接口异常并无法继续后续的操作时,应当告知用户有服务有异常了,可以稍后重试。

对于第 4 种,通常可能会发生在高并发的抽奖过程中,越是让用户重试,并发量就越高。因此在抽奖异常时,可以直接告诉用户未中奖,而不是“服务异常”之类的话术。要不然,一方面会引起用户的不满,另一方面会造成用户的大量重试。

这个百度在春晚发红包中,就有用到过,在服务器短时间内承受到高并发量时,则直接告诉用户未抽中红包;同时,对于一些抽奖会同时发放多个奖品时,也要做好每个奖品服务都可以会挂掉的准备,比如同时会发放 3 个奖品:

1.服务都正常,正常发放;

2.2 个正常,就只发放 2 个奖品,左右排列;

3.只有 1 个服务正常,则只发放 1 个奖品,居中排列;

4.均异常,则告诉用户未中奖;

千万不要留有空间或者槽位告诉用户“该位置本应该有奖品,但实际上没有”的感觉。

4.3 懒加载

懒加载是一个老生常谈的话题,这里我们只针对图片懒加载来进行梳理。

在页面中图片比较多时,请尽量使用图片懒加载,并考虑好图片加载失败的情况,可以先创建一个 Image 来先加载图片,加载城后再给到页面中的 dom 元素,否则使用兜底图片:

// 判断图片是否可以加载成功
const loadImage = (imgUrl: string): Promise<HTMLImageElement> => {
  return new Promise((resolve, reject) => {
    const img = new Image();
    img.src = imgUrl;
    if (img.complete) {
      return resolve(img);
    }
    img.onload = () => {
      resolve(img);
    };
    img.onerror = reject;
  });
};

// IntersectionObserver的回调,当dom元素进入到可是区域内时
const targetExposeCallback = async (dom: HTMLElement) => {
  let original = dom.getAttribute('data-original');
  if (original) {
    try {
      await loadImage(original);
    } catch (err) {
      // 1x1的图片
      original = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==';
    }
    setLoading(false);
    if (dom.tagName.toLowerCase() === 'img') {
      dom.setAttribute('src', original);
    } else {
      // eslint-disable-next-line
      dom.style.backgroundImage = `url("${original}")`;
    }
    dom.setAttribute('data-original', '');
  }
};

同时,我们在体验的过程中发现,在有些华为手机里,图片还没加载完毕时,会展示一个裂开的图片,如果该图片 alt 注释,也把 alt 注释显示出来,稍过一会儿,等图片加载完毕了,就正常展示图片了。

这种情况,我们也可以使用图片懒加载,或者将图片设置为背景图片,避免出现图片裂开的状态。

5. 总结

我们在移动端开发的过程中,总会有多种解决方案。如果我们站在用户的角度多想一想,就能让产品的交互体验变的更好。

以上就是JS如何让你的移动端交互体验更加优秀的详细内容,更多关于JS让你的移动端交互体验更加优秀的资料请关注我们其它相关文章!

(0)

相关推荐

  • JS+CSS实现的一种交互体验 表单页面

    JS+CSS实现的一种交互体验 @charset "gb2312"; /*元素初始值*/ html {background: #FFF;} body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,form,fieldset,input,p,blockquote,th,td,ins,hr{margin: 0px;padding: 0px;} p{cursor: text;} h1,h2,h3,h4,h5,h6{font-size:100%;} o

  • js验证符合用户体验的网页表单特效

    JS+CSS实现的一种交互体验 @charset "utf-8"; /*元素初始值*/ html {background: #FFF;} body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,form,fieldset,input,p,blockquote,th,td,ins,hr{margin: 0px;padding: 0px;} p{cursor: text;} h1,h2,h3,h4,h5,h6{font-size:100%;} ol

  • JavaScript体验异步更好的解决办法

    一.异步解决方案的进化史 JavaScript的异步操作一直是个麻烦事,所以不断有人提出它的各种解决方案.可以追溯到最早的回调函数(ajax老朋友),到Promise(不算新的朋友),再到ES6的Generator(强劲的朋友). 几年前我们可能用过一个比较著名的Async.js,但是它没有摆脱回调函数,并且错误处理也是按照"回调函数的第一个参数用来传递错误"这样一个约定.而众所周知的回调地狱仍然是一个比较突出的问题,直到Generator改变了这种异步风格. 但是ES7的async

  • JavaScript实现统计文本框Textarea字数增强用户体验

    现在流行的Twitter等微博客网站,有一个很好的用户体验,就是在文本框中输入文字的时候,会自动统计输入的字符,并显示用户还能输入的字符,在限制了140个字的微博客中,这样的小提示可以很好的增强用户体验. 如果实现这种技术呢,我进行了一些研究,发现实现其实挺简单,几行代码就能完成输入字符统计功能,经过实际测试,其对文字的统计与Twitter等微博客的完全相同. 使用方法是,先增加一个span,用于显示剩余的字数,然后在Textarea中,加入一个onkeydown和onkeyup的事件,调用另一

  • Javascript类库的顶层对象名用户体验分析

    由于顶级对象是使用类库几乎所有功能的入口,在编码中会频繁地输入这几个字符,因此在设计类库时,顶级对象名输入的简便性也是相当重要的. 在此将从对象名称的长度.输入对象名称的按键顺序这两个方面对三个类库进行重点比较,同时力求照顾到一些特别的点,从而得出一个较为全面的评测结果. 名称长度 jQuery共有6个字符组成,从长度上而言是三个类库中最长的一个,同时包含了一个大写字母Q,在输入过程中需要加入一次SHIFT键,因此长度值给定为7. Ext共有3个字符组成,在长度上是最有优势的,由于首字母为大写字

  • vue.js 初体验之Chrome 插件开发实录

    背景 对于经常和动画开发打交道的开发者对于Animate.css这个动画库不会陌生,它把一些常见的动画效果都封装起来了,非常实用.但是有时候在开发中,仅仅只是需要某一两个动画效果,把整个CSS文件都引入,这样不是太好. 需求就出现了,能不能有一个工具可以直接预览Animate.css对应的动画效果,并且生成对应的动画代码呢? 作为一个UI开发,平时跟Chrome浏览器打交道最多,于是就整了一个Chrome插件可以及时预览对应Animate.css中的动画效果并生成对应的动画代码,这样在实际开发中

  • nodejs初步体验篇

    前言:写这篇文章的由来: 1.前段时间单位有新项目启动,服务端要做的工作不多也不算麻烦,就是处理一些中间层的服务,而且我们团队里面个个都会JavaScript,领导就决定试试服务器端的JavaScript,结果本人有幸被派去研究了几天Node,怀着鸡冻的心情开始了node.js的篇章,这篇文章也就是为这几天研究的总结. 2.一个JavaScript工程师如果没听过node.js那么我想你是不是错过了什么,每个优秀的前端工程师都有必要去了解后台处理流程,那么如果又能从JavaScript出发,岂不

  • 几个优化WordPress中JavaScript加载体验的插件介绍

    WordPress 本身以及主题和插件通常需要加载一些 JavaScript 来实现某些特殊功能.为了最大限度地保证兼容性,不至于出现 JavaScript 失效的情况,所以一般在页头加载 JavaScript 文件.但是根据 Yahoo 开发者论坛的建议,加载 JavaScript 应该尽量在页尾以提高页面的显示(响应.渲染)速度.本文根据作者的使用经验介绍几个相关插件,并说明如何在某些特殊页面仍然在页头加载 JavaScript. 下面先简单介绍几个相关的优化 JavaScript 的 Wo

  • 用javascript实现改善用户体验之alert提示效果

    类似于新浪邮箱的提示效果.比较独立.在wenming版主的帮助下,已解决了高度不能适应的BUG. 使用方法很简单,在需要弹出提示的页面先引用alert.js脚本文件,如:  程序代码 <script type="text/javascript" src="alert.js"></script> 然后直接在需要提出处使用:  程序代码 sAlert("需要提示的信息"); 即可.不需要额外添加HTML代码. 完整代码: ht

  • JS如何让你的移动端交互体验更加优秀

    1. 即时反馈 我们在玩游戏的过程中,通常都会遇到一个词:"打击感",通俗的理解就是我们做出的每一个操作,都有很强烈的反馈,比如视觉上的动画变化,听觉上产生的声音,或者移动设备的震动感等. 1.1 按钮的即时反馈 在前端页面中,也应当像游戏中的打击感一样,用户任何的操作都应当予以即时的反馈,告诉用户他的操作是有效的,系统已收到他的操作,内部正在处理中. 例如用户在点击页面中的按钮时,按钮最好有一种被按下的效果: button:active { transform: translateY

  • Vue.js与 ASP.NET Core 服务端渲染功能整合

    http://mgyongyosi.com/2016/Vuejs-server-side-rendering-with-aspnet-core/ 原作者:Mihály Gyöngyösi 译者:oopsguy.com 我真的很喜欢在前端使用 Vue.js,Vue 服务端渲染直到第二个版本才被支持. 在本例中,我想展示如何将 Vue.js  服务端渲染功能整合 ASP.NET Core. 我们在服务端使用了 Microsoft.AspNetCore.SpaServices 包,该包提供 ASP.N

  • node.js中ws模块创建服务端和客户端,网页WebSocket客户端

    首先下载websocket模块,命令行输入 npm install ws 1.node.js中ws模块创建服务端 // 加载node上websocket模块 ws; var ws = require("ws"); // 启动基于websocket的服务器,监听我们的客户端接入进来. var server = new ws.Server({ host: "127.0.0.1", port: 6080, }); // 监听接入进来的客户端事件 function webs

  • Spring Boot集成netty实现客户端服务端交互示例详解

    前言 Netty 是一个高性能的 NIO 网络框架,本文主要给大家介绍了关于SpringBoot集成netty实现客户端服务端交互的相关内容,下面来一起看看详细的介绍吧 看了好几天的netty实战,慢慢摸索,虽然还没有摸着很多门道,但今天还是把之前想加入到项目里的 一些想法实现了,算是有点信心了吧(讲真netty对初学者还真的不是很友好......) 首先,当然是在SpringBoot项目里添加netty的依赖了,注意不要用netty5的依赖,因为已经废弃了 <!--netty--> <

  • AngularJS使用$http配置对象方式与服务端交互方法

    如下所示: //$http函数的形参就是一个配置对象 $http({ //method属性表示HTTP请求时的方式,它是一个字符串,值是POST.GET.JSONP.DELETE.PUT和HEAD其中之一 method: //url表示向服务器请求的地址,是一个相对或者绝对的字符串形式 url: //data属性是一个对象,该对象将作为消息体的一部分发送给服务端,常用于POST或PUT数据时使用: data: //params属性是一个字符串对象,当发送HTTP请求时,如果是对象,将自动按jso

  • python 如何用urllib与服务端交互(发送和接收数据)

    urllib是Python3中内置的HTTP请求库,不需要单独安装,官方文档链接如下:https://docs.python.org/3/library/urllib.html从官方文档可以看出,urllib包含4个模块,如图所示. 这4个模块的功能描述如下: request:最基本的HTTP请求模块,可以用来发送HTTP请求,并接收服务端的响应数据.这个过程就像在浏览器地址栏输入URL,然后按Enter键一样. error:异常处理模块,如果出现请求错误,我们可以捕获这些异常,然后根据实际情况

  • java Nio使用NioSocket客户端与服务端交互实现方式

    NioSocket 客户端与服务端交互实现 java Nio是jdk1.4新增的io方式-–nio(new IO),这种方式在目前来说算不算new,更合适的解释应该是non-block IO. non-block是相对于传统的io方式来讲的.传统的Io方式是阻塞的,我们拿网络io来举例,传统的io模型如下: 服务端主线程负责不断地server.accept(),如果没有客户端请求主线程就会阻塞,当客户端请求时,主线程会通过线程池创建一个新的线程执行. 简单解释就是一个线程负责一个客户端的sock

  • Parcel.js + Vue 2.x 极速零配置打包体验教程

    继 Browserify.Webpack 之后,又一款打包工具 Parcel 横空出世 Parcel.js 的官网有这样的自我介绍 "极速零配置Web应用打包工具" 简单接触了一下,单从效率上来说,确实要比 webpack 强上不少,可坑也挺多,未来升级之后应该会逐渐普及 官方文档:https://parceljs.org/getting_started.html 官方 GitHub:https://github.com/parcel-bundler/parcel 一.基本用法 Par

  • js原型链与继承解析(初体验)

    首先定义一个对象obj,该对象的原型为obj._proto_,我们可以用ES5中的getPrototypeOf这一方法来查询obj的原型,我们通过判断obj的原型是否与Object.prototype相等来证明是否存在obj的原型,答案返回true,所以存在.然后我们定义一个函数foo(),任何一个函数都有它的prototype对象,即函数的原型,我们可以在函数的原型上添加任意属性,之后通过new一个实例化的对象可以共享其属性(下面的两个例子会详细介绍). function foo(){} fo

随机推荐