• 学完这 4 个小技巧,让你的移动端交互体验更加优秀


    现在在手机等移动端设备访问的人越来越多,我们前端开发者一直致力于将设计稿还原成页面,供用户访问。但除高度还原设计稿外,交互上的良好体验也是我们应该做到的。

    玩玩手机

    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 个方法:

    • lock: 锁定区域,传入 dom 元素,则表示该 dom 区域内是可以滚动的;
    • 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 标签。但这种方式,总感觉与页面元素之间产生了割裂,因此如果可以的话,尽量模拟出一个 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. 总结

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

    感谢您的阅读,欢迎关注我的公众号:

    蚊子的博客-公众号

  • 相关阅读:
    Google Map Api V3 系列之 导航(包括清除线路)
    ENSP学习华为防火墙功能
    让Chrome 浏览器显示隐藏的https和www
    win7 x64 SP1把IE从8升级到11的先决条件
    解决“Windows照片查看器无法显示此图片,因为计算机上的可用内存可能不足……”
    火绒软件没有离线病毒库独立安装包的解决方法
    成功注册GitHub20211116
    网络基础知识积累
    【问题解决】win10连接了不可路由的以太网后,会阻止使用 WWAN 访问 Internet
    ENSP学习华为防火墙(第二天,20211127 )
  • 原文地址:https://www.cnblogs.com/xumengxuan/p/14474706.html
Copyright © 2020-2023  润新知