岁月不居,时节如流。转眼之间2019悄然而过,回首2019,我们豪情满怀,漫天风雪风景好;展望2020,我们重任在肩,阳光普照写佳绩。舟至中流需奋进,风好正是扬帆时。
在2019最后一天里,可能你还想做这么的一件事,给客户发一份精美的邮件祝福,但却无从下手,怎么办?拉易网能帮到你,准备好素材后,只需一分钟就能生成精美的HTML邮件格式。话不多说,直接上视频。
一分钟制作精美HTML邮件格式的元旦祝福邮件
走着走着…
2019已接近尾声
一眨眼就是一天
一回头就是一年
一转身就是一辈子
人生总有太多的来不及
B 端工作看起来总是没有 C 端工作那么有趣,面临的限制比较多,面对客户(金主爸爸),很多时候总是左右为难。在实际工作中,面对这些情况该怎么办?笔者根据自己的 B 端工作经验,总结了一些交互设计的要点。
从事 B 端 SaaS 行业已经两年有余,从最开始面对需求的茫然无措,到现在已经有了自己的一些基本方法论,这期间经历了从有人带到自己摸索的一个阶段。
每天看看文章、看看书,大家讲的都是 C 端的产品,C 端的交互和体验;每天看同行们分析的不是如何提高用户活跃量,就是 APP 的设计风格。这在我一个 B 端交互看来,无比羡慕啊。
C 端项目的设计师感觉每天和一线用户打交道,忙得不亦乐乎,可以与用户直接对接,可以添加有趣生动的文案。
而对于一个做 SaaS 的 B 端来说,Boss 常说的话就是:
你这个颜色太鲜艳了。
我们是 B 端,你这种页面布局不合适吧。
这个文案太幼稚了,不适合我们产品的调性。
中规中矩就好,不要太跳。
整理了一堆的字段,画了无数的线框和流程图,却拿不出 C 端设计师才有的丰富多彩的作品集,
尽管如此,设计的原则是通用的,无论是尼尔森十大可用性原则,还是格式塔原理,在设计方案的落实上,都有着相同的方法论。
无意在此讨论 B 端和 C 端之间的差异,仅以此文章来总结下我自己的一些工作经验,如有错误,敬请指出。
从业务需求的对接,到界面交互的细节,从功能的设计要点,再到产品的发展导向,我总结出了以下几个方面,逐步展开:
C 端设计师最开心的可能就是收到用户的反馈需求了吧,这样表示自己的产品还有人在用,然后建个群让用户开开心心吐槽,完了就从里面提炼适合产品的一些需求和建议。
而对于 B 端设计师来说,如何处理需求是其成长的第一关,尤其是 SaaS 行业,不会处理需求,产品的功能更新将会遇到极大的问题。
B 端的用户不像 C 端,虽然可以用用户画像来进行归类和分析。但是由于 B 端的直接用户是付费用户,付了钱的就是大爷,因此大爷提的需求你敢不应?
用户提的需求乱七八糟,有些是体验问题,有些是功能问题,有些就是属于比较极端的需求。那种传说中甲方要你做一个可以根据手机屏显示颜色而改变手机壳颜色的需求,在 B 端行业也是存在的。
那么问题来了,我们该如何处理纷繁杂乱的需求呢,我从收集需求-评估需求-需求落地挨个讲起:
如果你也打算像 C 端产品体验群那样建立一个群,完了将自己的用户聚集在那里,静静等待需求的话,我劝你三思而后行。你可以这么做,但是应该明确群的目标,可以收集需求,可以是 bug 反馈群,也可以是更新通知群;但是不要将其变成一个纯粹的用户反馈群,因为这会导致用户的吐槽声音过大,而让潜在的用户流失。
B 端的客户一旦使用你们的系统,就要付出相应的金钱和时间代价,不是说想换一家就能换。因此他对于已经使用中的用户反馈是极其看重的,B 端的产品很大程度是靠着同行间的口碑传播从而取得了良好的效益。如果一个新用户想进群了解,结果一进去就发现大家都在吐槽这个系统的不足之处,那么可想而知他的选择是什么。
因此,最好的需求收集方式是通过运营、市场以及客服的同事们的反馈,因为他们是离客户最近的人,他们每天都跟客户打交道,了解这个行业从业者的一些基本情况。很多时候,客户回访和运营对接的时候用户会提出一些功能的建议。
通常的一种情况是,在 SaaS 行业,你的一个客户的流失意味着你的竞争对手多一个客户。因此一般市场都会有竞争对手的信息,他们会知晓客户从我们平台流失到其他平台的一些原因。最重要的是,他们也为你提供了绝佳的竞品资料,进而可以进一步明确需求。
收集好的需求,该怎么处理呢?
工具之前我用的是 trello,很好用,你可以将需求按照类型分为不同的看板,规划产品的进度。
之后由于团队的原因,改用 teambition,功能要丰富点,可以写测试案例等,对于国内用户比较友好;可以按照 KANO 模型将需求划分为不同的类型,用以安排产品。
KANO模型
基本(必备)型需求——Must-beQuality/ Basic Quality
一个产品应该具有的基本功能,能满足用户的基本需求,比如,微信的基本功能是即时通讯。
用户不会因为该功能的出现而觉得满意,因为这是基本的、必备的一项功能。如果连这个都没法满足,用户满意度会大幅下降。
期望(意愿)型需求——One-dimensional Quality/ Performance Quality
用户迫切希望产品能提供的功能,当提供该需求时,用户满意度会大幅上升,当不提供该需求时,用户满意度会下降。
比如百度网盘一直为人诟病的下载限速,无法对单次下载而付费。而需要开通会员,用户的抱怨恰好说明了其痛点;当提供单次下载付费的服务时,用户满意度明显提升。
兴奋(魅力)型需求—Attractive Quality/ Excitement Quality
用户对该类型的需求并无明显的期望,但是若产品能够提供该类需求,用户满意度会极大提升,也会培养用户的忠诚度。
比如,很多产品都有实验室功能,即对那些不是必备需求的功能设计一个开关,用户打开后可以进行使用。对于有的用户来讲,这些功能很大程度上会带来惊喜。当然,每个人期望不一样,实验室方案也可以视为另类的灰度测试,用以验证用户对于功能的需求。
无差异型需求——Indifferent Quality/Neutral Quality
产品无论是否满足该类需求,用户的满意度是不会变化的,正如用户不会因为收到「玛莎拉蒂5元代金券」而感到开心。因此在 B 端行业,开发时间有限的情况下,无需为该类需求投入资源。
比如优化一个用户访问量很少的网页,也无需因为领导或者客户的个人喜好而改变后台页面的颜色。无论使其鲜艳活泼还是稳重大气,后台满足基本的视觉要求和规范即可。当然,这并不意味着你可以把后台设计的简陋和杂乱。
反向(逆向)型需求——Reverse Quality
当提供方向性需求的功能时,会招致大部分用户满意度的大幅下降。比如常见的在搜索中掺加广告、强制用户授权过多个人信息以及推送大量无用的消息等,会导致用户的反感。
通常需求的收集不是你一个人在进行,产品经理、老板以及其他同事也会帮助你收集,汇总到你这里的需求会开会进行讨论,下一步要做什么。
做之前首先要对需求进行评估,评估的原则基本是按照 KANO 模型来,但是也会有比较特殊的情况。
交互设计师需要注意的是,你除了需要关注用户体验的问题以外,还要与开发一起评估该需求的实现;你不懂技术没关系,开发会告诉你该需求的落地会出现什么问题,在实现上会有什么难度。这些在评估需求的阶段都要提出来,并且在交互层面解决掉,否则你画出了原型以后,开发告诉你这个页面必须要修改,否则开发成本过大,无法在排期内完成,这个时候你再去改交互稿将会费时费力。
评估完需求,定好下期开发的需求后就结束了么?
其实并没有,因为需求收集不可能是一个固定的阶段,它是渐进的、不确定的。因此收集需求和评估需求会不断在实际工作中叠加和重复,比如你制定好了需求优先级,已经着手开发了;这时候老板或者领导突然又增加了一个优先级很高的需求,所以你得重新安排这些需求。如何在敏捷开发中保质保量的完成工作任务,这是一场斗智斗勇的 battle。
前面讲到需求评估的时候,需要与开发人员一起评估技术难度。其实在你将需求落地为交互原型的时候,也需要持续沟通,这完全是为了避免因为技术问题而产生修改设计稿或沟通不顺畅的问题。
如果你是在做的过程中,持续与开发人员保持沟通,能了解到他们是如何实现这个功能的。当然,有些基本的设计原则所不允许的事完全不用屈服于他们的「淫威」,毕竟他们得按照你的方案来。如果开发懒惰而想通过最简单的办法来实现,用户体验又是极其不友好,那么请直接对其说「NO」。
比如常见的删除的二次确认等弹窗,一般最好的体验是在按钮旁边弹出;但有些开发懒得写弹窗,直接调用浏览器的弹窗,即浏览器顶部经常出现的那种确认弹窗,这个时候你要坚决告诉开发,这样搞是坚决不行的。
需求的落地伴随着竞品分析,判断一个需求是否靠谱、落地方案是否成熟的一个重要途径就是竞品分析,可以通过调查研究相关竞品是如何进行设计的。这对于我们有着非常大的帮助,可以避免很多的弯路,甚至能避免犯错,提高交互方案的可靠性。竞品分析又是个比较繁杂的事项,如果是有特殊要求可以做成报告的形式,如果仅是去参考对照的话只需要去体验竞品即可。
B 端的业务比较重要,尤其是涉及到数据的增删改查和金额的计算,一旦出错,将会导致无法挽回的后果。因此在出错前和出错后,应该有相应的挽回机制。
1. 出错前
内容编辑类的功能应该提供自动保存草稿功能,防止没有及时保存而丢失内容;删除、禁止等较重操作应该有二次确认,防止用户误删。
对于操作流程应该建立明确的引导机制,长表单可以分开按步骤填写。
提示信息应该明确而及时。比如一个表单需要登录才能填写,在未登录的状态下,应该先提示其登录再填写否则用户在填写大量信息后,因为一个错误而前功尽弃。
系统内的禁止信息、警告信息、提示信息建立一套相应的规则。
2. 出错后
用户的时间就是金钱,这对于 B 端商家角色中尤为重要,大量订单的处理、表格化的导入和导出、批量管理和网站运营等方面,对于效率有着极高的要求,商家通过可视化界面来完成某项任务。
在这其中,影响最大的莫过于交互方式了,相信各位一定用过各大银行的网站,页面的导航关联性弱、结构不合理、提示不明确、交互流程过长,甚至有的网站光是登录,就得大费周章。
如何提率,我认为主要从以下几个方面着手:
1. 提高易用性
如果你的产品,让人看一眼就能上手,那么说明是非常友好的设计。易用性不一定意味着简单和低智,依据复杂守恒定理(泰勒斯定理),每个应用程序都有自己内在的、无法简化的复杂度。
所以,提高易用性意味着要设计合理的交互,易用性分为对新手(小白用户)友好和对老用户(专家用户)友好,包括数量最大的中间用户。
如果你的界面是仅仅对于新手友好,那么可能完成的任务都是简单而轻松的。但是对于老用户,他需要更复杂的功能来处理大量庞杂的任务;因此在设计的时候,既要提供傻瓜式的操作方式,也要对专家用户提供提率的工具。
2. 智能化
此处的智能化既包括通过大数据或者人工智能自动将操作步骤变得简洁,也包括诸如一些字段输入、自动定位、图片识别、OCR 等方式来使用户的效率得以提升的功能。
以前的用户要抠图可能会在 ps 中操作好几个步骤才能完成,但是随着机器学习技术的发展,ps 已经可以更加智能的抠图。当然,还包括其他功能的智能化。
在 B 端 SaaS 领域,智能化也是一个重要的趋势,针对不同的商家所面临的不同的行业领域,我们或许可以提供更加全面且友好的服务。
3. 场景化思维
如果你深入了解你的用户,去观察他们整个行业的运作模式,以及他们对于业务的处理方式,你就会明白你的产品的走向。
你需要深入每一个场景,比如,在户外旅游领域,发布旅游产品线路的可能是在办公室中的编辑人员,带队出行的是在户外场景中的导游,现场签到的可能是集合点的管理人员,而处理订单的又是另一个坐在办公室里的小伙伴。
他们需要协同工作,才能使各项工作顺利展开,发布活动-用户报名-订单管理-报名人统计-活动成行-集合点签到-带队出发-旅游结束-活动评价-领队评价-交易成功,这一系列流程中,面临的角色是复杂的,而意外也是常有的事。比如报名人无法参加活动而导致的退款、活动因为天气原因而无法成行、户外活动发生意外等。
你需要做的就是:
场景化的思维会让你设身处地为一线操作用户的体验而努力。因此,交互稿完成以后,彻底回退到小白用户的身份,假装自己是在某个场景下要做某件事,通过你的交互方案,能否顺利完成自己的目标。
此处的通用性是指,在 SaaS 软件领域,不同客户所面临的行业有一定的差别,可能这个功能对于 A 用户极其重要,但对于 B 用户,该功能并不重要。比如有的客户想要在前台展示某活动的报名人的姓名以增加真实性,用以吸引用户参加;但是有的客户就明确反对该功能,认为这个功能侵犯了用户的隐私。
诸如此类的需求相离、甚至相反的情况太普遍了。合适的解决方式是建立两套开关,一套是由 SaaS 服务提供商的统一后台来配置,用以区分比较大的行业差别,比如电商领域中,可以配置店铺类型为:美妆、服饰、食品、电子产品等;另一套开关在客户的站点后台,用以控制不同的站之间的差别,比如前台界面样式、交易流程、相关字段或菜单的前台显示等。
另外比较重要的一点,也是最基本的一点,软件设计中存在一个原则就是高内聚低耦合,模块化设计,用以提高系统内部组件的复用。
交互设计也是同样的道理,可以将你的商品视为一个模块,那么这个模块无论是添加到网站,还是小程序,我只需要创建一个商品即可,可以同步更新到不同的平台。
另外,订单的管理只需要添加相关的标记即可(比如来自小程序的订单标记为小程序,淘宝订单标记为淘宝等),一个平台发布,多个平台同步,有效提高了运营人员的效率。
同样的,如果你的 pc 端产品和移动端产品没有实质性的运营差异(即当成两种模式来运营),那么很多数据(如文案、图片、banner等)的获取可以采用同一个数据源 。
最后,提高系统内的一致性,减少用户认知成本。在同一平台内的页面,样式和交互上要尽量保持一致性,不要有的地方是总金额,有的地方是总价格,这会让用户犯迷糊。提高通用性,也意味着你需要关注并熟悉系统内不同功能之间的关联性,尽量做到统一处理。
在进行商业化运作和产品功能的优化时,对于正向的用户需要激励,对于负向的用户需要限制。
这是我在读完有赞的白鸦写的关于有赞产品设计原则的文章后,影响最深的一个原则,他写到:
我们的使命是帮助每一位重视产品和服务的商家成功。「每一位」和「商家成功」是我们最重要的关键词,我们要服务的是每一位商家,然后帮助每一位商家成功,但是为了整个生态的健康,那些不重视产品和服务的商家,我们是(可以)不服务的。所以我们在产品设计原则上,在产品做一些功能的选择上,如果这个功能做完了会导致商家不重视产品和服务,我们是不会/能做的。
举个例子:消费者购买之后(可以)有一个评价,我们的购物评价是要么开启要么不开启这个功能。我们不接受商家去删购物评价,因为商家一旦可以删了消费者的差评,他就(很可能)不会那么重视产品和服务了。所以有赞永远不会提供删除商品评价的功能,商家要么就不开启。可以不用,如果要用就要接受有人说你不好,商家可以去跟消费者沟通,沟通完了消费者自己改,但是我们不提供让商家删坏评价的功能。所以,这就是最基本的有赞产品设计原则,我们只服务重视产品和服务的商家,我们所有的产品设计原则都是需要这样。
——《白鸦内部培训:企业服务类产品的底层逻辑和有赞产品设计原则》
更多内容请看:
曾任支付宝首席产品设计师,现任有赞创始人的白鸦是很多人心里的产品大神,这份由他在内部分享的《产品设计原则》值得设计师、产品、运营等阅读学习。
阅读文章 >>我将其概括为一个产品的中正原则,即中立且保持正向原则。
一方面,对于企业未来的发展而言,正向的用户能带给平台无尽的潜力,平台可以和商家一起成长,而负向的用户,则会给平台带来隐患。比如,淘宝既限制商家的违规运营和欺诈客户,也限制 C 端用户的恶意薅羊毛,在商家和用户之间做一个中立者和调节者的角色。
我在做需求的时候,也遇到过很多的负向需求,这在商家看来是一个必须的功能,作为平台应该提供。但是若是提供给商家,则会对消费者的利益造成损害,删除差评就是一个很好的例子。
看了白鸦对于有赞产品原则的阐释,我觉得每一个平台类的产品,都应该保持自己的中正原则:
在 B 端行业,口碑传播是非常重要的一种被动营销方式,很多 B 端公司都是低调的潜行者,坚持中正原则,并不会损害自己的利益,反而会获得商家的尊重和消费者的信赖。
啰啰嗦嗦说了这么多,比较细碎,不乏逻辑凌乱的片段,但也算是对自己两年以来对于 B 端交互的一些心得吧。
其实交互的原则基本都是通用的,无非就是根据平台属性做一定的调整,不同的是产品所处的行业,每一个产品都无法脱离其所处的行业而存在,这也是使用产品的具体场景。
因此做一个产品前,一定要了解行业,去熟悉行业的通用做法,了解行业的专业术语和规范,研究行业的所属群体等,这样你就会做出真正适合市场且能让大多数用户满意的产品。
文章来源:优设
给大家看两张图,这两页的文字内容相同,你更想看哪本?
不出意外的话,大家应该会选右手边的吧(选左边的请自觉去面壁)。(所拍书籍为《西文字体》,高冈昌生 )
虽然大家不是专业做文字排印的,但对文字排版的感知力,其实是生而有之的。
中文也是如此,优秀的出版社(译林出版社、广西师范大学出版社等),为了让阅读体验顺畅、版面舒适,在文字排印上做了许多工序。
正是这些工序,提升了书籍正文的阅读体验。想要在阅读软件上打造优秀的阅读页,这些工序正是我们需要借鉴的。
通过设计师与开发的共同努力,我们最近完成了这一文字排版能力的建造,让阅读页的效果能够更上一层楼。
最终效果如下:
本文将从以下几个部分说明这些工序存在的理由、实现的逻辑等。
细致的来看,文章包括以下内容:
通过前期的大量学习与调研(专家观点:小林章先生、鸟海修先生、刘庆先生等人关于字体排印 or 字体设计的讲座、W3C 中文排版需求(强烈建议大家看这个)、孔雀计划的文章、字体排印的专著:《平面设计中的网格系统》、《字体排印》、《西文字体》等;本次改版几乎所有的功能与逻辑都参照了以上专著与文章。)
我们把文字书排版时的工序,总结为「文字排印要遵循的三个原则」:
原则说明
中文排版中,字与字之间的间距被称为「字间距」。
文字间距会影响阅读节奏。字间距大的文章,阅读速度会变慢。因此,散文、诗歌在排版时,会刻意调大字间距。
下面的图,仅凭自己感受,选一张更好的:
不出意外的话,应该是觉得下图更好看。
尝试默读一下,你会发现,上边的图片,最后两行字间距被拉大,阅读速度放慢;而这不是作者的本意,换言之,这会破坏阅读节奏。
因此,我们把「保证字间距恒定」作为首要原则,来保障阅读节奏感。
备注(建议第二次看文章的时候再读):需要说明的是,部分字面较大的字体(方正博雅宋、兰亭黑等)在书籍排版时为了契合书籍内容的调性,有时会刻意设置字间距,这与「字间距保持恒定」的原则并不冲突。在电子阅读软件中,由于无法针对特定书籍进行调整,因此本次设计实际上是保持「密排」(字与字之间没有额外添加字间距,保留字体原始的间距)。
工序
行长是字号的整数倍。相同字号下,汉字字宽固定(就是字号本身),汉字标点的字宽同样也是字号本身(除了个别标点之外,例如破折号)
汉字排版时,没有额外字间距的情况下,是上图所示的字面框依次密排。
因为中文书籍的正文排版常用两端对齐,如果行长不是字号的整数倍,则汉字之间会有异常的行间距出现。
更严重的是:阅读软件字号可变的情况下,行长不可能做到适应所有字号且字间距不会被拉大。
行长是字号的整数倍是中文字体排印中标点挤压等的前提。
——《孔雀计划》,原文链接:https://thetype.com/2017/07/12513/
在阅读软件中,随着字号调整,如果沿用「版心宽度固定」的思路,难免存在字间距被拉大的情况。
对此,我们调查了国内外知名中文阅读软件,发现:KindleAPP 能随着字号变化自由变动,但这会导致:改变字号大小时,版心宽度略微变化。
有此顾虑,我们做了一个测试。结果证明,大家不会发现版心宽度有变化。这说明用户投入到阅读当中、调整字号时,并不会因为版心宽度变化而有不适,甚至不会感知。通过测试,打消了我们的顾虑。
最终我们大胆采取了「版心宽度跟随字号调整而变化」,来确保「行长是字号的整数倍」。
虽然「版心宽度跟随字号变化」并没有不适,但我们需要保证在多种屏幕尺寸、字号下,版心占据屏幕的区域都舒适。
面对这个问题,我们制定了一个公式,可根据屏幕大小、字体大小等,自动调整版心宽度。确保「行长是字号的整数倍」的同时,保证页面美观。
标点符号「优先推入式避头尾」。如图所示,为宋抄本《孙子算经》;在古代,书籍排版可以做到字间距恒定,原因是古代不存在「标点」,也就没有「标点避头尾」导致的种种问题。
而现代汉语存在标点符号,有的标点不能放在行首,有的不能放在行尾。
我们把不能放在行首的标点叫做避头标点,如逗号、顿号、句号等;把不能放置在行尾的叫做避尾标点,如前引号、前括号等。
「推出式」避头尾是大部分阅读软件的做法:
以避头标点为例,若此标点被排到了行首,「推出式」 的做法是从上一行拉一个字放在本行。如下图所示:
然而这么做的话,上一行的字间距被拉大,打断了阅读节奏(阅读节奏放慢)。
我们发现专业的排版软件(Indesign)和出版社(广西师范大学理想国系列、人民文学出版社、译林出版社等知名出版社)的做法是「优先推入式避头尾」,这种方式可以很好地解决「仅推出式」造成的问题。如下图所示:
△ 《少数派报告》译林出版社
通过「优先推入式避头尾」,上图中标出的双引号的宽度被挤压了一半,如果它保留为「全宽」,就没办法排在这一行,这就是「优先推入式避头尾」的最终效果。
以避头尾标点为例,「优先推入式」避头尾在这种情况下会将本行内标点宽度挤压,为避头尾的标点腾出空间,如下图所示:
上面的图可以看到:通过「优先推入式标点挤压」,第一行的字间距没有被拉大,保持了密排。
通过将本行内的标点宽度进行挤压后,腾出了空间给本来排不到的逗号,确保了字间距的恒定。然后只有在本行内标点无法压缩出足够空间时,才会选择「推出式」的处理方式。
因此这种处理方式叫做「优先推出式」标点避头尾。
行内标点挤压。因为相邻标点挤压、行首段首挤压,会出现部分标点符号占据半宽的情况。这种时候,一行的末尾可能正好有汉字或标点轧在了边框上,如下图所示,为汉字轧在边框的情况:
遇到这种情况,通过挤压行内标点宽度,从而腾出空间给最后一个字。这种做法叫做「行内标点挤压」
标点悬挂的逻辑和配套内容。存在另外一种处理方式来避头,叫做「标点悬挂」,即将标点悬挂在文本框外。
然而这种采用「标点悬挂」,需要配套做「行尾强制半宽」,如下图所示:
然而行尾强制半宽带来的问题是字间距被拉大,违反了原则一(尽量保证字间距恒定),对于宽度有限的手机屏幕,尽量不要改动。因此最终我们没有采用标点悬挂的处理方式。
Type is Beautiful 网站中对此有详尽的思考,如有兴趣请看:https://thetype.com/kongque/
原则说明
书籍排版中,文字所在的范围称为「版心」。
经验老道的文字排印设计师,检查正文排版效果时,最常用的方法是:离远看页面,就像蒙上一层磨砂玻璃一样,检查整个页面是否疏密均衡(《字体排印》,高冈昌生先生),也叫做「灰度均衡」(龟仓雄策先生称之为「浓淡匀称」,from《疾风迅雷》)。灰度均衡的版心可以让整个页面美观的同时,也保证了读者阅读中不会被突然的空白打断。
如下图所示,第一张图因为一些原因导致了页面中有许多「窟窿」,显得零碎,灰度明显不均衡。而第二张图,通过「标点挤压」将这些「窟窿」填上,整个版面更像是一个整体,灰度更均衡。
看倒数第二行的「乃跪地罪曰:‘大人何故’」两个标点连续的地方,从整体的角度看,会不会觉得这里有一个窟窿?
总的来说,灰度均衡的版面整体感更强、视觉上更舒适,因此也是相当重要的原则。
工序:
相邻标点挤压
众所周知的,汉字是方块字,在字体设计时会被放在一个方形里;中文标点同样也会被放置于同等大小的方框之中,如下图所示。
如上图所示,中文标点所占体积一般远小于汉字,因此当多个标点符号连续排列时,会让版面在这里好像有一个窟窿。专业的中文排版中会做的事情是「相邻标点挤压」。如下图所示:
如上图所示,有连续标点存在时,通过压缩标点所占的宽度,从而补上「窟窿」。
原则说明
与西文书籍的左侧对齐不同,中文书籍(横排)传统而言是讲究两端对齐。这是被大多数国人认可的中文排版方式,因此不再赘述。
工序
行首段首标点挤压。当行首出现标点符号,会感觉左侧不齐:
可以看到,处理前版心左侧因为有单引号,看起来第一行没有和第二行左对齐,处理后效果回归正常。同样的,段首的标点也需要挤压。
知道了以上内容,我们需要把它变成开发需要的逻辑。具体如下:
1. 相邻标点挤压逻辑
在 W3C 的《中文排版需求》中,对相邻标点挤压的具体做法为:
通过梳理,我们将其简化描述为 4个逻辑(实际逻辑与 W3C 基本一致):
备注:成对出现的标点叫做夹注符,如双引号、书名号等;其中细分为开始夹注符与结束夹注符。
此外,我们注意到,一些出版书在以上逻辑之外,把问号与叹号与[顿、逗、句]归为一类,实际效果良好,因此出于问号与叹号同样占据字面不多的逻辑,采用了这种分类法。然而随后在走查阶段发现线上部分字体的问号与句号占据字面的位置不同。
这促使我们关注到标点在字面中占据的位置,我们梳理了客户端所有字体的中文标点。
发现汉仪乐喵、方正兰亭黑、汉仪启体等几款字体的问号字面占据异常,如果进行相邻标点挤压可能会造成标点粘连的情况,因此最终我们决定保持问号与叹号不参与相邻标点挤压。
2. 避头、避尾标点汇总表:
以上逻辑实施需要首先让程序判定哪些标点是避头尾标点,因此我们梳理了所有汉字标点并分为避头、避尾两类(部分标点即避头又避尾)。
汉字标点符号与西文的标点符号许多时候仅凭肉眼难以分辨(如,与,前者是西文标点中的逗号,后者是中文标点中的逗号),因此我们使用Unicode 码为每一个标点精准划分,确保不会造成错误。
3. 行内标点挤压与「优先推入式避头尾」逻辑:
「行内标点挤压」与「优先推入式避头尾」其实本质上都是对行内标点宽度进行压缩,因此在逻辑上归为一类。根据具体解决逻辑的不同将其分为以下四类:
针对这四种情况要做的具体事项补充在右侧:
至于推出逻辑,则稍简单些,书籍中有时候会遇到连续几个标点符号都是避尾标点的情况(或连续避头标点),因此对推出的逻辑设定为:
备注:国内知名的字体设计与排印网站 Type is Beautiful 中有介绍,行内标点挤压的方式有多种,「开明式」「全部半宽式」「平均式」等等…我们选择了「平均式」(有权重),开发难度会稍小些、效果也更可控制,不再赘述。
4. 整体逻辑流程图
但是仅仅通过文字性质的描述还不够,我们需要能让程序理解的逻辑。
由于部分处理方式之间互相干扰,因此根据这四种方式影响的内容不同,以「对其他处理方式的干扰程度」从高到低排序,并串联成整整体的逻辑图,以保证整体逻辑简单、不重复,如下图所示。
在此基础上,我们将文章中第二部分所梳理的逻辑细节填充进流程图内,并合并重复流程,最终获得如下流程图,方便开发理解和工作:
5. 小结
通过「避头尾标点列表」「可挤压标点与挤压空间」「广义的推入逻辑具体说明」「整体逻辑流程图」四个文件,我们可以顺畅的将设计要求传达给开发。
最终,通过这些工序,我们可以在最大限度上保证字间距恒定、版面齐整和内容灰度均衡。提升阅读流畅性和阅读页的体验。
文字排印作为一个古老的技艺,从排版工人操作实体字模的时代,经历了照排时代,来到了数字排版的当下,排版的自由度和效率已然成倍提高,然而由于许多原因至今这些排版的工序只在出版社等专业领域流通。
相对于纸质书籍,电子阅读在易携带性、阅读方便性、多媒体辅助阅读上有得天独厚的优势,然而最基础的阅读体验有时候不如纸质书籍,我们希望通过我们的努力,电子阅读在未来,能够让读者获得全面超过纸质书籍的阅读体验。
当然,文字在移动端的体验上限远不止如此,一些产品仅靠网格系统与字体排印加上优秀的字体,已经做出了令人惊艳的体验。
如上图所示,通过优秀的明朝体、网格系统,物书堂出品的几个词典 APP 的界面让人惊艳,文字之美还有很多可能,这也是我们的努力方向。我们也知道,当前客户端内中英混排、英文排版等方面,依然有进步空间,未来也会进一步完善。
文章来源:优设
微信小程序跳转方式
1.navigator 跳转
最常见的跳转方法就是运用<navigator url="../../.."></navigator>进行跳转,只要在url中添加跳转页面的路径即可。
代码
<navigator url="../skill/skill">
........//跳转涵盖内部所有代码形成的页面
</navigator>
1
2
3
2.运用 bindtap在js中实现页面的跳转
比起navigator的跳转,个人更喜欢用bindtap跳转,运用更灵活,也不用去除点击样式。
bindtap='home'(home位置随便替换)
代码
//wxml
<view bindtap='home'>
</view>
//js
home: function (e) {
wx.navigateTo({
url: '../../..' //跳转链接
})
}
**重点
在开发的过程中,突然遇到以上两种方式都无法实现跳转,也不会报错的情况,经过查询资料,发现内部调用wx.switchTab可以很好的解决这一现象
代码
home: function (e) {
wx.switchTab({
url: '../../..'
})
}
一、前言
我们都知道,vue组件中通信是用props进行父子通信,或者用provide和inject注入的方法,后者类似与redux的porvider,父组件使用,包含在里面的子组件都可以使用,provide/inject用法看我的博客(provide/inject用法),provide/indect官方文档,不过provide/indect一般用的不多,都是用前者,但是props有一个问题,父传子没问题,但是子后面还有子,子后面还有子,子子孙孙无穷尽也,所以这就要一层层的传下去,太麻烦了,所以vuex就派上用场了,vuex作为一个很轻量的状态管理器,有着简单易用的的API接口,在任意组件里面都能使用,获取全局状态,统一获取改变,今天,就看一下源码怎么实现的。
二、准备
1)先去github上将vuex源码下载下来。
2)打开项目,找到examples目录,在终端,cd进入examples,执行npm i,安装完成之后,执行node server.js
3)执行上述之后,会得到下方的结果,表示编译完成,打开浏览器 输入 localhost:8080
4) 得到页面,点击counter
5)最终,我们就从这里出发调试吧。
三、调试
找到counter目录下的store.js 写一个debugger,浏览器打开F12,刷新页面,调试开始。
1.Vue.use()
先进入Vue.use(Vuex),这是Vue使用插件时的用法,官方文档也说了,Vue.use,会调用插件的install方法,所以如果你要写插件的话,你就要暴露一个install方法,详情请看vue官方文档之use的用法
即use会调用下方的方法(debugger会进入)
function initUse (Vue) {
Vue.use = function (plugin) {
var installedPlugins = (this._installedPlugins || (this._installedPlugins = []));
if (installedPlugins.indexOf(plugin) > -1) {
return this
}
// additional parameters
var args = toArray(arguments, 1);
args.unshift(this);
if (typeof plugin.install === 'function') {
// 如果插件的install是一个function,调用install,将 this指向插件,并将Vue作为第一个参数传入
// 所以调用vuex吧this指向vuex,并吧vue当参数传入
plugin.install.apply(plugin, args);
} else if (typeof plugin === 'function') {
plugin.apply(null, args);
}
installedPlugins.push(plugin);
return this
};
}
1.1 vuex的install方法
在源码目录的src目录下的store.js文件里最下方有个install函数,会调用它
debugger进入后
export function install (_Vue) { // _Vue是上面说的Vue作为第一个参数 ,指针this指向Vuex
if (Vue && _Vue === Vue) {
// 如果你在开发环节,你使用了两次Vue.use(Vuex) 就会报下方的错误,提醒你vue只能被use一次,可以自行试试
if (process.env.NODE_ENV !== 'production') {
console.error(
'[vuex] already installed. Vue.use(Vuex) should be called only once.'
)
}
return
}
Vue = _Vue
applyMixin(Vue) // 调用applyMixin方法
}
1.2 vuex的applyMixin方法
applyMixin方法在mixin.js文件里 同样在src目录下
export default function (Vue) {
const version = Number(Vue.version.split('.')[0]) // 获取vue的版本
if (version >= 2) { // vue的版本是2.xx以上 执行vue的mixin混入,该函数不懂用法请查看官方文档,
// mixin合并混入操作,将vuexInit 方法混入到beforeCreate生命周期,意思就是当执行beforeCreate周期的时候
// 会执行vuexInit 方法
Vue.mixin({ beforeCreate: vuexInit })
} else { // 1.xxx版本太老了 现在基本不用,暂不讲解,可以自行了解
// override init and inject vuex init procedure
// for 1.x backwards compatibility.
const _init = Vue.prototype._init
Vue.prototype._init = function (options = {}) {
options.init = options.init
? [vuexInit].concat(options.init)
: vuexInit
_init.call(this, options)
}
}
/*
Vuex init hook, injected into each instances init hooks list.
/
function vuexInit () {
// 因为该方法是在beforeCreate下执行,而beforeCreate的this指向为Vue 所以this === Vue
// 获得vue里面的option配置,这里涉及到vue的源码,以后再讲vue ,
//所以这就是我们为什么要在new Vue的时候,传递一个store进去的原因,
//因为只有传进去,才能在options中获取到store
/
var vm = new Vue({
el: "#app",
data() {return{}},
store
})
*/
const options = this.$options
// store injection
if (options.store) {
// 如果options对象中store有,代表是root ,如果options.store是函数,执行调用options.store()
// 如果是对象直接options.store 赋值给this.$stroe
// 这也就是我们为什么能够在Vue项目中直接this.$store.disptach('xxx')的原因,从这里获取
this.$store = typeof options.store === 'function'
? options.store()
: options.store
} else if (options.parent && options.parent.$store) {
// 如果options.store为空,则判断options.parent.$store有没有 从父元素判断有没有store,
//从而保证子元素父元素公用一个store实例
this.$store = options.parent.$store
}
}
}
1.3 Vue.use(Vuex)总结
至此,Vue.use(Vuex)全部分析完成,总结,就是Vue.use调用Vuex的install的方法,然后install使用mixin混入beforecreate生命周期中混入一个函数,当执行生命周期beforecreate的时候回执行vuexInit 。你可以慢慢调试,所以要好好利用下方的调试按钮,第二个按钮执行下一步,第三个进入方法,两个配合使用。
2.new Vuex.Store
Vue.use(Vuex)已经结束,再回到counter目录下的store.js文件
export default new Vuex.Store({
state,
getters,
actions,
mutations
})
debugger进入,Vuex.Store方法在src目录下的store.js文件下
export class Store {
constructor (options = {}) {
// 这里的options是在counter定义的 state,getters,actions,mutations当做参数传进来
// Auto install if it is not done yet and window
has Vue
.
// To allow users to avoid auto-installation in some cases,
// this code should be placed here. See #731
if (!Vue && typeof window !== 'undefined' && window.Vue) {
// 挂载在window上的自动安装,也就是通过script标签引入时不需要手动调用Vue.use(Vuex)
install(window.Vue)
}
if (process.env.NODE_ENV !== 'production') {
// 开发环境 断言,如果不符合条件 会警告,这里自行看
assert(Vue, must call Vue.use(Vuex) before creating a store instance.
)
assert(typeof Promise !== 'undefined', vuex requires a Promise polyfill in this browser.
)
assert(this instanceof Store, store must be called with the new operator.
)
}
const {
plugins = [],
strict = false
} = options
// store internal state
//下方是在Vuex的this上挂载一些对象,这里暂且不要知道他们要来干什么
// 因为是源码分析,不要所有的代码都清除,第一次源码分析,你就只当他们是挂载对象,往下看
this._committing = false
this._actions = Object.create(null)
this._actionSubscribers = []
this._mutations = Object.create(null)
this._wrappedGetters = Object.create(null)
// ModuleCollection代表模块收集,形成模块树
this._modules = new ModuleCollection(options) //碰到第一个不是定义空对象,debugger进去,分析在下面
this._modulesNamespaceMap = Object.create(null)
this._subscribers = []
this._watcherVM = new Vue()
this._makeLocalGettersCache = Object.create(null)
// bind commit and dispatch to self
const store = this
const { dispatch, commit } = this
this.dispatch = function boundDispatch (type, payload) { // 绑定dispatch的指针为store
return dispatch.call(store, type, payload)
}
this.commit = function boundCommit (type, payload, options) { // 绑定commit的指针为store
return commit.call(store, type, payload, options)
}
// strict mode
this.strict = strict
const state = this._modules.root.state // 获取到用户定义的state
// init root module.
// this also recursively registers all sub-modules
// and collects all module getters inside this._wrappedGetters
// 初始化root模块 注册getters,actions,mutations 子模块
installModule(this, state, [], this._modules.root)
// initialize the store vm, which is responsible for the reactivity
// (also registers _wrappedGetters as computed properties)
// 初始化vm 用来监听state getter
resetStoreVM(this, state)
// apply plugins
// 插件的注册
plugins.forEach(plugin => plugin(this))
// 下方的是开发工具方面的 暂不提
const useDevtools = options.devtools !== undefined ? options.devtools : Vue.config.devtools
if (useDevtools) {
devtoolPlugin(this)
}
}
}
2.1 new ModuleCollection
ModuleCollection函数在src目录下的module目录下的module-collection.js文件下
export default class ModuleCollection {
constructor (rawRootModule) { // rawRootModule === options
// register root module (Vuex.Store options)
this.register([], rawRootModule, false)
}
}
2.1.1 register()
register (path, rawModule, runtime = true) {
// register([],options,false)
if (process.env.NODE_ENV !== 'production') {
assertRawModule(path, rawModule) // 开发环境断言,暂忽略
}
const newModule = new Module(rawModule, runtime)
if (path.length === 0) { // path长度为0,为根节点,将newModule 赋值为root
this.root = newModule
} else {
const parent = this.get(path.slice(0, -1))
parent.addChild(path[path.length - 1], newModule)
}
// register nested modules
if (rawModule.modules) { // 如果存在子模块,递归调用register,形成一棵树
forEachValue(rawModule.modules, (rawChildModule, key) => {
this.register(path.concat(key), rawChildModule, runtime)
})
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2.1.2 new Module()
Module函数在同目录下的modele.js文件下
export default class Module {
// new Module(options,false)
constructor (rawModule, runtime) {
this.runtime = runtime
// Store some children item
this._children = Object.create(null)
// Store the origin module object which passed by programmer
this._rawModule = rawModule // 将options放到Module上 用_raModele上
const rawState = rawModule.state // 将你定义的state取出
// Store the origin module's state
// 如果你定义的state为函数,调用函数,为对象,则取对象 赋值为module上的state上
this.state = (typeof rawState === 'function' ? rawState() : rawState) || {}
}
}
所以Module的this内容为如下:
2.1.3 installModule
function installModule (store, rootState, path, module, hot) {
// installModule(Vuex,state,[],module) module是前面 new ModuleCollection产生的对象
const isRoot = !path.length
const namespace = store._modules.getNamespace(path)
// register in namespace map
if (module.namespaced) {
if (store._modulesNamespaceMap[namespace] && process.env.NODE_ENV !== 'production') {
console.error([vuex] duplicate namespace ${namespace} for the namespaced module ${path.join('/')}
)
}
store._modulesNamespaceMap[namespace] = module
}
// set state
if (!isRoot && !hot) {
const parentState = getNestedState(rootState, path.slice(0, -1))
const moduleName = path[path.length - 1]
store._withCommit(() => {
if (process.env.NODE_ENV !== 'production') {
if (moduleName in parentState) {
console.warn(
[vuex] state field "${moduleName}" was overridden by a module with the same name at "${path.join('.')}"
)
}
}
Vue.set(parentState, moduleName, module.state)
})
}
// 设置当前上下文
const local = module.context = makeLocalContext(store, namespace, path)
// 注册mutation
module.forEachMutation((mutation, key) => {
const namespacedType = namespace + key
registerMutation(store, namespacedType, mutation, local)
})
// 注册action
module.forEachAction((action, key) => {
const type = action.root ? key : namespace + key
const handler = action.handler || action
registerAction(store, type, handler, local)
})
// 注册getter
module.forEachGetter((getter, key) => {
const namespacedType = namespace + key
registerGetter(store, namespacedType, getter, local)
})
// 逐一注册子module
module.forEachChild((child, key) => {
installModule(store, rootState, path.concat(key), child, hot)
})
}
2.1.4 makeLocalContext
// 设置module的上下文,绑定对应的dispatch、commit、getters、state
function makeLocalContext (store, namespace, path) {
const noNamespace = namespace === ''
const local = {
//noNamespace 为true 使用原先的 至于后面的 不知道干啥用的 后面继续研究
dispatch: noNamespace ? store.dispatch : (_type, _payload, _options) => {
const args = unifyObjectStyle(_type, _payload, _options)
const { payload, options } = args
let { type } = args
if (!options || !options.root) {
type = namespace + type
if (process.env.NODE_ENV !== 'production' && !store._actions[type]) {
console.error([vuex] unknown local action type: ${args.type}, global type: ${type}
)
return
}
}
return store.dispatch(type, payload)
},
commit: noNamespace ? store.commit : (_type, _payload, _options) => {
//noNamespace 为true 使用原先的
const args = unifyObjectStyle(_type, _payload, _options)
const { payload, options } = args
let { type } = args
if (!options || !options.root) {
type = namespace + type
if (process.env.NODE_ENV !== 'production' && !store._mutations[type]) {
console.error([vuex] unknown local mutation type: ${args.type}, global type: ${type}
)
return
}
}
store.commit(type, payload, options)
}
}
// getters and state object must be gotten lazily
// because they will be changed by vm update
Object.defineProperties(local, {
getters: {
get: noNamespace
? () => store.getters
: () => makeLocalGetters(store, namespace)
},
state: {
get: () => getNestedState(store.state, path)
}
})
return local
}
function getNestedState (state, path) {
return path.reduce((state, key) => state[key], state)
}
2.1.5 registerMutation
mutation的注册,会调用下方方法,将wrappedMutationHandler函数放入到entry中
function registerMutation(store, type, handler, local) {
// entry和store._mutations[type] 指向同一个地址
var entry = store._mutations[type] || (store._mutations[type] = []);
entry.push(function wrappedMutationHandler(payload) {
handler.call(store, local.state, payload);
});
}
2.1.6 forEachAction
action的注册,会调用下方方法,将wrappedActionHandler函数放入到entry中
function registerAction(store, type, handler, local) {
var entry = store._actions[type] || (store._actions[type] = []);
// entry和store._actions[type]指向同一个地址
entry.push(function wrappedActionHandler(payload) {
var res = handler.call(store, {
dispatch: local.dispatch,
commit: local.commit,
getters: local.getters,
state: local.state,
rootGetters: store.getters,
rootState: store.state
}, payload);
if (!(0, _util.isPromise)(res)) {
res = Promise.resolve(res);
}
if (store._devtoolHook) {
return res.catch(function (err) {
store._devtoolHook.emit('vuex:error', err);
throw err;
});
} else {
return res;
}
});
}
因为entry和上面的_action[type],_mutations[type] 指向同一个地址,所以:
2.1.7 forEachGetter
getter的注册,会调用下方方法
function registerGetter(store, type, rawGetter, local) {
if (store._wrappedGetters[type]) {
if (true) {
console.error('[vuex] duplicate getter key: ' + type);
}
return;
}
store._wrappedGetters[type] = function wrappedGetter(store) {
return rawGetter(local.state, // local state
local.getters, // local getters
store.state, // root state
store.getters // root getters
);
};
}
2.1.8 resetStoreVM
function resetStoreVM (store, state, hot) {
const oldVm = store._vm //将_vm用变量保存
// bind store public getters
store.getters = {}
// reset local getters cache
store._makeLocalGettersCache = Object.create(null)
const wrappedGetters = store._wrappedGetters // 获取installModule方法完成的_wrappedGetters内容
const computed = {}
forEachValue(wrappedGetters, (fn, key) => {
// use computed to leverage its lazy-caching mechanism
// direct inline function use will lead to closure preserving oldVm.
// using partial to return function with only arguments preserved in closure environment.
computed[key] = partial(fn, store)
Object.defineProperty(store.getters, key, {
// 拦截get返回store._vm[key]中的值,即可以通过 this.$store.getters.xxx访问值
get: () => store._vm[key],
enumerable: true // for local getters
})
})
// use a Vue instance to store the state tree
// suppress warnings just in case the user has added
// some funky global mixins
const silent = Vue.config.silent
// silent设置为true,取消所有日志警告等
Vue.config.silent = true
store._vm = new Vue({ // 将state,getter的值进行监听,从这里就可以看出getter其实就是采用的vue的computed
data: {
$$state: state
},
computed
})
// 恢复原先配置
Vue.config.silent = silent
// enable strict mode for new vm
if (store.strict) {
enableStrictMode(store)
}
// 若存在旧的实例,解除对state的引用,等dom更新后把旧的vue实例销毁
if (oldVm) {
if (hot) {
// dispatch changes in all subscribed watchers
// to force getter re-evaluation for hot reloading.
store._withCommit(() => {
oldVm._data.$$state = null
})
}
Vue.nextTick(() => oldVm.$destroy())
}
}
看到这里,你应该对vuex有初步的了解
// 这也是我们为什么能用访问到state,getter的原因
//this.store.dispatch('xxx') ,this.$store.dispatch('xxx')
1
2
相信你也有点乱,其实上面很多我没讲到的不是我不想讲,是具体我也不知道干啥的,看源码学习呢,看主要就行,后面理解多了,其他的就慢慢都会,你不可能刚开始看,就每一行,他写的每一句的用途都知道是干什么的,只能先看主要方法,在慢慢琢磨,一步步来吧,别急,现在我来画一个流程图,更好的去理解吧。
2.1.9 流程图
3.连贯
import Vue from 'vue'
import Counter from './Counter.vue'
import store from './store'
new Vue({
el: '#app',
store,
render: h => h(Counter)
})
当运行new Vue的时候,传入了store,前面minix beforecreate,执行到beforecreate钩子时,会调用vueInit函数,就可以在this.$store取到store对象了,因为options.store有值了 ,不为空,这样就连贯到了,所以这就是为什么可以用this.$store取值。
HTML常用meta大全
Meta标签是HTML语言head区的一个辅助性标签,它位于HTML文档头部的head标记和title标记之间,它提供用户不可见的信息。
Meta : 即 元数据(Metadata)是数据的数据信息。
元数据可以被使用浏览器(如何显示内容或重新加载页面),搜索引擎(关键词),或其他 Web 服务调用。
用我们的大白话来说,它本身是一个没什么用的标签,但是一旦在它内部通过其他属性设置了某些效果,它就起作用了,所以我们称之为“ 元数据 ”。
Code
<!-- 定义文档的字符编码 -->
<meta charset="utf-8" />
<!-- 强制Chromium内核,作用于360浏览器、QQ浏览器等国产双核浏览器 -->
<meta name="renderer" content="webkit"/>
<!-- 强制Chromium内核,作用于其他双核浏览器 -->
<meta name="force-rendering" content="webkit"/>
<!-- 如果有安装 Google Chrome Frame 插件则强制为Chromium内核,否则强制本机支持的最高版本IE内核,作用于IE浏览器 -->
<meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1"/>
<!--
设置视窗大小
width 设置layout viewport 的宽度,为一个正整数,或字符串"width-device"
initial-scale 设置页面的初始缩放值,为一个数字,可以带小数
minimum-scale 允许用户的最小缩放值,为一个数字,可以带小数
maximum-scale 允许用户的最大缩放值,为一个数字,可以带小数
shrink-to-fit=no IOS9中要想前面的属性起作用需要加上这个
height 设置layout viewport 的高度,这个属性对我们并不重要,很少使用
user-scalable 是否允许用户进行缩放,值为"no"或"yes", no 代表不允许,yes代表允许
-->
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
<!-- 页面描述 -->
<meta name="description" content="不超过150个字符"/>
<!-- 页面关键词 -->
<meta name="keywords" content=""/>
<!-- 网页作者 -->
<meta name="author" content="name, email@gmail.com"/>
<!--
搜索引擎抓取
all:文件将被检索,且页面上的链接可以被查询;
none:文件将不被检索,且页面上的链接不可以被查询;
index:文件将被检索;
follow:页面上的链接可以被查询;
noindex:文件将不被检索;
nofollow:页面上的链接不可以被查询。
-->
<meta name="robots" content="index,follow"/>
<!-- 忽略页面中的数字识别为电话,忽略email识别-->
<meta name="format-detection" content="telphone=no, email=no"/>
<!-- IOS begin -->
<!-- 添加到主屏后的标题(iOS 6 新增) -->
<meta name="apple-mobile-web-app-title" content="标题">
<!-- 当网站添加到主屏幕快速启动方式,可隐藏地址栏,仅针对ios的safari (ios7.0版本以后,safari上已看不到效果) -->
<meta name="apple-mobile-web-app-capable" content="yes"/>
<!-- 是否启用 WebApp 全屏模式,删除苹果默认的工具栏和菜单栏 -->
<meta name="apple-touch-fullscreen" content="yes"/>
<!-- 添加智能 App 广告条 Smart App Banner(iOS 6+ Safari) -->
<meta name="apple-itunes-app" content="app-id=myAppStoreID, affiliate-data=myAffiliateData, app-argument=myURL">
<!-- 设置苹果工具栏颜色:默认值为 default(白色),可以定为 black(黑色)和 black-translucent(灰色半透明) -->
<meta name="apple-mobile-web-app-status-bar-style" content="black"/>
<!-- 不让百度转码 -->
<meta http-equiv="Cache-Control" content="no-siteapp" />
<!-- 针对手持设备优化,主要是针对一些老的不识别viewport的浏览器,比如黑莓 -->
<meta name="HandheldFriendly" content="true">
<!-- 微软的老式浏览器 -->
<meta name="MobileOptimized" content="320">
<!-- uc强制竖屏 -->
<meta name="screen-orientation" content="portrait">
<!-- QQ强制竖屏 -->
<meta name="x5-orientation" content="portrait">
<!-- UC强制全屏 -->
<meta name="full-screen" content="yes">
<!-- QQ强制全屏 -->
<meta name="x5-fullscreen" content="true">
<!-- UC应用模式 -->
<meta name="browsermode" content="application">
<!-- QQ应用模式 -->
<meta name="x5-page-mode" content="app">
<!-- windows phone 点击无高光 -->
<meta name="msapplication-tap-highlight" content="no">
<!--
iOS 图标 begin
网站添加至ios桌面时的图标
-->
<!-- iPhone 和 iTouch,默认 57x57 像素,必须有 -->
<link rel="apple-touch-icon-precomposed" sizes="57x57" href="table_ico57.png">
<!-- Retina iPhone 和 Retina iTouch,114x114 像素,可以没有,但推荐有 -->
<link rel="apple-touch-icon-precomposed" sizes="72x72" href="table_ico72.png">
<link rel="apple-touch-icon-precomposed" sizes="114x114" href="table_ico114.png">
<!-- Retina iPad,144x144 像素,可以没有,但推荐有 -->
<link rel="apple-touch-icon-precomposed" sizes="144x144" href="table_ico144.png">
<!-- iOS 启动画面 begin -->
<!-- iPad 竖屏 768 x 1004(标准分辨率) -->
<link rel="apple-touch-startup-image" sizes="768x1004" href="/splash-screen-768x1004.png"/>
<!-- iPad 横屏 1024x748(标准分辨率) -->
<link rel="apple-touch-startup-image" sizes="1024x748" href="/Default-Portrait-1024x748.png"/>
<!-- iPad 竖屏 1536x2008(Retina) -->
<link rel="apple-touch-startup-image" sizes="1536x2008" href="/splash-screen-1536x2008.png"/>
<!-- iPad 横屏 2048x1496(Retina) -->
<link rel="apple-touch-startup-image" sizes="2048x1496" href="/splash-screen-2048x1496.png"/>
<!-- iPhone/iPod Touch 竖屏 320x480 (标准分辨率) -->
<link rel="apple-touch-startup-image" href="/splash-screen-320x480.png"/>
<!-- iPhone/iPod Touch 竖屏 640x960 (Retina) -->
<link rel="apple-touch-startup-image" sizes="640x960" href="/splash-screen-640x960.png"/>
<!-- iPhone 5/iPod Touch 5 竖屏 640x1136 (Retina) -->
<link rel="apple-touch-startup-image" sizes="640x1136" href="/splash-screen-640x1136.png"/>
<!-- IOS end -->
<!-- Windows 8 磁贴颜色 -->
<meta name="msapplication-TileColor" content="#000"/>
<!-- Windows 8 磁贴图标 -->
<meta name="msapplication-TileImage" content="icon.png"/>
<!-- 添加 RSS 订阅 -->
<link rel="alternate" type="application/rss+xml" title="RSS" href="/rss.xml"/>
<!-- sns 社交标签 begin -->
<!-- 参考微博API -->
<meta property="og:type" content="类型" />
<meta property="og:url" content="URL地址" />
<meta property="og:title" content="标题" />
<meta property="og:image" content="图片" />
<meta property="og:description" content="描述" />
<!-- sns 社交标签 end -->
低版本IE浏览器访问问题
添加好强制Webkit内核的代码,使用国产浏览器访问网站已经不存在IE兼容问题了,IE访客量将大大减少。但仍然不可避免会有一些老旧电脑通过低版本IE浏览器访问,如果我们专门为了这极小部分用户进行 CSS HACK ,将严重加重我们的工作量。
更何况自2016年1月起微软已经停止为IE11以下版本提供支持和更新,已经这么久没有更新,低版本IE不仅对CSS3和HTML5支持有问题,更有安全问题。
那么,我们不去支持低版本IE,这小部分用户怎么办呢?
我们可以使用 if IE 语句给网站添加IE升级提示,提示用户进行浏览器升级,或者切换更先进的浏览器再访问。
我们可以在刚刚的meta标签下添加一段跳转到IE升级提示页的代码(当IE版本低于IE11时跳转),实现低版本IE用户访问时提示他们进行升级或者更换浏览器。
强制Webkit内核和提示低版本IE访问用户升级完整代码如下所示,把这段代码添加到头部模板文件标签下即可:
<meta name="renderer" content="webkit"/>
<meta name="force-rendering" content="webkit"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
<script>/@cc_on window.location.href="http://support.dmeng.net/upgrade-your-browser.html?referrer="+encodeURIComponent(window.location.href); @/</script>
1
2
3
4
(@cc_on 是 IE10 及更旧版IE特有的条件编译语句,因此可以用来判断是否除 IE11 以外的其他IE版本。)
因为低版本IE访问时因为不兼容CSS3和HTML5网站往往是错版的,添加了上面这段代码,当低版本IE用户访问时就会跳转到升级提示页,避免不必要的资源加载,降低网站服务器开销。
测试代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<meta name="renderer" content="webkit"/>
<meta name="force-rendering" content="webkit"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
<script>/@cc_on window.location.href="http://support.dmeng.net/upgrade-your-browser.html?referrer="+encodeURIComponent(window.location.href); @/</script>
</head>
<body>
<h1>Hello world</h1>
</body>
</html>
IE 11 会正常输出
IE 10 将会看到以下页面
参考
前端 Meta 用法大汇总 - MR_LIXP
通过meta代码强制浏览器使用WebKit内核极速模式(解决 meta name=“renderer” content=“webkit” 不起作用)- 艾欢欢
概述
replace的参数是char和CharSequence,即可以支持字符的替换,也支持字符串的替换(CharSequence即字符串序列的意思,说白了也就是字符串)
replaceAll的参数是regex,即基于规则表达式的替换,比如:可以通过replaceAll("\d", “*”)把一个字符串所有的数字字符都换成星号
相同点
相同点:都是全部替换,即把源字符串中的某一字符或字符串全部换成指定的字符或字符串
不同点
不同点:replaceAll支持正则表达式,因此会对参数进行解析(两个参数均是),如replaceAll("\d", “"),而replace则不会,replace("\d","”)就是替换"\d"的字符串,而不会解析为正则
另外还有一个不同点:“\”在java中是一个转义字符,所以需要用两个代表一个。例如System.out.println( “\” ) ;只打印出一个""。但是“\”也是正则表达式中的转义字符,需要用两个代表一个。所以:\被java转换成\,\又被正则表达式转换成\,因此用replaceAll替换“\”为"\",就要用replaceAll("\","\\"),而replace则replace("\","\")
如果只想替换第一次出现的,可以使用replaceFirst(),这个方法也是基于规则表达式的替换,但与replaceAll()不同的是,只替换第一次出现的字符串
HTTP响应状态码
200 OK
201 Created
202 Accepted
203 Non-Authoritative Information
204 No Content
205 Reset Content
206 Partial Content
207 Multi-Status (WebDAV)
226 IM Used (HTTP Delta encoding)
200 OK
请求成功。成功的含义取决于HTTP方法:
GET:资源已被提取并在消息正文中传输。
HEAD:实体标头位于消息正文中。
POST:描述动作结果的资源在消息体中传输。
TRACE:消息正文包含服务器收到的请求消息
PUT 和 DELETE 的请求成功通常并不是响应200 OK的状态码而是 204 No Content 表示无内容(或者 201 Created表示一个资源首次被创建成功)。
201 Created
表示请求已经被成功处理,并且创建了新的资源。
这通常是在POST请求,或是某些PUT请求之后返回的响应。
新的资源在应答返回之前已经被创建。同时新增的资源会在应答消息体中返回,其地址是原始请求的路径或者是 Location 首部的值。
202 Accepted
表示服务器端已经收到请求消息,但是尚未进行处理。
但是稍后无法通过 HTTP 协议给客户端发送一个异步请求来告知其请求的处理结果。该状态码适用于将请求交由另外一个进程或者服务器来进行处理,或者是对请求进行批处理的情形。
203 Non-Authoritative Information
表示服务器已成功处理了请求,但返回的实体头部元信息不是在原始服务器上有效的确定集合,而是来自本地或者第三方的拷贝。
当前的信息可能是原始版本的子集或者超集。例如,包含资源的元数据可能导致原始服务器知道元信息的超集。
使用此状态码不是必须的,而且只有在响应不使用此状态码便会返回200 OK的情况下才是合适的。
204 No Content
表示该请求已经成功了,但是客户端客户不需要离开当前页面。
默认情况下 204 响应是可缓存的。一个 ETag 标头包含在此类响应中。
使用惯例是:
在 PUT 请求中进行资源更新,但是不需要改变当前展示给用户的页面,那么返回 204 No Content。
如果创建了资源,则返回 201 Created 。
如果应将页面更改为新更新的页面,则应改用 200 。
205 Reset Content
用来通知客户端重置文档视图,比如清空表单内容、重置 canvas 状态或者刷新用户界面。
与204响应一样,该响应也被禁止包含任何消息体,且以消息头后的第一个空行结束。
206 Partial Content
服务器已经成功处理了部分 GET 请求。
类似于 FlashGet 或者迅雷这类的 HTTP 下载工具都是使用此类响应实现断点续传或者将一个大文档分解为多个下载段同时下载。
该请求必须包含 Range 头信息来指示客户端希望得到的内容范围,并且可能包含 If-Range 来作为请求条件。
如果只包含一个数据区间,那么整个响应的 Content-Type 首部的值为所请求的文件的类型,同时包含 Content-Range 首部。
示例:
HTTP/1.1 206 Partial Content
Date: Wed, 15 Nov 2015 06:25:24 GMT
Last-Modified: Wed, 15 Nov 2015 04:58:08 GMT
Content-Range: bytes 21010-47021/47022
Content-Length: 26012
Content-Type: image/gif
... 26012 bytes of partial image data ...
包含多个数据区间,那么整个响应的Content-Type首部的值为multipart/byteranges,其中一个片段对应一个数据区间,并提供 Content-Range 和 Content-Type 描述信息。
示例:
HTTP/1.1 206 Partial Content
Date: Wed, 15 Nov 2015 06:25:24 GMT
Last-Modified: Wed, 15 Nov 2015 04:58:08 GMT
Content-Length: 1741
Content-Type: multipart/byteranges; boundary=String_separator
--String_separator
Content-Type: application/pdf
Content-Range: bytes 234-639/8000
...the first range...
--String_separator
Content-Type: application/pdf
Content-Range: bytes 4590-7999/8000
...the second range
--String_separator--
207 Multi-Status (WebDAV)
代表之后的消息体将是一个XML消息,并且可能依照之前子请求数量的不同,包含一系列独立的响应代码。由WebDAV(RFC 2518)扩展的状态码.
226 IM Used (HTTP Delta encoding)
服务器已经完成了对资源的 GET 请求,并且响应是对当前实例应用的一个或多个实例操作结果的表示。
根据遇到的问题,总结的一些小经验,个人的一点薄见,欢迎大家交流。
最近有小伙伴接到了国外的运营外包项目,发现国外和国内对于运营的需求是有差别的,这篇文章就谈谈国内外:中国、韩国、欧美,它们运营设计的各自特点和里面的门道。
主要从以下三方面来说:
三个地区文化差异大不相同。
中国从古到今讲究遵循传统,过年过节讲究气氛,营造一种氛围。用红包来表达祝福和心意;其次,就是社会环境,中国人喜欢「热闹」,逢年过节,一帮人聚到一起才热闹,别人都买、卖的火的肯定就是好的,所以就「跟风效仿」。
韩国建国很晚,历史相对比较短。所以文化氛围比较年轻,偶像文化在韩国盛行,传统的东西相对较少,都是比较年轻化的:年轻化的偶像,年轻化的文化,年轻化的价值观。
欧美整个文化环境受移民的影响,比较开放国际化,体现的文化也是比较多元的,包容性、简洁、时尚是这些它最直接的特点。
接下来分别谈一下三个地区运营推广的设计特点差异化:
中国
中国的运营突出的是优惠:送红包、满减、优惠券等;必须要喜庆,鲜艳的色彩:以红色、橙色、黄色居多,来刺激消费。就好像进了卖场「瞧一瞧看一看,季末打折满100减99」
韩国
韩国的文化里「社交」这一关键词体现的尤为重要,「Line」这款APP对韩国影响比较大,里面的矢量涂鸦风格的插画深入人心,Line 官方设计可爱且特色鲜明的「馒头人」、「可妮兔」、「布朗熊」和「詹姆士」四个形象也很有特点,所以Line的「IP属性」和「矢量涂鸦」就成为了韩国主流设计风格。
而韩国的偶像明星文化也颇具影响力,在推广时也会用到明星来制造效应。
我们从整体构图解析一下三个地区的设计细节。首先把一个运营 banner 按照组件层级进行拆分,分别来看一下。
分为:文字层、主体物层、装饰层、背景层
文字层
从文字层可以看出中国的文字层级划分更加清晰。运用「格式塔」原理,主文案和副标题的突出,能让用户对信息提取更加准确直观。
中文结构的复杂性,排版上必须严格区分,所以在层级划分上装饰线的运用也是比较常见的。(后面有针对性字体设计形式,详细解读,这里就不细说了)
韩文的本身的图形特质,为保证识别度,文案提炼精简,信息层级相对较少。文字层级一般分为两层。
有意思的是韩文由于字形结构的单一,排版上很多时候会搭配字体形式的变化来增加排版样式的变化,装饰线的运用也比较多变。
英文排版上本质上和韩文相同——符号化,通过字形的特点来特出主要信息。
排版的优势性,让其排版变化上自带韵律感,加上装饰线的运用,本身字形的符号特性视觉呈现很有设计感。
主体物层
为了辅助信息传达,往往采用图文结合,注意主体物的构图布局朝向,主题物对主要文字起到视觉引导作用,用户的聚焦点一定是主文案,而不是主体物。
下图里「手」作为整个画面的「支点」,把模特的脸部向主要文案指引,最终达到活动的最核心。
下图里「眼睛」为整个画面的「支点」,利用眼睛视线,把用户向文案核心指引。
利用周围的物体形成三个「支点」,把视觉中心聚焦到中间主文案。
装饰层
点线面是运用最多的元素,中国把平面设计的三大构成运用到了。
为了营造构图的稳定性,在设计里加入点和线的元素让画面占比更加平衡,同时弥补画面中空白的地方,减少负空间,让构图更加饱满。
同时点和线的元素也是为了辅助衬托主体元素,分布要合理,避免过多,造成使画面的拥挤。
面简单的理解成形状,用形状配合主体物,达到聚焦视觉中心,来突出主题,但是形状不易过于复杂,「格式塔原理」——「简单」原则(我们的眼睛在观看时,眼脑并不是在一开始就区分一个形象的各个单一的组成部分,而是将各个部分组合起来,使之成为一个更易于理解的统一体),用户更容易理解。
三角形——利用稳定的特点来达到视觉的平衡
圆形——最简单直接有效的图形元素,视觉聚焦更明确
多边形——丰富画面,多边形的边角多的特点也多用做突出设计的高逼格
形状流体在营销中突出了活动的促销感和气氛,在重大活动中经常看到,色彩上也比较鲜艳。相较欧美、韩国,中国电商运用比较多,这也侧面反映出出中国运营推广竞争的残酷性映射出来的「浮夸」。
背景层
中国设计里最多变的就属背景了。可以利用多彩渐变、放射的线、还有图案叠加,让画面迎合表达的主体基调
韩国的设计分为涂鸦放射背景和纯色背景简单几何形状
欧美撞色对比和性冷淡纯色底,也会运用视错觉,来吸引用户眼球
文案是营销第一要素,想办法突出运营信息,是所有运营必须要注意的,这就有了根据活动气氛进行字体设计。
中国字体的复杂和文化的久远特殊性,对于字体我们是有很深的研究和造诣,表现空间也很大。另外由于中文字形的复杂,导致字库设计成本比较大,字体种类相对较少,为了避免字体版权问题,多采用一些设计变形来达到营销推广目的。比如针对字形进行改变、针对笔画进行加工、针对体积和质感进行变化等。
层次叠加——提取等比重的笔画,变现字体的体积和空间感
笔画链接——让文字直接更紧凑,空间运用更整体合理
笔画装饰——增加文字设计感,突出表现风格
厚度层次——让字体突出,增加厚重感
字形改变设计——考虑到字体版权,对字体进行再设计,结合主题进行宣传
字体内部装饰——字体呼应主题的一种简单方式
韩国字形相对简单,变化不是很多,运用厚度体积和笔画装饰来表现字体
欧美的西文字体更多的是运用和主体物穿插、和字形搭配的变换,和复古风字体厚度设计
可以看到不同的国家地区根据本国习惯、审美、风格不同,都有各自不同的设计。中国营销快速简单直接出效果;韩国偶像路线,不管是对于模特的选择,还是网红形象的选择,把粉丝效应运用到了;而欧美则简单兼顾设计创意表现。
随着教育在大趋势下崛起,很多学习类产品开始出现。很多在做教育的小伙伴求助,学习类的运营 banner 无从下手,思路不清晰,那么我稍稍的屡了一下思路,浅谈一下学习 banner 的个人薄见。
在很多设计师刚开始缕思路的时候,总因为无从下手而着急盲目,下意识的认为「我不会啊」!那复杂的不会,就从简单的开始缕。从设计开始,我构图都没想好,设计啥设计?那再简单点,从构图开始吧,我思路都没想好,构啥图?继续再简单点,一步一步往前推,直到推到最简单的细节,从最初第一步开始做。你会发现,首先第一步就是先建立一个符合主题的思路。
谈到学习类 banner,光听一听就觉得头大甚至很讨厌的事情。因为本来学习就是让人很拒绝的,如何引导用户主动做一个不想做的事,就得用到一些特殊的手段了。
咱们这次主要从以下方面来多维度探讨:
K12教育
K12 也就是九年义务教育和三年高中,所以人群定位青少年,年龄 7-18 岁。这个年龄段非常单纯。
大家可以回想一下我们小时候,在这个年龄最享受的是什么?那就是「满足感」和「参与感」,我们小时候玩积木、做游戏,最重要的不是赢了能得到什么东西,而是参与到其中享受快乐。所以建立一种参与感,参与进来「一起玩」的感觉,就会得到满足。
我们看到下面这类 APP,为了能够吸引这一部分用户,大多都以有趣好玩为主,「游戏性」是最大的特点,所以情感化设计是非常好的选择,通过丰富的「体积感插画」用游戏的思路激发兴趣,让他们感觉有参与感,从而吸引他们「想看」并「点击」。
知识付费
而对于成年人来说,学习需求变成一种「插件思维」,能够快速学会,或者说是能够收获很多的干货,突出不枯燥、学会、能懂。这类的情感化设计相对于少儿学习插画,更多的突出主题所以,以更极简的设计形式。
有时候文字作为主视觉中心更加直观,整体设计风格可能更加简单,对于成年人来说,这种设计更能直达我们需求本身,更容易理解。
高端知识分享
对于这部分人来说,定位跟上面两类完全不同,从所处层级来说就不同。首先这类人,学习的可能就不只是干货了,更多的需求是职场环境带来的。比如,我怎么跟同事、朋友、下属更好的相处,如何具有更好的说服力,或者是想进修一下,就有了学习需求,基于「马斯洛金字塔」里的,这里学习需求可能也是更高层次的,为了体现自己的价值。
所以更关心的是,这是谁讲的课;通过设计情绪版,映射出当前课程的专业、严谨、课程的价值高,这些关键点。
课程的品质感的体现,颜色不能太多,插画设计元素少,更多的利用文字排版和少量图形来突出主题。
总之,根据所代表的用户来针对性设计。就好像剪头发,Tony 老师肯定不会给一个 20 多岁的年轻人理一个小平头;穿衣服也一样,你上班肯定不会穿个背心裤衩就去了,但在海边放松你也不可能穿个西装、牛仔,我相信大家都能明白这个道理。
良好的构图,目的是能够让用户易懂,首先结构要清晰、简单,主要构图比如:居中、左右。
居中结构
突出活动文案,居中构图是个很好的选择,不足就是体现不了有趣好玩的调性。直白说主要就是明确干什么。所以用户的视觉焦点会聚集在重心区域,忽略掉周围的东西,在设计的时候,周围元素主要是衬托,不能抢主视觉重心。
所以说运用插画风格的话,简单轮廓插画和剪影插画是最适合的。
设计的时候注意几点:一,主体物要突出饱满,太小容易画面太空;二,弱化辅助元素并不是要把它做的粗糙。
左右结构
左右结构分为两种,一种是左图右文,左文右图。两种构图的秘密在于,用户的浏览顺序是「从左至右」,如果图在左边,图在表意性不明显的情况下,我们需要看一遍文字,再看一遍图,这样图相当于重复浏览了两次;文字在左边就减少了重复阅读,提高了阅读效率,在运营推广「3秒原则」里,是首先要考虑的因素。而插画设计本身也是为了烘托气氛,表意性不是很明显,所以突出文案尤为重要。
所以,市面上大部分的学习知识类左右构图的,更倾向第二种左文右图。
左右构图受限于屏幕显示内容,所以,有一个明确的主体物至关重要。比如:像 VIPKID 和哒哒英语,会有一个自己的 IP 主形象,比较生动突出了品牌特征,还能让用户有代入感;还有像一些知识分享的,就会有一个明确的讲师或者人物,体现专业权威性,在设计上一切都以突出主人物来展开,就不要设计太过于复杂。
不同于电商的模特,主人物首先穿着上不能太花哨,要正式;周围装饰上不要太浮夸,要精简,甚至像高端知识分享的,背景就一个简单颜色来衬托。
衍生结构
还有以上面构图衍生的构图形式,比如倾斜构图,受限于 banner 尺寸高度,过大的倾斜角度会让画面失衡,负空间留白不均,会让画面不协调。
构图比例合理
注意画面整体构图比例,文案占比永远都是大的,不要让主要画面辅助元素过大抢了文案的风头,因为即使图形辅助再精彩,用户更关心的是「你能给他看什么」。
文案和辅助元素大概是六四开,黄金分割比例能让焦点更多的关注内容,有些小伙伴可能在做的时候过多的沉浸在放大的画布里,由于没有直观感受,错误的预估当前构图的合理性,所以做图的时候多缩小画布离远看是个非常不错的方法。还有一个就是,做完了 banner 导出图片,双击打开图片,把图拖小到无法拖动为止,再看当前实际尺寸大小,用实际尺寸来检查。
更多构图形式:《文案超多的海报设计该怎么排版?16个实用模板送给你!》
文案选字
文字的选择也是衡量当前产品用户定位的标准,首先文字结构不能太过于复杂,K12 教育因为本身用户年轻化的特点,字形简单而且饱满有趣,所以一些艺术手写可爱字体比较适合。
免费商用可爱字体推荐:沐瑶软笔手写体、站酷快乐体、郑庆科黄油体、站酷小薇 LOGO 体、锐字真言体。
其他可爱字体推荐:汉仪唐美人、汉仪糯米团、汉仪铸字童年体、汉仪小麦体、方正胖娃体、方正字迹新手书、造字工房童心、文悦方糖体。
如果是高端知识分享和知识付费,体现高端。文字就要简单,字形不要那么随意,一些黑体和简单的衬线体就比较合适。
免费商用黑体衬线体推荐:思源黑、思源宋、站酷文艺体、方正书宋简体。
其他黑体衬线体推荐:汉仪瑞意宋、方正清刻本悦宋、方正兰亭、造字工房朗宋、造字工房黄金时代体、造字工房尚雅体。
文案排版
当用户人群比较年轻,意味着所有的呈现方式都要直接,文案精简并且排版直观,提炼关键主标题,信息层级要拉开明显,如果信息层级不清楚,识别性是很差的,先看下面的案例:
看这两个,明显右边的对于我们识别,和对当前功能的认知更清晰,还有一个明显的视觉引导,突出了最主要的交互点;而左边的由于信息比较密集,导致我们无法短时间反应出关键信息是什么,这是比较糟糕的,运营同样如此,而运营比较尴尬的一点是,如果用户看不懂或不感兴趣,是根本不会点击、不予理睬的。那么流量入口的意义就没有了,设计的再精美,都是一个非常差的作品。
提炼文案是很有必要的,下面两个同样的设计,右边对于文案的认知就更明显。
有的时候往往文案的关键信息比较多,我们在排版的时候第一个重要点,就是排版怎么拉开信息对比,同时又让排版紧凑?有时需要增加一些特定图形,或按钮。但是又会考虑,加了这些特殊图形和文案又显得相对独立不整体;还有就是关键点该怎么取舍,强化那些文案、弱化哪些,或是主文案是重要的,同时关键的数字也要突出怎么办?
左图,左边主文案放大了关键点,由于右边文案文字比较粗,所以层级拉开还是不够突出,如果想拉开对比的话那就让他们截然不同。右图,字体选择上拉开强弱,让右边的文字比划选择细一些;左图的关键数字不够突出,通过提亮改变颜色,来强化;按钮在左边画面显得太独立,和文案没有形成统一关系,反而按钮看起来变得要比文案重要,那么把按钮插到里面。
现在所有的信息,一环插一环,并且突出的文案明显,关键数字也明显;最后让他们整体在画面构图中面积比例放大,改版后是不是舒服很多。
还有一些小伙伴可能觉得亲密性原则不就是距离相近么,然后没有把控好各个部分的文字距离,导致反而该拉开的没有拉开,该近的没有近。
上图这个案例里,主文案分为了两行,但左边由于主文案行距比较宽,而跟辅助信息的距离太近,导致亲密性不够,而由于副标题文案又比较孤立,上半部的信息和下半部信息太散,整体统一度不够。
右图改进后,调整合适行距,并且加装饰线,协调一下辅助信息比较短的问题,同时也起到分割的作用让上下文案有关联和统一。
文案排版的其他细节还有错位排版时,注意错位的大小,太大会丢失掉排版的平衡性;注意文案排版对齐,往往有的时候不注意,会让你的作品看起来不够精细;文案做倾斜处理的时候,一般情况都是往右边倾斜,第一,右边都是我们的主要习惯方向;第二,往右可以很好的把用户引导到关键信息上,这些也是做 banner 排版里经常犯的错也是要规避的一些坑。
颜色倾向
如果是代表年轻化,多用绿色和黄色,绿色代表活力、生机、积极向上;黄色系代表温暖、希望、舒服。
这两个颜色由于是临近色的关系所以也会搭配出现在画面中,色环 90° 角的颜色搭配所以是最舒服的配色,还有就是颜色对比非常和谐。
由于蓝色是绿色的邻近色、是黄色的对比色,红色是绿色的互补色、黄色的邻近色,所以绿、黄、蓝、红这些会在画面中组成主色、辅色、点缀色。
注意协调好每部分颜色之间占比,主色 60% 左右、辅色 30% 左右、剩下的辅助色 10% 左右。
如果是代表科技、互联网,就会以蓝色为主,颜色也不会像上面那样丰富,颜色体现的设计也没有那么活泼,代表冷静、自然、科技。
一般画面在2-3种颜色,以蓝色为主,再搭配紫色。因为紫色和蓝色是邻近色,不会像暖色调一样太冲撞,所以我们经常会看到蓝紫这种「好基友」的搭配。
知识分享类,分为普通的讲师分享和高端知识分享。讲师分享颜色要亮一些,颜色搭配大概是 1-2 种。
而高端知识分享,要体现知识的稀缺性和专属感,代表尊贵、品质、价值,颜色大多用暗色或消色(消色就是黑白灰),大概也是 1-2 种。
颜色方面我总结几点坑需要避开的:一,如果用插画表现,一定要避免颜色的灰和脏,因为学习代表着积极向上的,灰色不适合。颜色选取的时候大概要避开「颜色警示区」的位置。
二,插画风格避免选择紫和一些冷色调,以暖色调为主,才能贴合情绪版定位;三,同样的道理,颜色不要过于艳丽、过于刺激,反差明显了,反而阅读体验不好,影响观感;四,所有的颜色最终都要定位到主题上,做完后,反向推倒检查一下,颜色呈现符合当前文案定位吗?符合面向的用户人群吗?根据主题当前配色合适吗?
更多电商配色方法:《这是一篇不看会后悔的配色干货》
最后我想说的是,所做的东西把自己当成模拟用户自检一下,自己如果看到这个 banner 会点击么?会吸引到你么?能直观感受到么?会让你舒服么?如果连自己都无感,那是相当失败的。
运营专题在设计的时候,我们要考虑的是针对运营需求,给特定需求的用户传达零成本阅读体验,很多设计师做的 banner 画面特别乱,原因是画面中没有突出主体元素。
所以在 banner 设计上要考虑贴合当前传达的主题,所谓设计的「言之有物」。下面来详细的谈一下运营设计时如何突出主体元素,其中的言之有物。
运营活动最终想给用户快速传达文案信息,也就是「三秒阅读」体验,配合辅助文案的主体图形更能方便用户去理解。
可以看到下图左为了让画面丰富,设计师往往会加一些辅助元素或图形;而图右糟糕的元素添加反而会过度设计,干扰用户对于运营活动本身的理解和其表意性。
那么,如何突出视觉主体物,而达到最理想的设计作品的表意性呢?
我们从以下五方面细谈:
流畅线条的运用
我们在看音乐类运营 banner 的时候发现线条运用非常普遍,特别是利用 illustrator 里的「混合工具」来实现两条线之间的复制混合。
人物涂鸦剪影
往往为了表现特定的主题:比如年轻化、个性化,通过错位移动,添加亮色来使人物突出,往往更多的表现在:音乐、设计、嘻哈接头文化的电商运营宣传中。
这类设计个性鲜明,所以做之前考虑好,所要传达的特定的人群是不是符合当前的审美认知。
简单几何形状
有时候为了让传达主题更加明确,简洁化设计很有必要「少即是多」,所以比如学习和知识付费类的设计,干脆就用简单的圆形、方形、波浪曲线。
下面这个案例,设计者为了使这几个人物不会太散,用圆形包裹,目的也是为了更加整体,试想一下,如果把圆形去掉,人物不仅会显多,画面也显得既单调又乱。
立体几何载体
让风格调性显得品质高,同样让设计少而精。需要注意的是,品质感高逼格的设计所用的图形相对要规整一些,尽量不要显得太随意,多用方块形状和立体几何载体。
比如网易严选,为了突出「所卖东西都是精挑细选」,会采用立体几何载体衬托的方式,所表现的就是「隆重和百里挑一」,我们不一样~(会唱的朋友们一起唱)
多边图形
运用模特表现的时候,高品质感会用一些规则多边形,比如:四边形、多边形等。多边的形状会给我们带来稳重感,「尊贵感与众不同」往往是这类商品想传达给我们的。
不规则流体
促销类电商营造促销的氛围,激发购买欲,会用到多色彩的不规则流体,目的为了传达「降价、折扣、满减、超值、限时」等 ,往往会为围绕主体物四周,突出主体元素。
小结
图形化元素是最简单也是最实用的一种突出主体元素的表现方式,不管是用哪种,一定要想明白所表现的主题:符不符合当前主题;推广所属对应的用户群体:心理认知上能不能赞同;满足这两点,所加的图形才有意义。任何图形都是为了辅助突出主体元素,记住这一点,你就不会盲目的进行设计。
文字铺底
直接把相关文字铺底是最常见的一种方式,用当前所对应主题的人物名字、相关文案、对应英文等。
文字与主体穿插
利用文字与主体的穿插营造出空间感,也是在平面设计中经常用到的手法,同样也是在保证文字基本的识别度前提下。
注意文字颜色与主体颜色之间要拉开反差,不要糊成一坨,就适得其反了,适当的时候加一点点阴影还是很有必要的。
小结
不管是什么形式,所加的文字要有意义,跟主题相关。任何加的文案一定要能衬托主体物,也要有很强的表意性,毕竟文案才是最重要的。
主体物吸色衬托
从主体物上之间选取,作为背景颜色搭配,来衬托主体物简直不要太完美,前提是主体物的颜色相对够和谐舒服。需要衬托的背景颜色相对要弱一点,以突出主体。
颜色对比
我们一想到颜色对比,马上想到「红配绿赛狗屁」这句话,这句话的意思不是说红配绿不行,而是说错误的红配绿不行,听不懂啊?来,举个栗子…李子…梨子…例子!
左图的红绿颜色占比可以看到是比较平均的,大概1:1的比例,但是红色作为一个比较刺激的颜色,颜色很鲜艳,如果搭配的绿色也一样饱和度很高,两个撞到一起没有一个突出色,画面就不会那么和谐了。
右图如果我们把红色的占比相应的减少,饱和度不变,而绿色降低饱和度,从而面积占比增大,起到衬托的作用,画面之间是不是舒服很多。
下图其他的颜色对比同样如此。
小结
颜色对比的口诀是:主体如果是亮色,背景就用冷色;主体用重色,背景用亮色;主体用纯色,背景用灰色;不管哪种对比,主体物的颜色一定是面积最小的那一个,无论如何拉开颜色对比反差是王道。
人物面部打光
人物面部是最容易辨识,也是视觉焦点部分,所以让面部从画面凸现出来尤为重要,让光源聚焦到脸部,主体人物显得更加有质感和饱满度。
物体亮部打光
物体和人物相比相对简单,亮部高光部分为视觉焦点,让物体显得有质感,只需要给亮部特殊光源即可。
小结
光线能在突出主体物的同时,让构图更加丰富和饱满,切记光线不要过亮,看起来会很不舒服。
人物截取范围
截取摆放人物的时候注意,为了让视觉焦点集中、有辨识度。具有代表性的人物和明星一般截取一半左右,大约胸部以上部位;而电商模特为了展示所卖衣服,一般露出大约三分之二。
万万不要切头部
让人物完美的呈现在画面之中,让画面能够看起来舒服些,构图也相对完整,反之把头部一刀切,会使画面负空间变小,构图拥挤,而观看者对于人物的识别度也随之降低,阅读成本变高。
多人物摆放要求
多人物组合时,整体人物处理要基本保持一致,特别是眼睛视线要尽量保持统一的视觉基准线,不然会显得杂乱不堪。
多物体的摆放
表现美食产品的时候,文字居中构图,物体散点摆放要注意,角度的统一,统一俯视角度,不要有俯视有平视,保证统一度。
案例分析
这是一个小伙伴做的医美类的运营 banner,当看到这个设计的时候我的内心是崩溃的,我们分析一下问题。
问题一,首先主体物没有突出,主体人物偏灰,背景也灰;问题二背景太乱,没有视觉焦点;问题三,文案识别度完全丢失掉了,排版也太乱;好下面我们来改一下。整体看下来,并没有表现出医美要体现的「变美」,用户完全没有视觉感受。
调整改动
前后对比
最后我们来看看对比效果,是不是好很多。
无论什么样的设计,加什么样的元素,目的只有一个就是要有理有据、有道理,所有的运营设计都是为了辅助主体文案。不要让你的设计无用,或者减分,把设计元素最大化发挥它的作用才是设计的最终目的,你就说是不是吧!
在vue项目中,和后台交互获取数据这块,我们通常使用的是axios库,它是基于promise的http库,可运行在浏览器端和node.js中。他有很多优秀的特性,例如拦截请求和响应、取消请求、转换json、客户端防御cSRF等。
一 . 安装
npm install axios;
1
二 . 引入
在项目的src目录中,新建一个request文件夹,然后在里面新建一个http.js和一个api.js文件。http.js文件用来封装我们的axios,api.js用来统一管理我们的接口。
三 . 开始封装
在http.js中引入axios . vue及其他
import Axios from 'axios'; // 引入axios
import { Message, Loading, Notification } from 'element-ui'
import vue from 'vue';
1
2
3
http.js文件全部代码如下:
import Axios from 'axios';
import store from '../store';
import { Message, Loading, Notification } from 'element-ui'
import vue from 'vue';
// 环境的切换
if (process.env.NODE_ENV == 'development') {
Axios.defaults.baseURL = "http://10.230.39.58:33390/devops";
}
else if (process.env.NODE_ENV == 'production') {
Axios.defaults.baseURL = "http://10.230.39.58:33390/devops";
}
// request请求拦截器
Axios.defaults.withCredentials = true
vue.prototype.$axios = Axios
//请求超时时间
// Axios.defaults.timeout = 100000;
Axios.defaults.headers.get['Content-Type'] = "application/json"
Axios.interceptors.request.use(config => {
const Basic = sessionStorage.getItem("basicParam")
if (Basic) {
config.headers.Authorization = Basic ${Basic}
;
} else {
console.log("无校验值");
}
return config;
}, error => {
Promise.reject(error);
})
// respone返回拦截器
Axios.interceptors.response.use(
response => {
if (response.data.code !== 200) {
Notification.error({
title: '错误',
message: response.data.message
});
}
return response.data;
}, error => {
// Notification.error({
// title: '错误',
// message: '系统异常'
// });
console.log('err' + error);// for debug
return Promise.reject(error);
}
)
export default Axios;
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
44
45
46
47
48
49
50
api.js文件全部代码如下:
import axios from 'axios';
/
封装get方法
@param url
@param data
@returns {Promise}
*/
export function fetch(url, params = {}) {
return new Promise((resolve, reject) => {
axios.get(url, {
params: params
})
.then(response => {
resolve(response.data);
})
.catch(err => {
reject(err)
})
})
}
/*
封装post请求
@param url
@param data
@returns {Promise}
/
export function _post(url, data = {}) {
return new Promise((resolve, reject) => {
axios.post(url, data)
.then(response => {
console.log(response,
"response");
resolve(response);
}, err => {
reject(err)
})
})
}
/
蓝蓝设计的小编 http://www.lanlanwork.com