设计每日一贴

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

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

constemSize=2048;constascent=1854;constdescent=434;constcapitalHeight=1467;

//计算前需要已知给定的字体大小constfontSize=FONT_SIZE;//根据文字大小,求得文字的偏移constverticalAlign=((ascent-descent-capitalHeight)/emSize)*fontSize;return(TEXT)

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

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

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

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

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

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

//utils.jsexportfunctionfuncA(){return"Hellonamedexport!";}exportdefaultfunctionfuncB(){return"Hellodefaultexport!";}这里有两个导出。

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

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

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

//consumer.jsimport{funcA}from"./util.js";这种对应我们的命名导入方式.

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

//consumer.jsimport{funcA}from"./util.js";当然,我们可以导入同时导入命名和默认的:

//consumer.jsimportfuncB,{funcA}from"./util.js";funcB();funcA();我们也可以用星号导入整个模块:

import*asmyModulefrom'./util.js';myModule.funcA();myModule.default();注意,这里要使用默认到处的方法是使用default()而不是funcB()。

从远程模块导入:

ECMAScriptmodulesinthebrowser

Theresultis:

import{appendResult}from"./myModule.js";constel=document.getElementById("el");appendResult(el);

myModule.js内容如下:

exportfunctionappendResult(element){constresult=Math.random();element.innerText+=result;}动态导入ES模块是静态的,这意味着我们不能在运行时更改导入。随着2020年推出的动态导入(dynamicimports),我们可以动态加载代码来响应用户交互(webpack早在ECMAScript2020推出这个特性之前就提供了动态导入)。

考虑下面的代码:

DynamicimportsLoad!

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

//util.jsexportfunctionfuncA(){console.log("Hellonamedexport!");}exportdefaultfunctionfuncB(){console.log("Hellodefaultexport!");}为了动态导入util.js模块,我们可以点击按钮在去导入:

/loader.jsconstbtn=document.getElementById("btn");btn.addEventListener("click",()=>{//loadsnamedexportimport("./util.js").then(({funcA})=>{funcA();});});这里使用解构的方式,取出命名导出funcA方法:

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

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

//loader.jsconstbtn=document.getElementById("btn");btn.addEventListener("click",()=>{import("./util.js").then((module)=>{module.default();});});当作为一个整体导入一个模块时,我们可以使用它的所有导出

//loader.jsconstbtn=document.getElementById("btn");btn.addEventListener("click",()=>

{//loadsentiremodule//useseverythingimport("./util.js").then((module)=>{module.funcA();module.default();

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

constloadUtil=()=>import("./util.js");constbtn=document.getElementById("btn");btn.addEventListener("click",()=>{//});loadUtil返回的是一个promise,所以我们可以这样操作

constloadUtil=()=>import("./util.js");constbtn=document.getElementById("btn");btn.addEventListener("click",()=>{loadUtil().then(module=>{module.funcA();module.default();})})动态导入看起来不错,但是它们有什么用呢?

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

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

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

{"name":"Jules","age":43}现在,我们需要动态导入该文件以响应某些用户交互。

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

constloadPerson=()=>import('./person.json');constbtn=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:

constloadUtil=()=>import("./util.js");constbtn=document.getElementById("btn");btn.addEventListener("click",async()=>{constutilsModule=awaitloadUtil();utilsModule.funcA();utilsModule.default();})动态导入的名字使用import()导入模块时,可以按照自己的意愿命名它,但要调用的方法名保持一致:

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

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

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

一、!非空断言操作符

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

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

1.1忽略undefined和null类型

functionmyFunc(maybeString:string|undefined|null){//Type'string|null|undefined'isnotassignabletotype'string'.//Type'undefined'isnotassignabletotype'string'.constonlyString:string=maybeString;//ErrorconstignoreUndefinedAndNull:string=maybeString!;//Ok}

1.2调用函数时忽略undefined类型

typeNumGenerator=()=>number;functionmyFunc(numGenerator:NumGenerator|undefined){//Objectispossibly'undefined'.(2532)//Cannotinvokeanobjectwhichispossibly'undefined'.(2722)constnum1=numGenerator();//Errorconstnum2=numGenerator!();//OK}

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

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

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

"usestrict";consta=undefined;constb=a;console.log(b);

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

二、.运算符

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

obj.prop

obj.[expr]

arr.[index]func.(args)

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

constval=a.b;

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

varval=a===null||a===void0void0:a.b;

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

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

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

*

*if(

*a===null||a===void0

*void0:a.b){

*}

*/

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

2.1可选元素访问

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

functiontryGetArrayElement(arr:T[],index:number=0){returnarr.[index];

}

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

"usestrict";functiontryGetArrayElement(arr,index){if(index===void0){index=0;}returnarr===null||arr===void0void0:arr[index];

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

2.2可选链与函数调用

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

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

letresult=obj.customMethod.();

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

varresult=(_a=obj.customMethod)===null||_a===void0void0:_a.call(obj);

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

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

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

三、空值合并运算符

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

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

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

constfoo=null'defaultstring';console.log(foo);//输出:"defaultstring"constbaz=042;console.log(baz);//输出:0

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

"usestrict";var_a,_b;varfoo=(_a=null)!==null&&_a!==void0_a:'defaultstring';

console.log(foo);//输出:"defaultstring"varbaz=(_b=0)!==null&&_b!==void0_b:42;

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

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

3.1短路

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

functionA(){console.log('Awascalled');returnundefined;}functionB(){console.log('Bwascalled');returnfalse;}functionC(){console.log('Cwascalled');return"foo";}console.log(A()C());console.log(B()C());

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

Awascalled

Cwascalled

foo

Bwascalled

false

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

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

//'||'and''operationscannotbemixedwithoutparentheses.(5076)null||undefined"foo";//raisesaSyntaxError//'&&'and''operationscannotbemixedwithoutparentheses.(5076)true&&undefined"foo";//raisesaSyntaxError

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

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

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

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

interfaceCustomer{

name:string;

city:string;

}letcustomer:Customer={

name:"Semlinker"};letcustomerCity=customer.city"Unknowncity";console.log(customerCity);//输出:Unknowncity

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

四、:可选属性

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

interfacePerson{

age:number;

}letsemlinker:Person={

name:"semlinker",

age:33,

};

//Property'age'ismissingintype'{name:string;}'butrequiredintype'Person'.(2741)letlolo:Person={//Errorname:"lolo"}

}letlolo:Person={

name:"lolo"}

4.1工具类型

4.1.1Partial

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

interfacePullDownRefreshConfig{

threshold:number;

stop:number;

}/**

*typePullDownRefreshOptions={

*threshold:number|undefined;

*stop:number|undefined;

*/typePullDownRefreshOptions=Partial

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

/**

*MakeallpropertiesinToptional

*/typePartial={

[PinkeyofT]:T[P];

4.1.2Required

}typePullDownRefreshOptions=Partial/**

*typePullDownRefresh={

*threshold:number;

*stop:number;

*/typePullDownRefresh=Required>

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

*MakeallpropertiesinTrequired

*/typeRequired={

[PinkeyofT]-:T[P];

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

五、&运算符

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

typePartialPointX={x:number;};typePoint=PartialPointX&{y:number;};letpoint:Point={

x:1,

y:1}

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

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

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

interfaceX{

c:string;

d:string;

}interfaceY{

c:number;

e:string}typeXY=X&Y;typeYX=Y&X;letp:XY;letq: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的类型都是基本数据类型,那么如果是非基本数据类型的话,又会是什么情形。我们来看个具体的例子:

interfaceD{d:boolean;}interfaceE{e:string;}interfaceF{f:number;}interfaceA{x:D;}interfaceB{x:E;}interfaceC{x:F;}typeABC=A&B&C;letabc:ABC={

x:{

d:true,

e:'semlinker',

f:666}

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

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

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

六、|分隔符

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

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

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

sayHello("semlinker");

sayHello(undefined);

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

letnum:1|2=1;typeEventNames='click'|'scroll'|'mousemove';

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

6.1类型保护

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

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

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

6.1.1in关键字

interfaceAdmin{

privileges:string[];

}interfaceEmployee{

startDate:Date;

}typeUnknownEmployee=Employee|Admin;functionprintEmployeeInformation(emp:UnknownEmployee){console.log("Name:"+emp.name);if("privileges"inemp){console.log("Privileges:"+emp.privileges);

}if("startDate"inemp){console.log("StartDate:"+emp.startDate);

6.1.2typeof关键字

functionpadLeft(value:string,padding:string|number){if(typeofpadding==="number"){returnArray(padding+1).join("")+value;

}if(typeofpadding==="string"){returnpadding+value;

}thrownewError(`Expectedstringornumber,got'${padding}'.`);

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

6.1.3instanceof关键字

interfacePadder{

getPaddingString():string;

}classSpaceRepeatingPadderimplementsPadder{constructor(privatenumSpaces:number){}

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

}classStringPadderimplementsPadder{constructor(privatevalue:string){}

getPaddingString(){returnthis.value;

}letpadder:Padder=newSpaceRepeatingPadder(6);if(padderinstanceofSpaceRepeatingPadder){//padder的类型收窄为'SpaceRepeatingPadder'}

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

functionisNumber(x:any):xisnumber{returntypeofx==="number";

}functionisString(x:any):xisstring{returntypeofx==="string";

七、_数字分隔符

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

constinhabitantsOfMunich=1_464_301;constdistanceEarthSunInKm=149_600_000;constfileSystemPermission=0b111_111_000;constbytes=0b1111_10101011_11110000_00001101;

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

"usestrict";varinhabitantsOfMunich=1464301;vardistanceEarthSunInKm=149600000;varfileSystemPermission=504;varbytes=262926349;

7.1使用限制

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

//Numericseparatorsarenotallowedhere.(6188)3_.141592//Error3._141592//Error//Numericseparatorsarenotallowedhere.(6188)1_e10//Error1e_10//Error//Cannotfindname'_126301'.(2304)_126301//Error//Numericseparatorsarenotallowedhere.(6188)126301_//Error//Cannotfindname'b111111000'.(2304)//Anidentifierorkeywordcannotimmediatelyfollowanumericliteral.(1351)0_b111111000//Error//Numericseparatorsarenotallowedhere.(6188)0b_111111000//Error

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

//Multipleconsecutivenumericseparatorsarenotpermitted.(6189)123__456//Error

7.2解析分隔符

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

Number()

parseInt()

parseFloat()

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

Number('123_456')NaNparseInt('123_456')123parseFloat('123_456')123

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

constRE_NON_DIGIT=/[^0-9]/gu;functionremoveNonDigits(str){

str=str.replace(RE_NON_DIGIT,'');returnNumber(str);

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

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

八、语法

8.1TypeScript断言

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

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

类型断言有两种形式:

8.1.1“尖括号”语法

letsomeValue:any="thisisastring";letstrLength:number=(someValue).length;

8.1.2as语法

letsomeValue:any="thisisastring";letstrLength:number=(someValueasstring).length;

8.2TypeScript泛型

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

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

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

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

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

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

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

functionidentity(value:T,message:U):T{console.log(message);returnvalue;

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

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

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

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

九、@XXX装饰器

9.1装饰器语法

@Plugin({

pluginName:'Device',

plugin:'cordova-plugin-device',

pluginRef:'device',

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

})@Injectable()exportclassDeviceextendsIonicNativePlugin{}

var__decorate=(this&&this.__decorate)||function(decorators,target,key,desc){varc=arguments.length,r=c<3target:desc===nulldesc=Object.getOwnPropertyDescriptor(target,key):desc,d;if(typeofReflect==="object"&&typeofReflect.decorate==="function")r=Reflect.decorate(decorators,target,key,desc);elsefor(vari=decorators.length-1;i>=0;i--)if(d=decorators[i])r=(c<3d(r):c>3d(target,key,r):d(target,key))||r;returnc>3&&r&&Object.defineProperty(target,key,r),r;

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

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

Device=__decorate([

}),

Injectable()

],Device);returnDevice;

}(IonicNativePlugin));

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

9.2装饰器的分类

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

9.2.1类装饰器

declaretypeClassDecorator=(

target:TFunction

)=>TFunction|void;

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

target:TFunction-被装饰的类

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

functionGreeter(target:Function):void{

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

}@GreeterclassGreeting{constructor(){//内部实现}

}letmyGreeting=newGreeting();

myGreeting.greet();//consoleoutput:'HelloSemlinker!';

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

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

9.2.2属性装饰器

declaretypePropertyDecorator=(target:Object,

propertyKey:string|symbol)=>void;

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

target:Object-被装饰的类

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

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

functionlogProperty(target:any,key:string){deletetarget[key];constbackingField="_"+key;Object.defineProperty(target,backingField,{

writable:true,

enumerable:true,

configurable:true});//propertygetterconstgetter=function(this:any){constcurrVal=this[backingField];console.log(`Get:${key}=>${currVal}`);returncurrVal;

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

};//CreatenewpropertywithgetterandsetterObject.defineProperty(target,key,{get:getter,set:setter,

configurable:true});

}classPerson{@logPropertypublicname:string;constructor(name:string){this.name=name;

}constp1=newPerson("semlinker");

p1.name="kakuqo";

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

Set:name=>semlinkerSet:name=>kakuqo

9.2.3方法装饰器

declaretypeMethodDecorator=(target:Object,propertyKey:string|symbol,

descriptor:TypePropertyDescript)=>TypedPropertyDescriptor|void;

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

propertyKey:string|symbol-方法名

descriptor:TypePropertyDescript-属性描述符

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

functionLogOutput(tarage:Function,key:string,descriptor:any){letoriginalMethod=descriptor.value;letnewMethod=function(...args:any[]):any{letresult:any=originalMethod.apply(this,args);if(!this.loggedOutput){this.loggedOutput=newArray();

}this.loggedOutput.push({

method:key,

parameters:args,

output:result,

timestamp:newDate()

});returnresult;

descriptor.value=newMethod;

}classCalculator{@LogOutputdouble(num:number):number{returnnum*2;

}letcalc=newCalculator();

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

9.2.4参数装饰器

declaretypeParameterDecorator=(target:Object,propertyKey:string|symbol,

parameterIndex:number)=>void

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

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

functionLog(target:Function,key:string,parameterIndex:number){letfunctionLogged=key||target.prototype.constructor.name;console.log(`Theparameterinposition${parameterIndex}at${functionLogged}has

beendecorated`);

}classGreeter{

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

}//consoleoutput:Theparameterinposition0//atGreeterhasbeendecorated

十、#XXX私有字段

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

classPerson{

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

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

}letsemlinker=newPerson("Semlinker");

semlinker.#name;//~~~~~//Property'#name'isnotaccessibleoutsideclass'Person'//becauseithasaprivateidentifier.

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

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

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

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

10.1私有字段与private的区别

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

classPerson{constructor(privatename:string){}

}letperson=newPerson("Semlinker");console.log(person.name);

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

Property'name'isprivateandonlyaccessiblewithinclass'Person'.(2341)

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

console.log((personasany).name);

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

varPerson=/**@class*/(function(){functionPerson(name){this.name=name;

}returnPerson;

}());varperson=newPerson("Semlinker");console.log(person.name);

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

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

"usestrict";var__classPrivateFieldSet=(this&&this.__classPrivateFieldSet)

||function(receiver,privateMap,value){if(!privateMap.has(receiver)){thrownewTypeError("attemptedtosetprivatefieldonnon-instance");

privateMap.set(receiver,value);returnvalue;

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

||function(receiver,privateMap){if(!privateMap.has(receiver)){thrownewTypeError("attemptedtogetprivatefieldonnon-instance");

}returnprivateMap.get(receiver);

};var_name;classPerson{constructor(name){

_name.set(this,void0);

__classPrivateFieldSet(this,_name,name);

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

_name=newWeakMap();

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

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

基本使用:

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

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

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

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

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

//Getstoredbookstore.remove(‘book’);//Removestoredbookstore.clearAll();//Clearallkeys

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

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

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

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

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

letbarn=newBarn(localStorage);//原始类型barn.set(‘name’,‘Amy’);letname=barn.get(‘name’);

//Amy//Listbarn.lpush(‘names’,‘Amy’);

barn.lpush(‘names’,‘James’);letname1=barn.rpop(‘names’);//Amyletname2=barn.rpop(‘names’);

//James

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

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

letstorage=crypto;letbook={title:‘JavaScript’,price:13};storage.set(‘book’,book,function(error,results){if(error){throwerror;}//Dosomething});storage.get(‘book’,function(error,results){if(error){throwerror;}//Dosomething});

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

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

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

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

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

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中的回调才会被加入至微任务队列。

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

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

letp=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后输出,但是实际情况却是相反的。

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

接下来我们换个写法:

letp=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");return1;}).then(()=>{console.log("then1-2");});}).then(()=>{console.log("then2");}).then(()=>{console.log("then3");}).then(()=>{console.log("then4");});这题肯定是简单的,记住第一个结论就能得出答案,以下是解析:

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

Promise.resolve().then(()=>{console.log("then1");Promise.resolve().then(()=>{console.log("then1-1");returnPromise.resolve();}).then(()=>{console.log("then1-2");});}).then(()=>{console.log("then2");}).then(()=>{console.log("then3");}).then(()=>{console.log("then4");});当我们returnPromise.resolve()时,你猜猜then1-2会何时打印了?

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

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

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

if(xinstanceofMyPromise){if(x.currentState===PENDING){}else{x.then(resolve,reject);}return;}上述代码节选自手写Promise实现。

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

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

1.形体表现

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

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

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

2.光影表现

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

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

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

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

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

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

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

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

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

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

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

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

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

第一步:确认轮廓造型

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

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

第二步:完善图形细节

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

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

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

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

第四步:增加高光效果

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

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

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

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

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

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

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

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

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

字体是界面设计的基石

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

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

1.族类GenericFamily

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

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

衬线体

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

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

黑体

黑体是无衬线字体,特点是笔画没有额外的装饰,且笔画的粗细差不多。相比严肃的衬线体,简单干净的无衬线体给人一种休闲轻松的感觉。因此大多数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

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

8.字符选项Textoptions

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

注意英文大写

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

9.全角与半角Full-widthandhalf-width

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

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

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

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

1.iOS字体规范

可用字体

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

字体设置

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

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

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

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

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

iOS更全面的文字设置

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

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

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

2.Android字体规范

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

字体类型

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

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

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

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

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

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

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

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

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

3.话题扩展

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

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

OPPO的「OPPOSans」:

三星的「SamsungOne」:

字体基础知识小结

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

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

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

1.字间距与字偶间距

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

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

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

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

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

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

2.字间距的三种形式

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

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

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

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

3.西文词距

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

4.标点避头尾

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

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.段间距

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

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

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

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

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

大小对比

颜色对比

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

还有虾米音乐的驾驶模式

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

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

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

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

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

1.文字在代码中的实现

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

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

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

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

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

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

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

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

3.文本框行高的问题

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

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

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

去年六月,MicrosoftDesign赞助了一个新的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插件「SketchMeasure」为例,它是一款十分智能的标注插件,主要功能包含两大块:标注和规范。

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

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

△SketchMeasure导出标注的网页界面

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

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

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

△视觉检视表示例

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

参考文献:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1.微距拍摄

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

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

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

2.升格拍摄

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

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

3.无人机航拍

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

4.高质量色彩呈现

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

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

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

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

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

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

△Unrealengine4

△VisualASMRRockbyOnesal

△Unrealengine5

虚幻5

2020年5月13号,Unrealengine官网发布了UnrealEngine5并在Playstation5上运行进行展示Demo(「LumenintheLandofNanite」)

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

Nanite

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

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

Lumen

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

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

△Rebirth

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

MegascansEcosystem:GivingmorePowertoArtists(Megascans生态系统)

△AfterthemountainRain

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

△AfterthemountainRain(戴鑫祺)

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

△UnrealforallCreatorsbyTheMill

△《Bloom》byHubertBlajer

△《Mostlywood》byNejcPolovsak

△《DigitalDesignDays2020titles》

R&D

△Manvsmachine《NostressTest》

△《Awebsitemakesitreal》

△LukasVojirR&Dshowreel

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

TheLochbyBoxfort

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

△《PlusOneManifesto》ByMartijnHogenkamp

△《DisneyXDAndChildren’sHealth》ByBLIRPSTUDIO

△《企鹅诞生记》ByISUX

△《FuturoDarko》ByCraigFarquharson

5.影视手法的动态呈现

△《GogoroS3FutureFast》ByMIXCODESTUDIO

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

△《TheLegendofIQOO戦う!鉄拳》by茶山有鹿

△《MouseQ滑板俱乐部》ByISUX

△《ACEOFSPADES》byTonyBabel

△《Canofworms》byTonyBabel

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

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

△《BIC》byArtem‘Hinz’Yudin

△《MicrosoftSharePoint》

△《Beautyrest》byTendril

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

△CreativeCrew

△LeCantiche1320

△reed.be

△OlympicSportsWebsitebyDanielTan

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

AR/VR

△FórumInternacionaldeGaia2019

△《JOURNEY》byNOHLAB

△《Teleport》ByPITCH

多媒体的设计趋势在未来会如何发展,我们拭目以待。

在这个项目中,主要任务是为旧金山最古老的玩具品牌Jeffrey’sToys设计一个全新的品牌电商网站。

新电商网站最主要的目的是吸引顾客前往线下店铺选购商品,同时也希望通过快递和门店自提的方式来完善线上销售流程,拓展消费群体。

该网站的主要业务目标包括:

1.用户画像

谁才是这个网站真正的用户?当我接到这个任务的时候,客户已经绘制出了3个用户画像,每个用户画像都有特定的需求和痛点。

△客户绘制的三个用户画像

基于三个用户画像,我确定了该网站要满足的用户需求,同时也考虑了Jeffrey’sToys的需求。

确定的主要需求是:

2.竞品分析

为了获得启发,我确定了3个主要竞品,特别是旧金山的精品玩具店;还有3个玩具零售市场的间接竞品。直接竞品我分析了包括AmbassadorToys、Amazon和TANTRUM。间接竞品分析了Lululemon、Ikea和CVS。竞品分析的目的是比较并找出这些电商网站的共同特征以及Jeffrey’sToys能够脱颖而出的潜在机会。

竞品分析最重要的收获是了解了不同的电商网站的商品选择模块以及网站整体的布局。这些信息有助于巩固我第二阶段的研究。

△比较直接竞品和间接竞品的特征

2.思维导图

在完成竞品分析之后,我把这个项目中用户的需求和客户的需求列成了长长的信息清单。之后我又通过思维导图来理清了在这个项目中用户的需求和客户的需求。思维导图帮助我将信息组织成更清晰的想法,同时在各个想法之间建立层次结构关系。

△用来理清信息和想法的思维导图

接下来我通过卡片分类法来构造网站的导航系统,卡片分类法是一种利用人们现有思维模型的研究技术。在构造导航系统时,我了解到94种商店中最畅销的商品库存信息。拥有如此庞大的商品库存,就很有必要通过清晰易懂的方式组织、分类库存信息,以便用户能够快速的找到他们想要的商品。

1.卡片分类

△卡片分类的初期阶段

开放式卡片分类:我要求9位参与调研的用户通过他们自己觉得合理的分类规则将94种商品分类,然后给每个类别加上他们认为能够准确描述该类别的标签。这样做的目的是对于网站潜在的商品分类方式及类别名称有一个大致的了解。

封闭式卡片分类:基于开放式卡片分类的结果,我从最常见的类别标签中创建了13个预定类别。然后,我进行了封闭式的卡片分类,我邀请了20位参与调研的用户将商品逐一分类到我之前预定的13种类别中。封闭式卡片分类有助于让我在进行设计之前能够清楚判断这些类别是否符合大部分网站用户的分类逻辑。

△通过卡片分类得到的13种商品类别

2.站点图

根据卡片分类以及竞品分析的结果,我绘制了网站的站点图来明确整个网站的框架结构。这是为了确保所有产品都放置在用户期望的位置,同时使购物体验更加直观。

△站点地图

3.系统架构图

为了对用户将会如何浏览网站有一个全面的概述,我绘制一张系统架构图。这样做的主要目的是为了了解网站应该给用户提供什么功能以及功能拓展的广度。我还通过系统架构图来探索电商网站和实体店铺之间的联系。

△系统架构图演示用户将会如何浏览网站

4.用户流程

我绘制的第一个用户流程是关于用户画像是Jenny的。Jenny最主要的目是为自己的孙子购买初级魔术玩具。Jenny的用户流程(如下图所示)表明了她是如何搜索初级魔术玩具,以及为了成功购买她可能采取的几种不同路径。

△Jenny的用户流程

△Debbie的用户流程

1.草稿

在我整理了前期获取的所有信息之后,我就开始着手设计网站。基于之前整理出来的用户流程图,我开始绘制几个主要页面的草图,同时快速思考出几个不同的网站布局方案。之后我邀请3个用户参与了用户调研,以验证这些方案是否同时满足客户和网站用户的需求。

△网站首页和品类页面的初稿

2.线框图

基于用户对草图的反馈和我个人的想法,我开始使用Figma来绘制线框图。整个过程中,根据优先级不同,优先考虑最能满足网站用户的功能。

△网站首页和产品详情页的线框图

1.主页

首页我尽可能的保持简洁,避免用户进入网站时被过多的信息干扰而不知所措。

首页放置了全局导航、辅助导航和搜索栏,方便用户快捷的找到自己想要的商品。首页还放置了新品推荐,因为新品推荐能够展示品牌丰富的库存,同时能为用户提供有用的购买建议。我想通过介绍Jeffrey’sToys丰富的历史来树立用户品牌联系,所以在首页我还放置了「关于我们」部分,用户点击还可跳转到具体的介绍页面查看更多关于品牌故事的信息。在页脚,我放置了其他的用户可能需要的信息。除此之外,我还将用户交流系统放在页脚,这样是为了获得用户想法并在店铺进行活动时通过用户参与提高店铺活动氛围。

2.商品类别页面

当用户点击商店按钮或者是某个类别之后,用户将会前往一个列出了该类别下所有商品的商品列表页。有个用户画像想要给他的孙子买一个合适他孙子年龄的玩具,所以页面需要一个品类筛选器,同时支持商品按照不同的属性例如年龄、价格和品牌分类也很重要。除此之外,我还通过面包屑导航来方便用户定位页面位置。

△商品类别页线框图

3.商品详情页

在商品详情页,我希望能够提供尽可能多的商品详情来确保这个商品是用户想要的。

△商品详情页线框图

4.购物车预览页

当用户点击「选好了」按钮或者是购物车按钮,用户将会进入到一个如下图所示的购物车预览页。这是结账下单流程的第一步,用户在购物车可以管理他们所选的商品,作出合适的调整。用户在下单时,可以选择到店自提或者是物流配送,设置到店自提是为了引导用户去Jeffrey’sToy的线下店铺,同时降低运输成本。

△购物车预览页线框图

5.结帐页面

△下单页

△订单查看页

6.其他画面

我还创建了一些其他页面,例如订单确认页面。订单确认页面明确告知用户结账流程已经完成了,用户还能在订单确认页查看有关订单的其他信息,以供参考。

除此之外我还创建了一个「关于页面」用来着重强调Jeffrey’sToys与其他电商网站例如亚马逊的区别。我还在这个页面介绍了Jeffrey’sToys长达60年的独特历史,以增强用户对于品牌传统、有趣和创意的商业价值的印象。

△订单确认页

△联系方式页

△公司介绍页

7.可用性测试

我邀请了4位用户进行可用性测试,为了让他们吻合用户画像中的用户特征,我指定了三种用户场景来让他们完成测试。这三种用户场景包括:

THE END
1.免费教学课程大放送:探索那些值得一试的软件平台如果你对设计感兴趣,那么虎课绝对是一个不容错过的软件平台。在这里,你可以找到许多关于PS、绘画、3D等方面的设计课程。这些课程由专业的设计师亲自授课,讲解详细且实用。通过学习这些课程,你可以快速提升自己的设计能力和审美水平。而且,虎课还提供了打卡兑换课程的活动,让你在学习的同时也能获得额外的奖励。 https://www.pbids.com/aboutUs/pbidsNews/1861300114477387776
2.重庆平面设计线上培训,从基础到精通,全程指导平面设计前景 若要探讨当前最具发展潜力的互联网行业,平面设计无疑是一个不可忽视的重要领域。据多项市场调查显示,绝大多数的企业在当今市场上均对平面设计有着迫切的需求。因此,掌握平面设计技能,无论身处何地,都能拥有广阔的就业前景和受欢迎的程度。 http://www.feifanedu.cn/hyzx/ui/2024/1218/436210.html
3.平面设计主要是做美术排版平面广告海报灯箱等的设计制作4、线上专业网课,按时参加考试 5、考试通过,领取证书网查 四、 考试内容 考试专业理论知识,以选择、判断、简答等题型为主,满分100分,60分以上及格通过,好好看看题库,基本上通过考试没有问题。 五、 相关职业: 网站美工人员、设计助理、平面设计师、资深设计师、美术指导、设计总监。平面设计师可以会利用字体https://www.meipian.cn/58ugmtht
4.自己单干赚钱方法(稳定挣钱的方法)赚钱方法开设网课或线上培训 从事设计类工作 做电商代运营 在当今社会,越来越多的人选择自己单干,希望通过创业或自由职业等方式实现财富的积累,如何找到一个稳定挣钱的赚钱方法,是许多人关心的问题,本文将为大家介绍一些自己单干赚钱的稳定方法,帮助大家在创业或自由职业的道路上走得更远。 http://www.ixiaozhu.net/post/27719.html
5.平面设计培训网课费用多少平面设计培训网课费用多少 要知道平面设计可是大多数设计类型的鼻祖,所以在设计行业中,虽然有各式各样的设计类型,可是在他们之中都会有一定的相通之处,毕竟大多数的设计都是由平面设计衍生而出的。在加上目前各个企业都需要平面设计人才,所以现在学平面设计未来的发展前景还是非常可观的。https://wap.hxsd.com/content/11148/
6.[线上直播字体设计]图片免费下载线上直播字体设计素材千图网为您找到60张线上直播字体设计相关素材,千图网还提供线上直播字体设计图片,线上直播字体设计素材, 线上直播字体设计模板等免费下载服务,千图网是国内专业创意营销服务交易平台,一站式解决企业营销数字化、协同化,实现营销转化效果增长!https://m.58pic.com/tupian/xianshangzhibozitisheji.html
7.石家庄远拓单招学院的线上网课可靠吗口碑好不好随着教育行业的不断发展,线上网课成为了许多学生备考的途径,石家庄远拓单招学院在位河北省内较为知名的单招培训机构,凭借着专业的师资和教学,吸引了不少学生的关注,下面,就来了解一下石家庄远拓单招学院的线上网课可靠吗 口碑好不好的相关介绍吧。 1石家庄远拓单招学院的线上网课可靠吗 https://www.h3m.cn/news/40483.html
8.设计艺术史超星尔雅学习通网课答案5、【单选题】“新艺术”运动在平面设计上的发展与商业的莲勃发展、出版业的发展及 ( )技术的发展密切相关的。 A、制版 B、套色 C、印刷 D、装饰6、【单选题】麦金托什设计的( ),是其风格特征的集中体现,其中包括了“新艺术”运动的风格,也包含了现代主义的特点,是20世纪设计的经典之作。 A、圣家族教堂 http://changchun.ehqc.cn/html/44_68.html
9.2022年在线网课学习课堂《平面设计(珠海科技学院)》单元测试考核2024年在线网课学习课堂《平面设计(珠海科技学院 )》单元测试考核答案.pdf,注:不含主观题 第 1 题 多选题 (2 分) Photoshop 处理图像之前,首先必须打开该图像,以下哪些方式能够使 Photoshop 正确打开图像? A 使用快捷键 Ctrl+OB 双击文档窗口的空白区域 C 单击菜单 “https://max.book118.com/html/2022/0801/8107113061004124.shtm
10.腾讯课堂直播赠送千节免费网课,助力职场“追梦人”技能提升此外,广东00后也加入了线上技能学习的行列,并以35%的比例超越90后,成为新一代的在线终身教育的主力军。 助力广东“打工人”升级做“追梦人”,腾讯课堂免费送千节优质网课 疫情下广东人在线学习热情的激发只是一个缩影,作为国内最大的在线终身教育平台,腾讯课堂已在该领域深耕六年,累计服务学员超4亿,每周在线学习人https://www.donews.com/news/detail/4/3124569.html
11.如何才能从事高自由度的职业?推荐25个自由度比较高的职业14、LOGO设计师 / 海报设计师 / 平面设计师 / 广告设计师等 这些设计师,有些是受雇于公司的,比较厉害的,可能在工作方法、工作团队、服务对象等方面有一定自由度。有些是独立设计师,在工作方法、工作时间、工作地点、工作团队、服务对象等方面,都有比较高的自由度。 http://www.zzfmdn.com/article/556072
12.成都中公优就业有网络课程吗有的。中公优就业主要开设的是IT课程,比如:平面设计、UI设计、网店运营、Java等,采用的是线上线下两种教学方式,线上课程全年授课报名之后还可以领取到每节课的录播课程用来进行课后巩固,线下课程主要有周末班和全日制班,为学员提供多种选择。 开设的有哪些网课? https://www.youkee.com/wenda/3897.html
13.10177设计基醇试资料大全自考教材真题答案题库网课本栏目提供自学考试10177设计基础各类考试资料,包括设计基础自考教材及购买方式、设计基础复习资料、历年真题及答案、模拟试题、自考题库、搜题答案、考试大纲、备考方法、视频网课等自考10177设计基础考试资料,以供考生们复习使用。https://www.zikaosw.cn/zkkm/3277.html
14.高新网课+线下课半价or7折,你pick哪个?半价还是7折,你pick哪个? 报名咨询18959788535 远太电脑培训学校,创建于1997年,教学经验丰富,全电脑教学,学员一人一机实践操作,教会为止,两年内可申请重修课程,完全免费! 现开设有平面设计培训班、PS图片处理培训、AI软件培训、室内外装修设计、CAD制图培训、3D立体绘图培训、会计实务班、会计考证班、2020年会计初级职称http://www.ytxd.com/h-nd-414.html
15.小学生上网课用什么软件APP推荐小学生上网课用什么软件下载1、《钉钉》钉钉这款软件是一款专业的线上沟通打卡软件 10-11 目前线上网课教学用什么软件好 网课教学app下载排行 04-27 可以上网课的软件有哪些 免费上网课的软件推荐 02-02 可以上网课的软件免费下载 上网课的app排行榜 12-14 老师上网课用什么软件 好用的网课APP推荐https://www.wandoujia.com/bangdan/556107/
16.设计之美网课答案答案的文章设计之美网课答案 从本质上说,产品设计的终极目标是给人以( ) 马赛公寓具有( )的精神特征。 迪特·拉姆斯提倡less, but better.被称为“新功能主义”,其减约单纯的电子产品设计具有新理性主义风格。 新艺术运动**的平面设计师是( ) 以( )为特征的理性主义设计被索尼公司、菲利普公司等大集团采用。 https://wkzuixindaan.blog.bokee.net/bloggermodule/blog_viewblog.do?id=41641653
17.刘治治:设计师开店,就是个坑城南唠嗑Vol.5TOPYS创意内容平台刘治治,知名平面设计师,立入禁止&拂一个山坡联合创始人。设计作品曾获东京字体指导俱乐部提名奖,纽约国际艺术指导俱乐部提名奖,日本“Rong”平面设计竞赛最高奖等众多奖项,并受邀参加国内外众多平面设计展览。 点这里跳转收听城南唠嗑Vol.5。 2:00 “央美男神”宅家给学生上网课是一种什么样的体验? https://www.topys.cn/article/30267.html