首页

字体构造与文字垂直居中方案探索

seo达人

1. 引子

垂直居中基本上是入门 CSS 必须要掌握的问题了,我们肯定在各种教程中都看到过“CSS 垂直居中的 N 种方法”,通常来说,这些方法已经可以满足各种使用场景了,然而当我们碰到了需要使用某些特殊字体进行混排、或者使文字对齐图标的情况时,也许会发现,无论使用哪种垂直居中的方法,总是感觉文字向上或向下偏移了几像素,不得不专门对它们进行位移,为什么会出现这种情况呢?

2. 常见的垂直居中的方法

下图是一个使用各种常见的垂直居中的方法来居中文字的示例,其中涉及到不同字体的混排,可以看出,虽然这里面用了几种常用的垂直居中的方法,但是在实际的观感上这些文字都没有恰好垂直居中,有些文字看起来比较居中,而有些文字则偏移得很厉害。
垂直居中示例图
在线查看:CodePen(字体文件直接引用了谷歌字体,如果没有效果需要注意网络情况)

通过设置 vertical-align:middle 对文字进行垂直居中时,父元素需要设置 font-size: 0,因为 vertical-align:middle 是将子元素的中点与父元素的 baseline + x-height / 2 的位置进行对齐的,设置字号为 0 可以保证让这些线的位置都重合在中点。
我们用鼠标选中这些文字,就能发现选中的区域确实是在父层容器里垂直居中的,那么为什么文字却各有高低呢?这里就涉及到了字体本身的构造和相关的度量值。

3. 字体的构造和度量

这里先提出一个问题,我们在 CSS 中给文字设置了 font-size,这个值实际设置的是字体的什么属性呢?
下面的图给出了一个示例,文字所在的标签均为 span,对每种字体的文字都设置了红色的 outline 以便观察,且设有 line-height: normal。从图中可以看出,虽然这些文字的字号都是 40px,但是他们的宽高都各不相同,所以字号并非设置了文字实际显示的大小。
文字大小示意图
为了解答这个问题,我们需要对字体进行深入了解,以下这些内容是西文字体的相关概念。首先一个字体会有一个 EM Square(也被称为 UPM、em、em size)[4],这个值最初在排版中表示一个字体中大写 M 的宽度,以这个值构成一个正方形,那么所有字母都可以被容纳进去,此时这个值实际反映的就成了字体容器的高度。在金属活字中,这个容器就是每个字符的金属块,在一种字体里,它们的高度都是统一的,这样每个字模都可以放入印刷工具中并进行排印。在数码排印中,em 是一个被设置了大小的方格,计量单位是一种相对单位,会根据实际字体大小缩放,例如 1000 单位的字体设置了 16pt 的字号,那么这里 1000 单位的大小就是 16pt。Em 在 OpenType 字体中通常为 1000 ,在 TrueType 字体中通常为 1024 或 2048(2 的 n 次幂)。
金属活字

金属活字,图片来自 http://designwithfontforge.com/en-US/The_EM_Square.html

3.1 字体度量

字体本身还有很多概念和度量值(metrics),这里介绍几个常见的概念,以维基百科的这张图为例(下面的度量值的计量单位均为基于 em 的相对单位):
字体结构

  • baseline:Baseline(基线)是字母放置的水平线。
  • x height:X height(x字高)表示基线上小写字母 x 的高度。
  • capital height:Capital height(大写高度)表示基线上一个大写字母的高度。
  • ascender / ascent:Ascender(升部)表示小写字母超出 x字高的字干,为了辨识性,ascender 的高度可能会比 capital height 大一点。Ascent 则表示文字顶部到 baseline 的距离。

字符升部

  • descender / descent:Descender(降部)表示扩展到基线以下的小写字母的字干,如 j、g 等字母的底部。Descent 表示文字底部到 baseline 的距离。
  • line gap:Line gap 表示 descent 底部到下一行 ascent 顶部的距离。这个词我没有找到合适的中文翻译,需要注意的是这个值不是行距(leading),行距表示两行文字的基线间的距离。

接下来我们在 FontForge 软件里看看这些值的取值,这里以 Arial 字体给出一个例子:
Arial Font Information
从图中可以看出,在 General 菜单中,Arial 的 em size 是 2048,字体的 ascent 是1638,descent 是410,在 OS/2 菜单的 Metrics 信息中,可以得到 capital height 是 1467,x height 为 1062,line gap 为 67。
然而这里需要注意,尽管我们在 General 菜单中得到了 ascent 和 descent 的取值,但是这个值应该仅用于字体的设计,它们的和永远为 em size;而计算机在实际进行渲染的时候是按照 OS/2 菜单中对应的值来计算,一般操作系统会使用 hhea(Horizontal Header Table)表的 HHead Ascent 和 HHead Descent,而 Windows 是个特例,会使用 Win Ascent 和 Win Descent。通常来说,实际用于渲染的 ascent 和 descent 取值要比用于字体设计的大,这是因为多出来的区域通常会留给注音符号或用来控制行间距,如下图所示,字母顶部的水平线即为第一张图中 ascent 高度 1638,而注音符号均超过了这个区域。根据资料的说法[5],在一些软件中,如果文字内容超过用于渲染的 ascent 和 descent,就会被截断,不过我在浏览器里实验后发现浏览器并没有做这个截断(Edge 86.0.608.0 Canary (64 bit), MacOS 10.15.6)。
ascent
在本文中,我们将后面提到的 ascent 和 descent 均认为是 OS/2 选项中读取到的用于渲染的 ascent 和 descent 值,同时我们将 ascent + descent 的值叫做 content-area。

理论上一个字体在 Windows 和 MacOS 上的渲染应该保持一致,即各自系统上的 ascent 和 descent 应该相同,然而有些字体在设计时不知道出于什么原因,导致其确实在两个系统中有不同的表现。以下是 Roboto 的例子:
Differences between Win and HHead metrics cause the font to be rendered differently on Windows vs. iOS (or Mac I assume) · Issue #267 · googlefonts/roboto
那么回到本节一开始的问题,CSS 中的 font-size 设置的值表示什么,想必我们已经有了答案,那就是一个字体 em size 对应的大小;而文字在设置了 line-height: normal 时,行高的取值则为 content-area + line-gap,即文本实际撑起来的高度。
知道了这些,我们就不难算出一个字体的显示效果,上面 Arial 字体在 line-height: normal 和 font-size: 100px 时撑起的高度为 (1854 + 434 + 67) / 2048 * 100px = 115px
在实验中发现,对于一个行内元素,鼠标拉取的 selection 高度为当前行 line-height 最高的元素值。如果是块状元素,当 line-height 的值为大于 content-area 时,selection 高度为 line-height,当其小于等于 content-area 时,其高度为 content-area 的高度。

3.2 验证 metrics 对文字渲染的影响

在中间插一个问题,我们应该都使用过 line-height 来给文字进行垂直居中,那么 line-height 实际是以字体的哪个部分的中点进行计算呢?为了验证这个问题,我新建了一个很有“设计感”的字体,em size 设为 1000,ascent 为 800,descent 为 200,并对其分别设置了正常的和比较夸张的 metrics:
TestGap normal
TestGap exaggerate
上面图中左边是 FontForge 里设置的 metrics,右边是实际显示效果,文字字号设为 100px,四个字母均在父层的 flex 布局下垂直居中,四个字母的 line-height 分别为 0、1em、normal、3em,红色边框是元素的 outline,黄色背景是鼠标选取的背景。由上面两张图可以看出,字体的 metrics 对文字渲染位置的影响还是很大的。同时可以看出,在设置 line-height 时,虽然 line gap 参与了撑起取值为 normal 的空间,但是不参与文字垂直居中的计算,即垂直居中的中点始终是 content-area 的中点。
TestGap trimming
我们又对字体进行了微调,使其 ascent 有一定偏移,这时可以看出 1em 行高的文字 outline 恰好在正中间,因此可以得出结论:在浏览器进行渲染时,em square 总是相对于 content-area 垂直居中。
说完了字体构造,又回到上一节的问题,为什么不同字体文字混排的时候进行垂直居中,文字各有高低呢?
在这个问题上,本文给出这样一个结论,那就是因为不同字体的各项度量值均不相同,在进行垂直居中布局时,content-area 的中点与视觉的中点不统一,因此导致实际看起来存在位置偏移,下面这张图是 Arial 字体的几个中线位置:
Arial center line
从图上可以看出来,大写字母和小写字母的视觉中线与整个字符的中线还是存在一定的偏移的。这里我没有找到排版相关学科的定论,究竟以哪条线进行居中更符合人眼观感的居中,以我个人的观感来看,大写字母的中线可能看起来更加舒服一点(尤其是与没有小写字母的内容进行混排的时候)。

需要注意一点,这里选择的 Arial 这个字体本身的偏移比较少,所以使用时整体感觉还是比较居中的,这并不代表其他字体也都是这样。

3.3 中文字体

对于中文字体,本身的设计上没有基线、升部、降部等说法,每个字都在一个方形盒子中。但是在计算机上显示时,也在一定程度上沿用了西文字体的概念,通常来说,中文字体的方形盒子中文字体底端在 baseline 和 descender 之间,顶端超出一点 ascender,而标点符号正好在 baseline 上。

4. CSS 的解决方案

我们已经了解了字体的相关概念,那么如何解决在使用字体时出现的偏移问题呢?
通过上面的内容可以知道,文字显示的偏移主要是视觉上的中点和渲染时的中点不一致导致的,那么我们只要把这个不一致修正过来,就可以实现视觉上的居中了。
为了实现这个目标,我们可以借助 vertical-align 这个属性来完成。当 vertical-align 取值为数值的时候,该值就表示将子元素的基线与父元素基线的距离,其中正数朝上,负数朝下。
这里介绍的方案,是把某个字体下的文字通过计算设置 vertical-align 的数值偏移,使其大写字母的视觉中点与用于计算垂直居中的点重合,这样字体本身的属性就不再影响居中的计算。
具体我们将通过以下的计算方法来获取:首先我们需要已知当前字体的 em-size,ascent,descent,capital height 这几个值(如果不知道 em-size,也可以提供其他值与 em-size 的比值),以下依然以 Arial 为例:

const emSize = 2048; const ascent = 1854; const descent = 434; const capitalHeight = 1467

// 计算前需要已知给定的字体大小 const fontSize = FONT_SIZE; // 根据文字大小,求得文字的偏移 const verticalAlign = ((ascent - descent - capitalHeight) / emSize) * fontSize; return ( <span style={{ fontFamily: FONT_FAMILY, fontSize }}> <span style={{ verticalAlign }}>TEXT</span> </span> )

由此设置以后,外层 span 将表现得像一个普通的可替换元素参与行内的布局,在一定程度上无视字体 metrics 的差异,可以使用各种方法对其进行垂直居中。
由于这种方案具有固定的计算步骤,因此可以根据具体的开发需求,将其封装为组件、使用 CSS 自定义属性或使用 CSS 预处理器对文本进行处理,通过传入字体信息,就能修正文字垂直偏移。

5. 解决方案的局限性

虽然上述的方案可以在一定程度上解决文字垂直居中的问题,但是在实际使用中还存在着不方便的地方,我们需要在使用字体之前就知道字体的各项 metrics,在自定义字体较少的情况下,开发者可以手动使用 FontForge 等工具查看,然而当字体较多时,挨个查看还是比较麻烦的。
目前的一种思路是我们可以使用 Canvas 获取字体的相关信息,如现在已经有开源的获取字体 metrics 的库 FontMetrics.js。它的核心思想是使用 Canvas 渲染对应字体的文字,然后使用 getImageData 对渲染出来的内容进行分析。如果在实际项目中,这种方案可能导致潜在的性能问题;而且这种方式获取到的是渲染后的结果,部分字体作者在构建字体时并没有严格将设计的 metrics 和字符对应,这也会导致获取到的 metrics 不够准确。
另一种思路是直接解析字体文件,拿到字体的 metrics 信息,如 opentype.js 这个项目。不过这种做法也不够轻量,不适合在实际运行中使用,不过可以考虑在打包过程中自动执行这个过程。
此外,目前的解决方案更多是偏向理论的方法,当文字本身字号较小的情况下,浏览器可能并不能按照预期的效果渲染,文字会根据所处的 DOM 环境不同而具有 1px 的偏移[9]。

6. 未来也许可行的解决方案 - CSS Houdini

CSS Houdini 提出了一个 Font Metrics 草案[6],可以针对文字渲染调整字体相关的 metrics。从目前的设计来看,可以调整 baseline 位置、字体的 em size,以及字体的边界大小(即 content-area)等配置,通过这些可以解决因字体的属性导致的排版问题。

[Exposed=Window] interface FontMetrics {
 readonly attribute double width;
 readonly attribute FrozenArray<double> advances;
 readonly attribute double boundingBoxLeft;
 readonly attribute double boundingBoxRight;
 readonly attribute double height;
 readonly attribute double emHeightAscent;
 readonly attribute double emHeightDescent;
 readonly attribute double boundingBoxAscent;
 readonly attribute double boundingBoxDescent;
 readonly attribute double fontBoundingBoxAscent;
 readonly attribute double fontBoundingBoxDescent;
 readonly attribute Baseline dominantBaseline;
 readonly attribute FrozenArray<Baseline> baselines;
 readonly attribute FrozenArray<Font> fonts;
};

css houdini
从 https://ishoudinireadyyet.com/ 这个网站上可以看到,目前 Font Metrics 依然在提议阶段,还不能确定其 API 具体内容,或者以后是否会存在这一个特性,因此只能说是一个在未来也许可行的文字排版处理方案。

7.总结

文本垂直居中的问题一直是 CSS 中最常见的问题,但是却很难引起注意,我个人觉得是因为我们常用的微软雅黑、苹方等字体本身在设计上比较规范,在通常情况下都显得比较居中。但是当一个字体不是那么“规范”时,传统的各种方法似乎就有点无能为力了。
本文分析了导致了文字偏移的因素,并给出寻找文字垂直居中位置的方案。
由于涉及到 IFC 的问题本身就很复杂[7],关于内联元素使用 line-height 与 vertical-align 进行居中的各种小技巧因为与本文不是强相关,所以在文章内也没有提及,如果对这些内容比较感兴趣,也可以通过下面的参考资料寻找一些相关介绍。

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

这些 ECMAScript 模块知识,都是我需要知道的

seo达人

ES 模块是什么?

ECMAScript模块(简称ES模块)是一种JavaScript代码重用的机制,于2015年推出,一经推出就受到前端开发者的喜爱。在2015之年,JavaScript 还没有一个代码重用的标准机制。多年来,人们对这方面的规范进行了很多尝试,导致现在有多种模块化的方式。

你可能听说过AMD模块UMD,或CommonJS,这些没有孰优孰劣。最后,在ECMAScript 2015中,ES 模块出现了。

我们现在有了一个“正式的”模块系统。

ES 模块无处不在?

理论上,ES 模块应该在所有JavaScript环境中。实际上,ES 模块的主要应用还是在浏览器上。

2020年5月,Node.js v12.17.0 增加了在不使用标记前提下对ECMAScript模块的支持。 这意味着我们现在可以在Node.js中使用importexport ,而无需任何其他命令行标志。

ECMAScript模块要想在任何JavaScript环境通用,可能还需要很长的路要走,但方向是正确的。

ES 模块是什么样的

ES 模块是一个简单的文件,我们可以在其中声明一个或多个导出。以下面utils.js为例:

// utils.js export function funcA() { return "Hello named export!";
} export default function funcB() { return "Hello default export!";
}

这里有两个导出。

第一个是命名导出,后面是export default,表示为默认导出

假设我们的项目文件夹中有一个名为utils.js的文件,我们可以将这个模块提供的对象导入到另一个文件中。

如何从 ES模块 导入

假设我们在项目文中还有一个Consumer.js的文件。 要导入utils.js公开的函数,我们可以这样做:

// consumer.js import { funcA } from "./util.js";

这种对应我们的命名导入方式.

如果我们要导入 utils.js 中的默认导出也就是 funcB 方法,我们可以这样做:

// consumer.js import { funcA } from "./util.js";

当然,我们可以导入同时导入命名和默认的:

// consumer.js import funcB, { funcA } from "./util.js";

funcB();
funcA();

我们也可以用星号导入整个模块:

import * as myModule from './util.js';

myModule.funcA();
myModule.default(); 

注意,这里要使用默认到处的方法是使用 default() 而不是 funcB()

从远程模块导入:

import { createStore } from "https://unpkg.com/redux@4.0.5/es/redux.mjs"; const store = createStore(/* do stuff */)

浏览器中的 ES 模块

现代浏览器支持ES模块,但有一些警告。 要使用模块,需要在 script 标签上添加属性 type, 对应值 为 module

<html lang="en"> <head> <meta charset="UTF-8"> <title>ECMAScript modules in the browser</title>

</head> <body> <p id="el">The result is:

</p> </body> <script type="module"> import { appendResult } from "./myModule.js"; const el = document.getElementById("el"); appendResult(el);

appendResult(el);

appendResult(el);

appendResult(el);

appendResult(el); </script> </html>

myModule.js 内容如下:

export function appendResult(element) { const result = Math.random();
  element.innerText += result;
}

动态导入

ES 模块是静态的,这意味着我们不能在运行时更改导入。随着2020年推出的动态导入(dynamic imports),我们可以动态加载代码来响应用户交互(webpack早在ECMAScript 2020推出这个特性之前就提供了动态导入)。

考虑下面的代码:

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8">

<title>Dynamic imports</title> </head> <body> <button id="btn">Load!</button> </body> <script src="loader.js"></script> </html>

再考虑一个带有两个导出的JavaScript模块

// util.js export function funcA() { console.log("Hello named export!");
} export default function funcB() { console.log("Hello default export!");
}

为了动态导入 util.js 模块,我们可以点击按钮在去导入:

/ loader.js
const btn = document.getElementById("btn");

btn.addEventListener("click", () => { // loads named export import("./util.js").then(({ funcA }) => {
    funcA();
  });
});

这里使用解构的方式,取出命名导出 funcA 方法:

({ funcA }) => {}

ES模块实际上是JavaScript对象:我们可以解构它们的属性以及调用它们的任何公开方法。

要使用动态导入的默认方法,可以这样做

// loader.js const btn = document.getElementById("btn");

btn.addEventListener("click", () => { import("./util.js").then((module) => { module.default();
  });
});

当作为一个整体导入一个模块时,我们可以使用它的所有导出

// loader.js const btn = document.getElementById("btn"); 

btn.addEventListener("click", () =>

{ // loads entire module // uses everything import("./util.js").then((module) => { module.funcA(); module.default();

}); });

还有另一种用于动态导入的常见样式,如下所示:

const loadUtil = () => import("./util.js"); const btn = document.getElementById("btn");

btn.addEventListener("click", () => { // });

loadUtil返回的是一个 promise,所以我们可以这样操作

const loadUtil = () => import("./util.js"); const btn = document.getElementById("btn");

btn.addEventListener("click", () => {
  loadUtil().then(module => { module.funcA(); module.default();
  })
})

动态导入看起来不错,但是它们有什么用呢?

使用动态导入,我们可以拆分代码,并只在适当的时候加载重要的代码。在 JavaScript 引入动态导入之前,这种模式是webpack(模块绑定器)独有的。

ReactVue通过动态导入代码拆分来加载响应事件的代码块,比如用户交互或路由更改。

动态导入JSON文件

假设我们项目有一个 person.json 文件,内容如下:

{ "name": "Jules", "age": 43 }

现在,我们需要动态导入该文件以响应某些用户交互。

因为 JSON 文件不是一个方法,所以我们可以使用默认导出方式:

const loadPerson = () => import('./person.json'); const btn = document.getElementById("btn");

btn.addEventListener("click", () => {
  loadPerson().then(module => { const { name, age } = module.default; console.log(name, age);
  });
});

这里我们使用解构的方式取出 name 和 age :

const { name, age } = module.default;

动态导入与 async/await

因为 import() 语句返回是一个 Promise,所以我们可以使用 async/await:

const loadUtil = () => import("./util.js"); const btn = document.getElementById("btn");

btn.addEventListener("click", async () => { const utilsModule = await loadUtil();
  utilsModule.funcA();
  utilsModule.default();
})

动态导入的名字

使用import()导入模块时,可以按照自己的意愿命名它,但要调用的方法名保持一致:

import("./util.js").then((module) => { module.funcA(); module.default();
  });

或者:

 import("./util.js").then((utilModule) => {
    utilModule.funcA();
    utilModule.default();
  });

原文:https://www.valentinog.com/bl...

代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug

细数 TS 中那些奇怪的符号

seo达人

TypeScript 是一种由微软开发的自由和开源的编程语言。它是 JavaScript 的一个超集,而且本质上向这个语言添加了可选的静态类型和基于类的面向对象编程。


本文阿宝哥将分享这些年在学习 TypeScript 过程中,遇到的 10 大 “奇怪” 的符号。其中有一些符号,阿宝哥第一次见的时候也觉得 “一脸懵逼”,希望本文对学习 TypeScript 的小伙伴能有一些帮助。


好的,下面我们来开始介绍第一个符号 —— ! 非空断言操作符。


一、! 非空断言操作符

在上下文中当类型检查器无法断定类型时,一个新的后缀表达式操作符 ! 可以用于断言操作对象是非 null 和非 undefined 类型。具体而言,x! 将从 x 值域中排除 null 和 undefined 。


那么非空断言操作符到底有什么用呢?下面我们先来看一下非空断言操作符的一些使用场景。


1.1 忽略 undefined 和 null 类型

function myFunc(maybeString: string | undefined | null) { // Type 'string | null | undefined' is not assignable to type 'string'. // Type 'undefined' is not assignable to type 'string'.  const onlyString: string = maybeString; // Error const ignoreUndefinedAndNull: string = maybeString!; // Ok }

1.2 调用函数时忽略 undefined 类型

type NumGenerator = () => number; function myFunc(numGenerator: NumGenerator | undefined) { // Object is possibly 'undefined'.(2532) // Cannot invoke an object which is possibly 'undefined'.(2722) const num1 = numGenerator(); // Error const num2 = numGenerator!(); //OK }

因为 ! 非空断言操作符会从编译生成的 JavaScript 代码中移除,所以在实际使用的过程中,要特别注意。比如下面这个例子:


const a: number | undefined = undefined; const b: number = a!; console.log(b);

以上 TS 代码会编译生成以下 ES5 代码:


"use strict"; const a = undefined; const b = a; console.log(b);

虽然在 TS 代码中,我们使用了非空断言,使得 const b: number = a!; 语句可以通过 TypeScript 类型检查器的检查。但在生成的 ES5 代码中,! 非空断言操作符被移除了,所以在浏览器中执行以上代码,在控制台会输出 undefined。


二、?. 运算符

TypeScript 3.7 实现了呼声最高的 ECMAScript 功能之一:可选链(Optional Chaining)。有了可选链后,我们编写代码时如果遇到 null 或 undefined 就可以立即停止某些表达式的运行。可选链的核心是新的 ?. 运算符,它支持以下语法:


obj?.prop

obj?.[expr]

arr?.[index] func?.(args)

这里我们来举一个可选的属性访问的例子:


const val = a?.b;

为了更好的理解可选链,我们来看一下该 const val = a?.b 语句编译生成的 ES5 代码:


var val = a === null || a === void 0 ? void 0 : a.b;

上述的代码会自动检查对象 a 是否为 null 或 undefined,如果是的话就立即返回 undefined,这样就可以立即停止某些表达式的运行。你可能已经想到可以使用 ?. 来替代很多使用 && 执行空检查的代码:


if(a && a.b) { } if(a?.b){ } /**

* if(a?.b){ } 编译后的ES5代码

*

* if(

*  a === null || a === void 0

*  ? void 0 : a.b) {

* }

*/

但需要注意的是,?. 与 && 运算符行为略有不同,&& 专门用于检测 falsy 值,比如空字符串、0、NaN、null 和 false 等。而 ?. 只会验证对象是否为 null 或 undefined,对于 0 或空字符串来说,并不会出现 “短路”。


2.1 可选元素访问

可选链除了支持可选属性的访问之外,它还支持可选元素的访问,它的行为类似于可选属性的访问,只是可选元素的访问允许我们访问非标识符的属性,比如任意字符串、数字索引和 Symbol:


function tryGetArrayElement<T>(arr?: T[], index: number = 0) { return arr?.[index];

}

以上代码经过编译后会生成以下 ES5 代码:


"use strict"; function tryGetArrayElement(arr, index) { if (index === void 0) { index = 0; } return arr === null || arr === void 0 ? void 0 : arr[index];

}

通过观察生成的 ES5 代码,很明显在 tryGetArrayElement 方法中会自动检测输入参数 arr 的值是否为 null 或 undefined,从而保证了我们代码的健壮性。


2.2 可选链与函数调用

当尝试调用一个可能不存在的方法时也可以使用可选链。在实际开发过程中,这是很有用的。系统中某个方法不可用,有可能是由于版本不一致或者用户设备兼容性问题导致的。函数调用时如果被调用的方法不存在,使用可选链可以使表达式自动返回 undefined 而不是抛出一个异常。


可选调用使用起来也很简单,比如:


let result = obj.customMethod?.();

该 TypeScript 代码编译生成的 ES5 代码如下:


var result = (_a = obj.customMethod) === null || _a === void 0 ? void 0 : _a.call(obj);

另外在使用可选调用的时候,我们要注意以下两个注意事项:


如果存在一个属性名且该属性名对应的值不是函数类型,使用 ?. 仍然会产生一个 TypeError 异常。

可选链的运算行为被局限在属性的访问、调用以及元素的访问 —— 它不会沿伸到后续的表达式中,也就是说可选调用不会阻止 a?.b / someMethod() 表达式中的除法运算或 someMethod 的方法调用。

三、?? 空值合并运算符

在 TypeScript 3.7 版本中除了引入了前面介绍的可选链 ?. 之外,也引入了一个新的逻辑运算符 —— 空值合并运算符 ??。当左侧操作数为 null 或 undefined 时,其返回右侧的操作数,否则返回左侧的操作数。


与逻辑或 || 运算符不同,逻辑或会在左操作数为 falsy 值时返回右侧操作数。也就是说,如果你使用 || 来为某些变量设置默认的值时,你可能会遇到意料之外的行为。比如为 falsy 值(''、NaN 或 0)时。


这里来看一个具体的例子:


const foo = null ?? 'default string'; console.log(foo); // 输出:"default string" const baz = 0 ?? 42; console.log(baz); // 输出:0

以上 TS 代码经过编译后,会生成以下 ES5 代码:


"use strict"; var _a, _b; var foo = (_a = null) !== null && _a !== void 0 ? _a : 'default string';

console.log(foo); // 输出:"default string" var baz = (_b = 0) !== null && _b !== void 0 ? _b : 42;

console.log(baz); // 输出:0

通过观察以上代码,我们更加直观的了解到,空值合并运算符是如何解决前面 || 运算符存在的潜在问题。下面我们来介绍空值合并运算符的特性和使用时的一些注意事项。


3.1 短路

当空值合并运算符的左表达式不为 null 或 undefined 时,不会对右表达式进行求值。


function A() { console.log('A was called'); return undefined;} function B() { console.log('B was called'); return false;} function C() { console.log('C was called'); return "foo";} console.log(A() ?? C()); console.log(B() ?? C());

上述代码运行后,控制台会输出以下结果:


A was called

C was called

foo

B was called

false

3.2 不能与 && 或 || 操作符共用

若空值合并运算符 ?? 直接与 AND(&&)和 OR(||)操作符组合使用 ?? 是不行的。这种情况下会抛出 SyntaxError。


// '||' and '??' operations cannot be mixed without parentheses.(5076) null || undefined ?? "foo"; // raises a SyntaxError // '&&' and '??' operations cannot be mixed without parentheses.(5076) true && undefined ?? "foo"; // raises a SyntaxError

但当使用括号来显式表明优先级时是可行的,比如:


(null || undefined ) ?? "foo"; // 返回 "foo"

3.3 与可选链操作符 ?. 的关系

空值合并运算符针对 undefined 与 null 这两个值,可选链式操作符 ?. 也是如此。可选链式操作符,对于访问属性可能为 undefined 与 null 的对象时非常有用。


interface Customer {

 name: string;

 city?: string;

} let customer: Customer = {

 name: "Semlinker" }; let customerCity = customer?.city ?? "Unknown city"; console.log(customerCity); // 输出:Unknown city

前面我们已经介绍了空值合并运算符的应用场景和使用时的一些注意事项,该运算符不仅可以在 TypeScript 3.7 以上版本中使用。当然你也可以在 JavaScript 的环境中使用它,但你需要借助 Babel,在 Babel 7.8.0 版本也开始支持空值合并运算符。


四、?: 可选属性

在面向对象语言中,接口是一个很重要的概念,它是对行为的抽象,而具体如何行动需要由类去实现。 TypeScript 中的接口是一个非常灵活的概念,除了可用于对类的一部分行为进行抽象以外,也常用于对「对象的形状(Shape)」进行描述。


在 TypeScript 中使用 interface 关键字就可以声明一个接口:


interface Person {

 name: string;

 age: number;

} let semlinker: Person = {

 name: "semlinker",

 age: 33,

};

在以上代码中,我们声明了 Person 接口,它包含了两个必填的属性 name 和 age。在初始化 Person 类型变量时,如果缺少某个属性,TypeScript 编译器就会提示相应的错误信息,比如:


// Property 'age' is missing in type '{ name: string; }' but required in type 'Person'.(2741) let lolo: Person  = { // Error name: "lolo" }

为了解决上述的问题,我们可以把某个属性声明为可选的:


interface Person {

 name: string;

 age?: number;

} let lolo: Person  = {

 name: "lolo" }

4.1 工具类型

4.1.1 Partial<T>

在实际项目开发过程中,为了提高代码复用率,我们可以利用 TypeScript 内置的工具类型 Partial<T> 来快速把某个接口类型中定义的属性变成可选的:


interface PullDownRefreshConfig {

 threshold: number;

 stop: number;

} /**

* type PullDownRefreshOptions = {

*   threshold?: number | undefined;

*   stop?: number | undefined;

* }

*/ type PullDownRefreshOptions = Partial<PullDownRefreshConfig>

是不是觉得 Partial<T> 很方便,下面让我们来看一下它是如何实现的:


/**

* Make all properties in T optional

*/ type Partial<T> = {

 [P in keyof T]?: T[P];

};

4.1.2 Required<T>

既然可以快速地把某个接口中定义的属性全部声明为可选,那能不能把所有的可选的属性变成必选的呢?答案是可以的,针对这个需求,我们可以使用 Required<T> 工具类型,具体的使用方式如下:


interface PullDownRefreshConfig {

 threshold: number;

 stop: number;

} type PullDownRefreshOptions = Partial<PullDownRefreshConfig> /**

* type PullDownRefresh = {

*   threshold: number;

*   stop: number;

* }

*/ type PullDownRefresh = Required<Partial<PullDownRefreshConfig>>

同样,我们来看一下 Required<T> 工具类型是如何实现的:


/**

* Make all properties in T required

*/ type Required<T> = {

 [P in keyof T]-?: T[P];

};

原来在 Required<T> 工具类型内部,通过 -? 移除了可选属性中的 ?,使得属性从可选变为必选的。


五、& 运算符

在 TypeScript 中交叉类型是将多个类型合并为一个类型。通过 & 运算符可以将现有的多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性。


type PartialPointX = { x: number; }; type Point = PartialPointX & { y: number; }; let point: Point = {

 x: 1,

 y: 1 }

在上面代码中我们先定义了 PartialPointX 类型,接着使用 & 运算符创建一个新的 Point 类型,表示一个含有 x 和 y 坐标的点,然后定义了一个 Point 类型的变量并初始化。


5.1 同名基础类型属性的合并

那么现在问题来了,假设在合并多个类型的过程中,刚好出现某些类型存在相同的成员,但对应的类型又不一致,比如:


interface X {

 c: string;

 d: string;

} interface Y {

 c: number;

 e: string } type XY = X & Y; type YX = Y & X; let p: XY; let q: YX;

在上面的代码中,接口 X 和接口 Y 都含有一个相同的成员 c,但它们的类型不一致。对于这种情况,此时 XY 类型或 YX 类型中成员 c 的类型是不是可以是 string 或 number 类型呢?比如下面的例子:


p = { c: 6, d: "d", e: "e" };



q = { c: "c", d: "d", e: "e" };



为什么接口 X 和接口 Y 混入后,成员 c 的类型会变成 never 呢?这是因为混入后成员 c 的类型为 string & number,即成员 c 的类型既可以是 string 类型又可以是 number 类型。很明显这种类型是不存在的,所以混入后成员 c 的类型为 never。


5.2 同名非基础类型属性的合并

在上面示例中,刚好接口 X 和接口 Y 中内部成员 c 的类型都是基本数据类型,那么如果是非基本数据类型的话,又会是什么情形。我们来看个具体的例子:


interface D { d: boolean; } interface E { e: string; } interface F { f: number; } interface A { x: D; } interface B { x: E; } interface C { x: F; } type ABC = A & B & C; let abc: ABC = {

 x: {

   d: true,

   e: 'semlinker',

   f: 666 }

}; console.log('abc:', abc);

以上代码成功运行后,控制台会输出以下结果:




由上图可知,在混入多个类型时,若存在相同的成员,且成员类型为非基本数据类型,那么是可以成功合并。


六、| 分隔符

在 TypeScript 中联合类型(Union Types)表示取值可以为多种类型中的一种,联合类型使用 | 分隔每个类型。联合类型通常与 null 或 undefined 一起使用:


const sayHello = (name: string | undefined) => { /* ... */ };

以上示例中 name 的类型是 string | undefined 意味着可以将 string 或 undefined 的值传递给 sayHello 函数。


sayHello("semlinker");

sayHello(undefined);

此外,对于联合类型来说,你可能会遇到以下的用法:


let num: 1 | 2 = 1; type EventNames = 'click' | 'scroll' | 'mousemove';

示例中的 1、2 或 'click' 被称为字面量类型,用来约束取值只能是某几个值中的一个。


6.1 类型保护

当使用联合类型时,我们必须尽量把当前值的类型收窄为当前值的实际类型,而类型保护就是实现类型收窄的一种手段。


类型保护是可执行运行时检查的一种表达式,用于确保该类型在一定的范围内。换句话说,类型保护可以保证一个字符串是一个字符串,尽管它的值也可以是一个数字。类型保护与特性检测并不是完全不同,其主要思想是尝试检测属性、方法或原型,以确定如何处理值。


目前主要有四种的方式来实现类型保护:


6.1.1 in 关键字

interface Admin {

 name: string;

 privileges: string[];

} interface Employee {

 name: string;

 startDate: Date;

} type UnknownEmployee = Employee | Admin; function printEmployeeInformation(emp: UnknownEmployee) { console.log("Name: " + emp.name); if ("privileges" in emp) { console.log("Privileges: " + emp.privileges);

 } if ("startDate" in emp) { console.log("Start Date: " + emp.startDate);

 }

}

6.1.2 typeof 关键字

function padLeft(value: string, padding: string | number) { if (typeof padding === "number") { return Array(padding + 1).join(" ") + value;

 } if (typeof padding === "string") { return padding + value;

 } throw new Error(`Expected string or number, got '${padding}'.`);

}

typeof 类型保护只支持两种形式:typeof v === "typename" 和 typeof v !== typename,"typename" 必须是 "number", "string", "boolean" 或 "symbol"。 但是 TypeScript 并不会阻止你与其它字符串比较,语言不会把那些表达式识别为类型保护。


6.1.3 instanceof 关键字

interface Padder {

 getPaddingString(): string;

} class SpaceRepeatingPadder implements Padder { constructor(private numSpaces: number) {}

 getPaddingString() { return Array(this.numSpaces + 1).join(" ");

 }

} class StringPadder implements Padder { constructor(private value: string) {}

 getPaddingString() { return this.value;

 }

} let padder: Padder = new SpaceRepeatingPadder(6); if (padder instanceof SpaceRepeatingPadder) { // padder的类型收窄为 'SpaceRepeatingPadder' }

6.1.4 自定义类型保护的类型谓词(type predicate)

function isNumber(x: any): x is number { return typeof x === "number";

} function isString(x: any): x is string { return typeof x === "string";

}

七、_ 数字分隔符

TypeScript 2.7 带来了对数字分隔符的支持,正如数值分隔符 ECMAScript 提案中所概述的那样。对于一个数字字面量,你现在可以通过把一个下划线作为它们之间的分隔符来分组数字:


const inhabitantsOfMunich = 1_464_301; const distanceEarthSunInKm = 149_600_000; const fileSystemPermission = 0b111_111_000; const bytes = 0b1111_10101011_11110000_00001101;

分隔符不会改变数值字面量的值,但逻辑分组使人们更容易一眼就能读懂数字。以上 TS 代码经过编译后,会生成以下 ES5 代码:


"use strict"; var inhabitantsOfMunich = 1464301; var distanceEarthSunInKm = 149600000; var fileSystemPermission = 504; var bytes = 262926349;

7.1 使用限制

虽然数字分隔符看起来很简单,但在使用时还是有一些限制。比如你只能在两个数字之间添加 _ 分隔符。以下的使用方式是非法的:


// Numeric separators are not allowed here.(6188) 3_.141592 // Error 3._141592 // Error // Numeric separators are not allowed here.(6188) 1_e10 // Error 1e_10 // Error // Cannot find name '_126301'.(2304) _126301 // Error // Numeric separators are not allowed here.(6188) 126301_ // Error // Cannot find name 'b111111000'.(2304) // An identifier or keyword cannot immediately follow a numeric literal.(1351) 0_b111111000 // Error // Numeric separators are not allowed here.(6188) 0b_111111000 // Error

当然你也不能连续使用多个 _ 分隔符,比如:


// Multiple consecutive numeric separators are not permitted.(6189) 123__456 // Error

7.2 解析分隔符

此外,需要注意的是以下用于解析数字的函数是不支持分隔符:


Number()

parseInt()

parseFloat()

这里我们来看一下实际的例子:


Number('123_456') NaN parseInt('123_456') 123 parseFloat('123_456') 123

很明显对于以上的结果不是我们所期望的,所以在处理分隔符时要特别注意。当然要解决上述问题,也很简单只需要非数字的字符删掉即可。这里我们来定义一个 removeNonDigits 的函数:


const RE_NON_DIGIT = /[^0-9]/gu; function removeNonDigits(str) {

 str = str.replace(RE_NON_DIGIT, ''); return Number(str);

}

该函数通过调用字符串的 replace 方法来移除非数字的字符,具体的使用方式如下:


removeNonDigits('123_456') 123456 removeNonDigits('149,600,000') 149600000 removeNonDigits('1,407,836') 1407836

八、<Type> 语法

8.1 TypeScript 断言

有时候你会遇到这样的情况,你会比 TypeScript 更了解某个值的详细信息。通常这会发生在你清楚地知道一个实体具有比它现有类型更确切的类型。


通过类型断言这种方式可以告诉编译器,“相信我,我知道自己在干什么”。类型断言好比其他语言里的类型转换,但是不进行特殊的数据检查和解构。它没有运行时的影响,只是在编译阶段起作用。


类型断言有两种形式:


8.1.1 “尖括号” 语法

let someValue: any = "this is a string"; let strLength: number = (<string>someValue).length;

8.1.2 as 语法

let someValue: any = "this is a string"; let strLength: number = (someValue as string).length;

8.2 TypeScript 泛型

对于刚接触 TypeScript 泛型的读者来说,首次看到 <T> 语法会感到陌生。其实它没有什么特别,就像传递参数一样,我们传递了我们想要用于特定函数调用的类型。




参考上面的图片,当我们调用 identity<Number>(1) ,Number 类型就像参数 1 一样,它将在出现 T 的任何位置填充该类型。图中 <T> 内部的 T 被称为类型变量,它是我们希望传递给 identity 函数的类型占位符,同时它被分配给 value 参数用来代替它的类型:此时 T 充当的是类型,而不是特定的 Number 类型。


其中 T 代表 Type,在定义泛型时通常用作第一个类型变量名称。但实际上 T 可以用任何有效名称代替。除了 T 之外,以下是常见泛型变量代表的意思:


K(Key):表示对象中的键类型;

V(Value):表示对象中的值类型;

E(Element):表示元素类型。

其实并不是只能定义一个类型变量,我们可以引入希望定义的任何数量的类型变量。比如我们引入一个新的类型变量 U,用于扩展我们定义的 identity 函数:


function identity <T, U>(value: T, message: U) : T { console.log(message); return value;

} console.log(identity<Number, string>(68, "Semlinker"));



除了为类型变量显式设定值之外,一种更常见的做法是使编译器自动选择这些类型,从而使代码更简洁。我们可以完全省略尖括号,比如:


function identity <T, U>(value: T, message: U) : T { console.log(message); return value;

} console.log(identity(68, "Semlinker"));

对于上述代码,编译器足够聪明,能够知道我们的参数类型,并将它们赋值给 T 和 U,而不需要开发人员显式指定它们。


九、@XXX 装饰器

9.1 装饰器语法

对于一些刚接触 TypeScript 的小伙伴来说,在第一次看到 @Plugin({...}) 这种语法可能会觉得很惊讶。其实这是装饰器的语法,装饰器的本质是一个函数,通过装饰器我们可以方便地定义与对象相关的元数据。


@Plugin({

 pluginName: 'Device',

 plugin: 'cordova-plugin-device',

 pluginRef: 'device',

 repo: 'https://github.com/apache/cordova-plugin-device',

 platforms: ['Android', 'Browser', 'iOS', 'macOS', 'Windows'],

}) @Injectable() export class Device extends IonicNativePlugin {}

在以上代码中,我们通过装饰器来保存 ionic-native 插件的相关元信息,而 @Plugin({...}) 中的 @ 符号只是语法糖,为什么说是语法糖呢?这里我们来看一下编译生成的 ES5 代码:


var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r;

}; var Device = /** @class */ (function (_super) {

   __extends(Device, _super); function Device() { return _super !== null && _super.apply(this, arguments) || this;

   }

   Device = __decorate([

       Plugin({ pluginName: 'Device', plugin: 'cordova-plugin-device', pluginRef: 'device', repo: 'https://github.com/apache/cordova-plugin-device', platforms: ['Android', 'Browser', 'iOS', 'macOS', 'Windows'],

       }),

       Injectable()

   ], Device); return Device;

}(IonicNativePlugin));

通过生成的代码可知,@Plugin({...}) 和 @Injectable() 最终会被转换成普通的方法调用,它们的调用结果最终会以数组的形式作为参数传递给 __decorate 函数,而在 __decorate 函数内部会以 Device 类作为参数调用各自的类型装饰器,从而扩展对应的功能。


9.2 装饰器的分类

在 TypeScript 中装饰器分为类装饰器、属性装饰器、方法装饰器和参数装饰器四大类。


9.2.1 类装饰器

类装饰器声明:


declare type ClassDecorator = <TFunction extends Function>(

 target: TFunction

) => TFunction | void;

类装饰器顾名思义,就是用来装饰类的。它接收一个参数:


target: TFunction - 被装饰的类

看完第一眼后,是不是感觉都不好了。没事,我们马上来个例子:


function Greeter(target: Function): void {

 target.prototype.greet = function (): void { console.log("Hello Semlinker!");

 };

} @Greeter class Greeting { constructor() { // 内部实现 }

} let myGreeting = new Greeting();

myGreeting.greet(); // console output: 'Hello Semlinker!';

上面的例子中,我们定义了 Greeter 类装饰器,同时我们使用了 @Greeter 语法糖,来使用装饰器。


友情提示:读者可以直接复制上面的代码,在 TypeScript Playground 中运行查看结果。

9.2.2 属性装饰器

属性装饰器声明:


declare type PropertyDecorator = (target:Object,

 propertyKey: string | symbol ) => void;

属性装饰器顾名思义,用来装饰类的属性。它接收两个参数:


target: Object - 被装饰的类

propertyKey: string | symbol - 被装饰类的属性名

趁热打铁,马上来个例子热热身:


function logProperty(target: any, key: string) { delete target[key]; const backingField = "_" + key; Object.defineProperty(target, backingField, {

   writable: true,

   enumerable: true,

   configurable: true }); // property getter const getter = function (this: any) { const currVal = this[backingField]; console.log(`Get: ${key} => ${currVal}`); return currVal;

 }; // property setter const setter = function (this: any, newVal: any) { console.log(`Set: ${key} => ${newVal}`); this[backingField] = newVal;

 }; // Create new property with getter and setter Object.defineProperty(target, key, { get: getter, set: setter,

   enumerable: true,

   configurable: true });

} class Person { @logProperty public name: string; constructor(name : string) { this.name = name;

 }

} const p1 = new Person("semlinker");

p1.name = "kakuqo";

以上代码我们定义了一个 logProperty 函数,来跟踪用户对属性的操作,当代码成功运行后,在控制台会输出以下结果:


Set: name => semlinker Set: name => kakuqo

9.2.3 方法装饰器

方法装饰器声明:


declare type MethodDecorator = <T>(target:Object, propertyKey: string | symbol,          

 descriptor: TypePropertyDescript<T>) => TypedPropertyDescriptor<T> | void;

方法装饰器顾名思义,用来装饰类的方法。它接收三个参数:


target: Object - 被装饰的类

propertyKey: string | symbol - 方法名

descriptor: TypePropertyDescript - 属性描述符

废话不多说,直接上例子:


function LogOutput(tarage: Function, key: string, descriptor: any) { let originalMethod = descriptor.value; let newMethod = function(...args: any[]): any { let result: any = originalMethod.apply(this, args); if(!this.loggedOutput) { this.loggedOutput = new Array<any>();

   } this.loggedOutput.push({

     method: key,

     parameters: args,

     output: result,

     timestamp: new Date()

   }); return result;

 };

 descriptor.value = newMethod;

} class Calculator { @LogOutput double (num: number): number { return num * 2;

 }

} let calc = new Calculator();

calc.double(11); // console ouput: [{method: "double", output: 22, ...}] console.log(calc.loggedOutput);

9.2.4 参数装饰器

参数装饰器声明:


declare type ParameterDecorator = (target: Object, propertyKey: string | symbol,

 parameterIndex: number ) => void

参数装饰器顾名思义,是用来装饰函数参数,它接收三个参数:


target: Object - 被装饰的类

propertyKey: string | symbol - 方法名

parameterIndex: number - 方法中参数的索引值

function Log(target: Function, key: string, parameterIndex: number) { let functionLogged = key || target.prototype.constructor.name; console.log(`The parameter in position ${parameterIndex} at ${functionLogged} has

   been decorated`);

} class Greeter {

 greeting: string; constructor(@Log phrase: string) { this.greeting = phrase;

 }

} // console output: The parameter in position 0  // at Greeter has been decorated

十、#XXX 私有字段

在 TypeScript 3.8 版本就开始支持 ECMAScript 私有字段,使用方式如下:


class Person {

 #name: string; constructor(name: string) { this.#name = name;

 }


 greet() { console.log(`Hello, my name is ${this.#name}!`);

 }

} let semlinker = new Person("Semlinker");


semlinker.#name; //     ~~~~~ // Property '#name' is not accessible outside class 'Person' // because it has a private identifier.

与常规属性(甚至使用 private 修饰符声明的属性)不同,私有字段要牢记以下规则:


私有字段以 # 字符开头,有时我们称之为私有名称;

每个私有字段名称都唯一地限定于其包含的类;

不能在私有字段上使用 TypeScript 可访问性修饰符(如 public 或 private);

私有字段不能在包含的类之外访问,甚至不能被检测到。

10.1 私有字段与 private 的区别

说到这里使用 # 定义的私有字段与 private 修饰符定义字段有什么区别呢?现在我们先来看一个 private 的示例:


class Person { constructor(private name: string){}

} let person = new Person("Semlinker"); console.log(person.name);

在上面代码中,我们创建了一个 Person 类,该类中使用 private 修饰符定义了一个私有属性 name,接着使用该类创建一个 person 对象,然后通过 person.name 来访问 person 对象的私有属性,这时 TypeScript 编译器会提示以下异常:


Property 'name' is private and only accessible within class 'Person'.(2341)

那如何解决这个异常呢?当然你可以使用类型断言把 person 转为 any 类型:


console.log((person as any).name);

通过这种方式虽然解决了 TypeScript 编译器的异常提示,但是在运行时我们还是可以访问到 Person 类内部的私有属性,为什么会这样呢?我们来看一下编译生成的 ES5 代码,也许你就知道答案了:


var Person = /** @class */ (function () { function Person(name) { this.name = name;

   } return Person;

}()); var person = new Person("Semlinker"); console.log(person.name);

这时相信有些小伙伴会好奇,在 TypeScript 3.8 以上版本通过 # 号定义的私有字段编译后会生成什么代码:


class Person {

 #name: string; constructor(name: string) { this.#name = name;

 }


 greet() { console.log(`Hello, my name is ${this.#name}!`);

 }

}

以上代码目标设置为 ES2015,会编译生成以下代码:


"use strict"; var __classPrivateFieldSet = (this && this.__classPrivateFieldSet)

 || function (receiver, privateMap, value) { if (!privateMap.has(receiver)) { throw new TypeError("attempted to set private field on non-instance");

   }

   privateMap.set(receiver, value); return value;

}; var __classPrivateFieldGet = (this && this.__classPrivateFieldGet)

 || function (receiver, privateMap) { if (!privateMap.has(receiver)) { throw new TypeError("attempted to get private field on non-instance");

   } return privateMap.get(receiver);

}; var _name; class Person { constructor(name) {

     _name.set(this, void 0);

     __classPrivateFieldSet(this, _name, name);

   }

   greet() { console.log(`Hello, my name is ${__classPrivateFieldGet(this, _name)}!`);

   }

}

_name = new WeakMap();

通过观察上述代码,使用 # 号定义的 ECMAScript 私有字段,会通过 WeakMap 对象来存储,同时编译器会生成 __classPrivateFieldSet 和 __classPrivateFieldGet 这两个方法用于设置值和获取值。

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

8个JavaScript库可更好地处理本地存储

seo达人

Local Storage Bridge

https://github.com/krasimir/l...

如果你必须在同一个浏览器中从一个标签页发送消息到另一个标签页,你不必用艰难的方式。Local storage bridge在这里让任务变得更简单。

基本使用:

// 发送 lsbridge.send(‘app.message.error’, { error: ‘Out of memory’ });

// 监听 lsbridge.subscribe(‘app.message.error’, function(data) { console.log(data); // { error: ‘Out of memory’ } });

Basil.js

image

Basil.js统一了session、localStorage和cookie,为你提供了一种处理数据的直接方法。

基本使用:

let basil = new Basil(options);

basil.set(‘name’, ‘Amy’);
basil.get(‘name’);
basil.remove(‘name’);
basil.reset();

store.js

https://github.com/marcuswest...

Store.js像其他东西一样处理数据存储。但还有更多的功能,它的一个高级特性是让你更深入地访问浏览器支持。

基本使用:

store.set(‘book’, { title: ‘JavaScript’ }); // Store a book store.get(‘book’);

// Get stored book store.remove(‘book’); // Remove stored book store.clearAll(); // Clear all keys

lscache

https://github.com/pamelafox/...

它与localStorage API类似。事实上,它是localStorage的一个封装器,并使用HTML5模拟memcaches函数。在上面的文档中发现更多的功能。

基本使用:

lscache.set(‘name’, ‘Amy’, 5); // 数据将在5分钟后过期 lscache.get(‘name’);

Lockr

image

Lockr建立在localStorage API之上。它提供了一些有用的方法来更轻松地处理本地数据。

是什么让你要使用此库而不是localStorage API?

好吧,localStorage API仅允许你存储字符串。如果要存储数字,则需要先将该数字转换为字符串。在Lockr中不会发生这种情况,因为Lockr允许你存储更多的数据类型甚至对象。

基本使用:

Lockr.set(‘name’, ‘Amy’);
Lockr.set(‘age’, 28);
Lockr.set(‘books’, [{title: ‘JavaScript’, price: 11.0}, {title: ‘Python’, price: 9.0}]);

Barn

https://github.com/arokor/barn

Barn在localStorage之上提供了一个类似Redis的API。如果持久性很重要,那么你将需要这个库来保持数据状态,以防发生错误。

基本使用:

let barn = new Barn(localStorage); // 原始类型 barn.set(‘name’, ‘Amy’); let name = barn.get(‘name’);

// Amy // List barn.lpush(‘names’, ‘Amy’);

barn.lpush(‘names’, ‘James’); let name1 = barn.rpop(‘names’); // Amy let name2 = barn.rpop(‘names’);

// James

localForage

https://github.com/localForag...

这个简单而快速的库将通过IndexedDB或WebSQL使用异步存储来改善Web的脱机体验。它类似于localStorage,但具有回调功能。

基本使用:

localforage.setItem(‘name’, ‘Amy’, function(error, value) { // Do something });

localforage.getItem(‘name’, function(error, value) { if (error) { console.log(‘an error occurs’);
  } else { // Do something with the value }
});

很神奇的是它提供中文文档

crypt.io

https://github.com/jas-/crypt.io

crypt.io使用标准JavaScript加密库实现安全的浏览器存储。使用crypto.io时,有三个存储选项:sessionStorage,localStorage或cookie。

基本使用:

let storage = crypto; let book = { title: ‘JavaScript’, price: 13 };

storage.set(‘book’, book, function(error, results) { if (error) { throw error;
  } // Do something });

storage.get(‘book’, function(error, results) { if (error) { throw error; 

} // Do something });

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

Promise 你真的用明白了么?

seo达人

前置知识

在开始正文前,我们先把本文涉及到的一些内容提前定个基调。

Promise 哪些 API 涉及了微任务?

Promise 中只有涉及到状态变更后才需要被执行的回调才算是微任务,比如说 then、 catch 、finally ,其他所有的代码执行都是宏任务(同步执行)。

上图中蓝色为同步执行,黄色为异步执行(丢到微任务队列中)。

这些微任务何时被加入微任务队列?

这个问题我们根据 ecma 规范来看:

  • 如果此时 Promise 状态为 pending,那么成功或失败的回调会分别被加入至 [[PromiseFulfillReactions]] 和 [[PromiseRejectReactions]] 中。如果你看过手写 Promise 的代码的话,应该能发现有两个数组存储这些回调函数。
  • 如果此时 Promise 状态为非 pending 时,回调会成为 Promise Jobs,也就是微任务。

了解完以上知识后,正片开始。

同一个 then,不同的微任务执行

初级

Promise.resolve()
  .then(() => { console.log("then1"); Promise.resolve().then(() => { console.log("then1-1");
    });
  })
  .then(() => { console.log("then2");
  });

以上代码大家应该都能得出正确的答案:then1 → then1-1 → then2

虽然 then 是同步执行,并且状态也已经变更。但这并不代表每次遇到 then 时我们都需要把它的回调丢入微任务队列中,而是等待 then 的回调执行完毕后再根据情况执行对应操作。

基于此,我们可以得出第一个结论:链式调用中,只有前一个 then 的回调执行完毕后,跟着的 then 中的回调才会被加入至微任务队列。

中级

大家都知道了 Promise resolve 后,跟着的 then 中的回调会马上进入微任务队列。

那么以下代码你认为的输出会是什么?

let p = Promise.resolve();

p.then(() => { console.log("then1"); Promise.resolve().then(() => { console.log("then1-1");
  });
}).then(() => { console.log("then1-2");
});

p.then(() => { console.log("then2");
}); 

按照一开始的认知我们不难得出 then2 会在 then1-1 后输出,但是实际情况却是相反的。

基于此我们得出第二个结论:每个链式调用的开端会首先依次进入微任务队列。

接下来我们换个写法:

let p = Promise.resolve().then(() => { console.log("then1"); Promise.resolve().then(() => { console.log("then1-1");
  });
}).then(() => { console.log("then2");
});

p.then(() => { console.log("then3");
});

上述代码其实有个陷阱,then 每次都会返回一个新的 Promise,此时的 p 已经不是 Promise.resolve() 生成的,而是最后一个 then 生成的,因此 then3 应该是在 then2 后打印出来的。

顺便我们也可以把之前得出的结论优化为:同一个 Promise 的每个链式调用的开端会首先依次进入微任务队列。

高级

以下大家可以猜猜 then1-2 会在何时打印出来?

Promise.resolve()
  .then(() => { console.log("then1"); Promise.resolve()
      .then(() => { console.log("then1-1"); return 1;
      })
      .then(() => { console.log("then1-2");
      });
  })
  .then(() => { console.log("then2");
  })
  .then(() => { console.log("then3");
  })
  .then(() => { console.log("then4");
  });

这题肯定是简单的,记住第一个结论就能得出答案,以下是解析:

  • 第一次 resolve 后第一个 then 的回调进入微任务队列并执行,打印 then1
  • 第二次 resolve 后内部第一个 then 的回调进入微任务队列,此时外部第一个 then 的回调全部执行完毕,需要将外部的第二个 then 回调也插入微任务队列。
  • 执行微任务,打印 then1-1 和 then2,然后分别再将之后 then 中的回调插入微任务队列
  • 执行微任务,打印 then1-2 和 then3 ,之后的内容就不一一说明了

接下来我们把 return 1 修改一下,结果可就大不相同啦:

Promise.resolve()
  .then(() => { console.log("then1"); Promise.resolve()
      .then(() => { console.log("then1-1"); return Promise.resolve();
      })
      .then(() => { console.log("then1-2");
      });
  })
  .then(() => { console.log("then2");
  })
  .then(() => { console.log("then3");
  })
  .then(() => { console.log("then4");
  });

当我们 return Promise.resolve() 时,你猜猜 then1-2 会何时打印了?

答案是最后一个才被打印出来。

为什么在 then 中分别 return 不同的东西,微任务的执行顺序竟有如此大的变化?以下是笔者的解析。

PS:then 返回一个新的 Promise,并且会用这个 Promise 去 resolve 返回值,这个概念需要大家先了解一下。

根据 Promise A+ 规范

根据规范 2.3.2,如果 resolve 了一个 Promise,需要为其加上一个 then 并 resolve

if (x instanceof MyPromise) { if (x.currentState === PENDING) {
  } else {
    x.then(resolve, reject);
  } return;
}

上述代码节选自手写 Promise 实现。

那么根据 A+ 规范来说,如果我们在 then 中返回了 Promise.resolve 的话会多入队一次微任务,但是这个结论还是与实际不符的,因此我们还需要寻找其他权威的文档。

根据 ECMA - 262 规范

根据规范 25.6.1.3.2,当 Promise resolve 了一个 Promise 时,会产生一个NewPromiseResolveThenableJob,这是属于 Promise Jobs 中的一种,也就是微任务。

This Job uses the supplied thenable and its then method to resolve the given promise. This process must take place as a Job to ensure that the evaluation of the then method occurs after evaluation of any surrounding code has completed.

并且该 Jobs 还会调用一次 then 函数来 resolve Promise,这也就又生成了一次微任务。

这就是为什么会触发两次微任务的来源。

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

轻拟物风格图标设计

资深UI设计者

轻拟物的核心知识

轻拟物本身也是拟物,所以它的核心基础和拟物设计师一致的,只是省略了更多复杂的细节。而对于整个拟物的体系来讲,最重要的东西实际上只有2个,形体、光影。

1. 形体表现

形体的表现,就是对图形外轮廓的样式的呈现。在过去我们写的图标分享中,有写过面性图标进阶的设计中,可以包含更多的细节、内部元素,而不是仅仅只有外轮廓。

大厂都在用的轻拟物设计风格,本文教你四步完成!

轻拟物的形体设计就要处于进阶面性图标或者更难的水平之上,即你要把这个图形的内容有明确的示意并画出来,而不是用抽象的图形做填充而已。

比如大众点评的快速入口图标,虽然看起来很复杂,但是那是配色上的复杂,而不是形体轮廓上的具象化。

大厂都在用的轻拟物设计风格,本文教你四步完成!

换句话说,拟物插画的图形基底,类似扁平插画风格图标,不能表现得太抽象,也不能增加过多的细节,需要一种恰到好处的平衡(玄学),这就非常考验设计师的判断和经验了。

并且,在描绘轮廓的时候,新手尽可能的采取正视图来进行绘制,而不要通过俯视图、侧视图、斜视图等方法来呈现图形的多个面,这样难度会大幅度上升,比如下面这种情况。

大厂都在用的轻拟物设计风格,本文教你四步完成!

2. 光影表现

除了形体外,光影就是整个拟物的灵魂了。

当一个完整的图形完成填充色时,它是扁平图案,如果完成光影呈现的时候,它就是三维空间的立体图形,比如下面这个圆的案例:

大厂都在用的轻拟物设计风格,本文教你四步完成!

在拟物的设计中,我们对光影的定义是至关重要的,所以首先就是针对该图形确定光源的方向,是上方、前方、左上还右上,这对后续的设计有一连串的影响。

大厂都在用的轻拟物设计风格,本文教你四步完成!

如果对光影没有正确的解释,那么在制作细节的渐变角度、投影的使用上,就会产生错误的设计,造成光影视觉冲突和矛盾。

在创建了光源以后,物体受到光线的影响就会产生明暗面和投影,可以简单划分成4个部分,高光、亮部、暗部、投影。

大厂都在用的轻拟物设计风格,本文教你四步完成!

这和我们学习的素描有一定的差异,美术中对光影的表现还会包含明暗交界、反光面,这对于轻拟物的来说负担太重,所以我们要去掉它们,接下来重点讲讲高光和暗部。

高光是物体作为受光物对光源的直接反映,比如人像摄影中人眼眸中的高光就是对闪光灯的镜像表现,再或者一拳超人中男主光头上长期存在的高光配饰(多数动画的光头角色都有)……

大厂都在用的轻拟物设计风格,本文教你四步完成!

高光可以非常有效的增加画面的层次和对比性,让物体看上去更有冲击力和观赏性。

而暗部,则完全是为了正常表现物体结构和弧度增加的示意,因为不在受光面,所以颜色会变暗。在实际操作过程中,我们可以通过渐变的方式开控制明暗的表达,但尽量不要直接手动设置一个渐变色出来,而是为它叠加暗部或亮部的黑白透明度渐变。

大厂都在用的轻拟物设计风格,本文教你四步完成!

了解这几个特性以后,下面,我们就通过一个实例来讲解一下轻拟物设计的过程吧。

轻拟物实例演练

作为轻拟物的演示,直接画个图标讲一遍怎么操作是没什么用的,我们要从实际场景出发,用它来解决一些真实的问题。比如看看下面的 KFC 官方 APP 首页:

大厂都在用的轻拟物设计风格,本文教你四步完成!

总结它的问题,不难发现首页顶部业务功能太多了,顶部图标就包含30个(加滑动的),虽然每个模块图标单看都没有硬伤,但堆积到一起,就使得顶部缺乏足够的信息层级和对比性。

我们要做的,就是通过轻拟物的方式,调整快速入口最大的三个图标,凸显它们的重要性以及和其它图标的视觉差异性。先从第一个图标开始,讲解一下如何完成轻拟物化设计的升级。

第一步:确认轮廓造型

第一个操作,即确定图标本身的轮廓。根据原有图标的样式我做了一些改动,包括加粗车头,减少高度,增加车灯等。并对每一个模块进行纯色的填充,定义它们的色彩和做出区分。

形体的重要性在于要对图形本身有比较合理的呈现,不要让比例失调和图不达意。

大厂都在用的轻拟物设计风格,本文教你四步完成!

第二步:完善图形细节

这一步,就要在原有基础上,进行下一步的深入。包括对一些细节交代得更清楚一点,增加一些有趣的小元素等等,完善它的具体样式。

大厂都在用的轻拟物设计风格,本文教你四步完成!

第三步:增加基础的暗部表现

在这里,我们就要开始为图标增加高光了,高光从右上角打下来,那么有叠加关系的元素就会产生一个向下的投影。并且反向暗部的表现,让整体的立体感稍强。

这一步在软件中主要使用蒙版功能,通过蒙版在背景上方创建一个图层,然后添加深色的透明度渐变,就可以表现出对暗部和投影的效果。

大厂都在用的轻拟物设计风格,本文教你四步完成!

第四步:增加高光效果

接着,就是最后一步,将高光添加到画面中来,将整个图标的质感进行拉升。

大厂都在用的轻拟物设计风格,本文教你四步完成!

通过上面的演示,我们可以将整个拟物设计流程精简成:

  • 确定图形基本轮廓、外形比例、模块色彩
  • 丰富细节样式增加趣味性和适当的拟真感
  • 通过蒙版添加暗部来完善表现的明暗和层级关系
  • 添加高光元素作为图形的亮点,拉升层次感

然后,通过这样的步骤,再来完成后续的两个图形,拆解完的效果如下。

大厂都在用的轻拟物设计风格,本文教你四步完成!

大厂都在用的轻拟物设计风格,本文教你四步完成!

然后,再用这三个修改后图标套用进原来的页面,并做出对应的修改,再来看看前后对比:

大厂都在用的轻拟物设计风格,本文教你四步完成!

通过这个对比,我们就可以看出在这个复杂的首页头部中,轻拟物风格可以从一众平面中被凸显出来,且不会显得太突兀和复杂。

而这就是轻拟物在项目设计中的实际作用,当画面元素已经开始超负荷,且容易导致同质化的审美疲劳和主次不清时,就是轻拟物登场的时候了。

总结

最后的总结,学习轻拟物就是增加我们完成界面视觉输出的可能性,为视觉创意增加一些储备弹药,以应对越来越复杂的互联网产品和职业要求。

我们只在这篇罗列了制作的顺序和思路,并没有把软件的操作完全放出来,一方面是因为时间上来不及,另一方面是希望大家不会被软件的使用框住。只要概念清楚了,那么使用 PS、AI、Skecth、Figma 还是 Affinity 等软件都可以做出来。


文章来源:站酷    作者:超人的电话亭

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



一万六千字!超全面的字体与排版应用指南

周周

文字是界面中最核心的元素,是产品传达给用户的主要内容,它的承载体即是字体。

前半部分从字体的最基本属性(字族、字号、字重、大小写等)说起,熟悉字体的那些特征,了解字体在界面中的作用,以及iOS与Android系统字体的使用规范。

字体基础知识

字体是界面设计的基石

字体是排版中最重要的元素,对用户的阅读体验有着至关重要的作用。一般来说,设计师需要了解的字体通常有中文字体和西文字体两种。西文字体由来已久,从最早的罗马字体到现在苹果手机中的SF-UI字体,经历了许多设计上的变革。而中文字体的发展并没有西文字体那么顺利,数量上也远远落后于其他字体。但中国设计正在崛起,我们也看到越来越多的设计团队和设计师加入字体设计的队伍,数量上正在呈指数级别增加。

设计是一门非常严谨的学科,里面蕴含了很多道理,就连最基础的字体选择和排版,都经过了将近千年的发展和演变,有非常多的专业知识。像平面设计一样,在UI设计中字体的使用也有相应的规范,设计师应懂得这些基础知识,才能将字体为自己所用。

本篇就从我们常用的设计软件(sketch、Figma、P hotoshop)字符面板开始,来聊聊有关字体与排版应用方面的知识。

字体的那些属性

  • Font 中文翻译为「字型」,是指字的粗细、宽度和样式,是一套具有同样风格和尺寸的字形。例如「Regular_16pt_SF-UI」。

  • Typeface 中文翻译为「字体」,是指一整套的字形,一个或多个字型的多尺寸的集合,例如「SF-UI」里有不同粗细(Regular、Blod、Light)和不同宽度(12pt、14pt、20pt)。

  • Glyph 中文翻译为「字形」,是指单个字的形体或是字体的骨骼。 同一字可以有不同的字形,而不影响其表达的意思,例如汉字中的「令」字,第三笔可以是一点或一撇, 最末两笔可以作「ㄗ」或「マ」。

Font和Typeface常常被混淆使用,其实可以这样理解,前者指一种设计,后者指具体的产品。

1. 族类 GenericFamily

族类就是不同字体类型,例如阿里巴巴普惠体、方正新书宋、站酷酷黑体等。

而这些众多字体又可分为「衬线体」和「无衬线体」。

衬线体

宋体就是衬线体,特点就是笔画开始和末端的地方都有额外的装饰,且笔画的粗细有所不同。在传统的正文印刷中,普遍认为衬线字体能带来更加的可读性。常见的衬线体有宋体、Times New Roman、Georgia等。

衬线体一般在APP中比较少见,文字阅读类偏爱这种衬线体,例如「单读」,大标题用的是「華康標宋體」、正文内容用的是「苹方-纤细」而英文用的是「XCross Traditional Bold」

黑体

黑体是无衬线字体,特点是笔画没有额外的装饰,且笔画的粗细差不多。相比严肃的衬线体,简单干净的无衬线体给人一种休闲轻松的感觉。因此大多数App都是使用黑体作为默认字体。如冬青黑体、思源黑体、Myriad等。

2. 字族 FontFamily

一个族类包含不同的字体,然而一个字体又可能有好几种字族。如果电脑安装了Helvetica,在Sketch字体选择器中会发现超过40多个前缀是Helvetica的字族。这是为了协助人们在不同的使用场景下表达合适的意思。

知识点:

基本字族包括细体、标准、粗体、斜体,值得注意的是,斜体字常用在引用文本上,代表「本段文字引用的是另一个著作」的含义。

例如:「若我们能以满怀新鲜的眼神去观照日常,「设计」的意义定会超越技术的层面,为我们的生活观和人生观注入力量。」(引自原研哉的《设计中的设计》)

3. X-height(X字高)

在西文字体中,x高度是指字母的基本高度,就是基线和主线之间的距离。它指一个字体中小写字母的x高度,在现代字体设计领域,x高度代表了一个字体的设计因素,因此在一些场合字母x本身并不完全等于x字高。

除了字母a、c、e、m、n、o等高度一样,还有一些小写字母的字高都比x字高要大,并分为两类:一是含有升部的字母,字母笔画含有向上部分,如字母b、d、h;另一类是含有降部的字母,字母的笔画向下超过了基线,如字母g、p、q。

4. 字号 Font-size

字号就是字体大小,通常在网页端使用px作为字号的单位。移动端兴起后,iOS字体单位是pt,Android是sp。

以iOS为例,正文字号不应小于11pt,这样才能被正常阅读,建议在14-18pt之间。在使用较大的字体来获得更好的易读性的同时,我们也应相应地减小字体的字重,考虑Light、Thin,因为过重的字体会太过醒目,影响其他内容的显示效果。

当字体大小为12-18pt时,建议使用Regular,18-24pt时,使用Light,24-32pt,使用Thin,当字体大小超过32pt时,建议使用Ultralight。

字号大小决定了信息的层级和主次关系,合理有序的字号设置能让界面信息清晰易读、层次分明;相反,糟糕无序的字号使用会让界面混乱不堪,影响阅读体验。

设计中的最小字号

我们都知道在界面设计中最小字号不能低于20px,那是因为,正常情况下,在手机距离眼睛30cm左右,使用视角计算公式,我们能识别到的的文字大小为h= 2*30·tan(0.3/2) ≈ 0.157cm ,拿我们经常使用iPhone7的尺寸1334×750为例。iPhone7的dpi为324,也就是一英寸上显示324个像素,1英寸为2.54cm,那么0.157cm=324*(0.157/2.54cm)= 20px。

字号的基数关系

我们在做设计时,字号的单位最好使用一个基数作为倍增,如2、4、6、8、10 或者3、6、9、12。但其实我们在做移动端设计时,单位需要遵循偶数原则,因为开发中的单位是以一倍图的基数来进行计算。那么其实在制定字体规范中,使用2为单位会导致字号过多,且2号字体的差异化不大。所以在字号方面我们使用4作为单位是比较合适的:一是适配后在@2x跟@3x不会出现半像素,二是使用4为单位,能满足字体大小的均衡。

5. 字重 FontWeight

Weight,中文翻译为「字重」,是指字体笔画的粗细,字体中很重要一个概念,不同字重传递出来视觉感受完全不一样。一般在字体家族名后面注名Thin、Light、Regular、Blod、Black、Heavy等。不同的字体厂商划分字重各有不同,例如「苹方」字体就有6种不同的字重。

一般都有细体、正常、粗体三种基本字族。在应用场景上,通常「细体」多用于超大号字体;「正常」用于正文内容;「粗体」表示强调,多用于标题;

两种字重属性

轻字重:传递出轻盈放松的视觉感受,常配合粗的字重使用,在一些辅助信息,说明文案时候使用;

重字重:视觉感受庄重,很重要,常用在重点强调的文字,页面大标题,数字,引导行动操作点上等;

例如百度网盘「发现」页就用了Regular、Medium、Semibold三种字重以拉开信息层次对比;

知识点:

需要注意的是:在进行界面设计时,不要用软件自带的文本加粗,它不仅破坏了字体本身的美感,还改变了文字原本的字宽,小字体下会模糊不清,合理的方式是使用字体本身的字重来控制粗细。

注意超细体的字体

字重超细的字体要谨慎使用。如果你设计的文本是装饰性倒还好,如果是需要用户能清晰阅读的,就要特别慎重,能不用就不用,否则在部分低分辨率的手机屏幕上看起来会非常糟糕。

6. 字色 FontColor

字色即文字对应的颜色,不做过多解释。需要大家注意的是 远离纯黑色和纯灰色!

纯黑色就像没有生命力的深渊,能吞噬所有细节,使用户陷入冷冰冰的极端情绪中。纯黑色还会与白色产生强烈的对比度,看久了就会感觉疲劳,让用户产生焦虑情绪。

还有就是真实世界中是不存在纯黑色的。尝试在色彩中加入一些色相,这样就不会让页面看上去死气沉沉的。例如iOS系统「设置」页面背景色就是加入了白色的低饱和度蓝色,看上去柔和自然。

7. 字符样式 FontStyle

除了以上几个最常用的文字属性外,还有几个使用频率比较低的字体设置。例如带下划线的、删除线的文本。「下划线文本」一般出现在「文字按钮」或带链接的网址,而「删除线文本」一般会出现在商品橱窗的现价、原价

例如「CCtalk」的课程现价和原价的区分,原价用删除文本,「微信读书」文章底部「加入书架 随时阅读」就是带链接的下划线文本。

8. 字符选项 Text options

Ps和Sketch都有文字(字符)选项一栏,主要针对西文字母大小写格式变换的设置。最常见有默认大小写、全部大写、全部小写和小型大写字母,Ps里面还有「上标」和「下标」。

  • 默认大小写:即正常大小写格式,软件不做干预;

  • 全部大写:如果输入的是小写字母,选择这个选项,软件会强制把小写改为大写;

  • 全部小写:如果输入的是大写字母,或者只是首字母大写,选择这个选项,软件会强制把所大写改为小写;

  • 小型大写字母:这个选项比较特殊,所谓「小型大写」就是,在字号一样的情况下,与小写字母一样高,外形与大写字母保持一致。

注意英文大写

纯大写的字母文本本身不太适合大篇幅阅读,会加大阅读障碍,用的时候注意要额外拉开字母之间的字间距,提升可读性。

9. 全角与半角 Full-width and half-width

全角是指一个字符占用两个标准字符的位置。中文字符、全角的英文字符、国标GB2312-1980中的图形符号、特殊符号都是全角字符。半角是指一个字符占用一个标准字符的位置。

通常情况下,英文字母、数字、符号等都是半角字符。半角和全角主要是针对标点符号来说的,因为正常情况下没有打全角英文的需求。

知识点:

在设计作品时也一定要记得中文搭配全角符号,英文使用半角符号。否则会出现诸如「你好.」或者「t h a n k s。」这样的错误。可按键盘「capslock」键切换全角和半角。这个小知识点虽然非常基础,却也是设计中经常出错的地方。

iOS与Android

众所周知,iOS和Android两大阵营都有各自的设计系统,要作出符合平台规范的设计,设计师应熟读各平台的设计规则。因为本篇以讲字体为主,我们就来看看iOS和Android各自字体的规范是什么样的。

1. iOS字体规范

可用字体

在iOS系统规范中,中文字体是「苹方」字体。英文字体是「San Francisco」也简称「SF-UI」,英文还有另外一个衬线体「NewYork」。除了在iOS和Mac OS上,还单独为Watch OS单独对字体进行了调整,命名为 San Francisco Compact。

字体设置

因为在英文字体下,字体环境比较复杂,为了让字体在任何地方看起来都最佳,苹果官方针对不同字号开发了两套「SF-UI Text」和「SF-UI Pro」字体,而每套字体下面又分为Text(文本模式)与Display(展示模式)两种属性,Text只有6个字重,而Display则有9个字重。

这么多类型的字体我们该怎么用呢?iOS的建议是,在字号小于20pt时,使用SF-UI Text,大于或等于20pt时,则使用SF-UI Display。这需要我们在界面设计时手动切换。

对于「NewYork」,小于20点的文本使用小号,20到35点之间的文本使用中号,36到53点之间的文本使用大号,54点或更大的文本使用特大号。

苹方字体提供了6个字重供设计开发者使用。所以从iOS11开始,iOS使用Semibold中粗体、大字号作为界面的标题变的更为流行起来,较为明显的有 iOS 中的一些原生APP,比如App Store、Apple Music…

知识点:

在iOS中,默认字体单位是「pt」,正文字号不应小于11pt,建议在15-18pt之间。在使用较大的字体来获得更好的易读性同时,也应该相应地减小字体的字重,因为过重的字体会太过醒目厚重,影响其他内容的显示效果。

iOS更全面的文字设置

动态类型可以通过让读者选择他们喜欢的文本大小来提供额外的灵活性,除了标准的动态类型大小之外,iOS系统还为有阅读大字体的需求的用户提供了许多字号上的调整(可在系统字体显示大小设置)

iOS「显示与亮度」下设置「文字大小」模式

「苹方」和「SF-UI」字体可在iOS规范网站免费下载

网址:https://developer.apple.com/fonts/

2. Android字体规范

可用字体

在Android设备中,Android始祖Google为了更好的追求视觉效果,联合了Adobe设计发布了「思源黑体」(Noto)来作为中文默认字体,「Roboto」为英文字体。

字体类型

思源黑体,英文名为「NotoSans CJK」。该字体不仅仅在字形上更易于在屏幕阅读,并且拥有7种字重,充分满足了设计的要求。

英文「Roboto」字体,只有6个字重,视觉语言与思源黑体Noto保持一致。该字体具有「现代的」和「平易近人」的气质,是「Material Design」设计风格下的推荐字体。

字体设置

Material Design字体规范,字体类型比例支持的十三种样式的组合。它包含可重用的文本类别,每种类别都有预期的应用程序和含义。

注:Web浏览器根据根元素大小计算REM(根em大小)。 现代网络浏览器的默认值为16px,因此转换为SP_SIZE / 16 = rem。

△Material Design设计类型比例。(字母间距值与Sketch兼容。)

知识点:

值得注意的是,在安卓的字体单位中,不再以px,pt作为单位而是统一的使用了sp,换算方式是:

px = sp*ppi/160 ,sp = px / (ppi / 160)

以iPhone7为例,尺寸是750×1334,密度326ppi 来换算,那么Android的1dp = 1 * 326/160 ≈ 2px

「思源黑体」和「Roboto」字体可在GoogleFonts免费下载,并且可以商用。

网址:http://www.googlefonts.cn/

3. 话题扩展

值得一提的是,越来越多的手机厂商,为了能够强化自身的品牌形象,推出了定制款的字体。

比如小米的「小米兰亭」:

OPPO的「OPPO Sans」:

三星的「SamsungOne」:

字体基础知识小结

正如开头所说,文字是界面中最核心的元素,字体作为基本语言,是设计中体现品牌很重要一点,字体选择非常重要,字体也是设计中占比(约 80%)最大的内容,所以我们一定要熟练掌握,接下来将从文字行高、字间距、行间距等说起,围绕字体排版继续聊。

人们是如何阅读的

设计中,好的排版能让用户愉快地阅读,而不好的排版则会给用户带来糟糕的阅读体验。因此排版的潜在重要性不容忽视。

无论是在西方国家还是亚洲国家,大部分人们的阅读习惯都是从左到右。这种阅读模式已经延续了几百年,因此如无特殊需求,你应该使你的文本左对齐,这样符合人们一贯的阅读习惯(阿拉伯地区除外)。人缺乏耐性,在阅读过程中更似是一种远近不定的跳跃「扫视」。枯燥的文字如果没有经过任何排版处理,会让读者瞬间失去阅读的兴趣,除非非读不可。所以通过改进文字内容的结构和排版来提高阅读性乃至「诱读性」,是一件十分必要的事情。

文字排版中的常见元素

1. 字间距与字偶间距

字间距,英文名为「spacing」,即字符间的距离,事实上他是字符图形外边界框的尺寸和字符在方框中的位置的距离。

字偶间距,英文名为「Kerning」,也叫做「字距调整」,是在字间距的基础上,为实现不同字偶(一对字符)可以有不同字间距的调整值。我们都知道,不同的字母外形不同,所以只有同样的字间距是不协调的。例如,「NA」间是标准的字间距,而「WA」由于W和A的形状可以重叠,所以需要负字偶间距才能达到协调一致的外观。

在大段落文字排版时,我们一般不需要更改字间距和字偶间距,因为字体设计师已经对他们做过了最优处理。在对一组字符单独设计时,就需要考虑字偶间距,以达到更协调的视觉效果。总的来说,字号越小,字距应当相对越大,行高也应该相对越大。反之亦然。

西文字间距还分为:比例字体和等宽字体

比例字体:根据字符外形特点设置不同字宽的字体,使得字体外形协调,可读性更好;

等宽字体:每个字符设置相同字宽的字体,字符间距较大,它们经常被用于显示计算机代码示例;

2. 字间距的三种形式

标准间距:即默认的字间距,字与字之间的距离不大也不小,在设计中要根据不同的字号设置不同的字间距来排版,往往需要我们根据字号、字重的不同动态调节间距参数,避免千篇一律使用软件默认间距。

紧凑间距:字与字之间的距离向里缩进,在字符工具里的「字间距」数值为负数,一般在-5%~-30%不等,通常用在标题中。

宽松间距:与紧凑型间距相反,字与字之间间距向外扩大,在字符工具里的「字间距」数值为正数,一般在5%~30%不等,通常用在正文中。

知识点:

提示:字间距虽然有以上三种形式,但是在实际工作中也要具体问题具体分析,例如有些中文字体本身「外边框」的距离就比较大,如果再加大字间距,就会显得过于分散。

3. 西文词距

在西文阅读时,视觉上的自然界限是「词距」而不是「字距」。如果排版时需要进行例如「两端对齐」的行内间距调整,中文直接可以动「字距」,把调整量均匀地放到每个字间里;而西文却是动「词距」,只能把调整量加到词距里,而单词内部的字距依然是保持字体设计师预设的原始字距,这是保证西文易读性的关键所在。

4. 标点避头尾

在古代,书籍排版可以做到字间距恒定,原因是古代不存在「标点」,也就没有「标点避头尾」导致的种种问题。而现代汉语存在标点符号,有的标点不能放在行首(如逗号、顿号、句号等),有的不能放在行尾(引号、前括号等)。处理方式叫做「优先推出式」标点避头尾,通过将本行内的标点宽度进行挤压后,腾出了空间给本来排不到的逗号,确保了字间距的恒定(篇幅限制,本文暂不谈文字编排具体调整方法)。更详细的介绍可移步字体设计与排印网站 Type is Beautiful 了解。

5. 文本框

在设计软件中,我们在添加文本时,就会创建一个文本区域,例如Sketch中文本区域有三种类型,自动宽度、自动高度、固定尺寸,而「固定尺寸」可配合「设置文字层垂直对齐方式」使用。

6. 对齐方式

文本的对齐方向有左、中、右三种对齐方式。文本对齐的标准是基于文本区域的边界决定的,只有设置固定的文本区域对齐才有意义。

7. 行高

行高或行距是文字排版的基础参数,也是排版品质的先决要素之一。行高是一行文本垂直方向的高度,这个高度和字高无关,文字内容水平居中,如下图所示:

8. 英文行高

英文的行高指的是一行英文的基线与下一行英文的基线之间的距离,基线(baseline)是英文字体结构中的概念,在css里文字的元素都是按基线来对齐的。西文基本行高是字号的1.2倍左右,字体有上伸部(ascender)和下延部(descender)可来创造行间空隙。

9. 中文行高

中文的结构属于方块字没有基线,所以中文的行高指的是一行中文的最底部与下一行中文最底部之间的距离。中文因为字符密实且高度一致,所以一般行高需要更大,根据不同用户人群(儿童、年轻人、老年人)以及使用环境,可达到1.5~2倍甚至更大。

知识点:

提示:不管是标题、正文还是注释文字,行高都不易过大或过小,会导致阅读困难。总的来说,字号越大行高应该越小,字号越小行高应该越大。

10. 行长

在《中文排版需求》里,明确写明了这项基本要求:

2.3.5 版心设计的注意事项:「一行的行长应为文字尺寸的整数倍,各行的位置尽可能头尾对齐。」

「一行的行长应为文字尺寸的整数倍」,这一基本的、理所应当的需求看似简单,但是在实际操作中,却往往由于单位换算等各个原因没有得到实现。对于后半句提到的「头尾对齐」,将另文讨论,但显然也和本文相关。正因为设计师想实现「头尾对齐」才会盲目地用软件的「两端对齐」功能,大家可以看看身边的印刷品,注意看一下每段的最后一行间距是不是统一,就可以知道设计师有没有按照这个原则排版。

中文的一个字占两个字符,英文一个字占一个字符。正文的行长通常在40到60个字符之间。在行长较宽的区域(例如桌面)中,包含最多120个字符的较长行将需要将行高增大。行长过长易读性就会变差,读者阅读时容易串行,造成阅读困难。合理的行长使用户在行间跳转时感到轻快和愉悦,反之则会使阅读成为一种负担。

11. 行间距

行距是指临近两行之间的距离。合适的行距让用户阅读舒服,阅读效率也高,行距太紧凑会让内容挤成一团,实现无法正常阅读;行距太宽松会让内容松散,产生了我们通常意义上的「河流」,阻断了行的视线,Photoshop中默认行距是1.2倍的字号,例如字号是30px,那么将行距设为36px和默认「自动」的效果一致。1.2倍的行距对中文排版来说通常过小,合适的行间距通常为1.5~2倍之间。文本字体越小,两行之间的行间距应该越大,确保字与行呼吸的空间。

12. 英文行间距

英文的行间距指的是一行英文的底部线与下一行英文的顶部线之间的距离。可以简单的理解为「行与行之间的距离」。另外英文文字底部和顶部都有对应的专有名词,英文顶部的那条叫「升部线」,底部那条叫「降部线」。

13. 中文行间距

中文的行间距就比较好理解了,是指一行文字的最底部与下一行文字的最顶部之间的距离。即行与行之间的距离。

14. 段间距

段间距:段落与段落之间的距离,可保持页面节奏,与字体、行高相互关联。

为保证文章易读性,正文段间距,可以简单地取一个空行(也就是一个行高),这是比较常规也比较合适的做法。举个例子:字号12,行高设定20,段间距 = 行高 + 行间距。行间距越大,段间距就越大;行间距越小,段间距就越小,行距与段间距成正比。段落之间首尾的行之间间距应该大于段内的间距,这时候就应该增加段间距,使得文本的阅读体验得到进一步的提升。

排版的CRAP原则

在任何一个设计中都需要把各个元素进行分级,分清主次,这样才能更好地抓住重点。为了能分清各元素的主次,就需要用到CRAP原则。这四个原则分别是对比、重复、对齐、亲密性。

1. 对比 Contrast (增强效果、组织信息)

对比的基本作用是突出重点,增加可读性。附加作用是有效增强视觉效果,打破平淡,吸引读者注意。

一些界面排版混乱,可读性非常差,用户的视线不知道集中在哪,导致这种情况的发生都是因为界面内容对比不明显造成。在同一个视觉区域内的逻辑不同的元素应该有所区别,以避免视觉上的相似,这样就可以有效的分清主次,为了使主要元素更突出,次要元素更弱化,可以尽量使它们的颜色,字体、大小,留白不同。如果两个元素不尽相同,那就让他们截然不同。比如,使用「14 号字」和「15 号字」进行对比,差异就很不明显,而使用「14号字」和「24 号字」,差异就明显得多,一眼就能看到大号的字体。

在这点上,「微信读书」的列表页就做得非常好,它通过标题与描述的字体粗细、大小、颜色进行对比,把最有用的信息直观地呈现在用户面前标题是吸引用户关注的关键,作者和评分只是给用户一种参考,不起决定性作用。因此,如果没有对比原则,标题和描述的字体同样粗细、大小,你就会发现视线总是会情不自禁的被评分所干扰。

大小对比

为了区分文字、图片、图标等元素的重要性,通常采用尺寸的大小来做对比。例如文章的正副标题,副标题一般用来解释主标题的内容,因此副标题的文字应该通过大小和颜色调整变成次级,让用户阅读时分清主次。

颜色对比

在排版中,首先要产生对比效果的就是背景和文字。文字与背景如果在颜色上很接近,那么就不容易区分开来吸引用户注意力,一般来说,人们习惯白纸黑字(也是因为人类有书写需求以来形成的),即白色背景和黑色文字。也有黑纸白字,例如现在APP都在做的DarkMode暗色模式,但其实暗色背景搭配浅色文字并不适合大量阅读。当然这也是为了配合用户使用场景,在夜晚光线较暗的环境下,深色模式或许更利于阅读。「冷知识:暗色模式其实就是厂商为了解决电池耗电量而出的计策,只是换了个噱头而已」总之,不管设计中使用黑白、红绿、蓝黄哪一种配色,一定要注意文字和背景的对比是否清晰便于阅读。

2. 重复 Repeated (统一有秩序)

重复是保持整齐的重要准则。既包括字体、字号的重复,也包括颜色、风格的重复。对于新人来说,要时刻牢记,尽量统一字体、字号、颜色等一系列元素,在统一的基础上,找出需要强调的部分,进行更改,通过对比原则进行强化。

如果相同内容(如标题)属于同一种逻辑关系,则应该使他们的字体、颜色、留白尽量保持一致。这样可以增加内容的条理性,并加强设计的统一性。在重复原则下,用户会因为视觉惯性继续选招设计线索,根据重复性设计线索顺场地浏览下去。

知识点:

重复不是单一的机械式的元素重复,我们可以理解为用统一的重复元素塑造一个新的元素。当然这是在保留基本的元素时所塑造出来的高度统一性的画面,从而增强我们所想要的设计效果。

3. 对齐 Alignment (统一而有条理)

在页面设计上每一元素都应该与页面上的另一个元素存在某种视觉联系,这样才能建立清晰的结构。任何元素内容在在版面上都应该尽量上下左右对齐,对于设计新人来说,最好严格遵循一种对齐方式,不然就会导致混乱,实在不行,至少保证在同一内容版块中遵循一种对齐方式。方法也很简单,就是找到一条明确的对齐线,并用它来对齐。

对齐包括左对齐、居中对齐、右对齐 3 种方式。

  • 左对齐:页面中的元素以左基线对齐。左对齐是最常见的对齐方式,简洁大方,利于阅读;

  • 居中对齐:页面中的元素以中基线对齐。居中对齐给人一种严肃与正式感,不过也会有呆板的感觉;

  • 右对齐:页面中的元素以右基线对齐。相对少见的对齐方式,给人一种人为干预的感觉,加强了形式感,降低了阅读效率;

4. 亲密性 Proximity (实现组织性)

亲密性是实现视觉逻辑化的第一步,它是指关系越近的内容,在视觉上应该靠得越近,反之,关系越疏远的内容,在视觉上应该越远。简单的来讲就是要把画面中的元素分类,把每一个分类做成一个视觉单位,而不是众多的孤立的元素。这有助于组织信息,减少混乱,为读者提供清晰的结构。

那做好亲密性有哪些方法呢,私以为有以下几点:

留白:留白是设计中通用的万金油原则,通过留白建立距离关系进行内容区分;

左图歌曲封面和歌曲名信息间隔比每首歌曲上下间距还大,导致用户的视线流呈垂直方向。

分割:简单来说就是分组,建立组合关系。常见的形式有线条分割,卡片分割等;

色相:通过颜色的对比,不同颜色的信息会暗示这是同一类。常见的日历行程就是通过不同颜色来区分时间和具体事项。

方向:不同的排版方式也可以很好的区分信息;

什么是信噪比及在设计中的作用

「信噪比」(Signal-to-Noise Ratio)原本是用在声音和图像领域的概念。在互联网产品中把 「信噪比」概念借用到了用户体验。合理的信噪比可改善与用户的交流。加大信号可以将有用的信息快速准确的传达给用户,减少噪音并使信号脱颖而出。

从人机交互角度,我们应该删除与任务不相关的内容或设计元素。你甚至可以将高信噪比的目标与极简主义联系起来。但是「信号」和「噪音」的确切含义会有所不同,一个人的信号可能是对另一个人的干扰,因此,用户界面的信噪比有低有高,取决于具体的用户和具体的任务。在用户界面中,信噪比所涉及的「信息」可以是任何内容,包括文本内容,视觉元素或动画等。为了提高设计传达信息的效率并帮助用户完成任务,需要提高信噪比。

知识点:

用户始终喜欢清晰、简单、自然、好用的设计和产品。但需要注意的是,除了交流必要信息之外,我们还希望界面在视觉上具备吸引力,以唤起用户的某些情感。有了额外的目标(比如品牌宣传、业务目标等),应该以合理的信噪比为目标,而不是以绝对的方式排除所有「无关」的信息。

例如iOS6到iOS7图标拟物到扁平到改变,让用户可以更快速准确的获取到有效信息。而这一过程,就是典型的放大「信号」。

还有虾米音乐的驾驶模式

我们都知道,在开车的时候操作手机是非常危险的。在40km/h的速度下,看手机3秒,相当于盲驶了35米。但有些情况下又不得不操作手机,比如紧急来电或者导航出错……这时,驾驶模式的界面就显得尤为重要了,让用户能够快速准确的识别信息并进行操作,可以大大提高行车的安全性。

在界面中无论是何种分割方式(分割线、卡片阴影、分割色块),过于浓重的表现都会影响有效信息的获取,成为界面中的「噪音」,因此我们应该让它们细一点、淡一点来降低表现,或者干脆不要(留白分割)。

图版率的高低对设计的影响

图版率就是页面中图片面积的所占比。在页面设计中,除了文字之外,通常都会加入图片或是插图等视觉直观性的内容。这种文字和图片所占的比率,对于页面的整体效果和其内容的易读性会产生巨大的影响。当然,除图片本身外,我们也可以通过填充底色,图形叠底等方式来提高界面中的图版率。

图版率高低的区别:同样的设计风格下,图版率高的页面会给人以热闹而活跃的感觉,反之图版率低的页面则会传达出沉稳、安静的效果。提高图版率可以活跃版面,优化版面的视觉度。但完全没有文字的版面也会显得空洞,反而会削弱版面的视觉度。

在没有图像素材的情况下想要呈现出高图版率,可以通过以下几种方式来实现:

  • 通过填充页面底色,取得与提高图版率相似的效果,从而改变页面所呈现出来的视觉效果;

  • 如果素材图像尺寸小,可以通过色块的延伸或是图像的重复来组织页面结构,同样可以提高图版率;

  • 利用排版的节奏感以及跳跃率(文字和图片的跳跃率,是指版面中最大标题和最大的图与最小正文字体和图片大小之间的比率)让无趣的版面充满活力,富有节奏的设计也能间接优化页面的图版率;

  • 增加页面中的图形也可以改善图版率低的问题。无论是数字、序号、图标,甚至是视觉处理后的标题文字,都能提高页面的视觉度,并给用户留下活跃生动的印象;

  • 如果页面中没有图片和插图,那么通过对文字及其颜色的处理,也可以起到提高图版率的作用;

上面的例子中,对于标题文字都进行了视觉加工,起到了整体页面的装饰效果。借助对这种文字大小、颜色、形状的灵活运用,来突出页面的重点,避免视觉上的单调感。

文字在代码中的实现及标注

1. 文字在代码中的实现

在开发落地的过程中,文字排版的开发实现是很重要的一个环节,也是经常让设计师和开发小哥哥头疼不已的地方。字体和排版在实现上经常会出现偏差,主要原因在于开发的标注方式和设计软件不一致。因此理解文字开发的实现方式,细节问题的解决方法至关重要。在Android中,文字开发工作是通过一个叫TextView控件来实现的,主要承担文本显示的任务,任何APP都不可避免的会用到它。TextView常用属性如下图:

2. 字体字重对应的font-weight值

在前文聊过,每种字体都对应有好几种字重(Regular、Normal、Medium、Light ),在给开发的 UI 设计稿中,我们给的字体标注通常有 PingFangSC-Regular、PingFangSC-Medium、PingFangSC-Bold,并不会直接给开发 font-weight 的值。虽然这需要开发去熟记,但作为设计师了解它们的对应关系,可以更顺畅的和开发沟通。

在W3C Fonts节章的规范标准中有给具体数值(100至900):

这些有序排列中的每个值,对应字体的字重。其大致符合下列通用重量名称:

当然,并不是每一种字体都有这么多字重,那遇到有些字体只有2、3种字重,该怎么对应font-weight 值呢?W3C Fonts也给出了解决方案,例如字重和400大致符合将会归为Regular、Book、Roman;和700大致符合将会归为Bold。若一个重量所指定的字形不存在,则应当使用相近重量的字形。通常,较重的重量会映射到更重的字形、较轻的重量会映射到更轻的字形。下图所示:灰色表示该重量的字形不存在、使用的是相近重量的字形。

△ 只包含400、700和900重量字形的字体家族的对应字重

△ 只包含300和600重量字形的字体家族的对应字重

3. 文本框行高的问题

我们都知道在设计的时候,可能字体使用的24pt,但其实字体本身占用的距离是包含了升部及降部区域的,这样就导致其占用空间大于24pt,而变成了33pt。每个字体都有相应设定的「字高」比例,可以通过sketch选中字体后的height值来进行查看。线高越大,问题就越大。下面的示例显示文本框之间的距离设置为32px。如你所见,即使你将所有垂直间距都设置为相同的值,它们在视觉上也远大于32px。

△ 虽然标注出来的参数都是一样大,但视觉上间距却是不一样的

4. Leading-Trim:数字排版的未来

去年六月,Microsoft Design赞助了一个新的css规范,称为「Leading-Trim」。这个css方案能很好的解决上面这个问题。

我们常用的UI设计工具,例如Figma和Sketch,似乎已经采用了「half-leading」模式并以此方式渲染文本。因此,我们在设计工具和浏览器中都遇到了这个问题。

设计方面的解决方法相对容易:你可以忽略边界框,而直接根据文本的大写高度和基线来测量空间。这是一个手动过程,因为大多数设计工具没有上限高度和基线的参照目标,尽管设计师将尽一切努力使我们的设计看起来更好!但是,如果采用这种方法,开发人员仍将仅在CSS中实现边界框间距。因此,它们会出现随机的间距值。

为了缓解此随机性问题,开发人员可以在CSS中以负边距「裁剪」文本框。但是负边距将需要手动确定,并且是特定于字体的,因此是「随机的」。任何字体,浏览器,操作系统或语言环境的更改都将不可避免地导致你不小心设置边距。此外,该技术通常不是良好的编码实践,并且可能导致意外的副作用。

Leading-Trim新规范

Leading-trim是CSS工作组正在引入的新CSS属性。顾名思义,它就像文本框剪刀一样工作。你只需使用两行CSS,就可以从你选择的参考点中修剪掉所有多余的空间。

代码示例:

上面的示例首先使用text-edge(也是新属性)来告诉浏览器,所需的文本边缘是大写高度和字母基线。然后,从两侧修剪多余部分。请注意,采用修剪仅会影响文本框。它不会切断其中的文本。这两行简单的CSS创建了一个干净的文本框。这可以帮助你获得更准确的间距并创建更好的视觉层次。

使用后再来对比一下:

△使用新规范对比发现,右图文字上下间距舒服多了,也更合理。

Leading-Trim修复对齐问题

借助Leading-Trim,可以解决在APP上看到的所有奇怪的对齐问题。例如,即使文字位于文本框内,你的文本也不总是在容器中垂直居中。

默认行高中保留的多余空间会导致文本不总是在文本框中居中。使用Leading-Trim修剪,就可以很省心的使文本垂直居中。

知识点:

原因是每种字体的设计也不同。它具有自己的默认行高,并且其中的文本可以具有不同的大小和基线位置,并不都是水平居中对齐的。因此,有时即使字体大小,行高和文本框位置保持不变,更改字体也会改变文本的对齐方式,如下例所示,文字很明显没有对齐。

在第二个示例中,你可以看到Leading-Trim如何防止这种情况并使文本完美对齐。

一致性和工作流程的改进

Leading-trim修整超出了使间距和对齐更准确的范围。它在建立的间距系统,为设计准确性和一致性以及的设计到开发交接铺平道路方面发挥着关键作用。

拥有间距系统有很多好处。设计师可以更快地确定间距,开发人员可以设置相应的间距变量以消除代码中的随机间距值。但是目前,即使我们设置了间距系统,由于文本框中的额外空间,对于文本元素来说也不是很准确。如果我们尝试忽略设计中的边界框并在代码中「裁剪」文本框,则会遇到那些棘手的解决方法问题。

△应用于文本元素且没有Leading-trim修剪的间距系统

借助领先的文字间隔系统,从设计到开发的交接过程也将更加顺畅,因为开发人员将能够建立完全相同的系统,并且避免在布局错误上花费大量时间。最重要的是,领先的微调间距系统将帮助我们提供用户信赖和喜欢的外观更美观的产品。

5. 设计中修改文字行高的方法

上面我们介绍了利用Leading-trim修剪字高的先进方法,但是这个新CSS的规范还在编写中,还未世界范围的推进,不过有「微软」团队的扶持相信国际化也不会太远了。

在这之前,我们想要尽可能的解决字符多出的间距问题,就需要在设计软件里手动修改了,手动把文字行高与客户端系统默认行高保持一致,从而给出准确的文字间距。开发在实现的时候iOS使用系统默认行高,Android系统去掉文字上下padding。

这种方式虽会花费不少时间,但也最,你可以据此设置出最美观合理的间距,而不用担心上线稿的还原度问题,也便于我们后期的页面校对和调整。

△在Sketch中修改文字高度

6. 什么是弹性适配

文字弹性适配一般涉及的是宽度适配,宽度适配普遍使用的是间距适配,即定好左右页边距,中间弹性拉伸。这种方式可以做到较好的适配,也是做快速常用的适配方法。

7. 标注工具

设计师将设计文件交付开发之前,应站在程序员的角度着想,做好前期沟通,提供他们开发所需要的资源。设计文件的标注可以使用Sketch插件或直接上传「蓝湖」,拿Sketch插件「Sketch Measure」为例,它是一款十分智能的标注插件,主要功能包含两大块:标注和规范。

工具栏汇合了Measure所有功能的快捷工具,永远置于画布顶层,有了它就不用再频繁通过菜单栏去使用功能。

做好规范后,点击「导出规范」一键自动生成Html页面,浏览器打开页面点击其中任何元素都可以查看其属性和间距,还包括代码样式,交给开发开发工程师后,不用沟通都能看明白。

△Sketch Measure导出标注的网页界面

设计验收环节

产品功能开发完成后,对产品对功能,视觉和交互操作进行测试和验收,确保产品的可用性。一般在功能模块验收完成后,就可以开始具体的视觉设计验收,这也是由主要设计师负责的模块,主要验收颜色、字体、图形、间距、控件和空状态等。

因本文主讲字体与排版,就拿这部分来说,需要检视的就有:

  • 字体:是否用的平台默认字体,如果是内置字体检查字体显示有没问题;

  • 字号:导航栏、栏目名称、分类页签、tab等字号大小是否符合规范;

  • 字重:标题和正文字重是否正确,粗体用的是哪一种,是Medium,还是SemiBold;

  • 字色:标题、正文、注释、提示等文字颜色;

  • 字间距:检查中文间距和英文间距,段落文字标点有无避头尾;

  • 行间距:段落文字行间距,有没有出现多余的行高 ;

  • ……

在检视过程中如发现问题,截图标示问题所在,并出具检视报告。

△ 视觉检视表示例

视觉设计的验收要追求细节上的完美,因为设计上的细节是很容易被挑错的,同时需要耐性和细心,要有像素级的视角,只有这样才能完美的还原设计稿。

写在最后

原本只是想结合工作积累,写一篇字体应用知识总结,没成想给自己挖了一坑,涉及的知识点真是超级多,很多地方都可以单独拿出来再另写一篇。另外其实在工作中,也建议大家对自己的经验进行总结,对关键信息进行提炼加以沉淀,一方面能让自己的知识更加牢固,另一方面也可以帮助后来者学习成长。字体与排版基础就分享到这里,希望对大家有所帮助。因为篇幅较长,几经修改,有细节不正确的地方,欢迎留言矫正,感谢阅读。

参考文献:

  • 《W3C-CSS字体规范标准》

  • 《从「行长为字号的整数倍」说起》

  • 《Leading-Trim: The Future of Digital Typesetting》

  • 《关于UI设计中字体应用的干货》

  • 《字体与排版》

  • 《深度剖析Baseline设计原理》

文章来源:优设网

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

抓住关键点,提升App搜索设计的体验!

ui设计分享达人

可能很多人认为搜索设计很简单:在主页顶部设计一个搜索框或者搜索图标,就可以完成操作。但搜索前、中、后的体验大家有没有仔细考虑过呢?

在搜索过程中需要展示什么?如何帮助用户尽快找到他们想要的?搜索的体验如何与其他体验相联系?这是很多设计师思维模糊的地方。

在本文中,将介绍搜索设计需要了解的内容,以及在不同App和案例中的搜索体验。 

一、搜索前的体验

搜索是产品中不可或缺的一部分,无论打开哪个App, 我们都能很快找到搜索功能。搜索作为一个功能入口,简单的呈现方式对用户来说很重要。

搜索设计常见的形式有3种:

  • 顶部搜索框

  • 图标(放大镜)搜索

  • 底部搜索导航

从视觉和优先程度来看,底部导航>搜索框>图标。那么每种形式的优缺点是什么,又分别在什么时候使用呢? 

1、顶部搜索框

搜索框通常位于App主页的顶部,作为一个大的触摸目标,更容易被用户发现和使用。搜索框的组成一般包括容器、预设文字、操作按钮。

容器通常填充为白色或灰色,里面带有预设好的产品文案。但是容器什么时候用白色,什么时候用浅灰色呢?这取决于App主页的背景色。

设计准则是始终将容器的颜色与页面背景色相对比。拿谷歌地图和微信读书相比较,灰色背景下用白色的搜索框,反之亦然。

当然也有例外,Airbnb在白色背景上就有一个白色的搜索框。为什么这样设计呢?因为它的搜索框带有阴影,能和背景作出区分,也是一种可行的设计方法。

预设文字的作用是提示用户要搜索的内容或搜索方向,告诉用户可以从搜索中得到什么。在App Store中,预设文字告诉用户可以搜索“游戏、应用、故事等”,Messenger中的预设文字只有“搜索”,为用户提供功能入口。

预设文字在内容浏览类App中很常见——不仅可以展示拥有的内容类别,还能结合搜索趋势来积极地推广热门内容。比如微信读书的搜索框使用最近流行的书作为预设文字,以此提高内容的曝光率,吸引用户阅读。

操作按钮虽然与搜索相关,但摆放的位置不固定。可能在搜索框内部,也可能在搜索框两侧。哪些图标/文本按钮对搜索有帮助呢?最常见的是搜索框中的语音输入功能和二维码图标

另外一个好的搜索体验的关键是能从视觉上区分与搜索相关的图标。

2、搜索图标

如果一款产品希望用户专注于浏览内容,或者不需要频繁使用搜索,可以采用图标的样式将搜索入口放在页面顶部。例如,在Medium上用户通常是漫无目的地浏览内容,而不是搜索特定的主题;Facebook上的用户通常只想浏览朋友的帖子,很少搜索内容。 

3、底部搜索导航

与前两种形式相比,底部搜索导航离用户距离最近,更容易作为接触目标也被赋予更多的特性。

UberEats通过主页的排序和过滤,帮助用户分类感兴趣的餐厅,并将搜索作为底部导航的第二个标签,这样用户就能够不断看到和探索新餐厅。

Tiktok也将搜索入口放在第二导航中,完整的首页有利于为浏览视频的用户提供沉浸式的体验。

二、搜索页内容展示

当用户想搜索某个内容,点击搜索框之后该做什么呢?App应该如何帮助用户搜索?

用户点击搜索框时会弹出一个页面,我们将其定义为“搜索页”。搜索页的目的是让用户做好搜索的准备,对要寻找的内容进行提示,拓宽用户对要搜索的内容的看法。

弹出的搜索页帮助产品完成两个主要任务:

  • 利用视觉和交互告诉用户已经做好搜索的准备;

  • 通过页面上的内容提醒用户他们正在寻找的东西,或者为他们提供可能感兴趣的东西。 

1、视觉和交互——做好搜索的准备

当我们有一个特定的搜索目标时,我们的注意力就会完全放在搜索框(或搜索图标)上,如果突然被引导到一个新的页面,可能会带来零碎的体验。

所以要把“搜索前”到“搜索页”的转换做得更自然,让用户感觉仍然在同一个页面上操作一样。点击Facebook的搜索图标,图标通过平滑的动效扩展成一个搜索框。

无论搜索在什么位置,显示搜索框的不同状态很重要,告诉用户“已经准备好进行搜索”。以领英为例,分析一下从搜索前到搜索页面发生了哪些变化:

  • 从搜索中返回的方法:取消。这对所有的搜索体验都是必要的,可以使用取消或者返回图标。

  • 搜索框改变了填充色。搜索框从灰色变为白色,反映当前搜索操作处于焦点状态。

  • 只留下必要的图标。二维码能帮助搜索所以仍然保留,而左边的个人资料图标和搜索图标都会消失。

  • 弹出光标来鼓励用户输入文字。

在某些App中,搜索框在“搜索前”和“搜索页”中的位置可能不相同,这样做的目的是在两种体验之间进行平滑的过渡。Google的搜索框向上滑动来展示搜索内容,保证过渡动效的流畅性。

有些搜索体验不可能让用户停留在同一页面上。Airbnb的搜索需要一系列的引导将用户转移到一个全屏的流程中。

关键的地方在于,搜索页面在屏幕中间弹出,键盘向上滑动,用户可以通过无缝的交互从“搜索前”平滑过渡到“搜索中”。 

2、内容——提示搜索的信息

历史记录可以帮助用户进行有针对性的搜索,因为过去的兴趣是当前兴趣的有力指标。但是只显示历史搜索记录会将用户的注意力集中到过去的搜索上,而不是保持搜索的状态。所以除了显示搜索记录外,显示推荐内容能拓宽用户对潜在事物的兴趣。

关键点1:“没有内容可推荐”

当一款产品的功能主要是为了让用户完成某个特定的任务或达到某个目标,那么它可能就没有动机去推广任何内容。这类产品只会显示最近的搜索记录,比如Notion是一个笔记应用,只负责存储用户的笔记,因此只显示过去的搜索记录来帮助用户进行搜索。

关键点2:“在搜索前推广内容”

有的产品通过将各种内容提供给用户查看和探索,为用户实现某个目标。谷歌地图的搜索框下面有热门的标签,用户很可能会从标签中找到他们感兴趣的类别。用户点击搜索框后,谷歌就会假设用户有特定的去处,只专注于帮助他们到达想去的地方。

关键点3:“平衡内容推荐和历史搜索”

主页上的推荐内容可能很容易被忽略(因为它可能与用户的操作无关),但是搜索页上的热门推荐很少会被忽视。因为用户是带着目标主动进入搜索页,所以不会省略页面上的任何信息。

热门类别

通过在最近搜索页上将各种美食放在首位,UberEats和Doordash这类送餐App就可以引导用户尝试更多的餐厅。

热门话题

展示热门话题能为用户创造一种社区感,会让用户觉得他们与更大的社会保持联系。Quora是很大的问答平台,它不仅显示推荐的话题,还显示了关注者的数量。看到这一点,用户会想:“如果一个话题有13万追随者,那一定很好!我也想成为其中的一员。”

热门新闻

新闻类搜索具有很强的时效性,用户很清楚明天的内容将不同于今天的内容。所以有些App会对新闻或热搜进行实时排名,向用户展示最热门的资讯。

Reddit展示了当前的趋势,让用户了解的内容,Robinhood每小时都会更新华尔街关于股市的文章,这样用户就可以做出及时和明智的财务选择。

搜索推荐

通过算法,很多App都能从用户过去的兴趣中推断用户的喜好,然后不断推送相似的内容,比如淘宝的相似商品推荐。

同样的策略也可以应用于搜索页。小红书作为一款流行App,搜索页显示根据用户口味定制的推荐;Twitter的“For you”标签根据过去的搜索展示的相关内容。显示更多与用户相关的内容可以拉近与产品的距离,用户也会花更多的时间使用产品。

搜索细分

Medium的搜索做得很聪明,它将搜索页面细分为两个步骤:先点击搜索框来浏览不同的主题,再次点击会显示光标和搜索记录。通过这种分解体验,用户在搜索时获得了更多的信息量和更简洁的体验。

总结

以上是关于搜索前和搜索页的体验设计。产品的搜索体验受到多方面的影响:自身目的、内容种类、受众类型。希望这篇文章能够让你了解搜索体验的可能性,为搜索设计带来更清晰的方向。


文章来源:站酷      作者:Clippp

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

2020-2021 设计趋势报告:多媒体篇

资深UI设计者

伴随着移动互联网的快速发展,5G通信,人工智能,物联网等新技术的发展也越趋成熟,人们接触信息的效率不断在提高,接受信息的纬度也更广泛,如何消化我们在生活中被大量切割的碎片化时间,如何能让用户更聚焦内容不被打扰,拉长用户的停留时间,我们正身处在一个内容快速消费的时代。

长短视频,动画电影,互动装置等形式能够更容易吸引用户的注意力,无论是消费者,创作者或是设计的从业者,制作的门槛降低了,以往是一个团队的输出,现在一个人也能够打造高质量的爆款,人人都是内容的生产者成为了这个时代的标签。文章从设计从业者的角度出发,从摄影摄像,视频动画,动态图形,交互装置等四个纬度来阐述内容设计的制作方法与近几年的风格趋势。


摄影摄像| Photo & Video

实拍一直是多媒体领域最常见也是最不可替代的呈现方式,在他的发展历程中这项技术也衍生出了不少有趣的风格和玩法,随着技术的进步,各种实拍新技术也是不停一直在影响着摄影师拍摄。

1. 微距拍摄

微距,特别适合用来展示被摄物体的细节,比如小昆虫的五官,花蕊上的露水,冰霜上的晶体结构等等。您可以在摄影棚或室外环境中拍摄微观照片和视频,只要您充分放大被摄体即可。通常来讲人眼最近对焦距离是15cm,低于这个距离就看不清东西了,而专业的光学矫正镜头按照近距离拍摄进行设计可以拍摄出一个极端的近景视图,可以得到肉眼无法看到的细节。

腾讯万字干货!2020-2021 设计趋势报告:多媒体篇

△ 来源于网络

产品为了吸引消费者的购买热情,自然离不开产品宣传片,如今的产品广告不再局限于片面的展示产品,而是开始寻找不同的视感令自己的产品看上去更具吸引力。在很多产品拍摄当中会经常用到微距拍摄的手法,正如前面所说,利用微距拍摄手法展示产品部分的细节有的时候或许可以让画面更有质感。

腾讯万字干货!2020-2021 设计趋势报告:多媒体篇

△ 威士忌广告片段

腾讯万字干货!2020-2021 设计趋势报告:多媒体篇

△ TOREAD探路者户外新品面料-「遇水搭桥」系列主题微距图拍摄

微距摄影是区别于常规摄影的一种特殊的摄影方法,这门拍摄技术带来的视感也非常的吸引人,但是往往这种特殊的拍摄手法却非常受限于硬件设备,正如我们前面所说的设备参数都是专门的微距镜头设计的。为了抓住这一点,让更多人知道这个有趣的拍摄手法,市面上也出现了越来越多不同的微距镜头给不同需求的摄影师使用。近两年比较经典的一个就是LAOWA24mm镜头,在于它独特的形状可以到达普通镜头无法企及的位置,机位更加独特。

腾讯万字干货!2020-2021 设计趋势报告:多媒体篇

△ 来源于网络

2. 升格拍摄

升格拍摄无疑是让视频表现提升几个档次的常用手段之一,电影的标准帧速率是24帧每秒,但是为了实现升格就需要一些技巧,比如拍摄的帧数高于24帧每秒,这就是我们常见的「慢动作」。现有的升格拍摄帧数基本上分为30帧,60帧,120帧,240帧,再往上则是影视和特殊拍摄会用到的了。由于肉眼观察高速运动物体是有限制的,在拍摄高速运动的物体的时候,利用升格将画面播放速度变慢便可以更好的观察到物体高速运动时的状态。

腾讯万字干货!2020-2021 设计趋势报告:多媒体篇

△ 来源于网络

在广告拍摄当中升格镜头也是一个十分常用的拍摄手法,正如我们所说肉眼观察高速运动物体的速度是有限, 往往人们没办法看清楚快速运动的物体,有些产品广告使用慢动作升格镜头加上充分美化的画面便更能吸引消费者的目光。

腾讯万字干货!2020-2021 设计趋势报告:多媒体篇

△ 来源于网络

拍摄影片的时候在不同的环境之下也会利用升格烘托气氛,在我们看到的很多片子里面有紧张刺激的,煽情的,都会利用升格来烘托片子的气氛。由于相机技术的进步,拍摄变得比以往任何时候都更加容易。今天任何人都可以通过相机和高质量的麦克风轻松地成为vlogger拍摄出好看的旅拍视频,加上升格镜头在硬件技术的加持下可以快速的出效果,令这部分人群创作出更好的拍摄作品。

腾讯万字干货!2020-2021 设计趋势报告:多媒体篇

3. 无人机航拍

航拍一直以来都是一个不可或缺的拍摄手法,很多的电影以及广告宣传片当中我们都可以看到不少的大范围运镜片段,天空中的视角对与陆地上行走的人来说还是一个十分新奇的视角,一般来说也很难看得到,所以片子中加入航拍的元素往往可以增加不少高级感。

腾讯万字干货!2020-2021 设计趋势报告:多媒体篇

△ 来源于网络片段

如今技术发展,无人机的民用化推进,市面上各大厂商开始推出自己的航拍无人机,航拍也开始出现在了普通人的视野中,让普通人也可以在高空拍出想要的风景。加上如今4G和5G技术的发展,短视频的流行,令网络上的自媒体人也拥有了更好的展现自己作品的平台,这些拍摄技术的平民化让自媒体人们可以更好的发挥视频创作,而不会总是局限于技术。

腾讯万字干货!2020-2021 设计趋势报告:多媒体篇

△ 来源于网络片段

4. 高质量色彩呈现

如果你有自己拍摄视频,那或许有听说过LOG配置文件,LOG模式通常在专业和专业消费级相机中所配置的拍摄模式,LOG模式的颜色看起来非常平均,因为这样可以地减少截取捕获的高光或阴影。这使得输出的视频几乎没法直接使用,直到对其进行编辑。它的优点在于,以输出高比溶度的视频方式来调整颜色和对比度(即,对其进行分级)从而可以得到自己想要的视频颜色风格。

腾讯万字干货!2020-2021 设计趋势报告:多媒体篇

△ 威士忌广告片段

腾讯万字干货!2020-2021 设计趋势报告:多媒体篇

△ ANGIE’S爆米花广告

LOG指的是数学上的曲线函数,并不是一个独立的拍摄风格,而是风格用上了LOG函数转换,在这个模式下我们可以看到无论是明处或暗部LOG都将细节保留了下来,在这个基础之上调出我们想要的颜色方可得到一个更加清晰的图像。在数据图当中我们也可以看到log模式下所有的颜色数据都处于中间值,不会有过度夸张的位置,编辑之后的图像所有颜色的明暗都区分开来了,也形成了自己想要的色调。

腾讯万字干货!2020-2021 设计趋势报告:多媒体篇

腾讯万字干货!2020-2021 设计趋势报告:多媒体篇

△ QQ x MINISO

实拍产业作为一个主流多媒体形式一直在发生着改变,随着科技的进步,曾经我们需要大费周章才能拍出来的效果,如今也越来越简单。各种新技术的出现不断地改变着人们拍摄的方式和形式,新的玩法也层出不穷并不断影响着其他的多媒体形式。未来实拍将会更加的简单平民化,让普通人也能拍出曾经大费周章才能做到效果。

视频动画|Animation

纵观整个互联网设计行业发展史,计算机图形技术一直在影响着设计。

1. 和高质量输出

在计算机图形输出里,最终效果呈现靠的是图像渲染(Renderding),渲染又分离线渲染和实时渲染,以往字面上理解则是实时渲染,高质量则是离线渲染。下面介绍一下为了如何可以将两者结合实现高质量输出CG,视频动画。

Realtime-Render (实时渲染):在虚拟世界的图形表现中,实时渲染占有很重要的地位。所谓实时渲染,就是图形数据的实时计算和输出。如果说实时渲染的概念对你很陌生,那如果用实时渲染领域中的一个重头戏来给你举例,相信你就不会有陌生的感觉:那就是游戏。游戏因为需要玩家去互动,因而对渲染的实时性有很高的要求。随着计算机图形技术的不断发展、硬件计算能力的不断升级,游戏实时渲染的画面逐渐从简陋走向逼近真实。

腾讯万字干货!2020-2021 设计趋势报告:多媒体篇

△ Unreal engine 4

以上Demo 是专业团队制作完成的高质量短片,但是普通用户想要达到这种级别还是有些难度的。放出这些短片也代表现有的软件技术和硬件设备已经可以达到这个水平,当然有也商业竞争的成分Unity和Unreal 是目前用户最多游戏开发引擎,想要在游戏以外其他领域竞争实时渲染市场份额。也不断在更新自己的技术。这代表除了游戏行业以为其他跟图形有关的(像电影,广告,互联网)行业也慢慢开始进入实时渲染时代,到目前为止实时渲染还没有真正得到广泛应用是因为实时渲染还不能达到想离线渲染那样影视广告级别高质量的画面。

腾讯万字干货!2020-2021 设计趋势报告:多媒体篇

△ Visual ASMR Rock by Onesal

离线渲染大概的流程需要经过模型-场景-绑定-材质-灯光--合成-输出,每一个步骤都需要大量的技术支持,像上面阿凡达这类大片级别的影片,为了某个效果很研究开发一些新的技术,所以要详细说明要花很多时间,我们就不详细说明了,回到主题高质量输出上面。所以每一步广告影视作品,都是靠大量的时间和人力堆出来的。那么广告影视这种渲染方式为离线渲染,而游戏为实时渲染。

腾讯万字干货!2020-2021 设计趋势报告:多媒体篇

△ Unreal engine 5

实际上二者的区别也就在这,但是就是因为这二者的这一个区别,就引发了不少的技术差别在里面。离线渲染对时间往往没有很极端的要求,用接近现实的光线跟踪算法技术,设置很高的采样值和迭代次数,就如阿凡达每一帧画面需要渲染几十个小时以上,只要画面质量够真实,这些时间成本都可以被容忍。而游戏画面的渲染,也就是实时渲染的话,则在时间上的资源尤其地珍贵游戏所用的实时渲染就没有那么滋润了,先不说几分钟渲染一张这么的事情,就连1秒钟出一张,都不够人看的。对于游戏来说,再不济,也要有1秒20多张,才能给玩家看(还不算能流畅操作的情况)。那么实时渲染有可能用于画面要求高质量的影视广告等行业吗。

虚幻5

2020年5月13号,Unreal engine官网发布了Unreal Engine 5 并在Playstation5上运行进行展示Demo(「Lumen in the Land of Nanite」)

腾讯万字干货!2020-2021 设计趋势报告:多媒体篇

△ Unreal engine 5

该演示展示了虚幻引擎5的两大全新核心技术:

腾讯万字干货!2020-2021 设计趋势报告:多媒体篇

△ Unreal engine 5

Nanite

虚拟微多边形几何体可以让美术师们创建出人眼所能看到的一切几何体细节。Nanite虚拟几何体的出现意味着由数以亿计的多边形组成的影视级美术作品可以被直接导入虚幻引擎——无论是来自Zbrush的雕塑还是用摄影测量法扫描的CAD数据。Nanite几何体可以被实时流送和缩放,因此无需再考虑多边形数量预算、多边形内存预算或绘制次数预算了;也不用再将细节烘焙到法线贴图或手动编辑LOD,画面质量不会再有丝毫损失。

腾讯万字干货!2020-2021 设计趋势报告:多媒体篇

△ Unreal engine 5

传统的模型资产做法–先是用Zbrush等雕刻软件又或者是3D扫描等数据模型–重新拓扑为高、中、底三种面数模型–展UV上材质–烘培法线、凹凸等贴图–导入游戏引擎中使用,那么为什么要做那么复杂呢?游戏引擎运行资源越大,可能会导致游戏的流程度和游戏体验不好,为了让玩家有流程的操作体验,通过高精度多边形几何体烘焙法线凹凸等贴图用在低精度多边形几何体上可以保留高模的细节从而节省运行资源提升游戏流畅度,Nanite完美解决了这个问题。

Lumen

是一套全动态全局光照解决方案,能够对场景和光照变化做出实时反应,且无需专门的光线追踪硬件。该系统能在宏大而精细的场景中渲染间接镜面反射和可以无限反弹的漫反射;小到毫米级、大到千米级,Lumen都能游刃有余。美术师和设计师们可以使用Lumen创建出更动态的场景,例如改变白天的日照角度,打开手电或在天花板上开个洞,系统会根据情况调整间接光照。Lumen的出现将为美术师省下大量的时间,大家无需因为在虚幻编辑器中移动了光源再等待光照贴图烘焙完成,也无需再编辑光照贴图UV。同时光照效果将和在主机上运行游戏时保持完全一致。官方的宣传已经非常亲民了,很多业外人士基本知道是怎么回事,也知道虚幻引擎5的优势。简而概之:简化的制作复杂程度,模型师的工作量将大大缩小,二是画质效果又将迈向更高的一个品质。

腾讯万字干货!2020-2021 设计趋势报告:多媒体篇

△ Unreal engine 5

Unreal 5 这两大功能可以说是克服了现阶段的难题让实时渲染更接近影视级渲染,然后简化了以前复杂的工作流程,让创造变得更简单了。这代表着以后只要涉及CG类的行业都会发展巨变。

2019年11月12日Quixel 公司被Epic Game(Unreal的公司)收购并宣布Quixel 旗下Bridge(材质管理软件) Mixer(材质编辑软件) Megascans(3D扫描资产)对所有Unreal engine 用户免费,这一爆炸新闻。此前Quixel 是靠卖高精度3D扫描资产盈利的。用Megascans 的3D资产可以创造电影级4K-8K的真实画面,Unreal engine 5的dome也是用的Megascans 的资产,下面的案例(Rebirth)也是用的Megascans的资产。

腾讯万字干货!2020-2021 设计趋势报告:多媒体篇

△ Rebirth

在没有免费开放Megascans的之前想要制作8K的3D扫描资产是非常困难的,需要大量的设备和人力支持,在网络上购买价格也不便宜导致普通个人用户是很难制作这样高精度的画面,这一消息让更多的自由职业者和个人艺术家加入了实时渲染的潮流趋势中。让4K创造不再那么困难。

Megascans Ecosystem: Giving more Power to Artists(Megascans生态系统)

腾讯万字干货!2020-2021 设计趋势报告:多媒体篇

腾讯万字干货!2020-2021 设计趋势报告:多媒体篇

△ After the mountain Rain

来自中国的艺术家Fisher Dai(戴鑫祺)使用Megascans和Unreal engine4创造的4K个人作品。

腾讯万字干货!2020-2021 设计趋势报告:多媒体篇

△ After the mountain Rain(戴鑫祺)

2020年7月14日Unreal Engine 官方发布一条宣传片(Unreal for all Creator )by The Mill,视频展示了Unreal Engine实时渲染在互联网、游戏、电影、电视、建筑、汽车等行业惊人的表现。

https://www.youtube.com/watch?v=6xbxA8tnlbY

腾讯万字干货!2020-2021 设计趋势报告:多媒体篇

△ Unreal for all Creators by The Mill

2. 更真实的自然质感

伴随硬件技术的发展和软件性能各方面的提升,能够更加真实的模拟自然的运动规律和真实的质感;画面趋向更为克制的颜色呈现;在一些品牌广告短片中,使产品的属性与抽象的自然属性相结合,使用相似的自然形态去表现产品的特性,突显产品的特点。

腾讯万字干货!2020-2021 设计趋势报告:多媒体篇

腾讯万字干货!2020-2021 设计趋势报告:多媒体篇

△ 《Bloom》by Hubert Blajer

腾讯万字干货!2020-2021 设计趋势报告:多媒体篇

腾讯万字干货!2020-2021 设计趋势报告:多媒体篇

△ 《Mostly wood》by Nejc Polovsak

腾讯万字干货!2020-2021 设计趋势报告:多媒体篇

腾讯万字干货!2020-2021 设计趋势报告:多媒体篇

△ 《Digital Design Days 2020 titles》

R&D

(Research and development研究与开发)在三维领域是一直有存在的,只是以前大公司才会有的职位,R&D早期是服务存在于影视动画公司的,像工业光魔,迪士尼,皮克斯,这些公司都会有自己的R&D部门,最近几年随着三维技术进步简化,使得更多的人加入这个行业,很多个人R&D艺术家大量的出现在网络社交品平台上,不只是影视动画,还有广告,汽车,消费品等行业。举例R&D在广告行业的应用,艺术家们会对某个产品的材质和物理学的多方面进行研究,并用三维软件(houdini C4D等)视觉化出来,比如下面这个案例的海绵材质物理碰撞模拟。

腾讯万字干货!2020-2021 设计趋势报告:多媒体篇

腾讯万字干货!2020-2021 设计趋势报告:多媒体篇

△ Manvsmachine 《No stress Test》

腾讯万字干货!2020-2021 设计趋势报告:多媒体篇

腾讯万字干货!2020-2021 设计趋势报告:多媒体篇

△ 《A website makes it real》

腾讯万字干货!2020-2021 设计趋势报告:多媒体篇

△ Lukas Vojir R&Dshowreel

3. 突破传统建模方式

使用VR设备进行环境建模工作。

The Loch by Boxfort

腾讯万字干货!2020-2021 设计趋势报告:多媒体篇

腾讯万字干货!2020-2021 设计趋势报告:多媒体篇

4. 2D与3D的结合

同样的环境下,人们对于手绘等真实朴实的质感又有了新的追求,各类动画的制作方式得到了不断优化和流程上的整合,在软件使用上也多了更多选择,使得动画的呈现方式趋向于多种形式结合。

例如常见的3D的场景和镜头运动搭配2D的角色动画,使用非常流畅的镜头运动和丰富细致的3D场景,而视觉表现上保留传统2D动画的一些特性,两者结合形成的一种新奇动画语言,在未来还会继续流行。

腾讯万字干货!2020-2021 设计趋势报告:多媒体篇

腾讯万字干货!2020-2021 设计趋势报告:多媒体篇

△ 《PlusOne Manifesto》 By Martijn Hogenkamp

腾讯万字干货!2020-2021 设计趋势报告:多媒体篇

△ 《Disney XD And Children’s Health》By BLIRP STUDIO

腾讯万字干货!2020-2021 设计趋势报告:多媒体篇

腾讯万字干货!2020-2021 设计趋势报告:多媒体篇

△ 《企鹅诞生记》By ISUX

Blender 是现阶段很热门的开源(免费)三维制作软件,因为是开源的而且功能丰富所以在市面上已经有了很多个人艺术家使用。Grease Pencil只是个方便三维空间中起稿的辅助性工具,在版本2.8之后这个功能被大幅加强成为了一个超级强大的画笔工具 ,发布以后出现了很多优秀的艺术家用它来创作。

腾讯万字干货!2020-2021 设计趋势报告:多媒体篇

腾讯万字干货!2020-2021 设计趋势报告:多媒体篇

△ 《Futuro Darko》 By Craig Farquharson

5. 影视手法的动态呈现

在这个快速变化的时代,我们趋向追求更快更紧张的刺激感受。受到《蜘蛛侠平行宇宙》、《阿丽塔》这样的动画电影的出现所带来的影响,动画广告在一定程度上混合了影视和游戏常用的一些表现方式,比如更大的镜头畸变和游戏风格的未来元素,快节奏的剪辑手法配合游戏电音,能够在短时间内给观众带来强烈的感官刺激与情感体验。

腾讯万字干货!2020-2021 设计趋势报告:多媒体篇

腾讯万字干货!2020-2021 设计趋势报告:多媒体篇

△ 《Gogoro S3 Future Fast》 By MIXCODE STUDIO

6. 复古怀旧风格回归

在充满了未来感科技感的3D大趋势下,颜色丢失,色调分离,质量损失的颗粒质感,低饱和低保真的画面呈现,也开始带来一种新的视觉感受。过往的动画风格与当下科技感、未来感的潮流碰撞又呈现了新的表现方式。这种风格应用在街头潮流的时尚产品的时候,跟以往60或80年代的复古元素相结合,使用新的设计语言去包装整合,能够强化产品的故事感,引起大家强烈的共鸣和代入感,激发大家头脑中那段美好回忆。

腾讯万字干货!2020-2021 设计趋势报告:多媒体篇

腾讯万字干货!2020-2021 设计趋势报告:多媒体篇

△ 《The Legend of IQOO 戦う!鉄拳》 by 茶山有鹿

腾讯万字干货!2020-2021 设计趋势报告:多媒体篇

腾讯万字干货!2020-2021 设计趋势报告:多媒体篇

△ 《MouseQ 滑板俱乐部》By ISUX

腾讯万字干货!2020-2021 设计趋势报告:多媒体篇

△ 《ACE OF SPADES》 by Tony Babel

腾讯万字干货!2020-2021 设计趋势报告:多媒体篇

△ 《Can of worms》 by Tony Babel

7. 更克制的色调与秩序感

在充斥着各式各样的新鲜元素的当下,节奏更快颜色更有吸引力,各类信息视觉都在捕捉你的视线。干净的色调、简单的视觉、真实的肌理、强调秩序感的动画的出现,使得人在视觉上获得了舒适的体验,很大程度上缓解了极速发展的时代所带给人们的焦虑。

腾讯万字干货!2020-2021 设计趋势报告:多媒体篇

腾讯万字干货!2020-2021 设计趋势报告:多媒体篇

△ 《BIC》by Artem ‘Hinz’ Yudin

腾讯万字干货!2020-2021 设计趋势报告:多媒体篇

腾讯万字干货!2020-2021 设计趋势报告:多媒体篇

△ 《Microsoft SharePoint 》

腾讯万字干货!2020-2021 设计趋势报告:多媒体篇

腾讯万字干货!2020-2021 设计趋势报告:多媒体篇

△ 《Beautyrest》 by Tendril

动态图形| Motion Graphic

突破限制的版式

在当下许多海报等平面视觉都有了动态化表现的需求的情况下,动态视觉的加入打破了很多条框和颜色的禁忌,去掉了华丽的修饰性的元素,在内容的呈现上体现了更多的创意,画面中不断变化的图形排列、动态的3D图形和字体起到了非常吸睛的效果。在大量时尚品牌和艺术活动的宣发当中,画面结合强烈的撞色荧光色,以及波谱的拼贴艺术手法,能够更好的表达品牌传递的时尚感和新鲜感。

1. 视觉设计

腾讯万字干货!2020-2021 设计趋势报告:多媒体篇

腾讯万字干货!2020-2021 设计趋势报告:多媒体篇

2. 网页设计

腾讯万字干货!2020-2021 设计趋势报告:多媒体篇

△ CreativeCrew

腾讯万字干货!2020-2021 设计趋势报告:多媒体篇

△ Le Cantiche 1320

腾讯万字干货!2020-2021 设计趋势报告:多媒体篇

△ reed.be

3. 界面设计

腾讯万字干货!2020-2021 设计趋势报告:多媒体篇

△ Olympic Sports Website by Daniel Tan

4. 交互装置|Interactive Installation

近些年来各媒介手段和智能装置应用的兴起,人们已经不满足于单一的视觉感受。电子音乐与拟态三维全息投影相互配合,在不断变化的声光交互光影和空间场景中,能迅速把观众带入多个不同的全新场景的沉静式体验。

AR/VR

腾讯万字干货!2020-2021 设计趋势报告:多媒体篇

腾讯万字干货!2020-2021 设计趋势报告:多媒体篇

△ Fórum Internacional de Gaia 2019

2. 沉浸式视听设计

腾讯万字干货!2020-2021 设计趋势报告:多媒体篇

腾讯万字干货!2020-2021 设计趋势报告:多媒体篇

△ 《JOURNEY》 by NOHLAB

3. 多媒体交互装置

腾讯万字干货!2020-2021 设计趋势报告:多媒体篇

△ 《Teleport 》By PITCH

在信息爆炸的2020年,Motion的流行趋势在不断变化,有的风格将会继续延续,例如在各个场景中大量应用的3D视觉,会在未来更加普及和优化并趋向于更轻量化的新极简主义;有的风格重新回归到大众视野,例如Y2K、色损故障、颗粒等复古风格和逐帧动画,它们与新环境下的设计语言相互碰撞,给人们带来新鲜的视觉感受;在视觉设计领域,网页设计中融入了更多精彩的交互动画,界面的UI设计中体现了更多激动人心动效语言,版式设计有了动画的加持更大胆更具活力且不断突破现有规则。

随着硬件和软件的跟新迭代,同时我们也看到了更多其他方向的可能性,例如实时渲染以及虚拟现实。据资料显示,虚拟现实的市场规模预计将达到447亿美元,复合年增长率为33.47%,这个市场会逐步打开,影视广告等行业将迎来前所未有的技术革命浪潮。大批更智能的应用软件横空出世,学习门槛的降低使得更多艺术家和设计师共同参与,跨媒介的应用将在未来百花齐放。

多媒体的设计趋势在未来会如何发展,我们拭目以待。

文章来源:站酷    作者:腾讯ISUX

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



如何从零开始设计一个购物网站?送你这份高手流程!

周周

在这个项目中,主要任务是为旧金山最古老的玩具品牌 Jeffrey’s Toys 设计一个全新的品牌电商网站。

新电商网站最主要的目的是吸引顾客前往线下店铺选购商品,同时也希望通过快递和门店自提的方式来完善线上销售流程,拓展消费群体。

该网站需要巩固品牌核心业务价值观:传统,有趣和创意。该网站还需要通过强调其庞大的库存体量和手工精选商品来将 Jeffrey’s Toys 和其他电子商务零售商区分开。

该网站的主要业务目标包括:

  • 能快速定位商品

  • 每一个商品都有单独的详情页

  • 用户能成功购买一个或多个商品

  • 为爆款商品引流

用户分析

1. 用户画像

谁才是这个网站真正的用户?当我接到这个任务的时候,客户已经绘制出了3个用户画像,每个用户画像都有特定的需求和痛点。

△ 客户绘制的三个用户画像

基于三个用户画像,我确定了该网站要满足的用户需求,同时也考虑了Jeffrey’s Toys的需求。

确定的主要需求是:

  • 通过清晰的商品组织分类来提供流畅的电商购物体验

  • 通过商品搜索使用户能够快速找到想要的商品

  • 通过实用的商品推荐来体现 Jeffrey’s Toys 的专业以及庞大的库存

  • 通过与用户建立品牌关系来树立信任

  • 通过产品细节信息来确保用户选购合适的商品

  • 通过商品评论来辅助用户作出消费决策并允许用户进行商品反馈

  • 通过的下单结账流程简化购买行为并节省用户时间

2. 竞品分析

为了获得启发,我确定了3个主要竞品,特别是旧金山的精品玩具店;还有3个玩具零售市场的间接竞品。直接竞品我分析了包括 Ambassador Toys 、 Amazon 和 TANTRUM 。间接竞品分析了 Lululemon 、Ikea 和 CVS 。竞品分析的目的是比较并找出这些电商网站的共同特征以及 Jeffrey’s Toys 能够脱颖而出的潜在机会。

竞品分析最重要的收获是了解了不同的电商网站的商品选择模块以及网站整体的布局。这些信息有助于巩固我第二阶段的研究。

△ 比较直接竞品和间接竞品的特征

2. 思维导图

在完成竞品分析之后,我把这个项目中用户的需求和客户的需求列成了长长的信息清单。之后我又通过思维导图来理清了在这个项目中用户的需求和客户的需求。思维导图帮助我将信息组织成更清晰的想法,同时在各个想法之间建立层次结构关系。

△ 用来理清信息和想法的思维导图

信息架构

接下来我通过卡片分类法来构造网站的导航系统,卡片分类法是一种利用人们现有思维模型的研究技术。在构造导航系统时,我了解到94种商店中最畅销的商品库存信息。拥有如此庞大的商品库存,就很有必要通过清晰易懂的方式组织、分类库存信息,以便用户能够快速的找到他们想要的商品。

1. 卡片分类

△ 卡片分类的初期阶段

开放式卡片分类:我要求9位参与调研的用户通过他们自己觉得合理的分类规则将94种商品分类,然后给每个类别加上他们认为能够准确描述该类别的标签。这样做的目的是对于网站潜在的商品分类方式及类别名称有一个大致的了解。

封闭式卡片分类:基于开放式卡片分类的结果,我从最常见的类别标签中创建了13个预定类别。然后,我进行了封闭式的卡片分类,我邀请了20位参与调研的用户将商品逐一分类到我之前预定的13种类别中。封闭式卡片分类有助于让我在进行设计之前能够清楚判断这些类别是否符合大部分网站用户的分类逻辑。

△ 通过卡片分类得到的13种商品类别

2. 站点图

根据卡片分类以及竞品分析的结果,我绘制了网站的站点图来明确整个网站的框架结构。这是为了确保所有产品都放置在用户期望的位置,同时使购物体验更加直观。

△ 站点地图

3. 系统架构图

为了对用户将会如何浏览网站有一个全面的概述,我绘制一张系统架构图。这样做的主要目的是为了了解网站应该给用户提供什么功能以及功能拓展的广度。我还通过系统架构图来探索电商网站和实体店铺之间的联系。

△ 系统架构图演示用户将会如何浏览网站

4. 用户流程

在确定了我需要给用户提供的体验「全局」之后,我为每一个用户画像创建了不同的用户流程,通过不同的用户流程来使购物体验更加符合他们的需求。构建用户流程的目的是确定每个用户为了实现各自的预期目标经过的页面和操作步骤。这不仅能使我专注于每个用户的操作,也使我能够在设计网站的时候将功能拆分,以便给用户提供更佳的购物体验。

我绘制的第一个用户流程是关于用户画像是 Jenny 的。Jenny 最主要的目是为自己的孙子购买初级魔术玩具。Jenny 的用户流程(如下图所示)表明了她是如何搜索初级魔术玩具,以及为了成功购买她可能采取的几种不同路径。

△ Jenny 的用户流程

Debbie 和 Jenny 都希望能有一个的下单结账流程,所以很有必要在用户流程中绘制出下单结账流程。下图的用户流程显示了Debbie在选定一个特技自行车之后该如何进行结账下单。通过允许她登录自己的帐号,自动输入她的结账信息来简化她的结账下单流程。

△ Debbie 的用户流程

开发阶段

1. 草稿

在我整理了前期获取的所有信息之后,我就开始着手设计网站。基于之前整理出来的用户流程图,我开始绘制几个主要页面的草图,同时快速思考出几个不同的网站布局方案。之后我邀请3个用户参与了用户调研,以验证这些方案是否同时满足客户和网站用户的需求。

△ 网站首页和品类页面的初稿

2. 线框图

基于用户对草图的反馈和我个人的想法,我开始使用Figma来绘制线框图。整个过程中,根据优先级不同,优先考虑最能满足网站用户的功能。

△ 网站首页和产品详情页的线框图

原型制作

1. 主页

首页我尽可能的保持简洁,避免用户进入网站时被过多的信息干扰而不知所措。

首页放置了全局导航、辅助导航和搜索栏,方便用户快捷的找到自己想要的商品。首页还放置了新品推荐,因为新品推荐能够展示品牌丰富的库存,同时能为用户提供有用的购买建议。我想通过介绍 Jeffrey’s Toys 丰富的历史来树立用户品牌联系,所以在首页我还放置了「关于我们」部分,用户点击还可跳转到具体的介绍页面查看更多关于品牌故事的信息。在页脚,我放置了其他的用户可能需要的信息。除此之外,我还将用户交流系统放在页脚,这样是为了获得用户想法并在店铺进行活动时通过用户参与提高店铺活动氛围。

2. 商品类别页面

当用户点击商店按钮或者是某个类别之后,用户将会前往一个列出了该类别下所有商品的商品列表页。有个用户画像想要给他的孙子买一个合适他孙子年龄的玩具,所以页面需要一个品类筛选器,同时支持商品按照不同的属性例如年龄、价格和品牌分类也很重要。除此之外,我还通过面包屑导航来方便用户定位页面位置。

△ 商品类别页线框图

3. 商品详情页

在商品详情页,我希望能够提供尽可能多的商品详情来确保这个商品是用户想要的。

在页面下方,网站会基于当前商品给用户推荐其他相似商品。在商品详情页,产品评论区占了很大一部分,因为用户可以在产品评论区反馈商品信息,便于树立用户信任,同时用户能够通过其他用户的评论来作出更明智的购买决定。为了简化下单流程,我设计了一个购物车预览页,用户在购物车预览页可以预览商品信息,用户每次将商品加入购物车后都会出现购物车预览页,在购物车预览页面,用户可以很方便的点击「选好了」按钮而进入结账下单流程。

△ 商品详情页线框图

4. 购物车预览页

当用户点击「选好了」按钮或者是购物车按钮,用户将会进入到一个如下图所示的购物车预览页。这是结账下单流程的第一步,用户在购物车可以管理他们所选的商品,作出合适的调整。用户在下单时,可以选择到店自提或者是物流配送,设置到店自提是为了引导用户去 Jeffrey’s Toy的线下店铺,同时降低运输成本。

△ 购物车预览页线框图

5. 结帐页面

客户提供的三个用户画像的主要痛点之一就是想要有一个的结账流程,所以我得确保结账下单体验要尽可能的流畅。作为一个回头客,用户可以选择登录自己的帐号,并自动保存物流信息和支付信息。我决定在一个页面上承载全部的结账流程,这样方便用户在结账流程中任何时刻都能编辑信息。顶部的进度栏显示了下单结账流程有多少步,用户当前在哪一步以及用户还剩下多少步骤。

△ 登录页

△ 下单页

△ 订单查看页

6. 其他画面

我还创建了一些其他页面,例如订单确认页面。订单确认页面明确告知用户结账流程已经完成了,用户还能在订单确认页查看有关订单的其他信息,以供参考。

我创建了一个店铺联系页面,当用户在网站找不到想要的商品时,可以在店铺联系页面查询线下店铺商品情况以及店铺的营业时间、位置和联系电话。

除此之外我还创建了一个「关于页面」用来着重强调 Jeffrey’s Toys 与其他电商网站例如亚马逊的区别。我还在这个页面介绍了Jeffrey’s Toys 长达60年的独特历史,以增强用户对于品牌传统、有趣和创意的商业价值的印象。

△ 订单确认页

△ 联系方式页

△ 公司介绍页

7. 可用性测试

在完成线框图之后,我就开始绘制网站原型以便进行可用性测试。这使我能够评估用户将会如何与网站进行交互,也能让我评估网站是否满足了用户的基本需求。在进行UI设计之前,通过中保真原型进行可用性测试来获取潜在用户真实、关键的反馈是很有必要的,可用性测试还能巩固网站的功能需求。

我邀请了4位用户进行可用性测试,为了让他们吻合用户画像中的用户特征,我指定了三种用户场景来让他们完成测试。这三种用户场景包括:

  • 您需要为您的女儿购买一个玩具。您会怎么查找商品并完成购买。

  • 您可以通过什么方法知道 Jeffrey’s Toys 下个月将要举办的店铺活动。

  • 当您在网站上没有找到您想要的玩具时,您将如何查询并购买。

这些是我通过可用性测试得到的用户反馈:

  • 总体而言,用户能够轻松地找到想要的产品和信息

  • 在结账流程中,促销活动部分会分散用户的注意力

  • 部分用户不想通过电话来查询商品信息,需要提供其他的查询方式。

  • 部分用户觉得前往线下店铺的引导部分还可以增强,目前的引导不够。

△ 可用性测试得到的反馈

8. 后续步骤

这个案例研究展现的只是电商网站设计的开端。后续我将会继续进行可用性测试,并进一步迭代我的设计。如果有更多的时间,我将会着重探索更多从网站引导用户前往线下店铺的方式。我也将会继续丰富网站页面并将Jeffrey’s Toys的视觉风格融入到网站中。下图展示了我最初设想的UI设计风格。UI设计我希望秉持简洁、创意、传统的设计理念。

△ 首页

△ 商品分类页

△ 商品详情页

编辑总结

本文以玩具购物网站这个小案例,清晰地记录了一个较为完整的产品设计流程。不论是设计方法,还是记录方法,都可以借鉴运用在工作、汇报和作品集上。

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

日历

链接

个人资料

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

存档