首页

vue使用路由进行页面跳转时传递参数

前端达人

一. 通过router-link进行跳转

<router-link

:to="{

path: 'yourPath',

    params: {

    name: 'name',

        dataObj: data

},

query: {

    name: 'name',

        dataObj: data

}

}">

</router-link>

二. 通过编程导航 $router进行路由跳转

1.路径后拼接参数

通过路径后直接拼接来传递参数



getDescribe(id) {

// 直接调用$router.push 实现携带参数的跳转

        this.$router.push({

          path: /describe/${id},

        })



对应路由配置

注意:此方法需要修改对应路由配置,需要在path中添加/:id来对应 $router.push 中path携带的参数。



 {

     path: '/describe/:id',

     name: 'Describe',

     component: Describe

   }



获取传递的参数值



this.$route.params.id

  1. 通过params来传递参数

    传递参数

    通过路由属性中的name来确定匹配的路由,通过params来传递参数。



     this.$router.push({

              name: 'Describe',

              params: {

                id: id

              }

            })



    对应路由配置

    注意这里不能使用:/id来传递参数了,因为已经使用params来携带参数了。



    {

         path: '/describe',

         name: 'Describe',

         component: Describe

       }



    获取参数



    this.$route.params.id

    1
  2. 通过query来传递参数

    传递参数

    使用path来匹配路由,然后通过query来传递参数

    这种情况下 query传递的参数会显示在url后面?id=?



    this.$router.push({

              path: '/describe',

              query: {

                id: id

              }

            })



    对应路由配置



     {

         path: '/describe',

         name: 'Describe',

         component: Describe

       }



    获取参数



    this.$route.query.id




人工智能行业常用名词科普

雪涛

本文整理了人工智能行业中设计师需要理解的一些名词和内容。

一方面供自己学习思考,另一方面也希望能帮助到准备投入到人工智能行业的设计师。之前听有的朋友讲到,觉得自己没有计算机背景,有点害怕进入到这样一个领域来。

没有计算机背景没有关系,只要对这个行业充满好奇,一个个的问题解决掉,在你眼前的迷雾都会散去的。

先简单举几个人工智能在生活中有在应用的例子:

像现在有的超市寄存物件,开箱时采用的人脸识别;像家里购置的智能音响,时不时还能跟它聊上几句;像接听到的银行电话(是的,对方可能是机器人噢);像在淘宝上咨询的客服小蜜;像你手机里的虚拟助手….等等这些都是人工智能在生活中的应用。

人工智能在设计领域的应用也相当广泛,具体可以看这篇文章:

这几个例子是在生活中比较普遍能接触到的,实际人工智能应用的领域还在不断的扩大,我们甚至都无法想象到,未来的生活会是怎样的状态和场景。

在这家公司之前,我做过语音交互类的产品交互设计。当时在定义人与设备进行语音交互时,会是怎样的一个交互场景。从说唤醒词到发出指令,从收到反馈到继续对话。唤醒后等待的时间、结束的规则等等这些。

而现在,我大部分时间是在设计工具,如何让使用者能快速的创建出一个智能机器人。如何让机器人的创建者方便快捷的添加机器人的相关数据和创建出对话场景。

所以在进行这些工具的设计之前,有些名词概念,会需要设计师来了解一下,能让我们更好的理解人工智能的一些原理以及能够让设计师具象化到实际的设计中,甚至能基于此技术/原理来进行相关的创新或研究。

整理内容如下:(内容基于工作及自身理解,如有概念理解错误,欢迎指正)

下面尝试用较易理解方式来解释这些名词:

与机器人进行对话,首先就需要让机器人懂我们说的话,这其中,就需要来关注到自然语言处理,通过自然语言处理技术,能够实现我们与机器之间「无障碍」对话。

  • 自然语言处理(NLP):是人类与机器沟通的中介,需要靠它来理解、处理和运用自然语言
  • 自然语言理解(NLU):指的是机器的语言理解能力,将人类语言转化为机器可理解的内容
  • 自然语言生成(NLG):指的是机器通过一系列的分析处理后,把计算机数据转化生成为自然语言内容,让人类可理解

我把这三者关系画了张图示,我是以这样的方式理解的

从图中可进一步看出,NLU 和 NLG 是 NLP 的子集,而 NLP 是人与机器沟通中很重要的存在。

涉及到语音就会经常听到 ASR 和 TTS

语音识别(ASR):将语音内容转为文字

如微信里面,当别人发的语音信息不方便外放收听时,可以转为文字查看

语音合成(TTS):将文字内容转为语音

如现在很多的阅读软件,支持播放,有的就是利用 TTS,直接将文本内容转为语音播放出来。

我试着将上面提到的 NLP 和 ASR、TTS 组合起来,关系可以如下图所示

当我们说一句话的时候,机器知道我们表达的是什么吗?

意图(Intent):一个人希望达到的目的,或者解释为想要做什么,他的动机是什么。

如:

  • 我对天猫精灵音箱说「声音太小了」,那我的意图是什么?意图是「将音量调大」。
  • 「看下明天上海飞北京的航班信息。」 直接意图:查航班信息,潜在意图:「买机票」?

槽位(Slot):可以理解为系统要向用户收集的关键信息。

如:

「买张明天从上海到北京的机票」

上面这句话中,获取到意图(买机票);提取关键信息 时间(明天)、地点(出发地:上海;到达地:北京)
这些关键的信息就是槽位,当系统获知到这些信息后,就能去执行下一步动作。

还可以这样理解,当我们去银行营业厅办理卡的时候,会填写一张表,表每个要填写的选项,就是一个个的槽位。槽位就是为你服务的人员要从你那收集的关键信息。

实体(Entity):用户在语句中提到的具体信息

实体这词放在生活中,我们很容易理解,就是实实在在的物体,像桌子、电脑、熊猫等等这些都是实体。

但是在人机对话中,机器理解人的语句内容,会识别出语句中的实体信息(如:地点、人名、歌曲名等),然后进行标记。

那槽位和实体是不是讲的是一回事?只是不同的说法?

我之前有一度陷入这样的困惑中,但其实这两者还是有所区别的。比如,一个实体是数字,但是在语句中,数字将代表不同的含义。

如:

人:有没有10元的鲜花? 机器人:玫瑰花10元一支 。

这句话中,实体number「10」,但这个 10 在句子中表达的是价格,所以收集到的槽位信息是价格:「10元」

这样说可能还是不太能理解,那我们可以先了解下,在一句表达中,需要进行槽位信息收集,但机器如何知道「买张明天从上海到北京的机票」中,「上海」是城市,并且「上海」是出发地呢?

「上海」这个词会被建立在一个城市实体词库中,这是「上海」能被识别到是「城市」的原因。

其次,通过将解析槽位加入语料中,加以训练让机器学习相关表述结构,来获知该句式中,收集到的第一个城市是出发地,于是把第一个城市填到对应的槽位中。

使用什么工具来让机器知道,这个信息是要提取的信息?

解析器(Parser):抽取/解析用户语句中的关键信息

上一个讲到实体,这里讲到的解析器则是这么个工具,用来抽取这些信息。比如会有些通用的解析器如时间解析器、城市解析器、歌手解析器等等。

解析器的类型也比较多,如通用解析器、词典解析器、正则解析器、组合解析器等等,这里就不再扩展开讲具体解析器,实在过于复杂了。

命名实体识别(NER):用来识别具有特定意义的实体。主要会包括像机构、地名、组织等。

是不是发现,解析器和 NER 在做差不多的事情?我是这样理解的,解析器的话是一个更大的存在,其中包括了 NER。解析器下会有不同类型和不同功能的工具来实现关键信息的识别/抽取。

在我们与机器人对话时,一般会涉及到四个不同类型的对话,开放域的聊天、任务驱动的对话、问答(FAQ)和推荐。

上面是在有次分享中提到的,这四个不同类型的对话,在机器人平台中,会需要借助不同的功能模块来实现。

任务对话(Task Dialogue ):有上下文联系,就像我们要去订票、订餐之类的一段任务型的对话。

我们公司产品中,任务引擎模块就是做这个任务对话的创建,比如,要订机票的场景。用户在这个订机票的场景中,会涉及到的对话内容、流程的设计。

知识图谱(Knowledge Graph):这个可以理解为可视化关联信息。
比如:查询一个明星的身高、年龄,他的学校、他的女友,他的相关作品,这些基于这个人而构建的信息库,都可以通过知识图谱在做整理。并且在构建时能够做到可视化的了解。

要让机器人知道,它脑子里有货了!

训练(Train):这个概念可以这样理解,比如你创建了个机器人,但是它什么都还不懂,于是你塞了堆知识给他,这时,它就需要自己训练学习了。训练好了,就能回答你塞的那堆知识里的问题了。

讲到这就忍不住想用这个学习的例子,来简单讲下一般机器人的创建流程。像我们在学校,会经历上课学习新知识-复习温习-考试-整理错题集,以此循环进行。

这个创建机器人的流程也是一样通过知识的导入/创建-训练-测试-优化-上线-优化,以此循环,不断强化机器人,让它越来越智能。

其他:

数据标注:将对话日志中的有价值数据做标注(标记/匹配/关联之类)。

因为人的表达万千,多种表达方式都代表的同一个意思。有时用户说了句话,是语料库中并不包含,于是机器人可能就答非所问了。

Ai 训练师们就可以将这些数据信息标注到对应的问题中去,这样当用户再用同样方式表述时,机器人就能如预期回答了。

讲到标注想到之前在朋友圈很火的你画我猜,谷歌推出的这个小游戏席卷朋友圈。他们用了个如此聪明的做法,其实我们参与其中的做法就是在做数据标注,而且还是主动提供数据的那种。

这也反映了,数据对于机器人的重要性,通过不断的进行数据维护和补充数据,机器人就会越来越理解人,表达也会越来越智能。就跟我们学习一样,不断学习才能够理解其他的含义,甚至当认知能力提升了,看待问题的角度才能不一样。

文章来源:优设

平面排版如何打造节奏感?

雪涛

我们在做设计的时候总是会听到,要注意「节奏感」。关于节奏感这个词,大多数人似懂非懂,可能明白它是什么意思,但是在设计中都应该注意哪些节奏感,却还没有一个比较清晰的思路。我们都知道音乐是有不同种类的,有古典音乐,有摇滚音乐等,它们之间的不同,很多时候是节奏给我们带来的感受不同。在设计上也是一样的,掌握节奏感就能控制画面的变化和气质的传达。

什么是节奏感

先理解什么是节奏感。

我们常说的生活节奏,可以理解为,假如你在规定时间里,你只需要上班和回家两点一线的生活。

但是当你有了孩子还要去幼儿园接送,晚上还要去健身等,这样就是生活节奏加快了。

我们常听到的节奏感其实是来源于音乐的。

不同节奏感的音乐会给人不同的感受,就像音乐1这种,会给你一种平静舒缓的感觉。音乐2这种就会有种比较喧闹、比较活泼的感觉。

说到设计的中的节奏感,从绘画里也可以看出,这是吴冠中的绘画作品,看似很简单的画面,但是这种水流的蜿蜒的感觉让这个画面变得灵动起来。

去掉这些流动的线之后,整个画面就变得很平静了。失去了那种蜿蜒流潺的感觉。

在我们很多图像网站里都用了这种瀑布流的方式,不仅是为了方便不同尺寸的图片的载入,在很多时候这种方式,相对于平铺的图片放置会更让人舒服而不枯燥。

节奏与韵律

在平面设计里,我们在学习平面构成的时候节奏与韵律是常常放在一起说的。

我们可以看海报去理解,第一张海报那几条鱼摆放的位置是没什么规律的,但是它却能形成节奏感。那么在第二张海报,我们能清晰地感受到它的节奏与韵律的变化。所以节奏是有规律或者无规律的变化的,韵律是有规律的变化的。

包括去百度百科查找相关信息也是表达类似这样的意思。

一个版面里的节奏影响是多方向面的,有文字编排、色彩搭配、图像处理等。

文字编排

今天我们要讲的是文字编排里的节奏感。在文字编排上我们为什么要掌握节奏感呢?

我们在看一些纯文字的书籍的时候,很容易犯困。

这就是因为书籍的文字编排几乎没有节奏感,所以相对来说是枯燥的。

有一些不同的书籍设计或者杂志会在里面加入图片、对文字与版式进行处理,使它们变得有变化性,就会调节这种节奏感。让画面阅读不再枯燥。当然了,画面左边是因为大量的信息的传达不适合做过多的变化,否则阅读过程会有阻碍。这也是根据不同种类的信息从而把握不同节奏感的结果。

所以在这个画面里标题是节奏感比较强的,在保证阅读性的同时也做到了装饰性。那么由于下方信息量比较多,这些文字又需要被快速传达,所以这些文字的编排就会趋向于阅读性。

掌握文字编排的节奏感对画面传达的气质的影响是非常大的。就像这两张海报,它们的背景图片气质是很像的,都是天空的大留白,但是,完全不同的文字编排,就让第一张海报表现出活泼可爱的感觉,第二张海报表现出平静安静的感觉。

我们可以理解为,随着几乎没有节奏感到节奏感比较弱再到节奏感强,它的画面是可以呈现出由静止到舒缓再到动感的。

在文字编排中有非常多的对比,是一个非常大的系统,今天我们主要去梳理一下文字编排时的一些影响因素,以及在文字编排上一些需要注意的细节。(我们今天会讲这些影响我们文字编排的节奏感,字体种类、大小、长短、位置、疏密、颜色、组合形状和方向。)

1. 字体种类

文字种类就是我们对文字的类别进行分类,在字体种类里一般分成了衬线体与非衬线体,但是在中文里,我觉得主要由这五大类组成,分别是黑体,宋体,楷体,圆体与书法体。

包括字体家族里的不同字重。

不仅是这样,对于同一个字体我们还分常规体、窄体、扁体。

一段文字只选择上面的所说的变化就已经可以有很多种了。我拿了一段数学公式装❌。这里我们算出有 105 种。所以文字编排时稍微不注意就会有太多的变化性。

更何况我们经常在版面里加入其他国家语言的文字,这些都会影响文字的节奏感。

所以我们要学会控制这种节奏感,一般来说我们日常比较常用的就是黑体配无衬线体,宋体配衬线体。并且在字重上我们都要注意协调。尽量使它们看一起是一样的粗细度。

不同的字体搭配起来是有一定难度的。这种节奏感就好像不同风格的音乐的结合,它们之间的衔接与融合会比单纯的某一种风格的音乐制作起来更难。

这种不同类型的文字的搭配,对于排版和运用的能力有一定要求,运用不好就会传达不了画面的气质与信息。在电商里的反例是比较多的。在我看到的这张图里,它同时用了无衬线体与衬线体的结合,画面没有清晰呈现出准确的气质。大家可能会说它是简约高贵风,但是这是一个大范围,在简约高贵里有现代的高贵,有复古的高贵,还有一些与众不同的高贵等。

我们尝试把左边的衬线体换成无衬线看看。是不是有种现代都市的气质?

如果都统一成衬线的话,是不是一种精致的时尚感就出来了呢。统一这种文字的种类能更加精准地传达画面的气质。

刚刚我们也说到字体的混搭会产生混乱和怪异感,要慎用,但是如果我们的画画就需要这种感觉呢,当然就可以用这种特性来故意营造一种怪异感,当画面的字体种类越多的时候,所营造出来的节奏感会更强烈。

我们看这个画面也是这样的,周围的很多图形和各种各种样的字体类型让这个画面变得搞怪活泼。

就算去掉了周围的图形,文字这传达出来的搞怪感觉也依然存在。

接下来我们用这个文案来做一个案例演示下,由于平时很多都是用同一种类型字体搭配,那么这一次就挑战一下,我就打算做一稿用不同类型字体的版面。看看会有什么样的效果。

首先我在版面中划分版块,填上相应的文字,在这里可以看到,我同时用了衬线与无衬线的字体,还用了具有手写性质的字体。不仅如此, 所有的文字我都用了窄体而不是常见的常规体来增强这种怪异的节奏感。

最后加上一些图形处理一下负空间,这个案例就完成了。

为了减少影响,我把图片遮住单看文字组,我把这些字体都变成黑体了,对比可以发现,还是原来的文字组更有搞怪奇异的节奏感。右边这个因为板式与图形的完整性使得它看起来并没有很大的问题,但是它的确是缺少了像左边这种古典与现代结合的节奏感。

2. 大小

文字的大小节奏可能会有人理解为这样,但是我们一般不会这么做。

我们更多地是用在标题与内文的对比。

节奏变化比较大的文字组会给人一种冲击力,让人无法忽视标题的存在,就好像我在大声告诉你一句话的感觉,会比较强硬与喧闹。

节奏比较平缓的文字组表现出一种精致、安静的感觉。

我们把它们放到画面里看下,这个标题与内文的对比很大,并且在这个版面中占据一定的大小,这种时候有没有觉得这个画面呈现出一种,好像在播放新闻的感觉,好像在说这个小岛有什么重大新闻一样。

但是当标题变小时,整个画面呈现出比较平缓的节奏感,很符合画面传达出来的安静舒缓的感觉。

这种方式很多作品都有,这种标题和内文的对比,或者说是文字在版面的占比比较大时,就可以体现出这种很强烈的节奏,让人很难忽视这些文字内容。同时画面也更容易传达出一种力量感。

往往想要营造这种安静感的时候,比较注重画面整体的感觉,就不需要太多的文字变化,甚至为了区别文字层级而需要有的文字大小,也尽量地在减少对比。

3. 长短

我们都知道音频都是这种长短不一的声波图,因为这种长短不一的变化感受会给人带来节奏感。

所以也有以这种形式来编排文字,表现节奏感的系列广告作品。

它比较经常出现在居中对齐的文字组上。

但是我们要注意的是,你会发现很少情况在两个极端之间直接过度,比如说一长一短。

在没有文字大小的对比情况下,如果文字的长短对比太小,我们先不说节奏感,我们可能会有种疑问,它到底是想两端对齐还是居中对齐的呢?所以这种两端模棱两可的情况最好避免。不能模棱两可,对比太大又会不够美观,所以我们去创造节奏感的时候要注意这些问题。

毕竟它不是让人阅读时间很长的文字,所以我们就需要调节这种节奏让它看起来美观而且不枯燥。

这是我随便在购物网站截图的,你会发现它们的文字编排都很注意我刚刚说的短长短的节奏感。

4. 位置

我找了两张都是黑色背景并且配图比例也差不多的海报作对比说明这个问题。

因为图像的干扰我就同时去掉了图片,还有也把右边红色的字变成了白色,它们之间的对比变得清晰了,左边的文字规划在版面中显得更有活力,右边版面的文字集中在一块,就像我们前面提到的小说书籍内页一样。这样的文字编排在阅读上会比左边的缺少节奏感。但是我在这里要说的一点是,左边海报本身的图像没有右边的有冲击力与活力,所以我们如果要真正做对比的话。

用左边的图像放置在两个不同的版面来作对比,这样大家应该能感觉到差别。左边的版面虽然没有表现特别强的节奏感觉,但是至少版面不是特别压抑的。至于右边的我们会觉得很沉静,配合这种黑色的背景比左边更压抑。这就是文字编排在版面的影响。

不仅是文字组之间,标题的有意放置不同的位置就可以营造这种节奏感,是因为它依然可以使得我们的阅读视线发生变化。

比较随意编排的文字组也会比,比较拘谨正式的文字组看起来更有节奏、更活泼。

接下来我们用案例来演示。

首先选择纤细的宋体会比较符合这个画面的气质,很多人可能把文字组放在画面四个角就算大功告成了,但是这个画面既没有一个亮点吸引我们,而且画面里的元素都非常得散,没有体现出一点活泼的味道。

这个时候我们可以效仿刚刚我们看到的标题的做法,拉开距离,调整位置让它有上下浮动的节奏感,包括文字上我都做了一些切割移动让它们活泼,再加上线条让它们更有联系感。这个画面就会比刚刚活泼多了。

案例完成。

5. 疏密

左边紧凑的文字字距会呈现出一种张力,一种急促的节奏,营造一种紧张感。右边宽松的字距画面会更缓和,不同的字距在版面中有宽松对比,也营造出了一种节奏对比。不会感到枯燥。

很多人可能没太去注意这些文字编排的小细节,这两版里哪一个更符合平缓的节奏感呢?答案是下面这个。第一张这样做也没错的,但是我们想让文字也能相应地呈现出一种透气感的话,第二张的会更加符合。

6. 颜色

颜色的问题大家应该了解了很多了,这里就简单提一下,就像我们在营造一种氛围的时候选择的气球颜色都是非常重要的,我们选择饱和度比较高,颜色种类比较多的气球的时候,是想营造一种热闹活泼的感觉的。但是如果想营造一种浪漫安静的氛围时就用了很多白色或者淡色调的颜色。

就像这个 banner 一样,中间不同颜色的文字为这个画面增加了很强的节奏感。变得很活泼。

很多时候我们不需要太强的节奏感,所以我们经常给文字做一点的颜色变化,来让画面更鲜活。就像这个 banner,如果它没有了左边那个粉色的颜色的跳跃。

这个画面就会变得很沉静。

如果,画面文字的粉色变多,它的节奏感又会变得更强。

不仅是因为这个颜色本身的跳跃性比较高,而且也是因为颜色的不停切换导致这种节奏感的增强。所以不同颜色的占比也是需要考虑的。在这里主要是因为模特身上没有玫红色,所以左边不适合用过多的玫红色,用全黑都会显得很沉闷,所以这里选择用一次玫红色让这个画面鲜活。

6. 组合形状

我们在这些变化的图形中替换两个不同形状的图形,我们可以感觉到替换后的图形里的变化会更多,呈现出来的活泼性更强了。当画面中的不同形状更多,就会趋向于一种混乱。

用不同的图形在版面里,有区分不同信息的作用,但是这种方式也是增加版面节奏感的一种方式。在很多促销的传单会经常看到。

这个画面没有人物图片,仅仅是通过文字的编排就能传达出这种热闹的节奏感。

除了颜色本身的热闹性,其中比较大的影响因素。就是文字在各种不同形状里的编排,让这个画面呈现出热闹的氛围。

7. 方向

随着方向变化越来越多,画面会趋向于更有动感的的节奏。但是节奏感觉也是有度的,一旦变化性很多,那么这个版面就会显得杂乱。我们也可以简单理解为乱的元素的占比决定着你画面呈现出来的节奏感的强烈。

像第一张海报一样,这种有点古老的作品我们一般会认为是比较正式和比较严肃的,但是这些方向不一的文字编排,向我们说明了这不是一个严肃的展览,而是具有活泼的属性,从而吸引不同的观看群体。包括右边这个海报的文字编排都让这个画面的节奏感变得更强了,与人物夸张的肢体语言也相呼应。

倾斜的文字编排在电商里是出现得比较多的,微微倾斜文字就可以强调这种活泼的节奏感。

我们来看一个比较明显的例子,画面只有一个黄色的色块和文字编排以及一个不规则的图形,但是这个画面呈现出来的感觉却很活泼。

其实去掉这图形,也并没有影响原有的气质,是因为文字的编排的方向性的对比让它的节奏感增强了。

7. 案例演示

那么说到这里其实今天的内容也就结束了,这次的案例就给大家演示下怎么用这些知识去做一张海报。

首先我们要分析这个画面,图片本身是比较具有节奏感的,因为不是我们平常看的袜子的视角,而且人物有一种运动过程的动势。

这个负空间非常不规则,如果文字在这个画面负空间上直接加字的话,可能就会显得比较乱,但是我们又要做出迎合这个图片的节奏但是不乱的画面,应该怎么做呢?

首先不采取直接在图上加字的方式,把图片缩小,再添加一个深色的底色,这样这张图片和这个画面里就是一个整体了。

由于我们要压住图片的节奏感,不让它太乱,所以我在周围的空间编排文字是呈现一个既有文字层级关系,但是整体是呈现矩形的文字组。现在这个画面看起来的确不会乱,但是文字还是少了和图片活泼气质契合的节奏感。

分析一下这个画面,是宇宙系列的感觉,所以我就加入了环形的文字,然后再加一点与图片呼应的颜色。这个画面就会比之前更和谐。在做的过程通过减少变化与增加变化让画面逐渐接近自己的预期。

总结一下今天我们讲的,我们今天讲的是文字编排中的节奏感,在一开始,我分别给大家讲了从生活节奏、音乐节奏、再到设计的节奏去理解什么是节奏感,然后我还讲了关于文字节奏感对于画面的重要性,无论是对信息的传达还是对气质的表现,它的影响是非常大的。最主要的部分,我讲了影响文字编排的节奏都有哪些因素,比如文字类型、大小、方向、长短、文字组合形态等。把握自己要传达的节奏,才能正确传达信息。大家也可以用这种方式去看作品,分析它的节奏感是通过什么方式形成的。久而久之,对节奏感的掌握就会更加熟练与精准。

文章来源:优设

「卡片式设计」知识点

雪涛

卡片式设计对于我们来说并不陌生,从设计类网站上或市场上的一些 APP 中也会看到很多的卡片式设计的案例,卡片式设计也是 UI 设计中最常用的方式之一。

最近在新项目的设计中也尝试使用了卡片式设计,结合实际项目中的一些思考进行总结并归纳出一些卡片式设计的小知识点。同时希望通过本次的总结进行知识沉淀,以及跟大家一起探讨下卡片式设计。

来源于日常

在现实生活中的卡片式设计可以说是无处不在,例如身份证、交通卡、银行卡、名片、便利贴、扑克牌、游戏卡……诸如此类的生活常见品都是以卡片的方式存在,其共同点都使用一个容器承载着内容,并且具有「便携性、信息简洁和相对独立性」。

UI设计中卡片的使用场景

在项目设计之初我分析了一些使用卡片设计的 App,并且从中整理总结了几个较为常见的卡片式设计的使用场景。

1. Feed流

卡片式的 feed 流设计是一种非常常见的设计,早在前几年 Facebook、Google+ 等产品就使用了这一方式,Feed 流作为一种长内容的媒介,用户需要长时间的滑动看内容并筛选有效信息,卡片式设计很好的解决了内容与内容的区块分隔,让用户在长屏幕滑动中依旧可以很好的明确识别每一块的内容。

实际案例-淘宝微淘

2. 瀑布流设计

瀑布流的出现让单屏区域内显示更多的内容,而内容较多的情况下,使用卡片式设计可以较好的对内容进行了区域划分,让上下左右的内容从整体中具有相对独立性。

实际案例-Pinterest

3. 左右滑动组合型内容

卡片式设计具有较强的层次感,相比于平铺更能呈现内容可滑动的感受,并且块状化的设计让内容具有较高的区域分割感。

实际案例-QQ音乐

4. Tips提醒

作为非界面固定内容,卡片式设计可以让 tips 提醒设计变得更自由,在符合用户体验的基础上,它可以出现在任何我们想要它出现的位置。

实际案例-淘票票会员提醒

5. 结合手势的单块可互动内容

若页面中有且只有一个主内容,并且需要用户进行快速筛选时,可考虑这种结合卡片式设计与手势设计的方式。大大增强了用户对于设计的体验感知和丰富视觉表现。

实际案例-探探首页

6. 卡券类设计

卡券类的设计实际上是一种物化映射的过程,我们在现实中看到的卡券造型,结合卡片式的拟物化设计,让用户在屏幕上可以更直观的感知,提升了设计的代入感。

实际案例-京东领券中心

7. 集合型功能入口

集合型功能入口往往会有多个入口,使用卡片式设计让入口形成一个区域整体,可以做到既统一又相对独立。

实际案例-淘宝微淘关注账号

8. 个人主页顶部内容卡片

个人主页的设计往往会在氛围上营造沉浸感,卡片式的设计可以把关键信息进行概括收归,让原本单个的内容形成一个整体。

实际案例-美团外卖会员

规则探讨

基础的卡片设计规则,相信大家在一些系统级别的设计指导规范中也或多或少都能了解到,不同平台的规范差异其实不会有太多本质性的区别,更多的是处理技巧或方式的差异,而每个设计师对其理解的角度也会具有一些差异化,这里分享下我对于卡片式设计的一些基础想法。

1.卡片的质感打磨

同样的卡片设计,不同的人做出来的感受可能会有所差别,而表达卡片质感的主要关键点在于:卡片形体、投影深度、卡片颜色对比,我们需要了解这些基础知识点之后,再结合实际的 APP 风格进行设计。

卡片形体

就像图标的图形设计一样,不一样的形体也能表达出不一样的气质,因此在设计的时候我们需要依据整体的风格进行表达。异形卡片的设计,可以让原有方方正正的卡片表达出差异化,从造型上打破一些传统的处理方式,再结合一些 IP 人物元素可以更加直观的表达出具体的内容氛围。

投影深度

投影的视觉效果,会直接影响整体卡片的质感,太深太大的投影会显得整体卡片过于厚重,太浅太小的投影则显得过度生硬,因此合理的数值比例可以让卡片看起来自然有质感。在项目中我常用的一组数值规律是 1:2 或 1:3,例如 Y 轴偏移 10px,模糊度则设定为 20 或 30px,这样成比例的数值出来的效果会较为自然,如下图:

卡片颜色对比

卡片与背景的颜色对比会影响这卡片的整体质感,在设计时我们需要把握好卡片与底色的对比,不同的明暗对比出来的质感也会有差异。这里有两点建议:

  • 卡片色与背景色不宜太过接近或使用同一颜色,因为会影响卡片整体的空间质感或使得卡片的边缘锐度下降;
  • 深色背景上,尽量让卡片与背景色在同一色系或者明度不要差异太大,避免过于突兀。

2. 边距的设定

在使用卡片式设计时,经常会纠结边距的设定,宽边还是窄边?多少像素更为合适?我经常会带着这种疑问去设计。

基于内容的简单规则

卡片式设计作为设计的表现形式,最终是为了承载内容,因此边距的宽窄也需要依赖于实际内容的判断。结合我在项目中的尝试分享以下几点:

多窄少宽

卡片内容较多是使用窄边距,让卡片具有足够的空间来展现内容,内容较少则可以考虑采用宽边距来打造整体的视觉空间感,如下图 app store 和淘宝的设计对比。当然这只是一个建议,实际还得具体问题具体分析。

再如一些瀑布流、宫格、横滑模块较多的 APP 设计亦是如此,在内容较多的情况下会把边距压缩到最小的合理间距。

内外成比例

以最外边为基础值往里设计,间距以固定比例进行缩减,虽然没有删格来得规范,但也可以让设计变得有迹可循。

基于删格

删格系统解决了一些基础的板式问题,有助于提升设计的规范性,让设计更加有迹可循。在设定卡片式的边距时可以适当应用删格系统,让边距与内容形成固定的关系,这样可以帮助整体的卡片设计更加具有细节和规则。

4.卡片的标题设定

标题的设定主要考虑以下几点:1.是在卡片内还是卡片外;2.标题的字号设定多少更合适;3.标题是否加粗?

卡片内或外的对比

在项目设计中让我较为纠结的是:标题应该在卡片内还是卡片外?通过了一些案例的尝试之后,我总结了一个规则(需要依据内容的形态而进行设计):当卡片内容是独立的模块或模块中只有一个大标题时可设定在卡片内;当卡片内容是以组合呈现或者具有延续性内容时设定在卡片外,形成最外层的主标题。

标题的字号设定

标题主要作用为 2 点:1.简短说明每个模块的内容;2.让用户在长页面浏览中起到引导、定位的作用。

通过一些尝试发现:1.当内容较少时,并不需要太大的字号即可起到标题的作用;2.当内容较多时,较小的标题字号则容易被沉入内容中,让用户在浏览的过程中难以发现,而导致信息获取缺失;3.建议标题与正文字号大小差异在 6-10px,这样可以更好的拉开差异,让标题更具有标题感。

字体是否加粗

常规思维下我们都会对标题进行加粗,我在实际中的经验得到的总结是:需要看手机系统或不同厂商的机型。我在项目之初都对标题进行了加粗,但后续在跟进还原时看到的效果并不理想,特别是 Android 的机型上,因为我们使用的是系统默认字体,android 系统很多字体并未对系统进行优化,而是使用微软雅黑,微软雅黑在android 系统上再加粗,就会显得整个系统的外轮廓特别粗糙,最后我们依据不同的机型进行了差异化的选择。

4. 圆角的规则

圆角的设定实际上没有太多的原则问题,只要符合整体的风格调性即可。当然不同的圆角也能表达出不同的质感,大圆角表达柔和、小圆角表达硬朗。

圆角的规则设定

以卡片的圆角作为基础的参考值往内推算整体的圆角使用规范,卡片与卡内的元素形成合理的比例规则,而非随意根据「经验」进行设定。

圆角大小差异对比

大小的差异化呈现出不同的视觉感受和风格差异,我们在设计时更多需要考虑我们设计的产品风格或气质是适合大圆角还是小圆角,而非依据一些设计网站上的流行趋势。因此基于不同的风格或者实际内容场景下进行设定才更为合理。

5. 宽窄间距对比

卡片式设计相比拉通式设计更需要考虑设计中的透气感。在常规情况下,对内容边距及四周边距进行调整,让内容之间具有较好的空间呼吸感,从而让设计得到留白的效果。如下图对比案例,在基础删格不变的情况下,每个间距都在原有基础上扩大了12px(接近 1.33 倍),从而让内容具有较为舒适的宽度进行阅读

优点分析

选择某一种设计方式的重点在于我们了解这种方式的优点,并且可以把这些优点融合到我们的设计当中。在项目设计中,我总结了几点卡片式设计的优点。

1. 优化模块化,提升内容区域感

模块化的设计也是我们日常中会应用到的方法,结合卡片式的设计可以让模块化的规则变得更加简单,增加了模块之间的可复用性和延展性。而当内容较多的情况下,使用卡片式设计可以有效直接的形成区域分隔,从视觉感知上就对内容进行了分隔,提升用户获取信息的效率。

2. 提升内容独立性

在组合型的内容设计上,使用卡片式设计可以让每个小块内容呈现相对独立的展现特性,结合模块化的设计,可以在一大片关联的内容中,做到既统一又相对独立。

3. 增强视觉空间感

卡片式的设计可以提升整体设计层次感,通过投影、前后颜色的设定,让内容与背景之间产生视觉空间感。

4. 增强视觉表现力

在设计中我们可以对卡片进行异形设计,用来达到我们想要的风格表现。当然在一个页面内尽量不要太多,尽量使用页面中的首个卡片进行差异化处理,让整体表现出一点不同即可。

5. 增强可点性

卡片式设计产生的空间感,让每个模块更加突出,相比扁平式的处理方式,卡片式从视觉感官上会较为突出,从感官上更具可点击感知。

缺点及建议

任何一种设计方式都会有其利弊,最终选择某一种其实不过就是当下最适合而已,而在尝试中我也总结了几点卡片式设计存在的一些缺点,当然只是个人的思考而已。

1. 横向空间利用率降低

卡片式设计的存在左右边距,因此在有限的屏宽内内容横向区域相比于拉通式设计有所减少,在内容较多的情况下可以适当调小卡片左右边距。

2. 避免过多的层级

从整体来说,卡片式的设计本身就是增加了基础背景的层级表现,其视觉层级相比拉通式更为丰富,因此不建议在卡片上再二次叠加块状式设计,避免造成层级复杂。在项目中也会遇到内容层级需要多层级的表现,从中总结了2种方式:

  • 利用不拉通分割线;
  • 利用浅色背景底色。

3. 不适合长文或内容多的表达

若在设计上使用了卡片式的设计风格,但在一些长文表现的界面建议去除卡片。长文章的页面更强调阅读的沉浸感,用户需要更多的专注于文字,这时候无边的体验更适合。

4. 把握好界面的分区,避免过于拥挤的排版

卡片设计具有独特的视觉空间感,但卡片与卡片之间也会有分隔,因此在设计时更应该对内容进行归纳,避免产生过多的小块卡片而导致排版过于拥挤、凌乱或者内容不够宽度展现。

总结

无论是卡片式或者拉通平铺的方式,其最终的目的都是为了服务于内容,我们在做设计的过程中只是选择适合于呈现我们内容的一种方式。根据具体的内容情况给出合理/合适的设计判断才是我们需要不断提升的关键点,切莫流于形式而忽略了内容设计本身的重要性。

文章来源:优设

B端产品的设计理念

分享达人

这篇文章主要是从什么是B端产品,B端产品与C端产品的差异性,以及如何从设计角度切入B端产品等做具体说明

前言:在当下的市场环境中,企业内部的运营管理效率问题因为团队规模迅速扩张而逐渐凸显。此时,B端产品的助力就显得尤为重要。这篇文章主要阐述了B端的定义和方向,以及与C端产品的差异性,并且如何从设计角度切入B端产品等方向做具体说明。


什么是B端产品?

B端产品也叫2B(to Business)产品,使用对象一般为企业客户或组织。B端产品帮助企业或组织通过协同办公,解决某类管理问题,承担着为企业或组织提升效率、降低成本、控制风险从而提高企业收入,减少企业内部损耗的重要职责。B端产品的工作是合理实现企业需求,提高产品核心竞争力,并提升市场价值。


B端产品有哪些方向?

根据B端产品的服务对象,我们归纳为三个方向:

1:业务平台方向  

2:办公协同方向

3:商家管理方向

这三个方向基本上涵盖了企业对内及对外的经营活动及业务运营的工作范围。接下来我会一一详细介绍。


1:业务平台方向

业务平台方向是指供业务平台使用并且对这些产品提供支持。其中再细分则包括垂直业务线、基础服务产品线、交易平台产品线。



举两个常见的例子:


CRM:客户关系管理(Customer Relationship Management)。广义上的CRM包括从客户开发、管理、营销、服务的客户全生命周期管理;狭义的CRM是指给销售人员使用的销售过程管理软件。是通过以客户为中心的管理模式,提高企业的销售力量来达到为企业赚钱为目的。


通过CRM系统我们可以知道: 

1:我们的客户在哪里?(售前市场调查)

2:哪个产品更畅销

3:针对客户进行分析

4:销售结果预测等等


ERP:企业资源计划(Enterprise Resource Planning)是针对物资资源管理、人力资源管理、财务资源管理、信息资源管理集成一体化的企业管理软件。例:ERP以一项计划为出发点,该计划可以是市场的一个大订单,或者是企业的一个战略目标,那实现该订单需要企业的多项的资源的支持,则需要用到人力,生产资源,设备,财务,采购,客户资源等。ERP是通过对这些资源的有效计划利用,公司高层通过掌握、管理、控制等手段来实现预期目标。适合大企业或者成熟的企业应用。



2:办公协同方向

支持企业内部办公管理运转的业务系统,属于办公协同产品。

例:OA,即办公自动化(Office Automation)。是比较常用的办公软件,基于工作流概念,使企业内部人员方便快捷地共享信息,协同工作。


3:商家管理平台

平台型互联网公司为商家提供了交易的平台。为了保证平台的持续、良性运转,公司需要对入驻的商家进行资质审核和服务管理,这就需要设计并开发企业内部使用的商户管理系统;同时公司需要给商家提供一套强大的经营管理后台,方便商家进行自主管理。从业务管理视角来看,商家管理方向大致分为图下所示的两大系统。

小结:上述所列分类为大类区分,有的产品即可归属于交易,又可归属于基础服务,所以不必严格按着分类走,还需根据公司具体情况做具体分析。



B端产品的特点?

1:B端产品大都有行业特性或场景特性,目标用户一般是群体。

B端产品用户群体是某个业务团队或组织,需要共同协作来完成工作,所以需要B端产品来帮助他们实现分工协作。


2:B端产品业务逻辑复杂,子业务多样化。

B端产品背后的业务复杂度高,人员、分工、协作、流程、规则随时可能调整,这就需要我们有非常强的抽象能力和逻辑思维,寻求看似散乱无章的业务共性,进行合理整理和设计。


3:效能第一

B端产品的目标是解决组织的某类业务问题,因此聚焦于流程,提升业务效能是最重要的。



B端产品和C端产品的差异性

1:需求来源不同

C端产品的需求来源于用户,使用C端产品的是独立的个人。而B端产品需求已经存在,来源于公司内部或外部。


2:产品设计不同

C端产品经理通常关注产品的点击率,转化率,增长率等。而设计B端产品的本质是提升企业内部工作效率,所以更注重优化用户操作流程,提能。


3:收益量化不同

C端产品关注的核心指标主要包括DAU、UV、PV、CVR等,任何功能的设计都可以明确考核指标,容易量化和评判项目收益。

B端产品要支持、解决业务问题,但业务成效的影响因素非常多,很多时候并非取决于B端产品设计的好坏。


4:核心功能点不同

C端产品有核心功能点,B端产品的功能多且每个功能都具有必要性。



面对B端产品如何分析和入手?

1:了解业务流程和产品定位

在做B端产品之前,我们需要对即将做的业务有一个充分的理解和认知,不同部门使用的产品不同,则相对应的设计方案也不相同,这就需要我们充分了解业务流程,针对性的进行梳理。

例:如果我们要做报税系统,那么我们要知道报税的流程有哪些,这样可以帮我们规避许多不必要的问题。


2:功能流程归类

把杂乱的功能整理清楚,提高用户效能。


3:让产品落地并不断生长(价值体系搭建)

这是整个产品中最核心的点。何谓价值体系?对于B端的产品而言,客户最关心它能为实际的工作带来哪些便利,所以对于一个B端产品,解决问题的价值就是最好的推广。


4:整合设计,迭代优化

对于设计部门,我们需要考虑设计的功能点有没有遗漏?交互框架搭建的过程中,随着产品发展,是否考虑到了其更多功能的扩展性。



提高B端产品的品质,需要考虑的方向

1:功能引导

功能引导是产品降低用户学习成本最通用的手段之一。简单说就是使产品学习起来简单,易用,用最快最清晰的方式触达产品核心,可划分为内嵌式和互动探索式两类。


来源:语雀(内嵌式)


来源:Teambition (互动探索式)


设计要点:

1: 文案精准,通俗易懂

2: 与品牌风格保持一致

3: 增加趣味性

4: 挖掘合适的出现场景,在最终呈现上做到简洁克制


功能引导最重要的是要契合产品本身,在合适的时机,以恰当的方式,在不剥夺用户探索权利的情况下,去引导用户更好地使用这款产品。



2:认知减负


帮助用户认知减负的常见手段有可读性优化、复杂性简化等。

可读性优化上,可以通过关键词提炼,可视化图表等方式,降低用户阅读大块内容时的产生的心理压力和抵触感。


例:图一中列表1和列表2的对比,通过数据可视化的方式让用户更为有效的查看数据,从而对业务有更加直观的了解。

图一:来源:某广告平台(可读性优化)


图二 来源: Teambition(可视化图表)



复杂性简化上,可以通过减少页面上不必要的功能信息,减少干扰信息。


例如teambition的登陆页面,点击“更多登录方式”则可以看到相对不常用的元素。将不常用的元素收起来,减少页面上低频率使用的功能,减少视觉上的混乱。

来源: Teambition 登录界面(复杂性简化)


设计要点:

1: 避免不必要的元素

2: 利用普遍的设计模式

3: 减少不必要的任务

4: 最小化可选项

5: 保证可读性


3:学习模式

对于一些面向固定人群使用的产品(比如企业数字化平台、智能工厂系统等),面对复杂的系统,有时简单的新手教程并不能解决业务复杂性的本身带来的操作门槛,此时向专业用户提供帮助文档或教学视频等学习工具,就变得尤为重要。

来源: 用友(教学视频)


结尾:以上就是对B端产品的初略思考,其实想说无论是B端还是C端,每个产品都有自己相应的价值,我们在设计的过程中需要根据具体的业务和场景进行具体分析。


一分钟制作精美HTML邮件格式的元旦祝福邮件

seo达人

岁月不居,时节如流。转眼之间2019悄然而过,回首2019,我们豪情满怀,漫天风雪风景好;展望2020,我们重任在肩,阳光普照写佳绩。舟至中流需奋进,风好正是扬帆时。



在2019最后一天里,可能你还想做这么的一件事,给客户发一份精美的邮件祝福,但却无从下手,怎么办?拉易网能帮到你,准备好素材后,只需一分钟就能生成精美的HTML邮件格式。话不多说,直接上视频。





一分钟制作精美HTML邮件格式的元旦祝福邮件



走着走着…



2019已接近尾声



一眨眼就是一天



一回头就是一年



一转身就是一辈子



人生总有太多的来不及

B端设计,我总结了这些交互设计要点

雪涛

B 端工作看起来总是没有 C 端工作那么有趣,面临的限制比较多,面对客户(金主爸爸),很多时候总是左右为难。在实际工作中,面对这些情况该怎么办?笔者根据自己的 B 端工作经验,总结了一些交互设计的要点。

从事 B 端 SaaS 行业已经两年有余,从最开始面对需求的茫然无措,到现在已经有了自己的一些基本方法论,这期间经历了从有人带到自己摸索的一个阶段。

每天看看文章、看看书,大家讲的都是 C 端的产品,C 端的交互和体验;每天看同行们分析的不是如何提高用户活跃量,就是 APP 的设计风格。这在我一个 B 端交互看来,无比羡慕啊。

C 端项目的设计师感觉每天和一线用户打交道,忙得不亦乐乎,可以与用户直接对接,可以添加有趣生动的文案。

而对于一个做 SaaS 的 B 端来说,Boss 常说的话就是:

你这个颜色太鲜艳了。

我们是 B 端,你这种页面布局不合适吧。

这个文案太幼稚了,不适合我们产品的调性。

中规中矩就好,不要太跳。

整理了一堆的字段,画了无数的线框和流程图,却拿不出 C 端设计师才有的丰富多彩的作品集,

尽管如此,设计的原则是通用的,无论是尼尔森十大可用性原则,还是格式塔原理,在设计方案的落实上,都有着相同的方法论。

无意在此讨论 B 端和 C 端之间的差异,仅以此文章来总结下我自己的一些工作经验,如有错误,敬请指出。

从业务需求的对接,到界面交互的细节,从功能的设计要点,再到产品的发展导向,我总结出了以下几个方面,逐步展开:

  • 提炼需求
  • 减少出错
  • 提率
  • 提高通用性
  • 中正原则

提炼需求

C 端设计师最开心的可能就是收到用户的反馈需求了吧,这样表示自己的产品还有人在用,然后建个群让用户开开心心吐槽,完了就从里面提炼适合产品的一些需求和建议。

而对于 B 端设计师来说,如何处理需求是其成长的第一关,尤其是 SaaS 行业,不会处理需求,产品的功能更新将会遇到极大的问题。

B 端的用户不像 C 端,虽然可以用用户画像来进行归类和分析。但是由于 B 端的直接用户是付费用户,付了钱的就是大爷,因此大爷提的需求你敢不应?

用户提的需求乱七八糟,有些是体验问题,有些是功能问题,有些就是属于比较极端的需求。那种传说中甲方要你做一个可以根据手机屏显示颜色而改变手机壳颜色的需求,在 B 端行业也是存在的。

那么问题来了,我们该如何处理纷繁杂乱的需求呢,我从收集需求-评估需求-需求落地挨个讲起:

1. 收集需求

如果你也打算像 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

当提供方向性需求的功能时,会招致大部分用户满意度的大幅下降。比如常见的在搜索中掺加广告、强制用户授权过多个人信息以及推送大量无用的消息等,会导致用户的反感。

2. 评估需求

通常需求的收集不是你一个人在进行,产品经理、老板以及其他同事也会帮助你收集,汇总到你这里的需求会开会进行讨论,下一步要做什么。

做之前首先要对需求进行评估,评估的原则基本是按照 KANO 模型来,但是也会有比较特殊的情况。

交互设计师需要注意的是,你除了需要关注用户体验的问题以外,还要与开发一起评估该需求的实现;你不懂技术没关系,开发会告诉你该需求的落地会出现什么问题,在实现上会有什么难度。这些在评估需求的阶段都要提出来,并且在交互层面解决掉,否则你画出了原型以后,开发告诉你这个页面必须要修改,否则开发成本过大,无法在排期内完成,这个时候你再去改交互稿将会费时费力。

评估完需求,定好下期开发的需求后就结束了么?

其实并没有,因为需求收集不可能是一个固定的阶段,它是渐进的、不确定的。因此收集需求和评估需求会不断在实际工作中叠加和重复,比如你制定好了需求优先级,已经着手开发了;这时候老板或者领导突然又增加了一个优先级很高的需求,所以你得重新安排这些需求。如何在敏捷开发中保质保量的完成工作任务,这是一场斗智斗勇的 battle。

3. 需求落地

前面讲到需求评估的时候,需要与开发人员一起评估技术难度。其实在你将需求落地为交互原型的时候,也需要持续沟通,这完全是为了避免因为技术问题而产生修改设计稿或沟通不顺畅的问题。

如果你是在做的过程中,持续与开发人员保持沟通,能了解到他们是如何实现这个功能的。当然,有些基本的设计原则所不允许的事完全不用屈服于他们的「淫威」,毕竟他们得按照你的方案来。如果开发懒惰而想通过最简单的办法来实现,用户体验又是极其不友好,那么请直接对其说「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 中文排版需求(强烈建议大家看这个)、孔雀计划的文章、字体排印的专著:《平面设计中的网格系统》、《字体排印》、《西文字体》等;本次改版几乎所有的功能与逻辑都参照了以上专著与文章。)

文字排印要遵循的三个原则

我们把文字书排版时的工序,总结为「文字排印要遵循的三个原则」:

1. 尽量保证字间距恒定

原则说明

中文排版中,字与字之间的间距被称为「字间距」。

文字间距会影响阅读节奏。字间距大的文章,阅读速度会变慢。因此,散文、诗歌在排版时,会刻意调大字间距。

下面的图,仅凭自己感受,选一张更好的:

不出意外的话,应该是觉得下图更好看。

尝试默读一下,你会发现,上边的图片,最后两行字间距被拉大,阅读速度放慢;而这不是作者的本意,换言之,这会破坏阅读节奏。

因此,我们把「保证字间距恒定」作为首要原则,来保障阅读节奏感。

备注(建议第二次看文章的时候再读):需要说明的是,部分字面较大的字体(方正博雅宋、兰亭黑等)在书籍排版时为了契合书籍内容的调性,有时会刻意设置字间距,这与「字间距保持恒定」的原则并不冲突。在电子阅读软件中,由于无法针对特定书籍进行调整,因此本次设计实际上是保持「密排」(字与字之间没有额外添加字间距,保留字体原始的间距)。

工序

行长是字号的整数倍。相同字号下,汉字字宽固定(就是字号本身),汉字标点的字宽同样也是字号本身(除了个别标点之外,例如破折号)

汉字排版时,没有额外字间距的情况下,是上图所示的字面框依次密排。

因为中文书籍的正文排版常用两端对齐,如果行长不是字号的整数倍,则汉字之间会有异常的行间距出现。

更严重的是:阅读软件字号可变的情况下,行长不可能做到适应所有字号且字间距不会被拉大。

行长是字号的整数倍是中文字体排印中标点挤压等的前提。
——《孔雀计划》,原文链接:https://thetype.com/2017/07/12513/

在阅读软件中,随着字号调整,如果沿用「版心宽度固定」的思路,难免存在字间距被拉大的情况。
对此,我们调查了国内外知名中文阅读软件,发现:KindleAPP 能随着字号变化自由变动,但这会导致:改变字号大小时,版心宽度略微变化。

有此顾虑,我们做了一个测试。结果证明,大家不会发现版心宽度有变化。这说明用户投入到阅读当中、调整字号时,并不会因为版心宽度变化而有不适,甚至不会感知。通过测试,打消了我们的顾虑。
最终我们大胆采取了「版心宽度跟随字号调整而变化」,来确保「行长是字号的整数倍」。

虽然「版心宽度跟随字号变化」并没有不适,但我们需要保证在多种屏幕尺寸、字号下,版心占据屏幕的区域都舒适。

面对这个问题,我们制定了一个公式,可根据屏幕大小、字体大小等,自动调整版心宽度。确保「行长是字号的整数倍」的同时,保证页面美观。

标点符号「优先推入式避头尾」。如图所示,为宋抄本《孙子算经》;在古代,书籍排版可以做到字间距恒定,原因是古代不存在「标点」,也就没有「标点避头尾」导致的种种问题。

而现代汉语存在标点符号,有的标点不能放在行首,有的不能放在行尾。

我们把不能放在行首的标点叫做避头标点,如逗号、顿号、句号等;把不能放置在行尾的叫做避尾标点,如前引号、前括号等。

「推出式」避头尾是大部分阅读软件的做法:

以避头标点为例,若此标点被排到了行首,「推出式」 的做法是从上一行拉一个字放在本行。如下图所示:

然而这么做的话,上一行的字间距被拉大,打断了阅读节奏(阅读节奏放慢)。

我们发现专业的排版软件(Indesign)和出版社(广西师范大学理想国系列、人民文学出版社、译林出版社等知名出版社)的做法是「优先推入式避头尾」,这种方式可以很好地解决「仅推出式」造成的问题。如下图所示:

△ 《少数派报告》译林出版社

通过「优先推入式避头尾」,上图中标出的双引号的宽度被挤压了一半,如果它保留为「全宽」,就没办法排在这一行,这就是「优先推入式避头尾」的最终效果。

以避头尾标点为例,「优先推入式」避头尾在这种情况下会将本行内标点宽度挤压,为避头尾的标点腾出空间,如下图所示:

上面的图可以看到:通过「优先推入式标点挤压」,第一行的字间距没有被拉大,保持了密排。

通过将本行内的标点宽度进行挤压后,腾出了空间给本来排不到的逗号,确保了字间距的恒定。然后只有在本行内标点无法压缩出足够空间时,才会选择「推出式」的处理方式。

因此这种处理方式叫做「优先推出式」标点避头尾。

行内标点挤压。因为相邻标点挤压、行首段首挤压,会出现部分标点符号占据半宽的情况。这种时候,一行的末尾可能正好有汉字或标点轧在了边框上,如下图所示,为汉字轧在边框的情况:

遇到这种情况,通过挤压行内标点宽度,从而腾出空间给最后一个字。这种做法叫做「行内标点挤压」

标点悬挂的逻辑和配套内容。存在另外一种处理方式来避头,叫做「标点悬挂」,即将标点悬挂在文本框外。

然而这种采用「标点悬挂」,需要配套做「行尾强制半宽」,如下图所示:

然而行尾强制半宽带来的问题是字间距被拉大,违反了原则一(尽量保证字间距恒定),对于宽度有限的手机屏幕,尽量不要改动。因此最终我们没有采用标点悬挂的处理方式。
Type is Beautiful 网站中对此有详尽的思考,如有兴趣请看:https://thetype.com/kongque/

2. 版心灰度均衡

原则说明

书籍排版中,文字所在的范围称为「版心」。

经验老道的文字排印设计师,检查正文排版效果时,最常用的方法是:离远看页面,就像蒙上一层磨砂玻璃一样,检查整个页面是否疏密均衡(《字体排印》,高冈昌生先生),也叫做「灰度均衡」(龟仓雄策先生称之为「浓淡匀称」,from《疾风迅雷》)。灰度均衡的版心可以让整个页面美观的同时,也保证了读者阅读中不会被突然的空白打断。

如下图所示,第一张图因为一些原因导致了页面中有许多「窟窿」,显得零碎,灰度明显不均衡。而第二张图,通过「标点挤压」将这些「窟窿」填上,整个版面更像是一个整体,灰度更均衡。

看倒数第二行的「乃跪地罪曰:‘大人何故’」两个标点连续的地方,从整体的角度看,会不会觉得这里有一个窟窿?

总的来说,灰度均衡的版面整体感更强、视觉上更舒适,因此也是相当重要的原则。
工序:

相邻标点挤压

众所周知的,汉字是方块字,在字体设计时会被放在一个方形里;中文标点同样也会被放置于同等大小的方框之中,如下图所示。

如上图所示,中文标点所占体积一般远小于汉字,因此当多个标点符号连续排列时,会让版面在这里好像有一个窟窿。专业的中文排版中会做的事情是「相邻标点挤压」。如下图所示:

如上图所示,有连续标点存在时,通过压缩标点所占的宽度,从而补上「窟窿」。

3. 版面齐整

原则说明

与西文书籍的左侧对齐不同,中文书籍(横排)传统而言是讲究两端对齐。这是被大多数国人认可的中文排版方式,因此不再赘述。

工序

行首段首标点挤压。当行首出现标点符号,会感觉左侧不齐:

可以看到,处理前版心左侧因为有单引号,看起来第一行没有和第二行左对齐,处理后效果回归正常。同样的,段首的标点也需要挤压。

实际落地时的经验与产出

知道了以上内容,我们需要把它变成开发需要的逻辑。具体如下:

1. 相邻标点挤压逻辑

在 W3C 的《中文排版需求》中,对相邻标点挤压的具体做法为:

  • 依照中国大陆的常见的排版规则,当结束夹注符号出现在顿号、逗号、句号之后时,缩减两者间二分之一个汉字大小的空白;而在港台则不会做此调整。
  • 当顿号、逗号、句号出现在结束夹注符号之后时,缩减其间二分之一个汉字大小的空白。
  • 当开始夹注符号出现在顿号、逗号、句号之后时,缩减其间二分之一个汉字大小的空白。
  • 当开始夹注符号出现在结束夹注符号之后时,缩减其间二分之一个汉字大小的空白。
  • 当两个(或以上)开始夹注符号连续排列时,缩减其间二分之一个汉字大小的空白。
  • 当两个(或以上)结束夹注符号连续排列时,缩减其间二分之一个汉字大小的空白。
  • 当间隔号出现于结束夹注符号之后时,缩减其间四分之一个汉字大小的空白。
  • 当间隔号出现于开始夹注符号之前时,缩减其间四分之一个汉字大小的空白。

通过梳理,我们将其简化描述为 4个逻辑(实际逻辑与 W3C 基本一致):

  • 「1个结束夹注符」后面是:开始夹注符、结束夹注符、顿、逗、句(包括全宽句点)、冒、分,就挤。
  • 「1个开始夹注符」后面是:开始夹注符,就挤。
  • 「顿、逗、句(包括全宽句点)」后面是:开始夹注符、结束夹注符,就挤。
  • 「分、冒」后面是:开始夹注符,就挤。

备注:成对出现的标点叫做夹注符,如双引号、书名号等;其中细分为开始夹注符与结束夹注符。
此外,我们注意到,一些出版书在以上逻辑之外,把问号与叹号与[顿、逗、句]归为一类,实际效果良好,因此出于问号与叹号同样占据字面不多的逻辑,采用了这种分类法。然而随后在走查阶段发现线上部分字体的问号与句号占据字面的位置不同。

这促使我们关注到标点在字面中占据的位置,我们梳理了客户端所有字体的中文标点。

发现汉仪乐喵、方正兰亭黑、汉仪启体等几款字体的问号字面占据异常,如果进行相邻标点挤压可能会造成标点粘连的情况,因此最终我们决定保持问号与叹号不参与相邻标点挤压。

2. 避头、避尾标点汇总表:

以上逻辑实施需要首先让程序判定哪些标点是避头尾标点,因此我们梳理了所有汉字标点并分为避头、避尾两类(部分标点即避头又避尾)。

汉字标点符号与西文的标点符号许多时候仅凭肉眼难以分辨(如,与,前者是西文标点中的逗号,后者是中文标点中的逗号),因此我们使用Unicode 码为每一个标点精准划分,确保不会造成错误。

3. 行内标点挤压与「优先推入式避头尾」逻辑:

「行内标点挤压」与「优先推入式避头尾」其实本质上都是对行内标点宽度进行压缩,因此在逻辑上归为一类。根据具体解决逻辑的不同将其分为以下四类:

针对这四种情况要做的具体事项补充在右侧:

至于推出逻辑,则稍简单些,书籍中有时候会遇到连续几个标点符号都是避尾标点的情况(或连续避头标点),因此对推出的逻辑设定为:

备注:国内知名的字体设计与排印网站 Type is Beautiful 中有介绍,行内标点挤压的方式有多种,「开明式」「全部半宽式」「平均式」等等…我们选择了「平均式」(有权重),开发难度会稍小些、效果也更可控制,不再赘述。

4. 整体逻辑流程图

但是仅仅通过文字性质的描述还不够,我们需要能让程序理解的逻辑。

由于部分处理方式之间互相干扰,因此根据这四种方式影响的内容不同,以「对其他处理方式的干扰程度」从高到低排序,并串联成整整体的逻辑图,以保证整体逻辑简单、不重复,如下图所示。

在此基础上,我们将文章中第二部分所梳理的逻辑细节填充进流程图内,并合并重复流程,最终获得如下流程图,方便开发理解和工作:

5. 小结

通过「避头尾标点列表」「可挤压标点与挤压空间」「广义的推入逻辑具体说明」「整体逻辑流程图」四个文件,我们可以顺畅的将设计要求传达给开发。

最终,通过这些工序,我们可以在最大限度上保证字间距恒定、版面齐整和内容灰度均衡。提升阅读流畅性和阅读页的体验。

总结

文字排印作为一个古老的技艺,从排版工人操作实体字模的时代,经历了照排时代,来到了数字排版的当下,排版的自由度和效率已然成倍提高,然而由于许多原因至今这些排版的工序只在出版社等专业领域流通。

相对于纸质书籍,电子阅读在易携带性、阅读方便性、多媒体辅助阅读上有得天独厚的优势,然而最基础的阅读体验有时候不如纸质书籍,我们希望通过我们的努力,电子阅读在未来,能够让读者获得全面超过纸质书籍的阅读体验。

当然,文字在移动端的体验上限远不止如此,一些产品仅靠网格系统与字体排印加上优秀的字体,已经做出了令人惊艳的体验。

如上图所示,通过优秀的明朝体、网格系统,物书堂出品的几个词典 APP 的界面让人惊艳,文字之美还有很多可能,这也是我们的努力方向。我们也知道,当前客户端内中英混排、英文排版等方面,依然有进步空间,未来也会进一步完善。

文章来源:优设

微信小程序总结跳转方式,解决跳转失效问题

seo达人

微信小程序跳转方式

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: '../../..'

      })

    }


Vuex源码分析

seo达人

一、前言

我们都知道,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取值。


日历

链接

blogger

蓝蓝 http://www.lanlanwork.com

存档