首页

为何如今的产品总给人千人一面的感觉?

博博

如果您想订阅本博客内容,每天自动发到您的邮箱中, 请点这里

编者按:这篇来自产品设计师 Eugen Eşanu 的文章解开了我长久以来的一个疑惑,其中的思考和经验值得我们共勉。结尾的 One more thing 是我额外补充的内容和一点想法,和当下产品设计的困境相关,也是试图补完这篇文章,希望能给你一点帮助。

为何如今的产品总给人千人一面的感觉?

当你在寻找一个能够满足你需求的应用的时候,会不会因为太多相似的产品而无从选择?当你想要在两个甚至更多相似的产品中进行选择,一切都显得那么困难。而唯一能够进行快速区分的因素似乎是设计,但是紧接着会发现连设计都是那么相似。

为什么市场上所有的应用看起来都长的同一副面孔呢?

在回答这个问题之前,我想简单澄清一些事情。为了创造出能够解决需求的产品,通常需要采用以人为本的方法来进行设计,但是事实上,那种状态太过于理想,几乎没人能够真正做到。

以人为本的设计方法,确实能够极富创造性地解决问题。当采用这种方法来为目标用户进行设计,最终能够得到了一个为这些用户量身定制的解决方案。但是,这种设计方法需要设计者具备无与伦比的同理心来真正站在目标用户的角度来思考问题,由此获得大量的想法和灵感,建立一大堆可能有效的原型,同目标用户分享你正在做的事情,验证你的感受和想法,最终将你的创新的解决方案推向世界。但是这是理想的状况。

那么为什么世界上那么多伟大的公司,依然无法真正采用这样的方法成就真正优秀的产品呢?因为几乎所有的产品身上都有一种病毒,它的名字叫做特征蔓延。

为何如今的产品总给人千人一面的感觉?

产品开发流行病:特征蔓延

特征蔓延的英文名叫做 Feature creep,它的主要症状就是不断向产品中添加功能。

现如今,绝大多数的产品,无论是物理实体还是存在于手机中的各色 APP,几乎全都面对着激烈的竞争。

激烈的竞争使得每个人都面临着压力,产品所属的企业中,从领导到产品经理再到最底层的开发者和设计师,所有人都面对着来自对手和潜在对手的压力。

这种竞争压力基本上是源自于三个方面:价格,功能和质量。非常不幸的是,绝大多数时候,这三者的压力从前到后是依次递减的。价格竞争的压力通常是最大也是最直接的,然后才是功能和产品质量,至于这个顺序意味着什么,就不赘述了。

同时,产品上线的速度和顺序也很重要。谁是进入市场第一人,这个是必须争一下的。

这样一来,想要尽快杀入市场的产品,在很大程度上是伴随着「偷工减料」的。没有足够的时间来对产品进行足够多的迭代,没有办法把系统调整到最优,没有办法把硬件缺陷都找出来,没有办法把软件中每一个 Bug 都尽量找出来,甚至绝大多数企业的领导都抱有「没事,我们随后再把问题找出来解决掉。」从电脑到汽车,从 Windows 到 iOS,从来都是如此。

没有什么 Bug 是一个补丁解决不了的,如果不行,多打几个。——Windows开发团队

为何如今的产品总给人千人一面的感觉?

当然,能够通过后期补丁解决的问题终究是少数。实际市场上绝大多数的产品确实随着补丁和修改逐步提升了,但是绝大多数仍然没有解决用户的问题。

对于特征蔓延这种病症,早在1976年就已经被发现,并且在产品设计圈当中有着非常广泛的认知。对于这种产品病有一个非常学术的描述:

特征蔓延是指一产品(如APP)的软件机能持续膨胀或增加的情形。产品基本机能以外的扩充机能,会使产品比原始设计要更复杂。长时间来看,额外或不需要的机能慢慢的进入系统中,使系统超出原来设定的目标。

假设我们拥有一个非常强大的产品团队,设计师拥有足够的同理心,开发和测试也非常靠谱,他们使用以人为中心的设计方法,探索了所有用户使用场景,并且遵循产品流程认真设计仔细测试,最终输出了一个用户想要购买的优质产品。假设这个问世的产品在各个层面上都是完美的:拥有直观的界面,良好的感觉和优秀的视觉,贴合用户的体验等等。它只有一个使命,那就是以有意义的方式满足人们的需求,让人们能够更好地生活,产品因此而走向成功。谁都想来一个。(就像解决了信号问题的 iPhone 4)

非常不幸的是,产品上市之后,各种各样的影响因素开始出现,情况开始向着并不理想的方向发展。

  • 现有的用户非常喜欢这款产品,但是他们想要更多的功能,各种各样的功能,包括带孩子。
  • 竞争对手开始推出新款,并且具备一些全新的、我们的产品所不具备的功能。从公司领导到用户都开始催我们的团队增加新功能。
  • 客户对于产品总体上是满意,但是买的人多了之后,市场趋于饱和,销售额不可避免的下降了。是时候添加新的或者创新的功能来促使用户更新或购买新版本了。

特征蔓延就是这样出现的,产品在现有的功能上不断增加更多的功能。各种各样的理由会促使产品不得不增加功能,各种各样。然后产品开始膨胀,臃肿,直到用户实在不太想用或者彻底没法用。

而在如今的市场和商业竞争中,特征蔓延并不是唯一的绝症。

为何如今的产品总给人千人一面的感觉?

竞争驱动式设计的泥潭

哈佛大学教授 Youngme Moon 认为,产品和竞品之间的攀比对抗是让产品陷入千篇一律的境地的主要原因。通常,企业为了提升产品的市场份额,会让自家产品拥有对手一样的功能,来确保竞争力。对手的团队协同软件的售价是20美元?没事,我们开发个功能一样强大的,定价15美元好了。他们的移动端的 APP 只需要加1美元就能获得?那我们的移动端版本直接免费和桌面端绑定好了。

当产品陷入和对手刺刀见红的局面之时,两败俱伤的结果就不远了。试图逐个功能和对手竞争,必然会陷入同质化的竞争,很难让用户真正爱上其中的某个产品。

这种就是竞争驱动型设计。令人遗憾的是,即使产品的第一版是以用户为中心设计出来的优质产品,在竞争驱动下诞生的后续产品,就很难赢得用户的真心了。

很多时候如果你想创造真正卓越的产品,你不得不花费更多的时间。而即使你创造出来这样的产品,在市场上也不一定能在销售排行榜上杀入前三,屈居第四是很正常的事情。那么你还愿意这么做么?

我们都听说过先发优势,但是你真的知道获得先发优势,将会付出什么样的代价么?

为何如今的产品总给人千人一面的感觉?

用1年写一册畅销书,还是花5年成就一本经典?

以写书来举例也许更加直观。当你决定写一本关于设计的书,然后登上畅销榜,名利双收,好像挺不错的。如果它的内容是之前没人写过的,并且确实比较有市场,在内容上稍加打磨,几个月后引爆设计圈似乎不是太大的问题。事实上,它上架之后,和自己的预期相差不远,挺好。不过,不知道为什么,1年之后,热点消退,这本书也没什么人买了。

换一个做法,写书的过程中可能需要勒紧裤腰带过日子,花上5年时间仔细揣摩,写一本拥有持续价值的书,然后在之后的100年时间当中,成为设计圈中每个设计师的必读书。写书的过程中,你需要专门地进行研究,仔细地调整内容,对于每个细节都精雕细琢,花费更多的时间,让这本书有对抗时间的价值。

从长远来看,只看眼前、偷工减料的公司会自然而然过时,然后被人们所忘记。浪潮过后,谁在裸泳一目了然。

为什么总忍不住添加新的功能?

和对手的对比,总能看到自家产品的缺陷,然后补完缺陷,这有什么不对么?似乎没问题,但是这种思维方式并不会打造更好的产品。更好的策略是:

专注于自家真正擅长的领域,并继续深挖自己的长处。在自己的优势领域,集中自己的研发能力,并作为营销重点。这样才能让自己的产品从平庸走向卓越,真正能在惨烈的红海中脱颖而出。用更少更精锐的功能来成就自己,而不是在成堆的功能中与对手同归于尽。

还记得一代的 Google Pixel Phone 么?他们的营销口号是「有耳机插孔的手机」,对标的正是取消耳机插孔的 iPhone ,而这个文案正是嘲讽 iPhone 的设计太过愚蠢。令人惊讶的是,随后的 Google Pixel 2 也跟着取消了耳机插孔。现在还有谁记得 Google Pixel 系列呢?

为何如今的产品总给人千人一面的感觉?

是聚焦长处,还是忙于跟随?

伟大的设计需要脱离苟且的竞争和来自客户的压力。开始专注于你认为重要的事情,以及你目光所及的远方。在你最优秀的地方,集中精力。你必须确保你的产品是一致且连贯的。这意味着领导层需要足够睿智,也足够坚定,这样才能抵挡来自用户和市场部门不断增加产品功能的各种需求。

成就最好的产品,设计者要屏蔽来自竞争对手的声音,专注于用户真实的深层需求。

不要觉得我的话是凭空而来的。Amazon 的 CEO 兼创始人 Jeff Bezos 也提到过类似的方法,被称为「客户迷恋」。在他看来,将所有精力集中在客户的需求上,而不是竞争。重点在于三个简单的问题上:「客户需要的是什么?」「他们的需求如何才能满足」「我们可以做什么来提高客户服务和价值?」Bezos 还认为,专注于客户才是首要目标,其他的问题会迎刃而解的。如果你一开始就被市场竞争吸引了注意力,很难作出真正对的决策。产品质量通常只关乎客户和解决真实的需求。

One more thing

想要从激烈的竞争中抽身出来, 创造真正独特的产品,是许多产品设计师的愿望。但是这真的不是一句话说得清楚的事情,真实的情况比我们想象中还要复杂。

竞争驱动型的设计确实是一个很难绕过的问题,驱动产品的不仅仅是领导和客户的声音,深陷市场竞争,许多时候确实身不由己。

采用以用户为中心的设计的设计流程,还绕不开一个常见的因素:最佳实践。经过前人验证、时间打磨、用户习惯之后所获得的最佳实践,是设计师和产品在很多时候必须依托的东西。比如我们如今所看到的许多表单的设计策略和汉堡菜单的使用。用户和市场已经「塑造」出了许多最优的设计策略,设计师通常会直接拿来使用,而我们感到「千人一面」的 UI设计当中,确实有各种最佳实践所「作出的贡献」。

为何如今的产品总给人千人一面的感觉?

包括我们现在正在不断探索的动效设计,虽然力图在视觉和体验上有所创新,但是早在近百年前,迪士尼的动画设计师们已经总结出一套人性化动画设计的策略,实际上我们今天许多优秀的动效设计,依然是围绕着这一套「最佳实践」来进行设计和重设计的。

为何如今的产品总给人千人一面的感觉?

违反「最佳实践」的特立独行的设计并非不可,只不过它通常需要遵循「每次仅只能打破一个规则」的原则。在用户已经被市场培养出大量习惯的前提之下,大量采用反直觉、反习惯的设计,只会让产品死的更快。

那么是不是就没有办法了呢?当然不是。新鲜有趣的、独树一帜的设计并非没有办法设计,设计师需要在最佳实践以外的部分寻求改变,探索可能性,甚至等待契机。不同的设计趋势、元素、技法、排版布局、配色、动效进行多样化的组合,依然可以创造出让人眼前一亮的设计。但是这还不够。

为何如今的产品总给人千人一面的感觉?

早在上世纪30年代的时候,包豪斯设计学院的先哲们就已经提出过「形式追随功能」的设计箴言。这句话强调的是设计的科学性,便捷性和经济效益,而不再是围绕着装饰性和简单的形式感来进行设计。在今天这个用户体验至上、以用户为中心的设计趋势之下,UI 和视觉所代表的「形式」所追随的「功能」应该是用户的真实需求。而在这个语境下试图创新,或者恰如其分地融入一些贴合场景和目标的艺术元素,也许是个不错的突破口。

正如同在上一篇文章《熟知用户行为的这7个层面,你的设计才会走进人心》中所说的,客户买钻头的时候,需要的并非钻头而是洞,同样的,用户下载一款阅读APP 的时候,他的真实需要并非是阅读器,他需要的是内容,是信息,是满足他求知欲的文章,或者填补碎片时间的有趣的故事。

为何如今的产品总给人千人一面的感觉?

内容为王这句话早就已经不是口号了。围绕着内容做设计已经成为了诸如 Medium 和 Twtter 这样的企业的新策略,而国内的许多一线企业也开始拥有自己的「首席内容官」。让设计追随内容,让真正吸引用户的东西来撬动产品,拉升体量,才是正途。

设计和内容的结合方式多种多样,根据内容所需要的语境来搭配相应的设计是目前已知的最常见的做法。根据内容本身所具备的故事性和环境特征,视觉化地表达,让 UI 和视觉服务于内容,是许多成功的设计所验证过的技巧。

比如下面的 voyage-electrique这个网站,借助 C4D 构建的 3D 可交互式的场景来呈现电力系统的运作,清新可爱的画风和令人愉悦的音乐让原本沉闷的主题显得颇为有趣,让人心生好感的设计,使得相关的信息更容易被用户接受。流程化的信息呈现方式隐约具备了故事性的表达,即使你并不懂法语也会流连忘返,在点击和探索中多停留一会儿。语言苍白,不如点进去看看。

为何如今的产品总给人千人一面的感觉?

最后需要注意一个问题:人类先天就是喜新厌旧的生物,再新鲜有趣的东西,在获得之后都会快速地适应(适应性认知偏差),并不再感到新鲜。在内容和设计策略上,适时地注入新鲜的内容(不违反基本设计规则和产品方向的前提下),是维持活跃度而必须做的事情。

原文作者 : Eugen Eşanu

译者/编辑 : 陈子木

译文地址:https://www.uisdc.com/design-product-like-everyone-else

蓝蓝设计www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 平面设计服务

js学习中的总结——几种继承模式

seo达人

如果您想订阅本博客内容,每天自动发到您的邮箱中, 请点这里

     js中构造函数的几种继承模式浅析

一、原型链模式继承

    利用原型让一个引用类型继承另一个引用类型的属性和方法 。

    用的最多。

    缺点:不可传参,不可多继承。


        
  1. function People(name, age) {//添加公有属性
  2. name = name || 'xiaolan';
  3. age = age || 18;
  4. this.name = name;
  5. this.age = age;
  6. }//创建一个名为People的类
  7. People.prototype.eat = function() {//添加私有属性
  8. console.log(this.name + '贼能吃');
  9. }
  10. function Cat(color) {//创建一个名为Cat的类
  11. this.color = color;
  12. }
  13. Cat.prototype = new People('小叮当', 200);//实例化一个People类,并赋值给Cat类的原型链
  14. var cat = new Cat('蓝白色')
  15. console.log(cat.name)//'小叮当'
  16. cat.eat();//'小叮当贼能吃'

二、混合模式继承

    用call的方法只能继承私有属性,所以再加一遍一遍原型链模式继承,原型链模式继承又把私有属性和公有属性都继承了一遍。


        
  1. function People(name, age) { //创建一个父级People类
  2. name = name || 'xiaolan';
  3. age = age || 18;
  4. this.name = name;
  5. this.age = age;
  6. }
  7. People.prototype.eat = function() {
  8. console.log(this.name + '贼能吃');
  9. }
  10. function Cat(color, name, age) {
  11. this.color = color;
  12. People.call(this, name, age); //通过call的形式继承
  13. //通过call(this),将People的指向改为Cat的实例
  14. }
  15. var cat = new Cat('蓝白色', '小叮当', 1);
  16. console.log(cat.name);//'小叮当'
  17. cat.eat();//报错,
  18. //继承不了公有属性,所以cat.eat()会报错;

为了继承公有属性,用原型链模式在把公有属性和方法继承过来,


        
  1. function People(name, age) { //创建一个父级People类
  2. name = name || 'xiaolan';
  3. age = age || 18;
  4. this.name = name;
  5. this.age = age;
  6. }
  7. People.prototype.eat = function() {
  8. console.log(this.name + '贼能吃');
  9. }
  10. function Cat(color, name, age) {
  11. this.color = color;
  12. People.call(this, name, age); //通过call的形式继承
  13. //通过call(this),将People的指向改为Cat的实例
  14. }
  15. Cat.prototype = new People()
  16. var cat = new Cat('蓝白色', '小叮当', 200)
  17. console.log(cat)
  18. console.log(cat.name); //'小叮当',在原型链继承的时候,就近原则,cat.name 先找到'小叮当',就不往下找了
  19. cat.eat(); //'小叮当贼能吃'

三、拷贝继承

    优点:可以多继承,可传参;

    缺点:浪费资源,不能判断父级;


        
  1. function People(name, age) { //创建一个父级People类
  2. name = name || 'xiaolan';
  3. age = age || 18;
  4. this.name = name;
  5. this.age = age;
  6. }
  7. People.prototype.eat = function() {
  8. console.log(this.name + '贼能吃');
  9. }
  10. function Cat(color, name, age) {
  11. this.color = color;
  12. var people = new People(name, age) //实例化一个People类
  13. for (let i in people) {
  14. this[i] = people[i]; //将people中的可枚举属性和方法遍历并附给Cat类,公有属性和私有属性都是可枚举属性;
  15. }
  16. }
  17. var cat = new Cat('蓝白色', '小叮当', 2);
  18. console.log(cat.name); //小叮当
  19. cat.eat(); //小叮当贼能吃

四、寄生组合方式继承

    优点:私有属性和公有属性都单独继承,可以传参;

    私有属性可以多继承,公有属性不可多继承;


        
  1. function People(name, age) {
  2. name = name || 'xiaolan';
  3. age = age || 18;
  4. this.name = name;
  5. this.age = age;
  6. }
  7. People.prototype.eat = function() {
  8. console.log(this.name + '贼能吃');
  9. }
  10. function Cat(color, name, age) {
  11. this.color = color;
  12. People.call(this, name, age) //用call的形式把私有属性继承过来
  13. }
  14. function Fn() {} //创建一个中间构造函数,用来接收People的公有属性,为了防止创建实例Cat实例是影响原来的people构造函数
  15. Fn.prototype = People.prototype;
  16. Cat.prototype = new Fn(); //将中间构造函数Fn继承people的公有属性传给Cat的原型链
  17. Cat.prototype.constructor = Cat; //由于上一步重置了Cat原型链的constructor属性,所以要重新给赋回来;
  18. var cat = new Cat('蓝白色', '小叮当', 3);
  19. console.log(cat.name); //'小叮当'
  20. cat.eat() //'小叮当贼能吃


注:若有不严谨与错误的地方,请多指教!






  1. 这里写图片描述



蓝蓝设计www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 平面设计服务


经验总结!Material Design和iOS 产品设计的差异化思考

资深UI设计者


如果您想订阅本博客内容,每天自动发到您的邮箱中, 请点这里

Material Design(以下简称MD)是谷歌设计的一套视觉语言设计规范,主要应用于安卓类应用。


iOS Human Interface Guidelines(以下简称iOS)是苹果公司针对 iOS设计的一套人机交互指南,目的是为了使运行在 iOS 上的应用都能遵从一套特定的视觉以及交互特性,从而能够在风格上进行统一。

相对来说,我们对于 iOS 的设计规范更加熟悉——因为考虑到成本,设计师进行产品设计的时候只会出一套 iOS 的设计稿,然后去适配安卓机型。

很少会针对安卓机型再出一套 MD风格的方案,这种现象虽然存在但是并不合理。不同的系统/平台采用了不同的设计语言,不同的设计语言会培养出不同的操作习惯。

例如使用安卓手机的用户平时见到的都是 MD风格的界面,突然下了一个 iOS风格的应用,那么操作起来就会很不习惯,增加了学习成本。

为了让产品可以适用不同平台用户的操作习惯,提供 MD 和 iOS 两套设计稿是非常有必要的。当然 MD 和 iOS 的差异不是一篇文章就能说清楚的,这里我就从阴影、导航和配色这三个方面来简单分析一下 MD 和 iOS 的差异。

一、阴影

对于不太熟悉 MD 的设计师来说,MD 意味着大色块+阴影。

为什么 MD 如此痴迷于阴影?

从它的名字就可以看出来——Material Design,这里的 material 指的是纸张;因为来源于现实生活,所以 MD 非常喜欢使用真实世界元素的物理规律与空间关系来表现组件之间的层级关系,而阴影就是最常见的表现形式:

同样的账户注册,安卓界面中按钮带有阴影而 iOS 界面中按钮没有阴影。

FAB(Floating Action Button),带有阴影的浮动操作按钮甚至成为了 MD 的一大招牌。相比较而言iOS更加扁平化。

二、导航体系

产品导航体系主要由菜单栏构成,而根据位置以及交互方式可以分为顶部栏菜单、底部栏菜单和侧边栏菜单。iOS 的导航体系主要由底部栏菜单构成,而 MD 大量使用了顶部栏菜单和侧边栏菜单。

下面我们来看几个例子:

网易云音乐在 iOS 中采用的是底部栏菜单导航,而在安卓机型上导航栏被移到界面顶部,「账号」被收到侧边栏中。

b站在 iOS 中有底部栏菜单有5个标签,而在安卓机型中只有4个标签,「我的」同样被侧边栏收起来。

推特在 iOS 中使用的底部栏菜单导航,在安卓机型中导航栏被移到了顶部。

而 Apple Music 做的更彻底,在安卓机型上直接舍弃了底部菜单栏,采用了侧边栏作为主导航模式。

我们发现前两款国产应用在安卓机型上都保留了底部栏菜单,而后两款国外应用直接砍掉底部栏菜单。不只是 Apple music 和推特,很多国外的安卓类应用都没有使用底部栏菜单。而国内的应用因为考虑到 iOS,即使 MD 化也是阉割版的,属于 iOS 和 MD 的混血儿。甚至很多安卓应用在设计风格上往 iOS 靠拢,以b站为例,其5.11之前的安卓版本中都是没有底部栏菜单的。

当然这里并不是去评价 MD 和 iOS 哪个导航体系更好用,我说下自己的观点:

底部栏菜单的使用非常方便用户在单手握持情况下的操作,但是对于大屏手机来说,单手操作会显得很吃力。如果用户改由双手握持手机,那么从易用性上来说底部栏菜单没有任何优势。

MD 的方案更加注重对界面空间的利用,侧边栏菜单就不说了。以底部栏菜单为例,在安卓机型中用户滑动的时候底部栏菜单会隐藏,方便的用户可以看下知乎,安卓版的底部栏用户滑动的时候会隐藏,而 iOS 则是固定不动的。

侧边栏的优势还体现在可以提供更多的标签,底部栏菜单最多可以放5个。但是侧边栏菜单需要用户点击才能调出来,比较隐蔽,而底部栏和顶部栏相对来说就会显得一目了然,总之各有千秋。

至于为什么 MD 会抛弃底部栏菜单,我个人的理解是设备原因。因为安卓机型底部有三个物理按键,如果采用底部栏菜单作为主导航模式,容易造成用户误点击。

类似的还有 web 类应用,因为浏览器的控件栏也在底部,所以如果采用了底部栏菜单同样会造成用户的误操作。

三、配色

MD 提倡使用高饱和度的对比色来提升产品的视觉表现力,也就是我在上面提到的大色块。同样的一个功能,从底部栏背景色、插画到按钮,我们可以发现 iOS 在色彩的使用上比较克制。

知乎之前的安卓版本使用了大面积的蓝色,后来改成跟 iOS 一样的白色。这样的调整褒贬不一,有的用户反馈这完全照搬了 iOS,要改回 MD。

产品界面设计中对比效果主要由配色、尺寸、间距和阴影来完成。MD 在配色和阴影上做的比较出彩,所以 MD风格的产品在视觉表现上更有冲击力。而 iOS 则显得比较小清新,追求扁平和简洁。所以我们无法去评判这两款设计规范的孰好孰坏,因为其各自的出发点就是不一样的。

当然前面也提到了 MD 和 iOS 的差异不仅仅体现在以上说的这三点,还有一些小细节也非常值得我们思考。我们都知道在 iOS系统中,用户向右滑动的时候会返回上一级页面。因为苹果手机没有物理返回按键,所以这种机制非常受欢迎,但是在一些特定场景的时候就会有问题。例如如果我想复制微博里的「一曲肝肠断,天涯何处觅知音」,选中光标从左向右滑动,这时就会返回上一级页面,特别不方便。所以我只能从右向左进行复制,我后来试了一下微信和 QQ,发现默认复制的是整条动态,这也算是一个折衷的方案。

总结

这篇文章并不是去评判 iOS 和 MD 两种设计风格孰好孰坏,也不是让我们现在就去为自己的产品做出两套设计稿,这个目前来说也不太现实。很多国民类应用都只采用了一套设计稿,大家都这么做,整个大环境就是这样。

但是还是那句话:存在不一定合理。出两套设计稿虽然现在看起来不现实,但是我们也要做好准备,要有危机感。


蓝蓝设计www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 平面设计服务

黄金分割在界面设计中的应用

ui设计分享达人

黄金分割大家应该早有耳闻,作为一名设计师,怎么来利用黄金分割线使其构图更加完美呢?

说实话,构图时是否使用黄金分割线构图并不是绝对的,它只是方法之一。但是黄金分割比例在全世界乃至全宇宙确实都是至高无上的。


UI按钮设计发展史

蓝蓝设计的小编

当我们在网上购物,提交我们个人信息都需要用到按钮。网页UI设计的增长很快,风格似乎也是一个月一变。最近几年,我们经历过从文 本链接到拟物化设计再到扁平化瀑布流设计。导航是网页设计中最重要的元素,并且按钮是最重要的行为工具。

vue在ie9中的兼容问题

seo达人

如果您想订阅本博客内容,每天自动发到您的邮箱中, 请点这里

问题总结  https://github.com/vuejs-templates/webpack/issues/260

  1. 首先npm install --save babel-polyfill

  2. 然后在main.js中的最前面引入babel-polyfill

    import 'babel-polyfill'

  3. 在index.html 加入以下代码(非必须)

    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">

  4. 在config中的webpack.base.conf.js中,修改编译配置


    
  1. entry:{
  2. app:['babel-polyfill','./src/main.js']
  3. }

当然,如果你只用到了 axios 对 promise进行兼容,可以只用 es6-promise

npm install es6-promise --save

在 main.js 中的最前面 引入

import 'es6-promise/auto' 
  • 以上配置,ie9兼容就完成了

那么,就有一个问题了,build之后的dist文件只有放在服务器上才能查看,但本地如何查看呢,参考一下配置

  1. 修改config文件夹中的index.js文件,将build对象中的打包路径,'/‘改为'./',由绝对路径改为相对路径,建议将sourceMap改为false,编译的时候会快一点

    build: { assetsPublicPath: './', productionSourceMap: false, },

  2. 修改完之后,重新 npm run build ,得到新的dist文件夹

  3. 然后进入dist文件夹

    cd dist

  4. 全局安装简易node服务器

    npm install http-server -g

  5. 启动简易node服务器

    http-server

  6. 出现如下图所示,就代表你的服务器启动成功了,那你也能在5000端口查看编译打包后的项目了,可以在ie浏览器中直接测试了

    这里写图片描述

  7. IE在处理日期的时候,不支持-支持/的日期方式 如 2017-01-01应该 


蓝蓝设计www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 平面设计服务


各家UI时代一览

蓝蓝设计的小编

前方山高水长,我们都在路上。

何为第一

蓝蓝设计的小编

如果您想订阅本博客内容,每天自动发到您的邮箱中, 请点这里

蓝蓝写

在班级考试的时候,在同行竞标时,在工作业绩评定时,大家都想赢,争第一才有机会。很多时候,大家在为争这个第一而烦恼。总是保持第一很难,得到后又怕失去,心情易受此情绪化。享受赢的感觉很好,但如果第一才是赢,那输的人岂不是很多?

得第一一旦养成习惯,做起事来会追求完美,不怕苦不怕累,但是一旦有场合一定要输,使不想参与,觉得不公平。人生不可能每次都是考试第一,每场投标都中,每次比稿都只自己的最好,成败是常见的事情,直面输赢,是对心智的磨练。

爸爸对我说,不要每次考试都让我女儿考第一,这样是不对的,要把身体锻炼好,身体好了,成绩自然就好。

期中考试考前我激励女儿:要好好学习,这次还能不能再有进步?期末考试是很重要的。女儿说:不就是期末考试吗?有什么重要的?也不可能永远都进步。

我觉得他们说的话都有道理,以前每次投标我基本上是晚12点睡早五点起,睡中朦胧也会思考怎么做的更好,投完标基本上都会病一场,追求完美给自己压力太大,影响健康。

所以这次考试中只问女儿:今天身体怎么样?累不累?热不热?放松去考,考砸了,下次再努力,女儿很高兴,放松,觉得得到理解和关心。

何为第一?今日读到一段定义:所有的人都需要你的时候,你能够给他们带来所需要的,你就是第一。任何事情都有它的顺序,讲因缘,时节,果报,要一步一步来,时间未到,你想也没有,时间一到,你不想它也有。

德和得分别代表了内在和外在,它也代表了同一种东西的两个状态:能量和质量。

当内在大于外在时,能量就会转化为物质,于是财富会自动存在;当外在大于内在,也就是能量不够的时候,物质会自动消失,对应到生活当中就是出现健康、财富等外在的损失甚至灾难。

由此就能更好地理解我们常说的“厚德载物”是什么意思,物质就是钱、地位、房子、子孙、家庭、名利。

厚德=后得。

厚德厚物,薄德薄物,缺德缺物,无德无物。因为能量和质量永远要追求一种平衡。WechatIMG90.jpeg

遵守职业道德,做人的品德是基础,否则水里来火里去存不住物质财富,赢得第一意味着名利,意味着才华和能力能够给带来机会和财富,但是品德修养是更为重要的根本,决定财富持有的时间和量级。

去掉攀比的好胜心,把视角转向对内的反省和探索,目标不再定为得第一,而定为不断地完善自我,完善工作品质。甘当无私的绿叶,在别人需要时帮助别人,减少狭隘的利益判断,更宽胸怀的帮助别的公司建立设计驱动的价值。

有德的人,不为自己求安乐,但为众生得离苦,众生是一个很大的团队,如果由己及人,把团队扩大到同事,社区,同行,胸怀越来越大,人也就会变得越来越包容,平和。追求亦也不再是自己的第一,而是带领更多的人走向卓越,完善自我的修行之路。

WechatIMG91.jpg


蓝蓝设计www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 平面设计服务



ES6新特性Promise异步调用

seo达人

如果您想订阅本博客内容,每天自动发到您的邮箱中, 请点这里

Promise 的含义

Promise 是异步编程的一种解决方案,比传统的解决方案–回调函数和事件--更合理和更强大。它由社区最早提出和实现,ES6将其写进了语言标准,统一了语法,原生提供了Promise

所谓Promise ,简单说就是一个容器,里面保存着某个未来才回结束的事件(通常是一个异步操作)的结果。从语法上说,Promise是一个对象,从它可以获取异步操作的消息。 
Promise 对象的状态不受外界影响

三种状态:

  • pending:进行中
  • fulfilled :已经成功
  • rejected 已经失败

状态改变: 
Promise对象的状态改变,只有两种可能:

  • 从pending变为fulfilled
  • 从pending变为rejected。

这两种情况只要发生,状态就凝固了,不会再变了,这时就称为resolved(已定型

基本用法

ES6规定,Promise对象是一个构造函数,用来生成Promise实例


    
  1. const promist = new Promise(function(resolve,reject){
  2. if(/*异步操作成功*/){
  3. resolve(value);
  4. }else{
  5. reject(error);
  6. }
  7. })
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去; 
reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

Promise的源码分析:

1.回调地狱

曾几何时,我们的代码是这样的,为了拿到回调的结果,不得不callback hell,这种环环相扣的代码可以说是相当恶心了


            
  1. let fs = require('fs')
  2. fs.readFile('./a.txt','utf8',function(err,data){
  3. fs.readFile(data,'utf8',function(err,data){
  4. fs.readFile(data,'utf8',function(err,data){
  5. console.log(data)
  6. })
  7. })
  8. })

终于,我们的盖世英雄出现了,他身披金甲圣衣、驾着七彩祥云。好吧打岔儿了,没错他就是我们的Promise,那让我们来看看用了Promise之后,上面的代码会变成什么样吧


            
  1. let fs = require('fs')
  2. function read(url){
  3. return new Promise((resolve,reject)=>{
  4. fs.readFile(url,'utf8',function(error,data){
  5. error && reject(error)
  6. resolve(data)
  7. })
  8. })
  9. }
  10. read('./a.txt').then(data=>{
  11. return read(data)
  12. }).then(data=>{
  13. return read(data)
  14. }).then(data=>{
  15. console.log(data)
  16. })

2.重点开始,小眼睛都看过来

2.1 Promise/A+

首先我们要知道自己手写一个Promise,应该怎么去写,谁来告诉我们怎么写,需要遵循什么样的规则。当然这些你都不用担心,其实业界都是通过一个规则指标来生产Promise的。让我们来看看是什么东西。传送门☞Promise/A+

2.2 constructor

我们先声明一个类,叫做Promise,里面是构造函数。如果es6还有问题的可以去阮大大的博客上学习一下(传送门☞es6


            
  1. class Promise{
  2. constructor(executor){
  3. //控制状态,使用了一次之后,接下来的都不被使用
  4. this.status = 'pendding'
  5. this.value = undefined
  6. this.reason = undefined
  7. //定义resolve函数
  8. let resolve = (data)=>{
  9. //这里pendding,主要是为了防止executor中调用了两次resovle或reject方法,而我们只调用一次
  10. if(this.status==='pendding'){
  11. this.status = 'resolve'
  12. this.value = data
  13. }
  14. }
  15. //定义reject函数
  16. let reject = (data)=>{
  17. if(this.status==='pendding'){
  18. this.status = 'reject'
  19. this.reason = data
  20. }
  21. }
  22. //executor方法可能会抛出异常,需要捕获
  23. try{
  24. //将resolve和reject函数给使用者
  25. executor(resolve,reject)
  26. }catch(e){
  27. //如果在函数中抛出异常则将它注入reject中
  28. reject(e)
  29. }
  30. }
  31. }

那么接下来我会分析上面代码的作用,原理

  • executor:这是实例Promise对象时在构造器中传入的参数,一般是一个function(resolve,reject){}
  • status:``Promise的状态,一开始是默认的pendding状态,每当调用道resolve和reject方法时,就会改变其值,在后面的then方法中会用到
  • value:resolve回调成功后,调用resolve方法里面的参数值
  • reason:reject回调成功后,调用reject方法里面的参数值
  • resolve:声明resolve方法在构造器内,通过传入的executor方法传入其中,用以给使用者回调
  • reject:声明reject方法在构造器内,通过传入的executor方法传入其中,用以给使用者回调

2.3 then

then方法是Promise中最为重要的方法,他的用法大家都应该已经知道,就是将Promise中的resolve或者reject的结果拿到,那么我们就能知道这里的then方法需要两个参数,成功回调和失败回调,上代码!


            
  1. then(onFufilled,onRejected){
  2. if(this.status === 'resolve'){
  3. onFufilled(this.value)
  4. }
  5. if(this.status === 'reject'){
  6. onRejected(this.reason)
  7. }
  8. }

这里主要做了将构造器中resolve和reject的结果传入onFufilledonRejected中,注意这两个是使用者传入的参数,是个方法。所以你以为这么简单就完了?要想更Swag的应对各种场景,我们必须得再完善。继续往下走!

3.异步的Promise

之前我们只是处理了同步情况下的Promise,简而言之所有操作都没有异步的成分在内。那么如果是异步该怎么办?

3.1 callback!!!!

最早处理异步的方法就是callback,就相当于我让你帮我扫地,我会在给你发起任务时给你一个手机,之后我做自己的事情去,不用等你,等你扫完地就会打手机给我,诶,我就知道了地扫完了。这个手机就是callback,回调函数。

首先我们需要改一下构造器里的代码,分别添加两个回调函数的数组,分别对应成功回调和失败回调。他们的作用是当成功执行resolve或reject时,执行callback。


            
  1. //存放成功回调的函数
  2. this.onResolvedCallbacks = []
  3. //存放失败回调的函数
  4. this.onRejectedCallbacks = []
  5. let resolve = (data)=>{
  6. if(this.status==='pendding'){
  7. this.status = 'resolve'
  8. this.value = data
  9. //监听回调函数
  10. this.onResolvedCallbacks.forEach(fn=>fn())
  11. }
  12. }
  13. let reject = (data)=>{
  14. if(this.status==='pendding'){
  15. this.status = 'reject'
  16. this.reason = data
  17. this.onRejectedCallbacks.forEach(fn=>fn())
  18. }
  19. }

然后是then需要多加一个状态判断,当Promise中是异步操作时,需要在我们之前定义的回调函数数组中添加一个回调函数。


            
  1. if(this.status === 'pendding'){
  2. this.onResolvedCallbacks.push(()=>{
  3. // to do....
  4. let x = onFufilled(this.value)
  5. resolvePromise(promise2,x,resolve,reject)
  6. })
  7. this.onRejectedCallbacks.push(()=>{
  8. let x = onRejected(this.reason)
  9. resolvePromise(promise2,x,resolve,reject)
  10. })
  11. }

ok!大功告成,异步已经解决了

3.2 resolvePromise

这也是Promise中的重头戏,我来介绍一下,我们在用Promise的时候可能会发现,当then函数中return了一个值,我们可以继续then下去,不过是什么值,都能在下一个then中获取,还有,当我们不在then中放入参数,例:promise.then().then(),那么其后面的then依旧可以得到之前then返回的值,可能你现在想很迷惑。让我来解开你心中的忧愁,follow me


            
  1. then(onFufilled,onRejected){
  2. //解决onFufilled,onRejected没有传值的问题
  3. onFufilled = typeof onFufilled === 'function'?onFufilled:y=>y
  4. //因为错误的值要让后面访问到,所以这里也要跑出个错误,不然会在之后then的resolve中捕获
  5. onRejected = typeof onRejected === 'function'?onRejected:err=>{ throw err ;}
  6. //声明一个promise对象
  7. let promise2
  8. if(this.status === 'resolve'){
  9. //因为在.then之后又是一个promise对象,所以这里肯定要返回一个promise对象
  10. promise2 = new Promise((resolve,reject)=>{
  11. setTimeout(()=>{
  12. //因为穿透值的缘故,在默认的跑出一个error后,不能再用下一个的reject来接受,只能通过try,catch
  13. try{
  14. //因为有的时候需要判断then中的方法是否返回一个promise对象,所以需要判断
  15. //如果返回值为promise对象,则需要取出结果当作promise2的resolve结果
  16. //如果不是,直接作为promise2的resolve结果
  17. let x = onFufilled(this.value)
  18. //抽离出一个公共方法来判断他们是否为promise对象
  19. resolvePromise(promise2,x,resolve,reject)
  20. }catch(e){
  21. reject(e)
  22. }
  23. },0)
  24. })
  25. }
  26. if(this.status === 'reject'){
  27. promise2 = new Promise((resolve,reject)=>{
  28. setTimeout(()=>{
  29. try{
  30. let x = onRejected(this.reason)
  31. resolvePromise(promise2,x,resolve,reject)
  32. }catch(e){
  33. reject(e)
  34. }
  35. },0)
  36. })
  37. }
  38. if(this.status === 'pendding'){
  39. promise2 = new Promise((resolve,reject)=>{
  40. this.onResolvedCallbacks.push(()=>{
  41. // to do....
  42. setTimeout(()=>{
  43. try{
  44. let x = onFufilled(this.value)
  45. resolvePromise(promise2,x,resolve,reject)
  46. }catch(e){
  47. reject(e)
  48. }
  49. },0)
  50. })
  51. this.onRejectedCallbacks.push(()=>{
  52. setTimeout(()=>{
  53. try{
  54. let x = onRejected(this.reason)
  55. resolvePromise(promise2,x,resolve,reject)
  56. }catch(e){
  57. reject(e)
  58. }
  59. })
  60. })
  61. })
  62. }
  63. return promise2
  64. }

一下子多了很多方法,不用怕,我会一一解释

  1. 返回Promise?:首先我们要注意的一点是,then有返回值,then了之后还能在then,那就说明之前的then返回的必然是个Promise
  2. 为什么外面要包一层setTimeout?:因为Promise本身是一个异步方法,属于微任务一列,必须得在执行栈执行完了在去取他的值,所以所有的返回值都得包一层异步setTimeout。
  3. 为什么开头有两个判断?:这就是之前想要解决的如果then函数中的参数不是函数,那么我们需要做处理。如果onFufilled不是函数,就需要自定义个函数用来返回之前resolve的值,如果onRejected不是函数,自定义个函数抛出异常。这里会有个小坑,如果这里不抛出异常,会在下一个then的onFufilled中拿到值。又因为这里抛出了异常所以所有的onFufilled或者onRejected都需要try/catch,这也是Promise/A+的规范。当然本人觉得成功的回调不需要抛出异常也可以,大家可以仔细想想。
  4. resolvePromise是什么?:这其实是官方Promise/A+的需求。因为你的then可以返回任何职,当然包括Promise对象,而如果是Promise对象,我们就需要将他拆解,直到它不是一个Promise对象,取其中的值。

那就让我们来看看这个resolvePromise到底长啥样。


            
  1. function resolvePromise(promise2,x,resolve,reject){
  2. //判断x和promise2之间的关系
  3. //因为promise2是上一个promise.then后的返回结果,所以如果相同,会导致下面的.then会是同一个promise2,一直都是,没有尽头
  4. if(x === promise2){//相当于promise.then之后return了自己,因为then会等待return后的promise,导致自己等待自己,一直处于等待
  5. return reject(new TypeError('循环引用'))
  6. }
  7. //如果x不是null,是对象或者方法
  8. if(x !== null && (typeof x === 'object' || typeof x === 'function')){
  9. //为了判断resolve过的就不用再reject了,(比如有reject和resolve的时候)
  10. let called
  11. try{//防止then出现异常,Object.defineProperty
  12. let then = x.then//取x的then方法可能会取到{then:{}},并没有执行
  13. if(typeof then === 'function'){
  14. //我们就认为他是promise,call他,因为then方法中的this来自自己的promise对象
  15. then.call(x,y=>{//第一个参数是将x这个promise方法作为this指向,后两个参数分别为成功失败回调
  16. if(called) return;
  17. called = true
  18. //因为可能promise中还有promise,所以需要递归
  19. resolvePromise(promise2,y,resolve,reject)
  20. },err=>{
  21. if(called) return;
  22. called = true
  23. //一次错误就直接返回
  24. reject(err)
  25. })
  26. }else{
  27. //如果是个普通对象就直接返回resolve作为结果
  28. resolve(x)
  29. }
  30. }catch(e){
  31. if(called) return;
  32. called = true
  33. reject(e)
  34. }
  35. }else{
  36. //这里返回的是非函数,非对象的值,就直接放在promise2的resolve中作为结果
  37. resolve(x)
  38. }
  39. }

它的作用是用来将onFufilled的返回值进行判断取值处理,把最后获得的值放入最外面那层的Promise的resolve函数中。

  1. 参数promise2(then函数返回的Promise对象),x(onFufilled函数的返回值),resolve、reject(最外层的Promise上的resolve和reject)。
  2. 为什么要在一开始判断promise2x?:首先在Promise/A+中写了需要判断这两者如果相等,需要抛出异常,我就来解释一下为什么,如果这两者相等,我们可以看下下面的例子,第一次p2是p1.then出来的结果是个Promise对象,这个Promise对象在被创建的时候调用了resolvePromise(promise2,x,resolve,reject)函数,又因为x等于其本身,是个Promise,就需要then方法递归它,直到他不是Promise对象,但是x(p2)的结果还在等待,他却想执行自己的then方法,就会导致等待。

            
  1. let p1 = new Promise((resolve,reject)=>{
  2. resolve()
  3. })
  4. let p2 = p1.then(d=>{
  5. return p2
  6. })
  1. called是用来干嘛的?:called变量主要是用来判断如果resolvePromise函数已经resolve或者reject了,那就不需要在执行下面的resolce或者reject。
  2. 为什么取then这个属性?:因为我们需要去判断x是否为Promise,then属性如果为普通值,就直接resolve掉,如果是个function,就是Promise对象,之后我们就需要将这个x的then方法进行执行,用call的原因是因为then方法里面this指向的问题。
  3. 为什么要递归去调用resolvePromise函数?:相信细心的人已经发现了,我这里使用了递归调用法,首先这是Promise/A+中要求的,其次是业务场景的需求,当我们碰到那种Promise的resolve里的Promise的resolve里又包了一个Promise的话,就需要递归取值,直到x不是Promise对象。

4.完善Promise

我们现在已经基本完成了Promise的then方法,那么现在我们需要看看他的其他方法。

4.1 catch

相信大家都知道catch这个方法是用来捕获Promise中的reject的值,也就是相当于then方法中的onRejected回调函数,那么问题就解决了。我们来看代码。


            
  1. //catch方法
  2. catch(onRejected){
  3. return this.then(null,onRejected)
  4. }

该方法是挂在Promise原型上的方法。当我们调用catch传callback的时候,就相当于是调用了then方法。

4.2 resolve/reject

大家一定都看到过Promise.resolve()、Promise.reject()这两种用法,它们的作用其实就是返回一个Promise对象,我们来实现一下。


            
  1. //resolve方法
  2. Promise.resolve = function(val){
  3. return new Promise((resolve,reject)=>{
  4. resolve(val)
  5. })
  6. }
  7. //reject方法
  8. Promise.reject = function(val){
  9. return new Promise((resolve,reject)=>{
  10. reject(val)
  11. })
  12. }

这两个方法是直接可以通过class调用的,原理就是返回一个内部是resolve或reject的Promise对象。

4.3 all

all方法可以说是Promise中很常用的方法了,它的作用就是将一个数组的Promise对象放在其中,当全部resolve的时候就会执行then方法,当有一个reject的时候就会执行catch,并且他们的结果也是按着数组中的顺序来排放的,那么我们来实现一下。


            
  1. //all方法(获取所有的promise,都执行then,把结果放到数组,一起返回)
  2. Promise.all = function(promises){
  3. let arr = []
  4. let i = 0
  5. function processData(index,data){
  6. arr[index] = data
  7. i++
  8. if(i == promises.length){
  9. resolve(arr)
  10. }
  11. }
  12. return new Promise((resolve,reject)=>{
  13. for(let i=0;i<promises.length;i++){
  14. promises[i].then(data=>{
  15. processData(i,data)
  16. },reject)
  17. }
  18. })
  19. }

其原理就是将参数中的数组取出遍历,每当执行成功都会执行processData方法,processData方法就是用来记录每个Promise的值和它对应的下标,当执行的次数等于数组长度时就会执行resolve,把arr的值给then。这里会有一个坑,如果你是通过arr数组的长度来判断他是否应该resolve的话就会出错,为什么呢?因为js数组的特性,导致如果先出来的是1位置上的值进arr,那么0位置上也会多一个空的值,所以不合理。

4.4 race

race方法虽然不常用,但是在Promise方法中也是一个能用得上的方法,它的作用是将一个Promise数组放入race中,哪个先执行完,race就直接执行完,并从then中取值。我们来实现一下吧。


            
  1. //race方法
  2. Promise.race = function(promises){
  3. return new Promise((resolve,reject)=>{
  4. for(let i=0;i<promises.length;i++){
  5. promises[i].then(resolve,reject)
  6. }
  7. })
  8. }

原理大家应该看懂了,很简单,就是遍历数组执行Promise,如果有一个Promise执行成功就resolve。

Promise语法糖 deferred

语法糖这三个字大家一定很熟悉,作为一个很Swag的前端工程师,对async/await这对兄弟肯定很熟悉,没错他们就是generator的语法糖。而我们这里要讲的语法糖是Promise的。


            
  1. //promise语法糖 也用来测试
  2. Promise.deferred = Promise.defer = function(){
  3. let dfd = {}
  4. dfd.promise = new Promise((resolve,reject)=>{
  5. dfd.resolve = resolve
  6. dfd.reject = reject
  7. })
  8. return dfd
  9. }

什么作用呢?看下面代码你就知道了


            
  1. let fs = require('fs')
  2. let Promise = require('./promises')
  3. //Promise上的语法糖,为了防止嵌套,方便调用
  4. //坏处 错误处理不方便
  5. function read(){
  6. let defer = Promise.defer()
  7. fs.readFile('./1.txt','utf8',(err,data)=>{
  8. if(err)defer.reject(err)
  9. defer.resolve(data)
  10. })
  11. return defer.Promise
  12. }

没错,我们可以方便的去调用他语法糖defer中的Promise对象。那么它还有没有另外的方法呢?答案是有的。我们需要在全局上安装promises-aplus-tests插件npm i promises-aplus-tests -g,再输入promises-aplus-tests [js文件名] 即可验证你的Promise的规范。


蓝蓝设计www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 平面设计服务


我用这个极限推敲法,有效推进视觉设计

资深UI设计者

每当提到设计方法论时,总难免会给人一种比较虚空偏理论,或者说比较难应用到设计实战中的感觉;但是这一次,经过了自己的实践之后,我觉得确实有一个比较靠谱的视觉推进方法可以分享给大家,所以才有了这篇总结。

先直接概括一下这个方法,极限推敲法:遇到难题时,先把问题拆解成一个或者几个维度,选取某个维度,往两个极端进行尝试,随后逐渐中和极端方案,逐步找到自己想要的预期点,明确该维度的方向。

一、使用方法

  • 第一步,提炼一个或者几个关键维度,作为尝试的方向;
  • 第二步,优先尝试两端极限的方案;
  • 第三步,进一步在其间琢磨并尝试方案,直到找到符合预期的方案;
  • 第四步,若一直没有满意的方案,则可以继续以某个方案为基准,精细微调方案进行探索;
  • 第五步,得出最终方向,并最终调整形成终稿。

二、案例

例一:雄狮的logo设计

期望比较简洁的同时,又不能丢了狮子的辨识度,比较纠结的是,这个度该如何把握?

按照极限推敲法来,我们先确立「极简」和「写实」作为关键维度,然后优先尝试两端比较极限的方案;了解清楚两种极限的可能性之后,继续尝试,加减细节,寻找一个符合预期的图形状态;最后的定稿,既保持了鲜明的特征和辨识度,同时也不失简洁。

(推进过程中出方案的顺序在图中用数字标出,「1」即为第一次尝试,以此类推;具体推敲过程不在此赘述。)

例二:网易邮箱大师Tab icon改版设计

在网易邮箱大师5.0改版中,常驻底部的 Tab icon 是单独进行推敲优化的。在之前的版本中,图标的样式相对比较的刻板,偏向纯表意,表现力不强;在5.0视觉改版的策略中,我们是以「优雅」作为一个关键方向进行优化的,所以轴的两端就定为「刻板」和「优雅」,虽然并不是完全对立的两个方向,同时也包含了一些「表意」等其他层面的因素,但是其中有部分感受是存在对立面的,我们就以此作为关键点进行推敲。

可以从图中看出,在不影响表意的前提下,各方案间的图标改动非常微小,在有限的空间内去做一些变化;几经尝试之后,还是选择了更常规更偏表意优先,但是也适当加入曲线因素体现「优雅」的方案作为最终方向。

例三:内容信息流列表设计

期望内容列表的展示能给人以精品的感觉,但又不失去过多的阅读效率。

首先确立以信息量的多少作为关键维度,在形成了初步的样式之后,继续调整在一屏内条目的疏密以及排版来感知信息量的多少;在尝试之后,最终讨论决定,在初期内容源还不够「精」的情况下,还是最右侧方案的信息呈现更为合适,并以此做了最终调整。

三、方法原理

很多时候,极端情况是很难想象或者预判出来的,只有尝试之后,才能更清楚的了解到实际会出来什么效果,达到怎样的预期;在多次推敲之后,就能更好的了解期望的状态到底应该在哪个「位置」,让模糊的概念逐渐变得清晰;该方法最大的好处在于,让尝试变的更有目标,而不是胡乱尝试,让每一次尝试都更有意义。

这个方法也算是视觉标准可量化的一次探索,我称之为「伪量化」,因为最后的决策仍然是依靠感官去衡量判断的;把错综复杂的感官拆解成一些更具体单一的关键维度也是非常关键的一个步骤,是开始「伪量化」的前提。

四、适用场景

在以上所举的例子中,有 logo设计,icon设计和界面设计(所用到的例子都是实战,均为过往项目);其实对象可以是各种各样的设计,只要存在某种程度把握上的纠结,无法预判或者不明确方向的情况,都可以通过这个方法有效的推进并找到当下的最优解。在实战中,假如没有那么多的时间和精力去细化,可以仅选取关键维度,然后在关键维度内尝试方向探索性的方案(粗略的尝试),只要能感受到差异,即是有效的方案;当然如果有充足的时间,那完全可以精细化出方案,这样方案所传达的感受也会更加精准细致。

五、误区

要注意的是,这个方法并不是一种妥协的方式,也不是一种择中方案;我们所要寻找的是在一个维度中感受符合预期的位置,如果适合,也可以是非常激进的方案。

六、注意事项

这个方法只是提供了一种思路,如何更有效的尝试方案来推进视觉产出的思路;其中最主要的环节还是靠自己去摸索出方案的实践部分,去实打实的尝试;在这其中投入精力的多少,也受其他客观因素的影响,例如项目截止时间等等;不过我认为个人的主观能动性才是主导,一定不能懒,要作图,要动起来,仅靠凭空想象方案在脑中「出图」,一来说服不了别人,二来自己也底气不足。只要图形图像语言一直存在,那么我觉得对视觉设计师来说有一句话就是永远的真理:没图说个XX。

日历

链接

个人资料

蓝蓝设计的小编 http://www.lanlanwork.com

存档