Web App防坑手册

最近几年,随着先后端分离、单页面应用的崛起,网页正变得愈来愈应用化。移动互联网端的发展更是滋长了这个趋势——对于交互、性能不敏感的场景,Web App在开发成本、跨平台兼容上有着明显优点。html

但在这火爆的行情背后,不少时候从产品经理到设计甚至开发,对Web平台的特性并无足够的了解与警觉,致使最终产品成了既不App也不Web的四不像,不只拖累用户体验,开发团队也容易无所适用。前端

这篇文章但愿围绕着Web的特性,探讨Web App与Native App的不一样,帮助读者在项目中尽早地识别出可能出现问题的场景。html5

Web App is NOT App

Web App 也要按照基本法git

Web App或者单页面应用(Single Page Application),名字里带个App,听起来很有鸟枪换炮的味道,不管是工程师仍是利益关系人(产品经理、客户),不免会有和尚摸得我摸不得,App作得我作不得?的错觉。更有甚者,把原生App的设计稿P上个浏览器地址栏就丢给团队,让照着作个Web App出来。若是这是你遇到的状况,那么恭喜,你已经在被坑的路上了程序员

俗话说入乡随俗,既然运行在浏览器上,即便Web App能很大程度上提供接近App的体验,但有几点,是从根本上不一样于App而又必须考虑的:github

1. 应用入口与访问路径

App对用户的访问路径拥有近乎绝对的控制权:用户从哪些地方进入应用(图标、通知栏、分享连接)有限且可预测。而每一页提供的操做入口之和,都是用户当前可访问的子路径的全集后端

Web对用户的访问路径控制权近乎为0:任何页面的URL都是对用户可见的,用户能够添加书签,分享给亲友——任何页面均可能被直接访问,必须在设计和架构层面考虑。浏览器

鉴权和被访问并不矛盾:本文中被访问严格来说是被用户请求,而鉴权或重定向则是对请求的处理服务器

2. 刷新、后退、前进

这三个操做是全部Web浏览器工具栏的标准配置,而App多数状况下只须要考虑返回操做。前端工程师

最麻烦是刷新:它会清空页面内存、从新发起请求、从新执行脚本。这意味着Web端跨页面的内存数据是不可靠的,若是A页面依赖了B页面放在内存中的数据,下次刷新时获得的只会是undefined

3. 上下文隔离

用户可能在应用或站点上进行一系列的、跨页面的操做,这些用户行为称为上下文,而操做产生的数据(好比用户选择或表单输入),本文接下来称做上下文数据

因为App对路径拥有绝对的控制权且不考虑刷新,某种程度上说App打开新的视图和在网页上打开模态窗的成本是同样的,下游页面能够假定上游页面生成的内存数据必定存在,而这些数据彻底经过代码存取,对数据结构没有限制,所以App端的上下文数据是复杂、可依赖的

反观Web,因为每一个页面均可以被直接访问、刷新、甚至是复制连接到另外一台设备上访问,存储在内存、LocalStorage中的数据都所以变得不可依赖,惟一能可靠传递的上下文信息仅限于URL能够表征的数据在没有服务器帮助的状况下,Web端的上下文数据是简单或不可依赖的

现代移动端浏览器提供了一种应用模式,能够屏蔽浏览器工具栏,让页面更像原生App。然而现阶段真实应用的案例并很少见,而且至少有两个问题:

  1. 后退没法彻底屏蔽:安卓平台的系统返回键等同于浏览器的后退

  2. 移动端随时吃紧的内存可能致使当前不活动的页面被内存回收,下次打开时从新加载——至关于一次刷新。

为何URL重要?

上面三点其实都围绕着核心——URL,某种程度上说,整个Web世界都是被URL标识并驱动的,每一个URL都应该定位到相应的资源

而现实的状况是:可能你在作一个面向消费者(如下称2C)的项目,但需求方压根就没提URL和刷新的事;也可能你作的只是一个后台管理系统,不会有人在意这东西,甚至会有一些和URL的定位特性相悖的需求。

这是最可怕的事,也是我写这篇文章的初衷:在真正遇到麻烦前,不多有人(甚至包括资深的前端工程师)会全面的思考这类问题,等掉到坑里的时候才发现积重难返。

在不假思索地接受这些设定前,但愿你能仔细思考如下几个问题:

  • 客户对URL与Web体验间的关系有无概念?

  • 客户是否能把浏览器工具栏操做与真实的业务场景联系起来?

案例:某2C项目

在项目初期,询问客户对刷新的见解时,客户明确表示全部页面,刷新一概回首页,由于客户所参考的另外一个竞品网站就是这么作的。做为技术团队,很容易就此得出没必要兼容刷新的结论。

然而随着了解的深刻,咱们发现客户有以下需求:

  1. 支持邮件推销:在发给用户的邮件里会附带某个产品的连接,打开连接但愿用户能看到相关产品页面

  2. 支持某些页面保存连接:在订单完成后,会为用户生成一个含有二维码的凭证页做为取货凭证。用户能够保存这个页面的连接,并在取货时(多是几天后)直接打开。

以上两个需求,从技术角度看和支持刷新是等价的:都要求能仅经过URL定位并展现页面资源。

然而先后沟通的结果却彻底不一样,若是技术团队按照最开始的结果作架构,到最后必定会付出代价。

回过头看,一开始客户给出的答案并不是刚性需求,背后没有业务价值,只是由于看到别人这么作而已,甚至客户还觉得本身主动削减了需求,为开发团队省了事。而这样的需求,后期改变的可能性和弹性都是很是大的。

后面两条,则是包含业务价值的刚性需求,直接影响到客户利益,几乎没有任何讨价还价的余地。

要避免这类问题,最重要的是警戒涉及技术架构的非刚性需求,从多种角度进行确认,引导并挖掘出更多的相关场景。客户是纯业务的,对不一样业务场景间的技术共性没有概念。而如何引导客户、挖掘真实需求,则是团队专业性最直接的体现

在上面这个案例中,当客户说”不用考虑刷新,一概回首页“的时候,团队只要多问一句”那有没有须要直接用URL打开的页面呢?“,就极可能引导出更真实的需求。客户并不知道二者在技术上等价,开发团队这时候须要承担起引导的责任。

当心模态窗

一个典型的模态窗(Modal)以下图所示:
img

上下文的黑魔法

它经过模仿窗口的方式,容许用户在不脱离当前页面上下文的状况下进行操做。同时它也有必定的阻塞性,可用来控制用户的操做流程,好比有的电商网站会经过模态窗来引导用户先选择所在地区再继续购物。

以前咱们讲过Web页面间的上下文是强隔离的,而模态窗在不脱离上下文的前提下能承载的内容很是可观,可观到什么承度呢?若是把模态窗的长宽设置为填满当前页面,那它看起来会和新的页面没什么两样!至关于额外100%的内容承载力!

这既提供了设计上的灵活性,也潜藏着风险:既然模态窗能够承载和新页面同样多的内容,为何不直接用它代替页面呢?和跳转到新页面相比,它还能够保持当前页的全部状态:表单内容、滚动位置……

谨防超载

问题在于,若是须要用URL定位到模态窗中的内容,渲染的顺序必定是先有模态依附的页面主体,再有模态窗。URL不只须要携带模态窗的相关信息,连页面主体的也得带上。要是模态套模态,套得越深,还原页面的所需信息越复杂,离URL友好也就越远。

这就像平房和楼房,虽然楼房利用了纵向空间,但想进门必定要从底楼一层层上去。空中楼阁是不存在的,模态窗也不可能脱离本身依附的主体独立存在。

案例:某后台管理系统

在我刚毕业的时候接触过一个后台管理系统,因为是小公司,彻底没有Web设计的相关经验。

那个系统的设计几乎是彻底忽略URL的,一开始是翻页等重要信息没有体如今URL中,致使资源没法定位,一旦脱离某个模块,则在该模块下的全部操做都得重来。

为了解决这个问题,设计(实际上是非设计出身的领导)给出的方案是用模态窗——既然找回上下文困难,不脱离上下文不就好了。

然而这时候的模态就像毒品:想要查看设备的子设备列表?开个模态窗;想看某个子设备的运行情况?再开个模态窗。就这样模态窗套模态窗,有碍观瞻是小事,当某天你意识到Web的世界还有URL这回事的时候,或者客户报怨说想经过打开保存的连接就能看到某个子设备的时候,要改回来已经几乎不可能了。

要避免相似的问题,至少须要在设计阶段(或开发review设计时)注意如下两点:

1. 控制内容复杂度

模态承载的内容和功能应尽可能单一。若是是但愿用URL定位的资源,尽可能不要设计到模态窗中

例如登陆窗、或者展现商家地图位置,都是适合模态窗的场景。而像订单这样的资源,因为资源间关系复杂,极可能再出现显示子资源或关联资源的需求,用模态窗展现的话未来就很容易嵌套。

有种特殊状况是资源自己有URL友好的页面显示,为了用户体验在其它地方也用模态窗展现该资源的信息,这样是没有问题的,由于模态窗并不须要被URL定位

2. 考虑新标签中打开连接

不想脱离当面页面又想访问其它资源?其实Web早就想到了,这就是你天天都在使用的新标签页打开连接

好比订单列表页,每一个订单要有订单详情的功能入口,而用户多是经过复杂的搜索条件以及漫长的鼠标滚动才看到当前的列表项,并不但愿脱离当前页面。把订单详情放模态?缺点前面已经讲过了,更好的方式是在新窗口中打开订单详情页,用户不只不会脱离当前页面,甚至能够同时打开多个子页面,比不能并发的模态高到不知道哪里去了。

伪页面

若是说内容超载的问题主要发生在桌面端,那在移动端还有另外一个涉及模态窗的虚胖问题,我的称这种设计为伪页面,如图所示:

ctrip_home
ctrip_datepicker

第一个页面是主页,点击箭头所示的日期会打开选择日期的模态窗。你能够亲自上携程移动端查看并操做。

相似设计移动端很是常见:因为屏幕过小,有些功能本质虽然单一(如城市选择、日期选择,本质上都是个选择框),体如今UI上仍然须要占满全屏。但这些功能(特别是表单类操做)又绝对不能脱离上下文存在,最终只能选择用模态窗来处理。

这就产生了矛盾:全屏显示的功能很容易给用户“这是一个页面”的错觉,而实际上它并非。这个矛盾在面对刷新后退前进等操做时尤为明显:

  • 当模态窗打开的时候,点击刷新,要不要从新打开模态窗?

  • 当模态窗打开的时候,浏览器后退是关闭模态窗仍是后退到真实的上一页?

  • 若是后退能够关闭模态窗,那前进是否能够打开它?

感兴趣的朋友不妨在刚才携程的连接上将以上几点操做一番,亲身感觉。

这些细节是需求分析和设计阶段很难考虑全面的,每每遇到问题了才开始头痛医头脚痛医脚,最终不管对用户体验仍是技术团队,都很容易形成伤害。

解决方案?

我的对此并无十全十美的解决方案:全屏显示的需求与功能并不是页面的本质,这二者都没法轻易解决。假如本文能让你在正式开发前就想到这个问题,对我来讲已是功德一件了。

不过也并不是彻底没有办法,既占满屏幕,又能暗示背景上下文的设计从iOS 7就开始流行了——没错,就是毛玻璃效果

粗略地改了一下携程的日期选择界面样式,以下图所示:

datepicker_imporved

经过毛玻璃效果消除了弹出层是新页面的错觉后,用户就不容易对刷新后退等操做出现错误的指望。

加载资源之What - When - How

要作到URL友好,一个必要条件就是页面能自行加载全部须要的外部资源

What

什么是外部资源?不就是服务器端数据吗?对,可是不全对。

随着接口的无状态化,Web App会保存一些本来在服务器端存在的状态,例如登陆信息。对每一个页面而言,这些跨页面共享的状态也应该视做页面的外部资源

这听起来有点奇怪:页面就是Web App的一部分,却要把Web App的状态视做外部资源

其实很好理解,核心仍然在于:组成Web App的每一个页面,其上下文是隔离的。

还记得文章开头的基本差别第三条吧?以用户登陆信息为例,即便你把信息放进了localStorage以保证刷新后可访问,也挡不住用户在其它浏览器访问同一个连接,甚至干脆把连接分享给别人——这意味你不能假定这些数据的可靠性,对单个页面而言,Web App的状态与服务器资源同样是不可知的黑盒。

所以,即便你能够像访问内存同样用同步代码访问localStorage,我仍然建议你把Web App的状态管理与API请求放到相同的层级进行考量,这样能更好地反映对问题本质的抽象。

插播技术观点一则:localStorage规范同步IO更像是历史遗留问题。将来必定会被异步存取的标准或第三方库取代(火狐就搞了一个异步的localforage),将应用状态与API同等对待的向前兼容性更优。

When

这个问题看起来没什么好说的——固然是在页面加载时。

但若是需求方给你的是App的设计稿,那请务必注意:App的设计有多是在上个页面加载下一页的数据,再根据结果决定是否跳转到下个页面;而Web因为URL的独立可访问性,一般是先跳转到目标页面再加载数据

这带来的第一个差别就是Loading进度的显示问题:App能够在上一个页面显示Loading,而Web则不建议这么作。缘由很简单,页面本身能加载资源,上个页面再帮它加载,结果必定是要么浪费资源,要么增长复杂度。

目前多数App的设计和Web同样是先跳转再加载,然而先加载再跳转的设计也是存在的,好比下图国外某航空公司App:

sa_loading

点击“Find flights”,会出现如图所示的小菊花,等到加载结束才跳离当前页面。

而携程移动版(不管Web仍是App)就是标准的页面自行显示loading:

ctrip_loading

How about failed?

第二个差别则是如何处理加载失败。在App端,若是由上一页面加载下个页面的数据,则错误的处理(例如弹窗)也会在上个页面显示,下游页面从设计上基本不会考虑加载失败时的渲染问题。

图片描述

上图是点击"Find flights"请求失败后,页面没有跳转,直接在当前页提示错误。

做为对比,携程的移动端如图:

图片描述

从以上截图也能够看出来,国外航空App的设计若是作成网站,是很难保证URL友好的。稍微总结一下的话就是:

Web因为URL的独立可访问,每一个页面都必须考虑加载中、加载失败的时候如何显示,若是设计给你的只有成功的场景,请立刻找他从新核对需求。

结语

苟利老板亏盈以,岂因祸福避趋之

工程即妥协,有时候为了更好的用户体验,上述任何一点均可能牺牲。本文的目的不是造成教条,而是帮助你在权衡利弊时能想得更加清楚。

更况且,不管是Web仍是App,都在高速发展并相互影响。浏览器赋予开发者更加丰富的、接近App的接口,而App也在借鉴Web的资源定位机制。

另外一方面,本文所讨论的实际上是处于设计与技术间的灰色地带,甚至须要工程师去挑战和影响设计。这是由于团队分工程度越高,这些灰色地带的衔接就越可能致使项目失败。我的很是鼓励程序员,特别是前端工程师去思考业务和设计,乃至于输出影响力。前端开发的本质是data -> design的函数,只有深刻地理解输入与输出,甚至主动出击修正误差,才能交付真正的价值。

相关文章
相关标签/搜索