首页

让你的 commit 更有价值

seo达人

提交规范

AngularJS 在开发者文档中关于 git commit 的指导说明,提到严格的 git commit 格式规范可以在浏览项目历史的过程中看到更易读的信息,并且能用 git commit 的信息直接生成 AngularJS 的 change log 。


commit messages 格式规范

commit messages 由 header 、body 、footer 组成。


header 又包含 type 、scope 、subject 。header 是必需的,不过其中的 scope 是可选的。


body 和 footer 可以省略。


<type>(<scope>): <subject>

// 空行

<BLANK LINE>

<body>

// 空行

<BLANK LINE>

<footer>

注:为了能在 github 以及各种 git 工具中看得更清晰,commit messages 的每一行都不要超过 100 个字符。

Header

Type

类型必须是以下几种之一:


feat: 新功能

fix: bug 修复

docs: 仅修改文档

style: 修改格式(空格,格式化,省略分号等),对代码运行没有影响

refactor: 重构(既不是修 bug ,也不是加功能)

build: 构建流程、外部依赖变更,比如升级 npm 包、修改 webpack 配置等

perf: 性能优化

test: 测试相关

chore: 对构建过程或辅助工具和库(如文档生成)的更改

ci: ci 相关的更改

除此之外,还有一个特殊的类型 revert ,如果当前提交是为了撤销之前的某次提交,应该用 revert 开头,后面加上被撤销的提交的 header,在 body 中应该注明: This reverts commit <hash>. ,hash 指的就是将要被撤销的 commit SHA 。


// 例如


revert: feat(user): add user type


This reverts commit ca16a365467e17915f0273392f4a13331b17617d.

Scope

scope 可以指定提交更改的影响范围,这个视项目而定,当修改影响超过单个的 scope 时,可以指定为 * 。


Sbuject

subject 是指更改的简洁描述,长度约定在 50 个字符以内,通常遵循以下几个规范:


用动词开头,第一人称现在时表述,例如:change 代替 changed 或 changes

第一个字母小写

结尾不加句号(.)

Body

body 部分是对本地 commit 的详细描述,可以分成多行。


跟 subject 类似,用动词开头,第一人称现在时表述,例如:change 代替 changed 或 changes。


body 应该说明修改的原因和更改前后的行为对比。


Footer

footer 基本用在这两种情况:


不兼容的改动( Breaking Changes ),通常用 BREAKING CHANGE: 开头,后面跟一个空格或两个换行符。剩余的部分就是用来说明这个变动的信息和迁移方法等。

关闭 Issue, github 关闭 Issue 的例子

// BREAKING CHANGE: 的例子

BREAKING CHANGE: isolate scope bindings definition has changed and

   the inject option for the directive controller injection was removed.


   To migrate the code follow the example below:


   Before:


   scope: {

     myAttr: 'attribute',

     myBind: 'bind',

     myExpression: 'expression',

     myEval: 'evaluate',

     myAccessor: 'accessor'

   }


   After:


   scope: {

     myAttr: '@',

     myBind: '@',

     myExpression: '&',

     // myEval - usually not useful, but in cases where the expression is assignable, you can use '='

     myAccessor: '=' // in directive's template change myAccessor() to myAccessor

   }


   The removed `inject` wasn't generaly useful for directives so there should be no code using it.




// Closes Issue 例子

Closes #2314, #3421

完整的例子

例一: feat

feat($browser): onUrlChange event (popstate/hashchange/polling)


Added new event to $browser:

- forward popstate event if available

- forward hashchange event if popstate not available

- do polling when neither popstate nor hashchange available


Breaks $browser.onHashChange, which was removed (use onUrlChange instead)

例二: fix

fix($compile): couple of unit tests for IE9


Older IEs serialize html uppercased, but IE9 does not...

Would be better to expect case insensitive, unfortunately jasmine does

not allow to user regexps for throw expectations.


Closes #392

Breaks foo.bar api, foo.baz should be used instead

例三: style

style($location): add couple of missing semi colons

查看更多例子

规范 commit message 的好处

首行就是简洁实用的关键信息,方便在 git history 中快速浏览

具有详实的 body 和 footer ,可以清晰的看出某次提交的目的和影响

可以通过 type 过滤出想要查找的信息,也可以通过关键字快速查找相关提交

可以直接从 commit 生成 change log

// 列举几个常用的 log 参数


// 输出 log 的首行

git log --pretty=oneline


// 只输出首行的 commit 信息。不包含 hash 和 合并信息等

git log --pretty=format:%s


// 查找有关“更新菜单配置项”的提交

git log --grep="更新菜单配置项"


// 打印出 chenfangxu 的提交

git log --author=chenfangxu


// 红色的短 hash,黄色的 ref , 绿色的相对时间

git log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr)%Creset'

用工具实现规范提交

上面介绍了规范提交的格式,如果让各位同学在 git commit 的时候严格按照上面的规范来写,首先心智是有负担的,得记住不同的类型到底是用来定义什么的,subject 怎么写,body 怎么写,footer 要不要写。其次,对人的规范大部分都是反人性的,所以很可能在过不了多久,就会有同学渐渐的不按照规范来写。靠意志力来控制自己严格按照规范来写是需要额外耗费一些精力的,把精力耗费在这种事情上面实在有些浪费。


用工具实现规范提交的方案,一种是在提交的时候就提示必填字段,另一种是在提交后校验字段是否符合规范。这两种在实际项目中都是很有必要的。


Commitizen

Zen-like commit messages for internet citizens. 嗯~~一种禅意

Commitizen 是一个帮助撰写规范 commit message 的工具。他有一个命令行工具 cz-cli,接下来会把使用 Commitizen 分成几个阶段来介绍。


体验 git cz

// 全局安装 Commitizen

npm install -g commitizen

你的仓库可能还不是对 Commitizen 友好的,此时运行 git cz 的效果跟 git commit 一样,也就是没有效果。 不过,可以执行 npx git-cz 来体验。


如果想直接运行 git cz 实现语义化的提交,可以根据 streamich/git-cz 文档中说的全局安装 git cz。


// 全局安装 git cz

npm install -g git-cz

除此之外还有一种更推荐的方式,就是让你的仓库对 Commitizen 友好。


Commitizen 友好

全局安装 Commitizen 后,用 cz-conventional-changelog 适配器来初始化你的项目


// 初始化 cz-conventional-changelog 适配器

commitizen init cz-conventional-changelog --save-dev --save-exact

上面的初始化做了三件事:


安装 cz-conventional-changelog 依赖

把依赖保存到 package.json 的 dependencies 或 devDependencies 中

在根目录的 package.json 中 添加如下所示的 config.commitizen

"config": {

   "commitizen": {

     "path": "./node_modules/cz-conventional-changelog"

   }

 }

或者,在项目根目录下新建一个 .czrc 文件,内容设置为


{

 "path": "cz-conventional-changelog"

}

现在运行 git cz 效果如下:




cz-customizable 自定义中文配置

通过上面的截图可以看到,提交的配置选项都是英文的,如果想改成中文的,可以使用 cz-customizable 适配器。


运行下面的命令,注意之前已经初始化过一次了,这次再初始化,需要加 --force 覆盖


npm install cz-customizable --save-dev


commitizen init cz-customizable --save-dev --save-exact --force

现在 package.json 中 config.commitizen 字段为:


"config": {

   "commitizen": {

     "path": "./node_modules/cz-customizable"

   }

 }

cz-customizable 文档中说明了查找配置文件的方式有三种,我们按照第一种,在项目根目录创建一个 .cz-config.js 的文件。按照给出的示例 cz-config-EXAMPLE.js 编写我们的 config。 commit-type 可以参考 conventional-commit-types 。


可以点击查看我配置好的文件 qiqihaobenben/commitizen-git/.cz-config.js ,里面中详细的注释。


commitlint 校验提交

Commitizen 文档中开始就介绍到,Commitizen 可以在触发 git commit 钩子之前就能给出提示,但是也明确表示提交时对 commit messages 的校验也是很有用的。毕竟即使用了 Commitzen,也是能绕过去,所以提交最后的校验很重要。


commitlint 可以检查 commit messages 是否符合常规提交格式,需要一份校验配置,推荐 @commitlint/config-conventional 。


npm i --save-dev @commitlint/config-conventional @commitlint/cli

在项目根目录创建 commitlint.config.js 文件并设置校验规则:


module.exports = {

 extends: ["@commitlint/config-conventional"],

 // rules 里面可以设置一些自定义的校验规则

 rules: {},

};

在项目中安装 husky ,并在项目根目录新建 husky.config.js 文件,加入以下设置:


// 安装 husky

npm install --save-dev husky



// husky.config.js 中加入以下代码

module.exports = {

 "hooks": {

   "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"

 }

}

注意:因为 @commitlint/config-conventional 校验规则遵循 Angular 的规范, 所以我们在用 cz-customizable 自定义中文配置时, 是按照给出的符合 Angular 规范的示例 cz-config-EXAMPLE.js 编写.cz-config.js 的。但是如果你自定义的 Commitizen 配置不符合 Angular 规范,可以使用 commitlint-config-cz 设置校验规则。(推荐还是按照 Angular 规范进行 cz-customizable 自定义配置)

// 安装 commitlint-config-cz

npm install commitlint-config-cz --save-dev



// commitlint.config.js 改为

module.exports = {

 extends: [

   'cz'

 ]

};

git commit 触发 git cz

在提交的时候,我们都习惯了 git commit ,虽然换成 git cz 不难,但是如果让开发者在 git commit 时无感知的触发 git cz 肯定是更好的,

而且也能避免不熟悉项目的人直接 git commit 提交一些不符合规范的信息。


我们可以在 husky.config.js 中设置:


"hooks": {

 "prepare-commit-msg": "exec < /dev/tty && git cz --hook || true",

}

注意: 在 window 系统,可能需要在 git base 中才能生效。

生成 CHANGELOG

standard-version

是一个使用 semver 和 conventional-commits 支持生成 CHANGELOG 进行版本控制的实用程序。

standard-version 不只是能生成 CHANGELOG , 还能根据 commit 的 type 来进行版本控制。


// 安装 standard-verison

npm i --save-dev standard-version


// 在 package.json 中的 scripts 加入 standard-version

{

 "scripts": {

   "release": "standard-version"

 }

}

示例项目

可以查看 commitizen-git ,里面归纳了快速配置 Commitizen 友好仓库的步骤。

差不多三五分钟就能搞定。


可以看一下配置完后,执行 git commit 的效果。




扩展

更复杂的自定义提示

cz-customizable 中自定义配置项通常情况是够用的,

commitlint 中校验的规则基本上也是够用的,但是会有比较硬核的开发者会觉得还是不够,还要更多。比如一些 prompt 更加自定义,

提交时询问的 question 添加更多的逻辑,比如可以把一些重要的字段校验提前到 Commitizen 中,或者添加更多自定义的校验。


如果真想这么干,那就去 fork 一份 cz-conventional-changelog 或者 cz-customizable 来改,

或者直接自己写一个 adapter。


Commitizen 友好徽章

如果把仓库配置成了对 Commitizen 友好的话,可以在 README.md 中加上这个小徽章

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

一文读懂 Web 安全

seo达人

Web 安全是互联网中不可或缺的一个领域,这个领域中诞生了大量的黑帽子与白帽子,他们都是安全领域的王者,在平时里,他们利用各种巧妙的技术互相博弈,时不时就会掀起一场 Web 安全浪潮,真可谓神仙打架,各显神通。


本文从一个吃瓜群众的角度,聊一聊 Web 安全的一些有趣故事。


安全世界观

安全攻防案例

总结与思考

安全世界观

在互联网发展之初,IE 浏览器垄断的时期,大家上网的目的都很单纯,主要通过浏览器分享信息,获取新闻。但随着互联网的不断发展发展,一个网页能做的事情越来越多,除了看新闻,我们还可以看视频、玩游戏、购物、聊天等,这些功能都大大丰富了我们的生活。


随着网页功能的逐渐增多,就开始出现了一些黑帽子,他们试图通过一些技术手段来牟取利益。在我小的时候,印象最深的就是木马病毒,它可以监控你的键盘,将你在键盘上敲打的内容发送到黑客的机器上,黑客通过分析这些内容,很容易就能得到你的游戏账号和密码。


在这之后,就诞生出了一些杀毒软件,致力于解决网络上的各种病毒,随着不断地发展,杀毒软件已经成为一台电脑必不可少的软件。


为什么会出现这样的安全问题?

安全归根到底是信任的问题,如果所有人都按照正常的流程去上网,不去谋取私利,也就没有安全问题可谈了。


安全的根本在于信任,但要让所有人互相信任谈何容易。在当前阶段,我们可以做到:持续做好安全防护,让漏洞越来越少,非法攻击越来越困难,这样就能逐渐减少黑帽子的数量,让病毒制造者越来越少。


如何做好安全

要做好安全,首先得理解安全问题的属性,前人通过无数实践,最后将安全的属性总结为安全三要素,分别为:机密性、完整性、可用性。


机密性


保护数据内容不被泄露。

通常使用加密的方法。

完整性


保护数据内容是完整的、没有被篡改。

通常使用数字签名的方法。

可用性


数据随时都能够使用。

通常是在防御 DOS。

有了安全 3 要素之后,我们就可以对安全问题进行评估了。


资产等级划分


找出最重要的数据。

找出最重要数据的宿主空间,如:在数据库里,那么数据库就得重点防御。

找出数据库的宿主空间,如:在一台服务器上,那么这台服务器就得做次等防御。

找出服务器的宿主空间,如:在 OSI 网络层级上,那么在网络层面就得做一般防御。

威胁分析


找出威胁(可能造成危害的来源)。

找出风险(可能出现的损失叫做风险)。

风险分析


采取多标准决策分析,即:风险 = 威胁等级 * 威胁可行性。

计算所有的威胁,将最终的风险进行排序,优先解决风险大的问题。

确认解决方案


找出不安全的实现方式,并确定解决方案。

解决方案不要改变商业需求的初衷。

解决方案需对用户透明,不要改变用户的习惯。

做好安全评估之后,我们就有了一份安全解决方案,后续的安全工作只需按照这个方案去做,就没有任何问题。


安全的原则

有了安全解决方案之后,我们还可以制定一些安全原则,遵守原则做事,可以让我们事半功倍。


黑名单、白名单原则


白名单方案指的是给安全的资源授权。

黑名单方案指的是禁用不安全的资源。

我们应该优先使用白名单方案,因为黑名单通常统计不完所有的不安全资源。

如:XSS 攻击的方式非常多,可以通过 script、css、image 标签等,尽管你将这些标签都加入黑名单,也不能保证其他的标签都没有 XSS 的攻击隐患。

最小权限原则


只授予必要的权限,不要过度授权,减少出错机会。

如:普通权限的 Linux 用户只能操作 ~ 文件夹下的目录,如果有人想删库跑路,在执行 rm -rf / 时,就会提示无权限。

纵深防御原则


这条原则类似 木桶理论,安全水平往往取决于最短的那块板。

即:不要留下短板,黑帽子们往往可以利用短板为突破口,挖掘更大的漏洞。

数据与代码分离原则


当用户数据被当成代码执行时,混淆了数据和代码的边界,从而导致安全问题。

如:XSS 就是利用这一点去攻击的。

不可预测性原则


这条原则是为了提高攻击门槛,有效防止基于篡改、伪造的攻击。

如:数据库中使用 uuid 代替 number 型的自增主键,可以避免 id 被攻击者猜到,从而进行批量操作。

token 也是利用不可预测性,攻击者无法构造 token 也就无法进行攻击。

有了这些安全原则,我们就可以开干了,接下来介绍几个常见的攻防案例。


安全攻防案例

安全攻防的案例非常多,这里主要介绍几个出镜率比较高的安全问题。


客户端攻击

XSS 攻击

CSRF 攻击

点击劫持

XSS 攻击

XSS 攻击的本质是将用户数据当成了 HTML 代码一部分来执行,从而混淆原本的语义,产生新的语义。




如图所示,我们注册了一个 <script>alert(document.cookie)</script> 的用户名,所有能看到此用户名字的页面,都会弹出当前浏览器的 Cookie,如果代码的逻辑是将 Cookie 发送到攻击者的网站,攻击者就能冒充当前用户进行登录了。


XSS 攻击方式有很多,所有和用户交互的地方,都有可能存在 XSS 攻击。


例如:


所有 input 框。

window.location。

window.name。

document.referrer。

document.cookie。

localstorage。

...

由于页面中与用户交互的地方非常多,肯定还有一些 XSS 的攻击方式没有被发现,而一旦被黑帽子发现,就可能造成严重的影响,所以我们务必引起重视。


XSS 攻击影响

被 XSS 攻击成功后,攻击者就可以获取大量的用户信息,例如:


识别用户 UA。

识别用户浏览器扩展。

识别用户浏览过的网站。


通过 CSS 的 Visited 属性。

获取用户真实的 IP。


通过 WebRTC 等。

盗取 Cookie


伪造用户登录,窃取用户资料。

XSS 钓鱼。


向页面注入一个登录弹窗,让用户认为是网站内的登录弹窗(其实是钓鱼网站的),一旦用户登录,账号密码就泄露给了钓鱼网站。

XSS 攻击防御

目前来说,XSS 已经得到了互联网行业的重视,许多开发框架都内置了安全的 HTML 渲染方法。


我们也可以自定义进行一些安全配置。


配置 HTTP 中的 http-only 头,让前端 JS 不能操作 Cookie。

输入检查,在用户提交数据时,使用 XssFilter 过滤掉不安全的数据。

输出检查,在页面渲染的时候,过滤掉危险的数据。

CSRF 攻击

CSRF(Cross-site request forgery)跨站请求伪造,是一种利用用户身份,执行一些用户非本意的操作。




如图所示:


用户先登录了服务器 B,然后去访问服务器 C。

服务器 C 通过恶意脚本,冒充 A 去调用服务器 B 上的某个功能,

对于服务器 B 来说,还以为这是 A 发起的请求,就当作正常请求处理了。

试想一下,如果 C 冒充 A 进行了一次转账,必定会造成大量的经济损失。


CSRF 防御方式

防御 CSRF 主要有以下几种方式:


验证码


每一次请求都要求用户验证,以确保请求真实可靠。

即:利用恶意脚本不能识别复杂的验证码的特点,保证每次请求都是合法的。

Referer 检查


检查发起请求的服务器,是否为目标服务器。

即:HTTP 请求中的 Referer 头传递了当前请求的域名,如果此域名是非法服务器的域名,则需要禁止访问。

Token


利用不可预测性原则,每一请求必须带上一段随机码,这段随机码由正常用户保存,黑帽子不知道随机码,也就无法冒充用户进行请求了。

点击劫持

点击劫持是一种视觉欺骗的攻击手段。攻击者将需要攻击的网站通过 iframe 嵌套的方式嵌入自己的网页中,并将 iframe 设置为透明,在页面中透出一个按钮诱导用户点击。


就像一张图片上面铺了一层透明的纸一样,你看到的是攻击者的页面,但是其实这个页面只是在底部,而你真正点击的是被攻击者透明化的另一个网页。




如果所示,当你点击了页面上的按钮之后,本以为会...... ,而真正执行的操作是关注了某人的博客。


点击劫持防御

由于点击劫持主要通过 iframe,所以在防御时,主要基于 iframe 去做。


方案一:frame busting


正常网站使用 JS 脚本判断是否被恶意网站嵌入,如:博客网站监测到被一个 iframe 打开,自动跳转到正常的页面即可。

if (self !== top) {  // 跳回原页面  top.location = self.location;}

方案二:使用 HTTP 中的 x-frame-options 头,控制 iframe 的加载,它有 3 个值可选:


DENY,表示页面不允许通过 iframe 的方式展示。

SAMEORIGIN,表示页面可以在相同域名下通过 iframe 的方式展示。

ALLOW-FROM,表示页面可以在指定来源的 iframe 中展示。

配置 iframe 的 sandbox 属性


sandbox = "allow-same-origin" 则只能加载与主站同域的资源。

服务器端攻击

服务器端的攻击的方式也非常多,这里列举几个常见的。


SQL 注入攻击

文件上传漏洞

登录认证攻击

应用层拒绝服务攻击

webServer 配置安全

SQL 注入攻击

SQL 注入和 XSS 一样,都是违背了数据和代码分离原则导致的攻击方式。


如图所示,我们利用 SQL 注入,就能在不需要密码的情况下,直接登录管理员的账号。




攻击的前提是:后端只用了简单的拼接 SQL 的方式去查询数据。


# 拼接出来的 sql 如下:select * from user where username = 'admin' or 1=1 and password = 'xxx'# 无论密码输入什么,这条 sql 语句都能查询到管理员的信息

除此之外,SQL 注入还有以下几种方式:


使用 SQL 探测,猜数据库表名,列名。


通过 MySQL 内置的 benchmark 探测数据库字段。

如:一段伪代码 select database as current if current[0]==='a',benchmark(10000,'猜对了') 如果表明猜对了,就延迟 10 s 并返回成功。

使用存储过程执行系统命令


通过内置的方法或存储过程执行 shell 脚本。

如:xp_cmdshell、sys_eval、sys_exec 等。

字符串截断


如:MySQL 在处理超长的字符串时,会显示警告,但会执行成功。

注册一个 admin + 50 个空格的用户,会触发截断,最终新增一个 admin 用户,这样就能拥有管理员权限了。

SQL 注入防御

防止 SQL 注入的最好的办法就是,不要手动拼接 SQL 语句。


最佳方案,使用预编译语句绑定变量


通常是指框架提供的拼接 SQL 变量的方法。

这样的语义不会发生改变,变量始终被当成变量。

严格限制数据类型,如果注入了其他类型的数据,直接报错,不允许执行。

使用安全的存储过程和系统函数。

CRLF 注入

在注入攻击中,换行符注入也是非常常见的一种攻击方式。


如果在 HTTP 请求头中注入 2 个换行符,会导致换行符后面的所有内容都被解析成请求实体部分。

攻击者通常在 Set-Cookie 时,注入换行符,控制请求传递的内容。

文件上传漏洞

上传文件是网页开发中的一个常见功能,如果不加处理,很容易就会造成攻击。




如图所示,攻击者上传了一个木马文件,并且通过返回的 URL 进行访问,就能控制服务器。


通常我们会控制上传文件的后缀名,但也不能完全解决问题,攻击者还可以通过以下方式进行攻击:


伪造正常文件


将木马文件伪装成正常的后缀名进行上传。

如果要避免这个问题,我们可以继续判断上传文件的文件头前 10 个字节。

Apache 解析方式是从后往前解析,直到找到一个认识的后缀名为止


如:上传一个 abc.php.rar.rar.rar 能绕过后缀名检查,但在执行时,被当成一个 php 文件进行执行。

IIS 会截断分号进行解析


如:abc.asp;xx.png 能绕过后缀名检查,但在执行时,被当成一个 asp 文件进行执行。

HTTP PUT 方法允许将文件上传到指定位置


通过 HTTP MOVE 方法,还能修改上传的文件名。

通过二者配合,就能先上传一个正常的后缀名,然后改为一个恶意的后缀名。

PHP CGI 路径问题


执行 http://abc.com/test.png/xxx.php 时,会把 test.png 当做 php 文件去解析。

如果用户正好是把一段恶意的 php 脚本当做一张图片进行上传,就会触发这个攻击。

文件上传漏洞防御

防御文件上传漏洞,可以从以下几点考虑:


将文件上传的目录设置为不可执行。

判断文件类型


检查 MIME Type,配置白名单。

检查后缀名,配置白名单。

使用随机数改写文件名和文件路径


上传文件后,随机修改文件名,让攻击者无法执行攻击。

单独设置文件服务器的域名


单独做一个文件服务器,并使用单独的域名,利用同源策略,规避客户端攻击。

通常做法是将静态资源存放在 CDN 上。

登录认证攻击

登录认证攻击可以理解为一种破解登录的方法。攻击者通常采用以下几种方式进行破解:


彩虹表


攻击者通过搜集大量明文和 MD5 的对应关系,用于破解 MD5 密文找出原文。

对于彩虹表中的 MD5 密码,我们可以加盐,进行二次加密,避免被破解。

Session Fixation 攻击


利用应用系统在服务器的 SessionID 固定不变机制,借助他人用相同的 SessionID 获取认证和授权。

攻击者登录失败后,后端返回了 SessionID,攻击者将 SessionID 交给正常用户去登录,登录成功后,攻击者就能使用这个 SessionID 冒充正常用户登录了。

如果浏览器每一次登录都刷新 SessionID 可以避免这个问题。

Session 保持攻击


有些时候,后端出于用户体验考虑,只要这个用户还活着,就不会让这个用户的 Session 失效。

攻击者可以通过不停发起请求,可以让这个 Session 一直活下去。

登录认证防御方式

多因素认证


密码作为第一道防御,但在密码验证成功后,我们还可以继续验证:动态口令,数字证书,短信验证码等,以保证用户安全。

由于短信和网页完全是 2 套独立的系统,攻击者很难获取到短信验证码,也就无法进行攻击。

除此之外,前端登录认证还有多种方式,如果你对此感兴趣,可以参考我之前写的 前端登录,这一篇就够了。


应用层拒绝服务攻击

应用层拒绝服务攻击,又叫 DDOS 攻击,它指的是利用大量的请求造成资源过载,导致服务器不可用。




通常有以下几种 DDOS 攻击方式:


SYN Flood 洪水攻击


利用 HTTP 3 次握手机制,消耗服务器连接资源。

如:攻击者发起大量的 HTTP 请求,但并不完成 3 次握手,而是只握手 2 次,这时服务器端会继续等待直至超时。这时的服务器会一直忙于处理大量的垃圾请求,而无暇顾及正常请求。

Slowloris 攻击


以非常低的速度发送 HTTP 请求头,消耗服务器连接资源。

如:攻击者发送大量 HTTP 请求,但每个请求头都发的很慢,每隔 10s 发送一个字符,服务器为了等待数据,不得始终保持连接,这样一来,服务器连接数很快就被占光了。

HTTP POST DOS


发送 HTTP 时,指定一个非常大的 Content-Length 然后以很长的间隔发送,消耗服务器连接资源。

CC 攻击


针对一些非常消耗资源的页面,不断发起请求。

如:页面中的某些页面,需要后端做大量的运算,或者需要做非常耗时的数据库查询。在大量的请求下,服务器的 CPU、内存等资源可能就被占光了。

Server Limit DOS


通过 XSS 注入一段超长的 Cookie,导致超出 Web 服务器所能承受的 Request Header 长度,服务器端就会拒绝此服务。

ReDOS


针对一些缺陷的正则表达式,发起大量请求,耗光系统资源。

应用层拒绝服务攻击防御

对于应用层拒绝服务攻击,目前也没有特别完美的解决方案,不过我们还是可以进行一些优化。


应用代码做好性能优化


合理使用 Redis、Memcache 等缓存方案,减少 CPU 资源使用率。

网络架构上做好优化


后端搭建负载均衡。

静态资源使用 CDN 进行管理。

限制请求频率


服务器计算所有 IP 地址的请求频率,筛选出异常的 IP 进行禁用。

可以使用 LRU 算法,缓存前 1000 条请求的 IP,如果有 IP 请求频率过高,就进行禁用。

其实,处理 DDOS 核心思路就是禁用不可信任的用户,确保资源都是被正常的用户所使用。


WebServer 配置安全

我们在部署 web 应用的时候,经常会用到 Nginx、Apache、IIS、Tomcat、Jboss 等 Web 服务器,这些服务器本身也存在一些安全隐患,如果配置不当,很容易收到攻击。


在配置 Web 服务器时,可以参考以下几点:


以用户权限运行 Web 服务器


遵守最小权限原则,以最小权限身份运行 Web 服务器,限制被入侵后的权限。

删除可视化后台


运行 Tomcat、Jboss 等 Web 服务器时,默认会开启一个可视化的运营后台,运行在 8080 端口,并且第一次访问是没有认证的。

攻击者可以利用可视化后台,远程加载一段 war 包或者上传木马文件,进行控制。

及时更新版本


主流的 Web 服务器,每隔一段时间就会修复一些漏洞,所以记得及时更新版本。

总结与思考

本文介绍了 Web 安全的基本概念,以及大量的攻防技巧,其实这只是 Web 安全中的冰山一角,如果你对此感兴趣,不妨在安全领域继续深耕学习,一定能看到更广阔一片天。


对于一个开发者来说,我们应该在写代码时就将安全考虑其中,形成自己的一套安全开发体系,做到心中有安全,时时考虑安全,就能无形之中化解不法分子的攻击。

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

关于医疗产品经理,这7件事你需要知道

雪涛

编辑导读:随着医疗行业与互联网的联系日益紧密,医疗行业对产品经理的需求也越来越迫切。在这个特殊行业中,医疗产品经理需要具备哪些能力,应该如何工作,创造哪些价值?本文围绕医疗产品经理展开,从七个方面展开介绍分析,希望对你有帮助。

越来越多的医疗机构开始考虑设置医疗产品经理这个岗位,但是对于产品经理具体应该做什么工作,可能产生何等价值,以及如何招聘到合适的人才,和这个角色在组织内部如何开展工作,都有很多的困惑。今天我们就简单聊聊这个话题。

总的来说医疗产品经理还是个非常新,甚至可以说有一些超前的职能,传统FMCG和互联网行业的产品经理对应的工作内容和思考方式并不能简单照搬过来使用。我们需要清空过去在这些行业积累的认知,从医疗经营的原点出发,从下面7个方面思考:

  1. 医疗产品经理的价值何在
  2. 医疗产品的设计逻辑
  3. 医疗产品经理的职责
  4. 好的产品经理应该具备的技能
  5. 产品经理需要和哪些部门沟通
  6. 与产品经理相关的组织架构
  7. 医疗产品经理的招聘和培养

一、医疗产品经理的价值何在

产品经理就是足球场上的中场大将,起到承上启下,功放转换的枢纽,具体说有三大作用:

  1. 进攻的发起
  2. 防守的第一道屏障
  3. 三条线的串联

什么是进攻?进攻就是尝试主动去占据一块领地。

对营利性医疗机构来说最常见的情况有4种:

  1. 地域上,新开了一个诊所,或者一家医院,要进攻这个网点覆盖的人群。
  2. 专业上,新增了一个科别,可以多覆盖若干种疾病人群,包括自己存量病人,也包括切割存量市场竞品的份额。
  3. 设备上,新装备了一种设备,可以开展之前不具备的检查、治疗和手术能力。
  4. 还有一种是阶段性行动,在没有新的项目情况下,为了扩大客户群,采取一定的促销行动,最典型的如双十一洗牙9.9。

那么,又何谓防守呢?简单来说,就是对应上述各种进攻的应对。

十年前私立医院还不算很多,也还没有那么多连锁诊所品牌的时候,事实上大家主要都在忙着跑马圈地,短兵相接的攻防其实并不多。现在随着市场参与者的倍增,慢慢开始出现了小区域内的半正面PK,并且我预计在未来两年内可能会出现直接内部指名道姓对标的战斗。

足球场上的三条线是进攻、防守和中场,这里我们所说的三条线,大体对应的是:市场营销、医疗质量和行政职能三大板块。产品经理的重要价值就是能够打通这三条线的隔阂,把整个医院的资源凝结成有效的市场成果。

二、医疗产品的设计逻辑

医疗产品不等于,不等于,不等于“打包套餐”!这个概念请务必建立起来。

首先要厘定什么是医疗产品。

可以用“三个明确”来界定之:

  1. 明确人:由专业医务人员实施的某种行为
  2. 明确物:有标准的资源配比、服务项目
  3. 明确钱:有公开的定价

医疗产品开发的逻辑的源头就在于评估一种医疗服务是否吻合这三个明确,因此不是所有的医疗服务都可能变成“产品”。

比如我们医院口腔科有一个非常棒的医生,专注于牙齿美容,我们称之为“微笑设计”。这种设计是完全量体裁衣的,我们市场团队对他的关注点就主要在故事性的传播,而不是试图将这种高度个性化、动态化的医疗行为产品化。

简单来说,对于符合三个明确的医疗服务,我们对其进行产品化的“化”,是一系列有序的动作:

  • 确定需求
  • 自我评估
  • 调配资源
  • 制定服务
  • 设定价格
  • 内部培训

囿于篇幅,这里我们就不展开详述了。

三、医疗产品经理的职责所系

理想中的医疗产品经理对下面4件事情负责:

  1. 开发新品
  2. 发起促销
  3. 产品监测
  4. 竞品追踪

很容易看出来,这4种不同的职责恰好也就对应了攻防转换的价值所在。其中促销是一个容易被忽视和轻视的事情,“不就是打折然后发个微信(十年前是发短信)推文嘛”——绝对不是这样,促销是一门大学问,打折、捆绑、买赠、兑奖、积分凡此种种。不仅花样很多,更重要的是背后的深层次的思考,是“为什么”。

另外,目前的医疗机构基本上也没有人比较认真、成体系地做竞品追踪。这样会失去潜在市场机会,非常可惜。

四、好的产品经理应该具备的技能

我认为一名出色的医疗产品经理应该在下面四个方面都具备一定的能力,没有特别的短板:

  1. 学习能力
  2. 同理心
  3. 数据敏感性
  4. 表达能力

特别就同理心和表达能力简单阐述。同理心,即换位思考,用现在更流行的话说,是场景意识。能否准确地设置出用户的场景,体会到用户的感受,会直接决定产品带给客户的体验,进而一系列结果:定价、毛利、传播ROI、客户口碑,口碑带来的新客增长,等等不一而足。

而表达能力则是决定这个产品经理是否能实现“串联三条线”价值的决定性因素。医院是一个观念高度保守,流程高度复杂的行业,很多人雄心勃勃地进来,最终死在“搞不定那些人”上。因此,优秀的表达能力,包括书面和口头表达能力,是遴选医疗产品经理必须考量的重要因素之一。

五、产品经理需要和哪些部门沟通

我不是危言耸听:产品经理几乎要和医院里所有部门打交道。

常见的如下面这些:

  • 医疗
  • 护理
  • 财务
  • 客服
  • 呼叫中心
  • 新媒体
  • 地推/线下活动
  • 推荐:独立的数据部门

医疗、护理和财务对于产品工作的重要性相信无需赘言。后面几个呢?

试想,你精心设计的卖点,是你自己拉着每一个潜在客户去吆喝么?当然不是,客服要帮你介绍,新媒体要帮你写文章、画插图。他们是不是要吃透你的意思?如果涉及填表、兑奖,要不要和客服商量流程?遇到产品的技术较为复杂,需要不需要策划一些活动帮助客户直观理解其价值?最后,当潜在用户感兴趣而打电话给呼叫中心的时候,接线员是否已经被你提前武装好,能充分回答各种提问了?

至于独立的数据部门,是我的一个强力推荐。传统上由财务和病案提供的数据,更多聚焦于“既往”而很少关注“开来”。如果不由同时懂得医疗业务和有商业经验的数据部门处理,很难直接推动运营的改善和提升。

六、与产品经理相关的组织架构

很多人问我,产品经理属于市场营销部门吗?难道不是属于运营部,或者医疗企划之类的部门吗?

别忘了,市场营销最基本的范式——4P中第一个P就是产品,Product。只要你所在的医疗机构设置有相对完整的市场部门,就应该在其中设置产品经理岗位。或者反过来说,如果你准备建制产品经理岗位,从一开始就应该将其设置在市场部内,并从头开始考虑这个职位所需要的上下游角色和他们之间的衔接。

如前面所分析的,产品经理的后端,一定要有提供数据支持的部门,前端一定要有专业的传播团队,这样才能实现产品的潜力。横向上,产品经理和他的上级,一定要高度重视与客户服务团队的紧密合作。

七、医疗产品经理的招聘和培养

老实说,现在几乎没有多少现成的、成熟的医疗产品经理。因为营利性医疗行业太新了,而产品经理这个岗位在这个行业又是近一两年刚刚兴起的角色。

从招聘角度来说,我建议不要拘泥于候选人必须有医疗背景。我就没念过医学院,十一年前入行,就这样摸着石头过河,也多多少少做过一些还不错的产品,有过几个“爆款”的心得。相对来说,我更看重候选人是否有完整的商业思考逻辑能力。

换一个角度来说,还可以在医院内部挖掘有潜力的人才,从临床部门转型为产品经理。最关键的环节在于这个人是否有足够强烈的兴趣。世上无难事只怕有心人,有医学院背景的人才,只要对产品工作发自内心感兴趣,就有很大的转型成功概率。

文章来源:人人都是产品经理    作者:易亮 

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

如何做好Banner设计?

雪涛

「空间陈列法」是说先构建一个空间,然后将主体元素用合适的形式陈列出来。

这是随着手机兴起而真正流行起来的一招,因为PC时代都是宽大的横屏设计,适合展现视野开阔的大场面,像大漠、海边等等,而「空间陈列」作为小场景,在PC端就显得不大气,因此使用较少;而手机端却刚好相反,瘦长竖屏就适合表现长焦特写的小场景,像微距下的花鸟鱼虫等等,这时「空间陈列」就用的恰到好处。

如何做好Banner设计?先掌握这个高手都会的空间陈列法!

如图所示,同样的产品展示图,在PC端就显得单薄,版面空缺,整体不饱满;而在手机端则用的刚刚好,确实这种长焦特写、微距放大的陈列小场景就是手机屏的最爱,所以在手机时代,空间陈列图才会呈现井喷式增长。

其实合成、三维和摄影都可以实现「空间陈列」,但本书还是以合成为主,而合成的难点就在于如何将产品和空间进行自然融合,不能有违和感。

若想合的天衣无缝,第一步就是要做到「透视准确」,而透视作为构图中的重要知识点,可以说是无处不在,在前面的章节里也多次提及。我们只有掌握透视的变化规律,才能准确表现出元素的空间关系,如果透视不对,那空间将会失真,下面就来详细讲讲这个理性知识点——透视。

焦点透视

日常生活中,当我们看周围事物时,会有远近、高低、长短、宽窄等不同,这是由于距离、方位等差异在视觉中呈现的不同反映,这种现象就是透视。透视学的出现可以帮我们非常科学的表现各种空间感和立体感,它广泛用于绘画、建筑、环艺、设计等诸多领域,而常见的透视共3类:空气透视、散点透视和焦点透视,这3类的侧重点各有不同。

空气透视又称「色彩透视」,由于空气介质的存在(雨、雪、雾、烟等),使人们看到近处景物比远处的轮廓更清晰、色彩更饱满的视觉现象,例如下方海报中的「烟雨蒙蒙」,这种近实远虚感就是典型的空气透视,随着镜头拉远,山川也变得越来越模糊。

如何做好Banner设计?先掌握这个高手都会的空间陈列法!

散点透视则是中国画特有的一种透视类型,例如下方的《清明上河图》就是这类透视的代表作,在这五米长的画卷中,很难找出画家的具体观察位置,好似在移动中作画,每到一处画一部分,最后拼接起来,这种视点不断移动的画法就是散点透视,散点透视适合表现景色的波澜壮阔,重在写意,体现一种气势和意境。

如何做好Banner设计?先掌握这个高手都会的空间陈列法!

而焦点透视才是本文重点,它是透视学中的核心理论,也是西方绘画所遵循的透视原则,最早研究透视就是从这里开始,如果散点透视是「写意」,那焦点透视则「写实」,一切都以客观还原为准。

例如名画《最后的晚餐》,所有视线都汇聚一点(称为灭点),营造出一种立体空间感,这些就像自己身处画面中央所看到的逼真景象。

如何做好Banner设计?先掌握这个高手都会的空间陈列法!

而我们在设计中常说的透视也都指「焦点透视」,这是我们需要掌握的重中之重。记得高中学习素描时,老师就说画静物要「近大远小」,其实就是对焦点透视最为形象的描述,例如草地上的奶牛,离我们越近就越大,越远则越小,正是这种近大远小的透视变化才使场景有了空间和层次。

如何做好Banner设计?先掌握这个高手都会的空间陈列法!

观察视角

在介绍焦点透视前,我们先说说透视中一个很重要的影响因素——观察视角。视角即指人眼(称为视点)在观察事物时视线之间所形成的角度。

如下图所示,其实就是人眼观看角度的变化,常见有3种:当我们平视前方时就是「平视视角」;仰头看时则是「仰视视角」;低头看时便是「俯视视角」。

其中平视时人眼和物体形成的假想连线称为「水平视线」,这是判断视角高低的参考线。

如何做好Banner设计?先掌握这个高手都会的空间陈列法!

当我们将产品放入空间时,就要根据陈列形式选择合适视角,从下方的示意图中能看到,三种视角给人的感受都不相同:

  • 平视有种方方正正感,给人一种非常自然的观察感受,虽然中规中矩但视觉舒服;
  • 仰视则能体现产品的高大和气势,用来烘托价值感;
  • 而俯视最接近我们日常看桌面小物品的视角,很真实也很亲切,同时还凸显了产品的立体感。

如何做好Banner设计?先掌握这个高手都会的空间陈列法!

而上图的仰视和俯视都属于小视角,即产品视线和水平视线的夹角较小,这是设计中的常见视角,大概就是把头微微抬起或低下时看到的场景,这时画面最自然也最舒服。反之若视角过大,即头抬的很高或压的很低时,这时产品的形变就很夸张,显得刻意、不舒服。

说完3种视角,现在正式讲解焦点透视,一般根据物体灭点的数量不同,焦点透视又分3种:平行透视(一点透视)、成角透视(两点透视)和斜角透视(三点透视),它们都有各自的透视效果和适用范围,但若铺开讲会很复杂,因此下面就结合「空间陈列」进行介绍。

1. 平行透视

用立方体简单说明,就是有一面与画面平行,这时物体的厚度边线若向内延伸,最后都会汇聚到1个点上,因此又称「一点透视」。这是最简单也最易掌握的一种透视形式,其中汇聚点称为「灭点」,而灭点所在的那条线则是「视平线」,即与人眼等高的一条水平线。

如何做好Banner设计?先掌握这个高手都会的空间陈列法!

再来看看平行透视在生活中的场景呈现,如图所示,将各种景物进行前后连线并延伸,最后都是汇到一点才消失。平行透视适合表现场景纵深,给人一种正式感和平和感。

如何做好Banner设计?先掌握这个高手都会的空间陈列法!

电商中的产品展示也一样,例如下方示意图中,不管哪种视角,产品和立方体都是正对观众,让人觉得摆放角度正正好。

如何做好Banner设计?先掌握这个高手都会的空间陈列法!

总体来说平行透视只有1个灭点,变化并不多,视觉表现单一,没有太多的空间变化,基本就是从正面来表现整个场景,因此上手简单,只要确保前后连线都汇聚一点即可,这样画面各元素也会显得整齐。但有时这种正视会让画面缺少层次感,显得很平,此时可尝试俯视视角或者强化背景的空间纵深。

下面展示平行透视在3种视角下的应用案例,注意观察不同视角下的产品呈现和透视变化,虽然微妙,但每种视角确实给人不一样的视觉感受。

平行平视

当画面为平行透视和平视视角时,这时的观察位置很正。如下图所示,空间和产品都显得有些平整,虽然场景的立体感较弱,但视觉舒服协调,表现起来也相对简单。注意平视的「视平线」基本位于主体元素的中心处,即是说人眼此时正对前方物体的中心,这样才会有平视效果。

如何做好Banner设计?先掌握这个高手都会的空间陈列法!

平行仰视

当画面为仰视时,一般视角都不会太大,微微仰视即可,这样视觉才舒服。如下图所示,其实和平视比起来,小角度仰视的透视变化并不明显,没有夸张形变,但依然能体现空间和产品的高大。此时「视平线」位于主体中心靠下的位置,这时人眼明显是从下往上看。

如何做好Banner设计?先掌握这个高手都会的空间陈列法!

平行俯视

如果觉得画面的层次感和立体感不够,那就尝试下俯视视角,如下图所示,由于俯视时我们能同时看到物体的顶面和正面,这样就能表现物体的厚度,立体感也明显增强。而画面的「视平线」则位于主体中心靠上的位置,这时人眼就是从上往下看,但同样属于小角度俯视。

如何做好Banner设计?先掌握这个高手都会的空间陈列法!

2. 正俯视

平行透视的俯视还有一种特殊情形——正俯视,即视角为90°的俯视,这时我们是从物体的正上方低头往下看,如下图所示,当产品平放桌面时,正俯视能清晰看到产品的全貌。

如何做好Banner设计?先掌握这个高手都会的空间陈列法!

在空间陈列中这是一种常见视角,上手简单,展现清晰。例如下方案例中,俯视下的产品摆放非常灵活,根据构图需求可以工整 ① 也可以随意 ② ,并且产品多以正面展现为主,整体直观、舒服。

如何做好Banner设计?先掌握这个高手都会的空间陈列法!

以上都是产品的陈列案例,其实正俯视有时也会用于场景呈现中,如下图所示,视点位于场景的正上方,有点类似无人机的俯瞰拍摄,这种看似「刁钻」的视角能给画面带来独特的戏剧效果,令人印象深刻。

如何做好Banner设计?先掌握这个高手都会的空间陈列法!

空间平行透视

除了3种视角,这里还要介绍一种平行透视的常见形式——空间平行透视,这种形式即画面的正前方有个类似方盒的纵深空间,而人物或产品就放置在空间里。

如图所示,该形式也有视角的3种变化,但为了确保视觉的自然舒服,仰视和俯视也都是小角度的上下摆动,所以产品的透视变化并不明显,场景呈现也没有很夸张。

如何做好Banner设计?先掌握这个高手都会的空间陈列法!

为何要将该形式单独列出?因为它非常适合手机端的竖屏构图。

手机不像PC,无法通过宽屏来表现视野开阔的大场面,手机屏更适合长焦特写的小场景,但这样有时就会显得左右拥挤不透气,这时「空间平行透视」就刚好取长补短,通过「深度」刻画将狭窄空间的纵深感体现出来,最终使观者视线在前后维度上有了延伸和舒展。

该形式正是利用平行透视的纵深性,才缓解了手机屏的拥挤感。下面分视角列举案例,要注意不管平视、仰视还是俯视,空间里的所有元素最后都要汇聚一点,这样透视才合理,纵深效果也最好。

3. 空间平视

3种视角中,空间平视最常见,因为这种方方正正的空间展示最适合手机的竖屏构图,看着最舒服,上手也简单,易于搭建。在平视下,由于没有视角的高低变化,空间基本位于人眼的正前方,无任何偏移,摆放角度非常正,构图给人一种稳定感,元素也没有夸张形变,组合方便,真实自然。

对于「空间平行透视」,「深度」刻画很关键,我们要根据版面构图选择合适的深浅。例如下方案例中:① ② 的深度浅,空间相对封闭,适合展现小空间,给人温馨感和趣味性;而 ③ 的深度深,空间开阔,适合展现大空间,这样能让视线更舒展,画面更透气。

如何做好Banner设计?先掌握这个高手都会的空间陈列法!

4. 空间仰视

在仰视下,我们是抬头向上看,这时空间显得高大,给人强烈的气势感。如下图所示,视平线贴近地面,像是我们蹲着向上看,这种仰望视角,建筑和人物都很高大,再加上强烈的纵深感,虽然空间左右依然狭窄,但上下和前后维度却变的非常开阔,画面通透。

空间仰视能渲染氛围,提升场景的戏剧效果,突出视觉冲击力,但要注意仰视视角不能太大,否则夸张的仰视效果反而给人一种压抑感。

如何做好Banner设计?先掌握这个高手都会的空间陈列法!

5. 空间俯视

在俯视下,视平线处于空间中心的上方,类似我们站在高处往下看,如下图所示,这时空间显得立体,远近的各类元素也能得到清晰展现,层次分明。

俯视适合展现空间的全局观,也让各物体有着丰富的体积感。除非有特殊的构图需求,不然和仰视一样,俯视视角不能过大,否则俯视就变成俯瞰,会产生遥远的距离感,空间也压缩的厉害,进而导致形变和失真。

如何做好Banner设计?先掌握这个高手都会的空间陈列法!

成角透视

还是拿立方体举例,就是物体与画面形成一定夹角,这时物体的所有边线分别向各自方向进行延伸,最后会在视平线上形成一左一右2个灭点,因此又称「两点透视」。这类透视最接近我们日常的观察角度,即是说大部分时候,我们看到的物体都属于成角透视。

如何做好Banner设计?先掌握这个高手都会的空间陈列法!

虽然成角透视只比平行透视多了1个灭点,但2个灭点的位置却很灵活,这样空间的透视变化也更加丰富。

例如下方是我们经常看到的景象,虽然都是典型的成角透视,但 ① 是2个灭点都在画面外,这时建筑给人的感觉结构平稳,立体感强,侧重写实;而 ② 则是1个灭点在画面内,另1个在画面外,这时空间右侧的透视形变较大,产生纵深感,整个场景更有张力和冲击。

其实还有第3种情形是2个灭点都在画面内,但由于空间会产生夸张形变和失真,因此总体少见,不再举例说明。

如何做好Banner设计?先掌握这个高手都会的空间陈列法!

再看看成角透视下的产品展示效果,如下图所示,不同于单面展示为主的平行透视,成角透视则以展示物体的两面为主,这样立体感更强,构图也更稳定。注意在成角透视中,画面所有的竖向边线都是平行,不会产生灭点。

如何做好Banner设计?先掌握这个高手都会的空间陈列法!

相比平行透视,成角透视在表现上更复杂一些,一般都以2灭点在画面外的情形为主,这时透视最舒服。注意要想画面只产生2个灭点,那当中的所有元素都需排列整齐,这也是成角透视的常用做法,此时画面会显得整齐统一。下面列举3种视角下的电商案例,其中以仰视和俯视最为常见。

1. 成角平视

平视下的成角透视相对少见,因为使用成角透视就是为了凸显物体的立体感,但平视由于视角很正,恰恰就会显得立体感较弱,这时2种效果会有矛盾,影响场景的协调性。例如下方的2个案例中,产品看着就有些平整,和方形盒子以及立方体稍显冲突,但整体视觉真实平和,没有形变。

如何做好Banner设计?先掌握这个高手都会的空间陈列法!

2. 成角仰视

仰视下的成角透视就显得刚刚好,如下图所示,所有元素均用2个立面展示,加上透视形变,这时空间的立体感强,产品和立方体也都有明显的体积感,视觉平稳、饱满,而且还能体现产品形象的高大,凸显价值感。

注意2个案例中,视平线上都只有2个灭点,这是因为产品和立方体的排列都很整齐,反之若无序排列,就会产生多个灭点,这样画面会显凌乱,视觉不舒服,所以在表现成角透视时,尽量确保所有元素都能整齐排列。

如何做好Banner设计?先掌握这个高手都会的空间陈列法!

3. 成角俯视

在成角透视中,俯视视角最常见。因为该视角下的物体可以展现3个面,能进一步强化元素的立体感和方位感,如图所示,物体的空间关系明显,层次分明,构图也平稳。

一般成角俯视适合小场景陈列(若是大场景则垂直方向会发生严重形变,这就是后面要讲的「斜角透视」),刚好这是手机屏的擅长,小空间配上小角度俯视会给人一种亲切感,类似长焦镜头的特写画面,很好的拉近了观者的心理距离,因此属于手机端的常用构图。

如何做好Banner设计?先掌握这个高手都会的空间陈列法!

空间成角透视

成角透视的优势在于画面立体、稳定和写实,这些优势刚好适合空间的立体呈现,如下图所示,成角透视即可用于室内塑造 ① 也可用于外形搭建 ② ,类似我们站在空间侧面看整体,此时空间立体、饱满,结构有张力。

如何做好Banner设计?先掌握这个高手都会的空间陈列法!

但由于成角透视都是在画面两端形成灭点,所以该透视下的空间更适合横构图,但手机屏却是竖构图,对于横向拥挤的竖长屏,成角透视就会有些施展不开,左右狭窄,无法像横版那样开阔的展现空间,也没有平行透视那样看着规整,因此使用较少。

斜角透视

物体与画面存在一定夹角,并且在2点透视的基础上,再加入了高度变化,这样垂直方向的连线会向上或者向下汇聚,最终画面形成3个灭点,又称「三点透视」。相比成角透视,斜角透视其实就是让本没有交集的竖线有了交集,这样垂直方向就有了强烈的汇聚感。

如何做好Banner设计?先掌握这个高手都会的空间陈列法!

斜角透视的形变夸张,常用于大型物体的仰视或高处俯瞰,类似广角拍摄。

如下图所示,该透视能表现出建筑或空间的宏大感,并且越宏大透视就越强烈。这时画面的夸张构图会显得观者渺小,给人一种压迫感,也让场景有着极强冲击力,同时带来了更加刺激的视觉感受。

如何做好Banner设计?先掌握这个高手都会的空间陈列法!

其实只要观者在场景中显得很小,这时看到的画面就会产生斜角透视,例如当我们仰望高楼时,相对高楼而言,渺小的我们就会看到斜角透视。

但如果产品展示采用斜角透视时,就会有一种强烈的不真实感,因为相对产品来说,我们并不渺小,所以日常是不会看到这样的场景,这种场景更像是「昆虫视角」,如图所示,斜角透视下,虽然画面不真实,但会有种特别的戏剧效果。

如何做好Banner设计?先掌握这个高手都会的空间陈列法!

另外斜角透视没有平视视角,因为平视物体的竖向边线依然平行,不会在垂直方向产生第3个灭点,因此仍属于成角透视。总之只有在大角度仰视或俯视大型物体时,才会看到斜角透视。

这种形变强烈的夸张透视虽然生活中相对少见,但电商中用的还真不少。还记得我们在第2章讲创意方法时提过的「独特视角」吗?其中一个方向就是使用斜角透视。

这种透视可以体现物体的巨大(仰视)或者场景的宏大(俯视),正是这样一种不真实也不自然的视觉感受,反倒给人一种强烈的气势和冲击,画面极具张力的构图往往能脱颖而出,并在第一时间抓人眼球,吸引注意,所以成角透视特别适合大促主题的场景搭建和氛围营造,下面分视角举例。

斜角仰视

视能让物体显得高大,而斜角仰视则让物体显得「巨大」。如图所示,2个案例中的产品都十分「巨大」,通过这样一种「刁钻」视角和夸张手法渲染出了产品气势,使产品显得分量十足,同时也提升了视觉冲击力,整个场景都变的大气。其中 ① 由于版面有限,有个灭点没有标示出来。

如何做好Banner设计?先掌握这个高手都会的空间陈列法!

斜角俯视

当我们站在一个很高的地方俯瞰周围,或者用无人机在高空航拍,这时看到的景象就是斜角俯视。如图所示,尽管竖构图并不适合展现辽阔的大场面,但在斜角俯视的帮助下,2个案例依然体现了场景的宏大,视觉冲击强,这种居高临下感使人视野开阔。

如何做好Banner设计?先掌握这个高手都会的空间陈列法!

平行斜角透视

斜角透视中还有一种特殊情形——平行斜角透视,就是当空间或产品的一面与画面平行时,此时不管透视多强烈,画面也只有2个灭点。

如图所示,当立方体的一面正对视点时,侧面便从2个主立面减为了1个,这时除了垂直方向的1个灭点外,原本视平线上的2个也就成了1个,虽然只剩2灭点,但本质仍属于透视强烈的斜角透视。

如何做好Banner设计?先掌握这个高手都会的空间陈列法!

既然是与画面平行,那和平行透视有何区别?

下面看张空间对比图,能看到2者的形变差异还是相当大:左图为平行透视,像是我们在看一个小方盒,亲切、自然、真实;而右图则是平行斜角透视,更像是我们在仰望一个巨大空间的入口,充满戏剧性,并有压迫感。

如何做好Banner设计?先掌握这个高手都会的空间陈列法!

其实对于手机端,「平行斜角透视」才是斜角透视中的常用形式,因为它的摆放角度很正,这种正面观察很适合手机端的竖屏构图,而且前后的纵深刻画也能缓解版面的左右拥挤,另外画面纵向的汇聚感还能迅速吸引注意,给人一种巨大冲击力和强烈氛围感,其中仰视比俯视更加常见。

如何做好Banner设计?先掌握这个高手都会的空间陈列法!

平行斜角仰视

近几年这类透视越来越多见,因为它既适合竖构图,也能提升画面的形式感和表现力,非常利于促销主题的氛围打造。

如图所示,整个画面就像是我们站在宏大场景的正中央仰望时看到的景象,各元素摆放很正,虽有压迫感,但空间和产品都显得非常高大,张力十足,能让用户牢牢聚焦,同时也产生了强烈冲击力,更易在用户脑海中形成记忆。

如何做好Banner设计?先掌握这个高手都会的空间陈列法!

平行斜角俯视

在这类透视下我们会感觉自己拥有高高在上的「上帝视角」,元素摆放同样很正,视野辽阔,场景宏大。但过大的俯视视角会对场景进行一定压缩,再加上俯瞰产生的遥远距离感,这样就显得元素有些「小气」,无法体现仰视下的高大,因此画面的刺激感没那么强烈,总体相对少见。

如何做好Banner设计?先掌握这个高手都会的空间陈列法!

3. 小结

以上便是焦点透视的3种类型,我们再来回顾下各自的使用情形,如下图所示,这是一张3种透视的转换示意图,都是仰视视角,旁边小人则是观察者的大小示意:

  • 当立方体的一面正对观察者时,就是「平行透视」,这时除了物体厚度的边线会汇聚1点,其余边线均无交集;
  • 而当立方体旋转一个角度,任何一面都不正对观察者时,就是「成角透视」,这时横向边线会向各自方向汇聚成2点,竖向边线则无交集;
  • 此时若将立方体变的巨大,大到需要仰望,就是「斜角透视」,这时在2点基础上,本无交集的竖向边线将汇成1个新点。

希望通过这张示意图能帮大家更好理解什么时候该用哪种透视,总之小场景搭建一般以「平行透视」和「成角透视」为主,而恢弘的大场景则以「斜角透视」为主。

如何做好Banner设计?先掌握这个高手都会的空间陈列法!

当然现实里的透视远不会这么单一,根据物体不同的摆放位置以及不同的观测距离,很多时候同一画面也会存在多种透视,例如平行透视和成角透视就经常组合出现。

电商设计也一样,例如下方案例中,整个空间是平行透视,而里面的盒子则是成角透视,这时视平线上会有3个灭点,其实若产品的摆放再凌乱些,还会出现更多灭点,但这种无序组合会让空间塑造变的复杂,看着也不规整,因此并不推荐。

但要注意不管画面的透视多复杂,当中的视平线却只能有1条,并且无论水平方向有多少个灭点,最后也都会落在视平线上。

如何做好Banner设计?先掌握这个高手都会的空间陈列法!

除了以上3种透视外,其实还有4点透视(超广角透视)、5点透视(鱼眼透视)等等,但都过于复杂,用的也很少,这里就不做展开。对于电商里的「空间陈列」,3种透视已够用,它是我们塑造空间的基础,如果一开始的透视错了,即便配色、光影做的再出彩那也无用。

总有人说透视难掌握,其实只要我们在生活中多观察、多留意身边物体的透视变化和规律,及时总结,那这种理性还原就不难做到。当然设计终归还是理性与感性的双重表达,所以透视虽要遵循,但切忌生搬硬套,视觉协调即可。

讲完了焦点透视,我们知道了空间塑造的基本原则,下面就来说说都有哪些陈列场景。

陈列场景

相对PC来说,手机端受屏幕所限,陈列场景其实没那么复杂,核心是要先构建一个空间,然后让产品以合适的视角及透视在空间里呈现出来,而这个空间场景则要和主题氛围、产品气质都高度匹配。

一般来说,手机端常用的陈列场景有4类:盒子陈列、台面陈列、自然陈列以及舞台陈列,选择哪类则要看哪个场景对产品的烘托效果最好。

1. 产品组合

在介绍每个场景前,我先说说关于产品组合的2原则,因为很多时候在空间摆放的产品数量较多,这时它们的组合形式就变的尤其重要,稍不注意就会显得画面杂乱无章,不够协调,而且凌乱的摆放也会降低产品的品质感,缺少吸引力。关于组合原则,核心有2点:大小合理以及三角构图。

大小合理

如果将多个产品摆一起,则要确保它们之间的相对大小符合现实中的真实差异,现实中尺寸大的产品就要相对大些,而尺寸小的产品就要相对小一点,这样才会真实并经得起推敲。

而有些设计师在组合产品时,也不管大小的真实差异,放进版面后就很随意的放大或缩小,最后出来的组合要不就大小一样,要不就比例失真,这些都会给用户一种强烈的不协调感和不真实感。

下面再看一组对比图,明显大小一样的左图会有不适感,也缺少层次;而大小合理的右图则更有美感也更舒服。

如何做好Banner设计?先掌握这个高手都会的空间陈列法!

其实大小合理的最终目的是希望整体结构错落有致,就像右图一样,这种有高有低的组合才能体现韵律感和结构美。所以如果可以选择,那我们就选择一些尺寸差异较大的产品,尽量避免出现大小差不多的情况。

当然如果必须陈列大小一样的产品,那也可以通过透视或者辅助元素来改善,例如空间里的近大远小、立方体加高都能改变高度一样的情形。

三角构图

当我们选好不同大小的产品后,就要注意它们的组合形式,千万不能乱堆一气,不同的摆放会形成不同结构,而每种结构又会给人不同感受。我们在「图形分割」中讲过「正三角」具有很强的稳定性,因此当产品采用正三角构图时,会让人觉得版面平稳、视觉舒服。

如图所示,所谓三角构图,其实就是将尺寸大的产品放中间,而尺寸小的产品放两边,这样不但构图稳定,而且画面也有节奏和变化。可以说「三角构图」就是空间陈列里最常用的构图方式,而本节展示的所有案例中,大部分也都是三角构图。

如何做好Banner设计?先掌握这个高手都会的空间陈列法!

明确了产品的组合原则,知道了如何陈列才舒服,下面就正式讲讲空间陈列的4类场景。

2. 场景

盒子陈列

「盒子陈列」就是在盒子里面放产品,而盒子多以礼盒为主,使用场景和主题相对单一,基本用于送礼之意的专题页。创意虽然普通,但却不易出错,是种相对安全的表现方式。当然若能在盒里加些小心思,画面也会很出彩,像我之前看过一个新年Banner,就是礼盒里装着一个大家庭在吃年夜饭的温馨场景,这样的新组合便让人眼前一亮。

另外若能提升礼盒刻画的精致度,那画面也会有不错的设计感。而盒子外形也不只有方形,常用的还有圆形和异形。观察视角则以小角度俯视居多,因为这个视角最接近我们日常看礼盒的真实情形,盒内产品在俯视下能看的一清二楚,展现也立体。

盒子陈列的难点在于当盒内要摆放很多产品时,如何能让产品真实、自然的呈现,这需要我们既注意摆放的合理性,也能准确表现透视,还要刻画出产品的明暗变化,总之只有把握好产品的空间感、立体感以及光影感,画面才会舒服协调。

方形盒是最常用的盒子类型,毕竟也是生活中最常用的礼盒外形,结构感强而且易表现。如下图所示,礼盒都是成角透视,且左右灭点都在画面之外,这样结构最稳定,立体感也强。注意盒里的产品呈现,特别是俯视视角下,产品越多越要注意它们是否协调统一,透视、光影等细节一个都不能少,把控不到位就会显得凌乱,画面别扭。

如何做好Banner设计?先掌握这个高手都会的空间陈列法!

圆形盒比较少见,因为和立方体比起来,圆柱体的透视没那么强烈,结构感偏弱,但圆润的外形能使画面变的柔和,给人一种亲和力和温馨感,如图所示,由于圆形盒没有明显块面,所以不管透视还是光影,刻画起来都相对简单。

如何做好Banner设计?先掌握这个高手都会的空间陈列法!

异形盒是指外形为不规则形状的盒子,总体也很少见,但易出彩。形状用的好便能打破「盒子陈列」的常规感,使画面变的新颖有创意。

例如下方案例中,不管是心形、猫头轮廓还是圣诞树,都能成为画面焦点并引人注意。另外盒子呈现均用了「正俯视」视角,其实除了小角度俯视外,这种视角也很常见,因为该视角下的产品陈列清晰完整,盒子外形也能直观显示,的展现了其外形的特别之处。

如何做好Banner设计?先掌握这个高手都会的空间陈列法!

台面陈列

在空间陈列的4类场景中,「台面陈列」用的最多,适用范围也最广,可以说电商中的大部分主题和产品都能使用,算是一种真正白搭的表现方式。

「台面陈列」就是在空间里搭建一个「台面」,然后在上面放置相关产品。由于该手法还是以放大特写的小场景为主,元素形变不能太大,因此画面常用平行透视和成角透视,视角则很灵活,跟着构图走,3种(平视、俯视、仰视)都有。「台面陈列」上手简单,场景多变,其中的关键元素——台面需要根据主题、场景进行灵活变化,常见有2类:桌面和几何体。

桌面很好理解,就是桌子顶部的陈列面,所有产品都放置其上。由于桌子是家里常见家具之一,因此桌面陈列往往能传递一种「家」的温暖和温馨。

可能有人觉得「桌面」形式有些单一,其实远没那么简单,我们不要固化思维,要能灵活变化桌子的样式和装饰,例如方桌还是圆桌?木桌还是大理石桌?光面还是铺桌布?这些都是可变量,再加上视角和周围环境的变化,总之形式可以很丰富。

如图所示,桌面陈列尤其适合各类美食的组合呈现,这时整个场景贴近生活,颇有带入感。

如何做好Banner设计?先掌握这个高手都会的空间陈列法!

就是将各种几何形体作为陈列产品的台面,几何体相对抽象,表现场景更多元,因此比具象「桌面」更加常用。

如图所示,几何体一般都是组合出现,特别适合多产品陈列,简约大方,能烘托出产品的品质感。同时通过高高低低的大小排列也能表现出画面的结构美以及层次感,总之是一种 「上手简单、易出效果」的表现方式。

如何做好Banner设计?先掌握这个高手都会的空间陈列法!

常用几何体有「立方体」和「圆柱体」,它们适合陈列,高度调节方便,使用灵活。

如下图所示,整体表现并不复杂,就是将各种产品放在几何体上。但作为画面的核心元素,这时几何体的形态、排列、视角和透视就变的非常重要,我们要根据创意需求和产品气质选择最合适的展现方式,而这些展现本身就有不错的形式感。

几何体陈列既能营造空间关系和简约气质,也能让用户聚焦产品本身,因为它的外形简单,不抢产品,不像一些复杂元素或场景,虽然视觉丰富但最后却让产品淹没其中,这样就本末倒置。

如何做好Banner设计?先掌握这个高手都会的空间陈列法!

自然陈列

「自然陈列」需要先创建一个合适的自然环境,然后再将产品以合适方式融入其中。相对其他场景,自然环境显然复杂一些,呈现手法常以「合成」和「插画」为主。因为产品都是实物拍摄,为了风格统一,自然环境会偏向写实风格,这样2者结合才协调。

从下方案例能看到,「自然陈列」常用于季节感受或者产地溯源等主题,画面通过「自然场景」营造出天然健康的绿色氛围。而场景中的元素繁多,呈现复杂,这就需要我们具备优秀的整合能力。

对于「自然」塑造,视角以平视和小角度俯视居多,但画面由于没有太多的几何型物体,所以透视没那么严谨,核心是注意近、中、远景的层次区分,还有光影的合理添加。如果这些把控不到位,就很可能出现场景杂乱、缺少层次、没有带入感的粗糙画面,最终沦为各种素材的乱堆一气。

如何做好Banner设计?先掌握这个高手都会的空间陈列法!

以上列举的都是以花草树木为主的自然环境,确实绿色场景在「自然陈列」中用的也是最多,但除了绿色场景外,有时也会用到其他环境。如下图所示,像海底 ① 、沙滩 ② 、海面 ③ 、冰山 ④ 也是适合陈列的自然场景,特别是夏季主题会经常用到。

如何做好Banner设计?先掌握这个高手都会的空间陈列法!

舞台陈列

最后一类「舞台陈列」常用于大促主题的气氛营造,这类场景不挑类目,任何产品放在「舞台」上,灯光一打,色彩再斑斓些,都能营造出热闹的促销氛围。

如图所示,舞台外形以圆形居多,因为圆形的透视感较弱,构图灵活,而且也符合大家对舞台的第一印象。舞台视角则很灵活,3种均很常见,核心是和产品视角保持一致。

如何做好Banner设计?先掌握这个高手都会的空间陈列法!

关于「舞台」塑造,还有2处需要注意的地方:

舞台外形除了最常用的「圆形」外,还有半圆形、方形和六边形等等;舞台造型也可以很丰富,并不局限于常规的表演舞台,各种造型都可尝试,例如上方左四图就是现代感十足的三维舞台,总之我们要根据创意和风格塑造相匹配的陈列舞台。

另外就是灯光运用,可以说这是「舞台陈列」和其他场景的最大区别,但灯光也不是越多越好,太多反而显得眼花缭乱,其实能渲染出绚烂气氛即可。而有光就有影,在灯光照射下,产品一定要有准确的光影呼应,这样才不会显得突兀。例如上方案例中,仔细观察灯光下的产品呈现,能看到产品表面都产生了被灯光照射后的色彩、明暗等变化,这些细节刻画才让画面更真实,融合更自然。

3. 实战案例

本次案例会用胶原蛋白口服液作主体元素,然后用这4类场景(盒子、台面、自然、舞台)来设计4张不同视角、不同风格的Banner,让大家看看在不同场景下,如何将产品融入其中。先展示案例会用到的3种产品视角, 下方案例会根据不同的场景视角选择对应素材。

如何做好Banner设计?先掌握这个高手都会的空间陈列法!

如何做好Banner设计?先掌握这个高手都会的空间陈列法!

总结

「空间陈列法」的内容量挺大,主要分成了「焦点透视」和「陈列场景」2大部分来介绍,其中焦点透视是立体「空间」的塑造基础;而陈列场景则是产品「陈列」的具体环境。

常用场景有4类:盒子陈列、台面陈列、自然陈列和舞台陈列,每种陈列都有各自适用的主题和氛围:「盒子」常用于温馨的送礼主题;「台面」则能根据不同主题灵活应变,属于百搭场景;而「自然」则适合季节或者溯源主题,体现天然清新感;最后的「舞台」则用于氛围浓烈的大促主题。不管哪种场景,都要确保产品和空间的视角、透视相一致,这样场景才会真实协调。另外多产品陈列时,还要注意它们之间的大小比例以及摆放结构,其中三角结构最常用。总之在手机时代,「空间陈列」是一种真正适合小屏竖构图的表现方式。

文章来源:优设    作者:贤辈

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

手机appUI界面设计赏析(四)

前端达人

与传统PC桌面不同,手机屏幕的尺寸更加小巧操作,方式也已触控为主,APP界面设计不但要保证APP功能的完整性和合理性,又要保证APP的功能性和实用性,在保证其拥有流畅的操作感受的同时,满足人们的审美需求。

接下来为大家介绍几款手机appui界面设计

微信图片_20200721175459.jpg

微信图片_20200721175502.jpg

微信图片_20200721175510.jpg

微信图片_20200721175514.jpg

微信图片_20200721175540.jpg

微信图片_20200721175544.jpg

微信图片_20200721175548.jpg

微信图片_20200721175624.png

微信图片_20200721175631.jpg

微信图片_20200721175635.jpg

微信图片_20200721175639.jpg

微信图片_20200727222354.png

微信图片_20200727222406.jpg

微信图片_20200727222412.png

微信图片_20200727222421.jpg

微信图片_20200727222431.jpg


   --手机appUI设计--


(以上图片均来源于网络)



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



  更多精彩文章:

       手机appUI界面设计赏析(一)

       手机appUI界面设计赏析(二)

       手机appUI界面设计赏析(三)



巧用伪元素before和after制作绚丽效果

seo达人

CSS :before 选择器

定义和说明

:before 选择器向选定的元素前插入内容。

使用content 属性来指定要插入的内容。


CSS :after 选择器

定义和说明

:after 选择器向选定的元素之后插入内容。

使用content 属性来指定要插入的内容。


这两个伪元素会在真正页面元素之前和之后插入一个额外的元素,从技术角度上讲,它们与下面的HTML标记是等效的。


1.伪类光圈



<div class="hover-circle">CSS</div>

.hover-circle {

 width: 100%;

 display: flex;

 align-items: center;

 justify-content: center;

 height: 100%;

 font-size: 3rem;

 letter-spacing: 0.3rem;

 font-weight: bold;

 position: relative;

 cursor: pointer;

 color: #666;

}


.hover-circle::before {

 width: 8.5rem;

 height: 8.5rem;

 border: 3px solid pink;

 content: "";

 border-radius: 50%;

 position: absolute;

 opacity: 0;

}


.hover-circle::after {

 width: 7.2rem;

 height: 7.2rem;

 border: 6px solid pink;

 content: "";

 border-radius: 50%;

 position: absolute;

 opacity: 0;

}


.hover-circle:hover::before,

.hover-circle:hover::after {

 animation-duration: 0.8s;

 animation-delay: 0.2s;

 animation: circle 0.8s;

}


@keyframes circle {

 0% {

   opacity: 0;

   scale: 1;

 }


 25% {

   opacity: 0.25;

 }


 50% {

   opacity: 0.5;

   scale: 1.03;

 }


 75% {

   opacity: 0.75;

 }


 100% {

   opacity: 1;

   scale: 1.03;

 }

}

2.伪类括号效果



<div class="hover-text">CSS</div>

.hover-text {

 width: 100%;

 display: flex;

 align-items: center;

 justify-content: center;

 height: 100%;

 font-size: 3rem;

 letter-spacing: 0.3rem;

 font-weight: bold;

 position: relative;

 cursor: pointer;

 color: #666;

}


.hover-text::before {

 content: "[";

 position: absolute;

 left: 0.8rem;

 opacity: 0;

 color: #999;

}


.hover-text::after {

 content: "]";

 position: absolute;

 right: 0.8rem;

 opacity: 0;

 color: #999;

}


.hover-text:hover::before {

 animation-duration: 0.8s;

 animation-delay: 0.2s;

 animation: hovertext1 0.8s;

}


.hover-text:hover::after {

 animation-duration: 0.8s;

 animation-delay: 0.2s;

 animation: hovertext2 0.8s;

}


@keyframes hovertext1 {

 0% {

   opacity: 0;

   left: 0.8rem;

 }


 100% {

   opacity: 1;

   left: 0.5rem;

 }

}


@keyframes hovertext2 {

 0% {

   opacity: 0;

   right: 0.8rem;

 }


 100% {

   opacity: 1;

   right: 0.5rem;

 }

}

3.炫酷丝带效果

双边丝带



<div class="tc">

   <div class="title1"><span>距离结束还有10天</span></div>

</div>

.title1 {

 position: relative;

 display: inline-block;

}


.title1 span {

 position: relative;

 z-index: 2;

 display: inline-block;

 padding: 0 15px;

 height: 32px;

 line-height: 32px;

 background-color: #dc5947;

 color: #fff;

 font-size: 16px;

 box-shadow: 0 10px 6px -9px rgba(0, 0, 0, 0.6);

}


.title1 span::before,

.title1 span::after {

 position: absolute;

 bottom: -6px;

 border-width: 3px 5px;

 border-style: solid;

 content: "";

}


.title1 span::before {

 left: 0;

 border-color: #972f22 #972f22 transparent transparent;

}


.title1 span::after {

 right: 0;

 border-color: #972f22 transparent transparent #972f22;

}


.title1::before,

.title1::after {

 position: absolute;

 top: 6px;

 content: "";

 border-style: solid;

 border-color: #dc5947;

}


.title1::before {

 left: -32px;

 border-width: 16px 26px 16px 16px;

 border-left-color: transparent;

}


.title1::after {

 right: -32px;

 border-width: 16px 16px 16px 26px;

 border-right-color: transparent;

}

右边丝带



<span class="title2">距离结束还有10天</span>

.title2 {

 position: relative;

 display: inline-block;

 padding: 0 15px;

 height: 32px;

 line-height: 32px;

 background-color: #dc5947;

 color: #fff;

 font-size: 16px;

}


.title2::before {

 position: absolute;

 top: -4px;

 left: 0;

 border-width: 2px 4px;

 border-style: solid;

 border-color: transparent #972f22 #972f22 transparent;

 content: "";

}


.title2::after {

 position: absolute;

 top: 0;

 right: -8px;

 border-width: 16px 8px 16px 0;

 border-style: solid;

 border-color: #dc5947 transparent #dc5947 #dc5947;

 content: "";

}

箭头丝带



<span class="title3">距离结束还有10天</span>

.title3 {

 position: relative;

 display: inline-block;

 margin-right: 16px;

 padding: 0 10px;

 height: 32px;

 line-height: 32px;

 background-color: #dc5947;

 color: #fff;

 font-size: 16px;

}


.title3::before {

 position: absolute;

 top: 0;

 left: -16px;

 border-width: 16px 16px 16px 0;

 border-style: solid;

 border-color: transparent #dc5947 transparent transparent;

 content: "";

}


.title3::after {

 position: absolute;

 top: 0;

 right: -16px;

 border-width: 16px 16px 16px 0;

 border-style: solid;

 border-color: #dc5947 transparent #dc5947 #dc5947;

 content: "";

}

多个箭头丝带



<div class="mt30 pl16">

   <span class="title3">距离结束还有10天</span>

   <span class="title3 ml5">距离结束还有10天</span>

   <span class="title3 ml5">距离结束还有10天</span>

</div>

.title4 {

 width: 200px;

 height: 140px;

 position: absolute;

 top: -8px;

 left: -8px;

 overflow: hidden;

}


.title4::before {

 position: absolute;

 left: 124px;

 border-radius: 8px 8px 0 0;

 width: 16px;

 height: 8px;

 background-color: #972f22;

 content: "";

}


.title4::after {

 position: absolute;

 left: 0;

 top: 124px;

 border-radius: 0 8px 8px 0;

 width: 8px;

 height: 16px;

 background-color: #972f22;

 content: "";

}


.title4 span {

 display: inline-block;

 text-align: center;

 width: 200px;

 height: 40px;

 line-height: 40px;

 position: absolute;

 top: 30px;

 left: -50px;

 z-index: 2;

 overflow: hidden;

 -ms-transform: rotate(-45deg);

 -moz-transform: rotate(-45deg);

 -webkit-transform: rotate(-45deg);

 -o-transform: rotate(-45deg);

 transform: rotate(-45deg);

 border: 1px dashed #fff;

 box-shadow: 0 0 0 3px #dc5947, 0 14px 7px -9px rgba(0, 0, 0, 0.6);

 background-color: #dc5947;

 color: #fff;

}

悬挂标签



<div class="pr mt30" style="background-color: #eee; height: 200px;">

   <div class="title4"><span>企业热门动态</span></div>

   <div class="title5"><span>企业热门动态</span></div>

</div>

.title5 {

 width: 140px;

 height: 200px;

 position: absolute;

 top: -8px;

 right: -8px;

 overflow: hidden;

}


.title5::before {

 position: absolute;

 right: 124px;

 border-radius: 8px 8px 0 0;

 width: 16px;

 height: 8px;

 background-color: #972f22;

 content: "";

}


.title5::after {

 position: absolute;

 right: 0;

 top: 124px;

 border-radius: 0 8px 8px 0;

 width: 8px;

 height: 16px;

 background-color: #972f22;

 content: "";

}


.title5 span {

 display: inline-block;

 text-align: center;

 width: 200px;

 height: 40px;

 line-height: 40px;

 position: absolute;

 top: 30px;

 right: -50px;

 z-index: 2;

 overflow: hidden;

 -ms-transform: rotate(45deg);

 -moz-transform: rotate(45deg);

 -webkit-transform: rotate(45deg);

 -o-transform: rotate(45deg);

 transform: rotate(45deg);

 border: 1px dashed #fff;

 box-shadow: 0 0 0 3px #dc5947, 0 14px 7px -9px rgba(0, 0, 0, 0.6);

 background-color: #dc5947;

 color: #fff;

}

4.几何图形

三角形



<div class="triangle"></div>

.triangle {

 width: 0;

 height: 0;

 margin: 50px auto;

 border-bottom: 100px solid #dc5947;

 border-left: 50px solid transparent;

 border-right: 50px solid transparent;

 cursor: pointer;

 transform: scale(1.2);

 transition: 0.5s;

}

五角星



<div class="pentagram"></div>

.pentagram {

 width: 0;

 height: 0;

 margin: 100px auto;

 position: relative;

 border-bottom: 70px solid #dc5947;

 border-left: 100px solid transparent;

 border-right: 100px solid transparent;

 -webkit-transform: rotate(35deg);

 -moz-transform: rotate(35deg);

 -ms-transform: rotate(35deg);

 -o-transform: rotate(35deg);

 transform: rotate(35deg);

 -webkit-transform: scale(1), rotate(35deg);

 -moz-transform: scale(1), rotate(35deg);

 -ms-transform: scale(1), rotate(35deg);

 -o-transform: scale(1), rotate(35deg);

 transform: scale(1), rotate(35deg);

}


.pentagram::after {

 content: "";

 width: 0;

 height: 0;

 border-bottom: 70px solid #dc5947;

 border-left: 100px solid transparent;

 border-right: 100px solid transparent;

 -webkit-transform: rotate(-70deg);

 -moz-transform: rotate(-70deg);

 -ms-transform: rotate(-70deg);

 -o-transform: rotate(-70deg);

 transform: rotate(-70deg);

 position: absolute;

 top: 0px;

 left: -100px;

}


.pentagram::before {

 content: "";

 width: 0;

 height: 0;

 border-bottom: 80px solid #dc5947;

 border-left: 30px solid transparent;

 border-right: 30px solid transparent;

 -webkit-transform: rotate(-35deg);

 -moz-transform: rotate(-35deg);

 -ms-transform: rotate(-35deg);

 -o-transform: rotate(-35deg);

 transform: rotate(-35deg);

 position: absolute;

 top: -45px;

 left: -60px;

}

5.水滴



<div class="drop"></div>

.drop::after {

 content: "";

 position: absolute;

 width: 30px;

 height: 20px;

 border-radius: 50%;

 background-color: #ace3ff;

 margin: 100px auto;

 top: -50px;

 left: 25px;

 box-shadow: 5px 12px 4px #ace3ff, -5px 11px 4px #ace3ff, 0px 14px 4px #4d576e;

 -webkit-transform: rotate(35deg);

}


.drop::before {

 content: "";

 position: absolute;

 width: 0px;

 height: 0px;

 border-style: solid;

 border-width: 0 40px 50px 40px;

 border-color: transparent transparent #ace3ff transparent;

 top: -30px;

 left: 10px;

}


.drop {

 width: 100px;

 height: 100px;

 border-radius: 50%;

 background-color: #ace3ff;

 position: relative;

 margin: 100px auto;

 box-shadow: 0px 6px 0 #3f475a;

}

6 绚丽流动边框





<div class="box-line1"></div>

.box-line2,

.box-line2::before,

.box-line2::after {

 position: absolute;

 top: 0;

 bottom: 0;

 left: 0;

 right: 0;

}


.box-line2 {

 width: 200px;

 height: 200px;

 margin: auto;

 color: #69ca62;

 box-shadow: inset 0 0 0 1px rgba(105, 202, 98, 0.5);

}


.box-line2::before,

.box-line2::after {

 content: "";

 z-index: 99;

 margin: -5%;

 box-shadow: inset 0 0 0 2px;

 animation: clipMe 8s linear infinite;

}


.box-line2::before {

 animation-delay: -4s;

}


.box-line2:hover::after,

.box-line2:hover::before {

 background-color: rgba(255, 0, 0, 0.3);

}


@keyframes clipMe {


 0%,

 100% {

   clip: rect(0px, 220px, 2px, 0px);

 }


 25% {

   clip: rect(0px, 2px, 220px, 0px);

 }


 50% {

   clip: rect(218px, 220px, 220px, 0px);

 }


 75% {

   clip: rect(0px, 220px, 220px, 218px);

 }

}


@keyframes surround {


 0%,

 100% {

   clip: rect(0px, 220px, 2px, 0px);

 }


 25% {

   clip: rect(0px, 2px, 220px, 0px);

 }


 50% {

   clip: rect(218px, 220px, 220px, 0px);

 }


 75% {

   clip: rect(0px, 220px, 220px, 218px);

 }

}


.box-line1:before,

.box-line1:after {

 position: absolute;

 top: 0;

 left: 0;

 bottom: 0;

 right: 0;

 content: "";

 z-index: 99;

 margin: -5%;

 animation: surround linear infinite 8s;

 box-shadow: inset 0 0 0 2px #69ca62;

}


.box-line1:before {

 animation-delay: -4s;

}


.box-line1 {

 border: 1px solid #69ca62;

 position: absolute;

 left: 500px;

 top: 200px;

 margin: auto;

 width: 200px;

 height: 200px;

 margin: auto;

}

7.Tooltip提示



<div class="tip" data-tip="CSS伪类">CSS伪类</div>

.tip::after {

 content: attr(data-tip);

 display: none;

 position: absolute;

 padding: 5px 10px;

 left: 15%;

 bottom: 100%;

 width: 150px;

 margin-bottom: 12px;

 transform: translateX(-50%);

 font-size: 12px;

 background: #000;

 color: #fff;

 cursor: default;

 border-radius: 4px;

}


.tip::before {

 content: " ";

 position: absolute;

 display: none;

 left: 15%;

 bottom: 100%;

 transform: translateX(-50%);

 margin-bottom: 3px;

 width: 0;

 height: 0;

 border-left: 6px solid transparent;

 border-right: 6px solid transparent;

 border-top: 9px solid #000;

}


.tip:hover::after,

.tip:hover::before {

 display: block;

}

8.CSS 伪类盒子阴影

使用伪元素:before and :after制作出了完美惊艳的相片阴影效果。其中的技巧是使用绝对定位固定伪元素,然后给它们的z-index一个负值,以背景出现。






<div class="box effect2">

   <h3>CSS 伪类盒子阴影</h3>

</div>

.effect2 {

   position: relative;

}


.effect2::before, .effect2::after {

   z-index: -1;

   position: absolute;

   content: "";

   bottom: 15px;

   left: 10px;

   width: 50%;

   top: 80%;

   max-width: 300px;

   background: #777;

   -webkit-box-shadow: 0 15px 10px #777;

   -moz-box-shadow: 0 15px 10px #777;

   box-shadow: 0 15px 10px #777;

   -webkit-transform: rotate(-3deg);

   -moz-transform: rotate(-3deg);

   -o-transform: rotate(-3deg);

   -ms-transform: rotate(-3deg);

   transform: rotate(-3deg);

}

.effect2::after {

   -webkit-transform: rotate(3deg);

   -moz-transform: rotate(3deg);

   -o-transform: rotate(3deg);

   -ms-transform: rotate(3deg);

   transform: rotate(3deg);

   right: 10px;

   left: auto;

}

CSS Box 阴影效果


9.Tabs当前激活状态



   <div class="sm-box flex">

       <div class="menu-tabs active">首页</div>

       <div class="menu-tabs">新闻</div>

       <div class="menu-tabs">视频</div>

       <div class="menu-tabs">图片</div>

   </div>

.menu-tabs {

 display: block;

 padding: 0.25rem 1.5rem;

 clear: both;

 font-weight: 400;

 color: #212529;

 text-align: inherit;

 white-space: nowrap;

 background-color: transparent;

 width: 50px;

 border: 0;

 height: 35px;

 justify-content: center;

 display: flex;

 cursor: pointer;

}


.menu-tabs:hover {

 color: #20a884;

 position: relative;

}


.menu-tabs:hover:after {

 position: absolute;

 content: "";

 border: 1px solid #20a884;

 width: 3rem;

 left: 0;

 bottom: 0;

 margin-left: 50%;

 transform: translateX(-50%);

}


.active {

 position: relative;

 color: #20a884;

}


.flex {

 display: flex;

}


.active::after {

 position: absolute;

 content: "";

 border: 1px solid #20a884;

 width: 3rem;

 left: 0;

 bottom: 0;

 margin-left: 50%;

 transform: translateX(-50%);

}

10.伪元素模糊背景



<div class="container">

  <div class="overlay">

     <h1>A blurred overlay</h1>

    <p>... mask or whatever

    <br>that is responsive and could be cross-browser compatible back to IE9</p>

  </div>

</div>

.container {

 width: 100%;

 height: 100%;

 margin: 0;

}


.container,

.overlay:before {

 background: url(https://wow.techbrood.com/assets/landing.jpg) no-repeat fixed 0 0 / cover;

}


.container {

 -webkit-box-align: center;

 -webkit-align-items: center;

 -ms-flex-align: center;

 align-items: center;

 display: -webkit-box;

 display: -webkit-flex;

 display: -ms-flexbox;

 display: flex;

 -webkit-box-pack: center;

 -webkit-justify-content: center;

 -ms-flex-pack: center;

 justify-content: center;

}


.overlay {

 max-height: 200px;

 margin: 0 auto;

 max-width: 768px;

 padding: 50px;

 position: relative;

 color: white;

 font-family: "Lato";

 position: relative;

 text-align: center;

 z-index: 0;

}


.overlay:before {

 content: "";

 -webkit-filter: blur(100px);

 filter: blur(100px);

 height: 100%;

 left: 0;

 position: absolute;

 top: 0;

 width: 100%;

 z-index: -1;

}

11.蓝湖文字



<span class="lanhu_text">

    本站由叫我詹躲躲提供技术支持

</span>

.lanhu_text {

 position: relative;

 color: #2878ff;

}


.lanhu_text::before {

 content: "";

 width: 80px;

 height: 20px;

 position: absolute;

 left: -86px;

 top: 0;

 background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAAABCAYAAABJwyn/AAAAjElEQVQoU22NSw7CQAxDX8ahICF2HIDTcf9d1c8kaDpthQSL6CmxHRuk8cZfMxqf6DGh+Y5uCxquaB7xdUCXilZHWvBorEiOaqAKrkRRUEmUOw283TKRRb9b4GnIEpWmGYrA237kDh1w6J5N7zzzZv13gtuvT7t++jefUTYmwvpk7v3fPaCzn//9LfsBvRpHnliu+xMAAAAASUVORK5CYII=) 0 no-repeat;

}


.lanhu_text::after {

 content: "";

 width: 80px;

 height: 20px;

 position: absolute;

 right: -86px;

 top: 0;

 background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAAABCAYAAABJwyn/AAAAhElEQVQoU22OSw7CQAxDXzJDWbHiEFyRJQflFggJSjyDph9oC6snx45lO52rrKJqzIyNlkFUPv6vnv+3uW9vrPpBq77/uWFPAXnrnfYVQ16JiWq0stjvxKB9vDc/MeaXWo5cqDEFUkKpEuEoB3ollHvUZ9QV4rFD3R3d9ujwJK5HxAW9AStemAIOwCNlAAAAAElFTkSuQmCC) 100% no-repeat;

}

12 主要标题



<div class="first-title">服务项目</div>

.first-title {

 position: relative;

 color: #a98661;

 font-weight: 400;

 font-size: 30px;

 text-align: center;

}


.first-title::before,

.first-title::after {

 position: absolute;

 content: "";

 width: 110px;

 border-bottom: 1px solid #a98661;

 top: 50%;

 transform: translateY(-50%);

}


.first-title::before {

 left: 100px;

}


.first-title::after {

 right: 100px;

}

13.鼠标浮层遮罩浮层



<div class="black-mask"></div>

.black-mask {

 position: relative;

 height: 100%;

 width: 100%;

 cursor: pointer;

}


.black-mask:hover {

 transition-duration: 1s;

 scale: 1.02;

}


.black-mask:hover:before {

 object-fit: cover;

}


.black-mask:hover:after {

 height: 100%;

 opacity: 1;

 transition-duration: 1s;

 display: flex;

 align-items: flex-end;

 padding: 0 30px 15px;

}


.black-mask::before {

 position: absolute;

 content: "";

 background: url(https://dcdn.it120.cc/2019/11/14/f17c5848-6d1f-4254-b3ba-64d3969d16b6.jpg) no-repeat;

 background-size: 100% 100%;

 width: 100%;

 height: 100%;

}


.black-mask::after {

 position: absolute;

 content: "雾在微风的吹动下滚来滚去,像冰峰雪山,似蓬莱仙境,如海市蜃楼,使人觉得飘然欲仙。山河景色在雾的装点下,变得更加美丽。远处的七连山巍峨挺拔,它们仿佛成了神仙住的宝山,令人神往。近处池塘边时时飘来雾气,在初升阳光的照耀下,呈现出赤、橙、黄、绿、青、蓝、紫七种色彩。......";

 width: 90%;

 height: 0%;

 bottom: 0;

 right: 0;

 z-index: 32;

 background: rgba(0, 0, 0, 0.3);

 opacity: 1;

 color: #fff;

 opacity: 0;

 padding: 0 30px 0;

}

14.绚丽光圈



<div class="aperture">光圈</div>

.aperture {

 width: 136px;

 height: 136px;

 background-color: #dc5947;

 border-radius: 50%;

 line-height: 136px;

 text-align: center;

 color: #fff;

 font-size: 24px;

 cursor: pointer;

 position: relative;

}


.aperture::before {

 border: 3px dashed #a0ff80;

 content: "";

 width: 144px;

 height: 144px;

 position: absolute;

 border-radius: 50%;

 left: -8px;

 top: -6px;

 animation: clockwise 5s linear infinite;

}


@keyframes clockwise {

 100% {

   transform: rotate(360deg);

 }

}

15.彩色流动边框



<div class="rainbow"></div>

.rainbow {

 position: relative;

 z-index: 0;

 width: 400px;

 height: 300px;

 border-radius: 10px;

 overflow: hidden;

 padding: 2rem;

}


.rainbow::before {

 content: '';

 position: absolute;

 z-index: -2;

 left: -50%;

 top: -50%;

 width: 200%;

 height: 200%;

 background-color: #399953;

 background-repeat: no-repeat;

 background-size: 50% 50%, 50% 50%;

 background-position: 0 0, 100% 0, 100% 100%, 0 100%;

 background-image: linear-gradient(#399953, #399953), linear-gradient(#fbb300, #fbb300), linear-gradient(#d53e33, #d53e33), linear-gradient(#377af5, #377af5);

 -webkit-animation: rotate 4s linear infinite;

 animation: rotate 4s linear infinite;

}


.rainbow::after {

 content: '';

 position: absolute;

 z-index: -1;

 left: 6px;

 top: 6px;

 width: calc(100% - 12px);

 height: calc(100% - 12px);

 background: white;

 border-radius: 5px;

}


@keyframes rotate {

 100% {

   -webkit-transform: rotate(1turn);

   transform: rotate(1turn);

 }

}

16.炫酷伪类边框



<div class="corner-button">CSS3</div>

.corner-button::before, .corner-button::after {

 content: '';

 position: absolute;

 background: #2f2f2f;

 z-index: 1;

 transition: all 0.3s;

}

.corner-button::before {

 width: calc(100% - 3rem);

 height: calc(101% + 1rem);

 top: -0.5rem;

 left: 50%;

 -webkit-transform: translateX(-50%);

 transform: translateX(-50%);

}

.corner-button::after {

 height: calc(100% - 3rem);

 width: calc(101% + 1rem);

 left: -0.5rem;

 top: 50%;

 -webkit-transform: translateY(-50%);

 transform: translateY(-50%);

}



.corner-button:hover {

 color: pink;

}

.corner-button {

 font-family: 'Lato', sans-serif;

 letter-spacing: .02rem;

 cursor: pointer;

 background: transparent;

 border: 0.5rem solid currentColor;

 padding: 1.5rem 2rem;

 font-size: 2.2rem;

 color: #06c17f;

 position: relative;

 transition: color 0.3s;

 text-align: center;

 margin: 5rem 12rem;

}

.corner-button:hover::after {

 height: 0;

}


.corner-button:hover::before {

 width: 0;

}

.bg-f2{

 background: #2f2f2f;

}

17.伪类美化文字



<div class="beautify-font" data-text='躲躲'>躲躲</div>

<div class="beautify-font2" data-text='躲躲'>躲躲</div>

.beautify-font{

 position: relative;

 font-size: 12rem;

 color: #0099CC

}

.beautify-font::before{

 position: absolute;

 font-size: 12rem;

 color: #333;

 content: attr(data-text);

 white-space:nowrap;

 width: 50%;

 display: inline-block;

 overflow: hidden;

 transition:1s ease-in-out 0s;

}

.beautify-font2{

 position: relative;

 font-size: 6rem;

 color: #0099CC

}

.beautify-font2::before{

 position: absolute;

 font-size: 6rem;

 color: #333;

 content: attr(data-text);

 white-space:nowrap;

 height: 50%;

 display: inline-block;

 overflow: hidden;

 transition:1s ease-in-out 0s;

}


.beautify-font:hover::before{

 width:0;

}

.beautify-font2:hover::before{

 height: 0;

}

18.照片堆叠效果

只使用一张图片来创造出一堆图片叠摞在一起的效果,能做到吗?当然,关键是要使用伪元素:before和:after来帮助呈现。把这些伪元素的z-index设置成负值,让它们以背景方式起作用。




<div class="stackthree"><img src="./images/city.jpg"></div>

.stackthree::before {

 background: #eff4de;

}


.stackthree, .stackthree::before, .stackthree::after {

 border: 6px solid #fff;

 height: 200px;

 width: 200px;

 -webkit-box-shadow: 2px 2px 5px rgba(0,0,0,0.3);

 -moz-box-shadow: 2px 2px 5px rgba(0,0,0,0.3);

 box-shadow: 2px 2px 5px rgba(0,0,0,0.3);

}


.stackthree::before {

 top: 5px;

 left: -15px;

 z-index: -1;

 -webkit-transform: rotate(-10deg);

 -moz-transform: rotate(-10deg);

 -o-transform: rotate(-10deg);

 -ms-transform: rotate(-10deg);

 transform: rotate(-10deg);

}

.stackthree::after {

 top: -2px;

 left: -10px;

 -webkit-transform: rotate(-5deg);

 -moz-transform: rotate(-5deg);

 -o-transform: rotate(-5deg);

 -ms-transform: rotate(-5deg);

 transform: rotate(-5deg);

}


.stackthree::before, .stackthree::after {

 background: #768590;

 content: "";

 position: absolute;

 z-index: -1;

 height: 0px\9;

 width: 0px\9;

 border: none\9;

}

.stackthree {

 float: left;

 position: relative;

 margin: 50px;

}

为元素的兼容性

不论你使用单冒号还是双冒号语法,浏览器都能识别。因为IE8只支持单冒号的语法,所以,如果你想兼容IE8,保险的做法是使用单冒号。

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

你不知道的 WebSocket

seo达人

在最后的 阿宝哥有话说 环节,阿宝哥将介绍 WebSocket 与 HTTP 之间的关系、WebSocket 与长轮询有什么区别、什么是 WebSocket 心跳及 Socket 是什么等内容。


下面我们进入正题,为了让大家能够更好地理解和掌握 WebSocket 技术,我们先来介绍一下什么是 WebSocket。


一、什么是 WebSocket

1.1 WebSocket 诞生背景

早期,很多网站为了实现推送技术,所用的技术都是轮询。轮询是指由浏览器每隔一段时间向服务器发出 HTTP 请求,然后服务器返回的数据给客户端。常见的轮询方式分为轮询与长轮询,它们的区别如下图所示:




为了更加直观感受轮询与长轮询之间的区别,我们来看一下具体的代码:




这种传统的模式带来很明显的缺点,即浏览器需要不断的向服务器发出请求,然而 HTTP 请求与响应可能会包含较长的头部,其中真正有效的数据可能只是很小的一部分,所以这样会消耗很多带宽资源。


比较新的轮询技术是 Comet)。这种技术虽然可以实现双向通信,但仍然需要反复发出请求。而且在 Comet 中普遍采用的 HTTP 长连接也会消耗服务器资源。


在这种情况下,HTML5 定义了 WebSocket 协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。Websocket 使用 ws 或 wss 的统一资源标志符(URI),其中 wss 表示使用了 TLS 的 Websocket。如:


ws://echo.websocket.org

wss://echo.websocket.org

WebSocket 与 HTTP 和 HTTPS 使用相同的 TCP 端口,可以绕过大多数防火墙的限制。默认情况下,WebSocket 协议使用 80 端口;若运行在 TLS 之上时,默认使用 443 端口。


1.2 WebSocket 简介

WebSocket 是一种网络传输协议,可在单个 TCP 连接上进行全双工通信,位于 OSI 模型的应用层。WebSocket 协议在 2011 年由 IETF 标准化为 RFC 6455,后由 RFC 7936 补充规范。


WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就可以创建持久性的连接,并进行双向数据传输。


介绍完轮询和 WebSocket 的相关内容之后,接下来我们来看一下 XHR Polling 与 WebSocket 之间的区别:




1.3 WebSocket 优点

较少的控制开销。在连接创建后,服务器和客户端之间交换数据时,用于协议控制的数据包头部相对较小。

更强的实时性。由于协议是全双工的,所以服务器可以随时主动给客户端下发数据。相对于 HTTP 请求需要等待客户端发起请求服务端才能响应,延迟明显更少。

保持连接状态。与 HTTP 不同的是,WebSocket 需要先创建连接,这就使得其成为一种有状态的协议,之后通信时可以省略部分状态信息。

更好的二进制支持。WebSocket 定义了二进制帧,相对 HTTP,可以更轻松地处理二进制内容。

可以支持扩展。WebSocket 定义了扩展,用户可以扩展协议、实现部分自定义的子协议。

由于 WebSocket 拥有上述的优点,所以它被广泛地应用在即时通信、实时音视频、在线教育和游戏等领域。对于前端开发者来说,要想使用 WebSocket 提供的强大能力,就必须先掌握 WebSocket API,下面阿宝哥带大家一起来认识一下 WebSocket API。


二、WebSocket API

在介绍 WebSocket API 之前,我们先来了解一下它的兼容性:




(图片来源:https://caniuse.com/#search=W...)


从上图可知,目前主流的 Web 浏览器都支持 WebSocket,所以我们可以在大多数项目中放心地使用它。


在浏览器中要使用 WebSocket 提供的能力,我们就必须先创建 WebSocket 对象,该对象提供了用于创建和管理 WebSocket 连接,以及可以通过该连接发送和接收数据的 API。


使用 WebSocket 构造函数,我们就能轻易地构造一个 WebSocket 对象。接下来我们将从 WebSocket 构造函数、WebSocket 对象的属性、方法及 WebSocket 相关的事件四个方面来介绍 WebSocket API,首先我们从 WebSocket 的构造函数入手:


2.1 构造函数

WebSocket 构造函数的语法为:


const myWebSocket = new WebSocket(url [, protocols]);

相关参数说明如下:


url:表示连接的 URL,这是 WebSocket 服务器将响应的 URL。

protocols(可选):一个协议字符串或者一个包含协议字符串的数组。这些字符串用于指定子协议,这样单个服务器可以实现多个 WebSocket 子协议。比如,你可能希望一台服务器能够根据指定的协议(protocol)处理不同类型的交互。如果不指定协议字符串,则假定为空字符串。

当尝试连接的端口被阻止时,会抛出 SECURITY_ERR 异常。


2.2 属性

WebSocket 对象包含以下属性:




每个属性的具体含义如下:


binaryType:使用二进制的数据类型连接。

bufferedAmount(只读):未发送至服务器的字节数。

extensions(只读):服务器选择的扩展。

onclose:用于指定连接关闭后的回调函数。

onerror:用于指定连接失败后的回调函数。

onmessage:用于指定当从服务器接受到信息时的回调函数。

onopen:用于指定连接成功后的回调函数。

protocol(只读):用于返回服务器端选中的子协议的名字。

readyState(只读):返回当前 WebSocket 的连接状态,共有 4 种状态:


CONNECTING — 正在连接中,对应的值为 0;

OPEN — 已经连接并且可以通讯,对应的值为 1;

CLOSING — 连接正在关闭,对应的值为 2;

CLOSED — 连接已关闭或者没有连接成功,对应的值为 3。

url(只读):返回值为当构造函数创建 WebSocket 实例对象时 URL 的绝对路径。

2.3 方法

close([code[, reason]]):该方法用于关闭 WebSocket 连接,如果连接已经关闭,则此方法不执行任何操作。

send(data):该方法将需要通过 WebSocket 链接传输至服务器的数据排入队列,并根据所需要传输的数据的大小来增加 bufferedAmount 的值 。若数据无法传输(比如数据需要缓存而缓冲区已满)时,套接字会自行关闭。

2.4 事件

使用 addEventListener() 或将一个事件监听器赋值给 WebSocket 对象的 oneventname 属性,来监听下面的事件。


close:当一个 WebSocket 连接被关闭时触发,也可以通过 onclose 属性来设置。

error:当一个 WebSocket 连接因错误而关闭时触发,也可以通过 onerror 属性来设置。

message:当通过 WebSocket 收到数据时触发,也可以通过 onmessage 属性来设置。

open:当一个 WebSocket 连接成功时触发,也可以通过 onopen 属性来设置。

介绍完 WebSocket API,我们来举一个使用 WebSocket 发送普通文本的示例。


2.5 发送普通文本



在以上示例中,我们在页面上创建了两个 textarea,分别用于存放 待发送的数据 和 服务器返回的数据。当用户输入完待发送的文本之后,点击 发送 按钮时会把输入的文本发送到服务端,而服务端成功接收到消息之后,会把收到的消息原封不动地回传到客户端。


// const socket = new WebSocket("ws://echo.websocket.org");

// const sendMsgContainer = document.querySelector("#sendMessage");

function send() {

 const message = sendMsgContainer.value;

 if (socket.readyState !== WebSocket.OPEN) {

   console.log("连接未建立,还不能发送消息");

   return;

 }

 if (message) socket.send(message);

}

当然客户端接收到服务端返回的消息之后,会把对应的文本内容保存到 接收的数据 对应的 textarea 文本框中。


// const socket = new WebSocket("ws://echo.websocket.org");

// const receivedMsgContainer = document.querySelector("#receivedMessage");    

socket.addEventListener("message", function (event) {

 console.log("Message from server ", event.data);

 receivedMsgContainer.value = event.data;

});

为了更加直观地理解上述的数据交互过程,我们使用 Chrome 浏览器的开发者工具来看一下相应的过程:




以上示例对应的完整代码如下所示:


<!DOCTYPE html>

<html>

 <head>

   <meta charset="UTF-8" />

   <meta name="viewport" content="width=device-width, initial-scale=1.0" />

   <title>WebSocket 发送普通文本示例</title>

   <style>

     .block {

       flex: 1;

     }

   </style>

 </head>

 <body>

   <h3>阿宝哥:WebSocket 发送普通文本示例</h3>

   <div style="display: flex;">

     <div class="block">

       <p>即将发送的数据:<button onclick="send()">发送</button></p>

       <textarea id="sendMessage" rows="5" cols="15"></textarea>

     </div>

     <div class="block">

       <p>接收的数据:</p>

       <textarea id="receivedMessage" rows="5" cols="15"></textarea>

     </div>

   </div>


   <script>

     const sendMsgContainer = document.querySelector("#sendMessage");

     const receivedMsgContainer = document.querySelector("#receivedMessage");

     const socket = new WebSocket("ws://echo.websocket.org");


     // 监听连接成功事件

     socket.addEventListener("open", function (event) {

       console.log("连接成功,可以开始通讯");

     });


     // 监听消息

     socket.addEventListener("message", function (event) {

       console.log("Message from server ", event.data);

       receivedMsgContainer.value = event.data;

     });


     function send() {

       const message = sendMsgContainer.value;

       if (socket.readyState !== WebSocket.OPEN) {

         console.log("连接未建立,还不能发送消息");

         return;

       }

       if (message) socket.send(message);

     }

   </script>

 </body>

</html>

其实 WebSocket 除了支持发送普通的文本之外,它还支持发送二进制数据,比如 ArrayBuffer 对象、Blob 对象或者 ArrayBufferView 对象:


const socket = new WebSocket("ws://echo.websocket.org");

socket.onopen = function () {

 // 发送UTF-8编码的文本信息

 socket.send("Hello Echo Server!");

 // 发送UTF-8编码的JSON数据

 socket.send(JSON.stringify({ msg: "我是阿宝哥" }));

 

 // 发送二进制ArrayBuffer

 const buffer = new ArrayBuffer(128);

 socket.send(buffer);

 

 // 发送二进制ArrayBufferView

 const intview = new Uint32Array(buffer);

 socket.send(intview);


 // 发送二进制Blob

 const blob = new Blob([buffer]);

 socket.send(blob);

};

以上代码成功运行后,通过 Chrome 开发者工具,我们可以看到对应的数据交互过程:




下面阿宝哥以发送 Blob 对象为例,来介绍一下如何发送二进制数据。


Blob(Binary Large Object)表示二进制类型的大对象。在数据库管理系统中,将二进制数据存储为一个单一个体的集合。Blob 通常是影像、声音或多媒体文件。在 JavaScript 中 Blob 类型的对象表示不可变的类似文件对象的原始数据。

对 Blob 感兴趣的小伙伴,可以阅读 “你不知道的 Blob” 这篇文章。


2.6 发送二进制数据



在以上示例中,我们在页面上创建了两个 textarea,分别用于存放 待发送的数据 和 服务器返回的数据。当用户输入完待发送的文本之后,点击 发送 按钮时,我们会先获取输入的文本并把文本包装成 Blob 对象然后发送到服务端,而服务端成功接收到消息之后,会把收到的消息原封不动地回传到客户端。


当浏览器接收到新消息后,如果是文本数据,会自动将其转换成 DOMString 对象,如果是二进制数据或 Blob 对象,会直接将其转交给应用,由应用自身来根据返回的数据类型进行相应的处理。


数据发送代码


// const socket = new WebSocket("ws://echo.websocket.org");

// const sendMsgContainer = document.querySelector("#sendMessage");

function send() {

 const message = sendMsgContainer.value;

 if (socket.readyState !== WebSocket.OPEN) {

   console.log("连接未建立,还不能发送消息");

   return;

 }

 const blob = new Blob([message], { type: "text/plain" });

 if (message) socket.send(blob);

 console.log(`未发送至服务器的字节数:${socket.bufferedAmount}`);

}

当然客户端接收到服务端返回的消息之后,会判断返回的数据类型,如果是 Blob 类型的话,会调用 Blob 对象的 text() 方法,获取 Blob 对象中保存的 UTF-8 格式的内容,然后把对应的文本内容保存到 接收的数据 对应的 textarea 文本框中。


数据接收代码


// const socket = new WebSocket("ws://echo.websocket.org");

// const receivedMsgContainer = document.querySelector("#receivedMessage");

socket.addEventListener("message", async function (event) {

 console.log("Message from server ", event.data);

 const receivedData = event.data;

 if (receivedData instanceof Blob) {

   receivedMsgContainer.value = await receivedData.text();

 } else {

   receivedMsgContainer.value = receivedData;

 }

});

同样,我们使用 Chrome 浏览器的开发者工具来看一下相应的过程:




通过上图我们可以很明显地看到,当使用发送 Blob 对象时,Data 栏位的信息显示的是 Binary Message,而对于发送普通文本来说,Data 栏位的信息是直接显示发送的文本消息。


以上示例对应的完整代码如下所示:


<!DOCTYPE html>

<html>

 <head>

   <meta charset="UTF-8" />

   <meta name="viewport" content="width=device-width, initial-scale=1.0" />

   <title>WebSocket 发送二进制数据示例</title>

   <style>

     .block {

       flex: 1;

     }

   </style>

 </head>

 <body>

   <h3>阿宝哥:WebSocket 发送二进制数据示例</h3>

   <div style="display: flex;">

     <div class="block">

       <p>待发送的数据:<button onclick="send()">发送</button></p>

       <textarea id="sendMessage" rows="5" cols="15"></textarea>

     </div>

     <div class="block">

       <p>接收的数据:</p>

       <textarea id="receivedMessage" rows="5" cols="15"></textarea>

     </div>

   </div>


   <script>

     const sendMsgContainer = document.querySelector("#sendMessage");

     const receivedMsgContainer = document.querySelector("#receivedMessage");

     const socket = new WebSocket("ws://echo.websocket.org");


     // 监听连接成功事件

     socket.addEventListener("open", function (event) {

       console.log("连接成功,可以开始通讯");

     });


     // 监听消息

     socket.addEventListener("message", async function (event) {

       console.log("Message from server ", event.data);

       const receivedData = event.data;

       if (receivedData instanceof Blob) {

         receivedMsgContainer.value = await receivedData.text();

       } else {

         receivedMsgContainer.value = receivedData;

       }

     });


     function send() {

       const message = sendMsgContainer.value;

       if (socket.readyState !== WebSocket.OPEN) {

         console.log("连接未建立,还不能发送消息");

         return;

       }

       const blob = new Blob([message], { type: "text/plain" });

       if (message) socket.send(blob);

       console.log(`未发送至服务器的字节数:${socket.bufferedAmount}`);

     }

   </script>

 </body>

</html>

可能有一些小伙伴了解完 WebSocket API 之后,觉得还不够过瘾。下面阿宝哥将带大家来实现一个支持发送普通文本的 WebSocket 服务器。


三、手写 WebSocket 服务器

在介绍如何手写 WebSocket 服务器前,我们需要了解一下 WebSocket 连接的生命周期。




从上图可知,在使用 WebSocket 实现全双工通信之前,客户端与服务器之间需要先进行握手(Handshake),在完成握手之后才能开始进行数据的双向通信。


握手是在通信电路创建之后,信息传输开始之前。握手用于达成参数,如信息传输率,字母表,奇偶校验,中断过程,和其他协议特性。 握手有助于不同结构的系统或设备在通信信道中连接,而不需要人为设置参数。


既然握手是 WebSocket 连接生命周期的第一个环节,接下来我们就先来分析 WebSocket 的握手协议。


3.1 握手协议

WebSocket 协议属于应用层协议,它依赖于传输层的 TCP 协议。WebSocket 通过 HTTP/1.1 协议的 101 状态码进行握手。为了创建 WebSocket 连接,需要通过浏览器发出请求,之后服务器进行回应,这个过程通常称为 “握手”(Handshaking)。


利用 HTTP 完成握手有几个好处。首先,让 WebSocket 与现有 HTTP 基础设施兼容:使得 WebSocket 服务器可以运行在 80 和 443 端口上,这通常是对客户端唯一开放的端口。其次,让我们可以重用并扩展 HTTP 的 Upgrade 流,为其添加自定义的 WebSocket 首部,以完成协商。


下面我们以前面已经演示过的发送普通文本的例子为例,来具体分析一下握手过程。


3.1.1 客户端请求

GET ws://echo.websocket.org/ HTTP/1.1

Host: echo.websocket.org

Origin: file://

Connection: Upgrade

Upgrade: websocket

Sec-WebSocket-Version: 13

Sec-WebSocket-Key: Zx8rNEkBE4xnwifpuh8DHQ==

Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits

备注:已忽略部分 HTTP 请求头

字段说明


Connection 必须设置 Upgrade,表示客户端希望连接升级。

Upgrade 字段必须设置 websocket,表示希望升级到 WebSocket 协议。

Sec-WebSocket-Version 表示支持的 WebSocket 版本。RFC6455 要求使用的版本是 13,之前草案的版本均应当弃用。

Sec-WebSocket-Key 是随机的字符串,服务器端会用这些数据来构造出一个 SHA-1 的信息摘要。把 “Sec-WebSocket-Key” 加上一个特殊字符串 “258EAFA5-E914-47DA-95CA-C5AB0DC85B11”,然后计算 SHA-1 摘要,之后进行 Base64 编码,将结果做为 “Sec-WebSocket-Accept” 头的值,返回给客户端。如此操作,可以尽量避免普通 HTTP 请求被误认为 WebSocket 协议。

Sec-WebSocket-Extensions 用于协商本次连接要使用的 WebSocket 扩展:客户端发送支持的扩展,服务器通过返回相同的首部确认自己支持一个或多个扩展。

Origin 字段是可选的,通常用来表示在浏览器中发起此 WebSocket 连接所在的页面,类似于 Referer。但是,与 Referer 不同的是,Origin 只包含了协议和主机名称。

3.1.2 服务端响应

HTTP/1.1 101 Web Socket Protocol Handshake ①

Connection: Upgrade ②

Upgrade: websocket ③

Sec-WebSocket-Accept: 52Rg3vW4JQ1yWpkvFlsTsiezlqw= ④

备注:已忽略部分 HTTP 响应头

① 101 响应码确认升级到 WebSocket 协议。

② 设置 Connection 头的值为 "Upgrade" 来指示这是一个升级请求。HTTP 协议提供了一种特殊的机制,这一机制允许将一个已建立的连接升级成新的、不相容的协议。

③ Upgrade 头指定一项或多项协议名,按优先级排序,以逗号分隔。这里表示升级为 WebSocket 协议。

④ 签名的键值验证协议支持。

介绍完 WebSocket 的握手协议,接下来阿宝哥将使用 Node.js 来开发我们的 WebSocket 服务器。


3.2 实现握手功能

要开发一个 WebSocket 服务器,首先我们需要先实现握手功能,这里阿宝哥使用 Node.js 内置的 http 模块来创建一个 HTTP 服务器,具体代码如下所示:


const http = require("http");


const port = 8888;

const { generateAcceptValue } = require("./util");


const server = http.createServer((req, res) => {

 res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8" });

 res.end("大家好,我是阿宝哥。感谢你阅读“你不知道的WebSocket”");

});


server.on("upgrade", function (req, socket) {

 if (req.headers["upgrade"] !== "websocket") {

   socket.end("HTTP/1.1 400 Bad Request");

   return;

 }

 // 读取客户端提供的Sec-WebSocket-Key

 const secWsKey = req.headers["sec-websocket-key"];

 // 使用SHA-1算法生成Sec-WebSocket-Accept

 const hash = generateAcceptValue(secWsKey);

 // 设置HTTP响应头

 const responseHeaders = [

   "HTTP/1.1 101 Web Socket Protocol Handshake",

   "Upgrade: WebSocket",

   "Connection: Upgrade",

   `Sec-WebSocket-Accept: ${hash}`,

 ];

 // 返回握手请求的响应信息

 socket.write(responseHeaders.join("\r\n") + "\r\n\r\n");

});


server.listen(port, () =>

 console.log(`Server running at http://localhost:${port}`)

);

在以上代码中,我们首先引入了 http 模块,然后通过调用该模块的 createServer() 方法创建一个 HTTP 服务器,接着我们监听 upgrade 事件,每次服务器响应升级请求时就会触发该事件。由于我们的服务器只支持升级到 WebSocket 协议,所以如果客户端请求升级的协议非 WebSocket 协议,我们将会返回 “400 Bad Request”。


当服务器接收到升级为 WebSocket 的握手请求时,会先从请求头中获取 “Sec-WebSocket-Key” 的值,然后把该值加上一个特殊字符串 “258EAFA5-E914-47DA-95CA-C5AB0DC85B11”,然后计算 SHA-1 摘要,之后进行 Base64 编码,将结果做为 “Sec-WebSocket-Accept” 头的值,返回给客户端。


上述的过程看起来好像有点繁琐,其实利用 Node.js 内置的 crypto 模块,几行代码就可以搞定了:


// util.js

const crypto = require("crypto");

const MAGIC_KEY = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";


function generateAcceptValue(secWsKey) {

 return crypto

   .createHash("sha1")

   .update(secWsKey + MAGIC_KEY, "utf8")

   .digest("base64");

}

开发完握手功能之后,我们可以使用前面的示例来测试一下该功能。待服务器启动之后,我们只要对 “发送普通文本” 示例,做简单地调整,即把先前的 URL 地址替换成 ws://localhost:8888,就可以进行功能验证。


感兴趣的小伙们可以试试看,以下是阿宝哥本地运行后的结果:




从上图可知,我们实现的握手功能已经可以正常工作了。那么握手有没有可能失败呢?答案是肯定的。比如网络问题、服务器异常或 Sec-WebSocket-Accept 的值不正确。


下面阿宝哥修改一下 “Sec-WebSocket-Accept” 生成规则,比如修改 MAGIC_KEY 的值,然后重新验证一下握手功能。此时,浏览器的控制台会输出以下异常信息:


WebSocket connection to 'ws://localhost:8888/' failed: Error during WebSocket handshake: Incorrect 'Sec-WebSocket-Accept' header value

如果你的 WebSocket 服务器要支持子协议的话,你可以参考以下代码进行子协议的处理,阿宝哥就不继续展开介绍了。


// 从请求头中读取子协议

const protocol = req.headers["sec-websocket-protocol"];

// 如果包含子协议,则解析子协议

const protocols = !protocol ? [] : protocol.split(",").map((s) => s.trim());


// 简单起见,我们仅判断是否含有JSON子协议

if (protocols.includes("json")) {

 responseHeaders.push(`Sec-WebSocket-Protocol: json`);

}

好的,WebSocket 握手协议相关的内容基本已经介绍完了。下一步我们来介绍开发消息通信功能需要了解的一些基础知识。


3.3 消息通信基础

在 WebSocket 协议中,数据是通过一系列数据帧来进行传输的。为了避免由于网络中介(例如一些拦截代理)或者一些安全问题,客户端必须在它发送到服务器的所有帧中添加掩码。服务端收到没有添加掩码的数据帧以后,必须立即关闭连接。


3.3.1 数据帧格式

要实现消息通信,我们就必须了解 WebSocket 数据帧的格式:


0                   1                   2                   3

0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1

+-+-+-+-+-------+-+-------------+-------------------------------+

|F|R|R|R| opcode|M| Payload len |    Extended payload length    |

|I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |

|N|V|V|V|       |S|             |   (if payload len==126/127)   |

| |1|2|3|       |K|             |                               |

+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +

|     Extended payload length continued, if payload len == 127  |

+ - - - - - - - - - - - - - - - +-------------------------------+

|                               |Masking-key, if MASK set to 1  |

+-------------------------------+-------------------------------+

| Masking-key (continued)       |          Payload Data         |

+-------------------------------- - - - - - - - - - - - - - - - +

:                     Payload Data continued ...                :

+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +

|                     Payload Data continued ...                |

+---------------------------------------------------------------+

可能有一些小伙伴看到上面的内容之后,就开始有点 “懵逼” 了。下面我们来结合实际的数据帧来进一步分析一下:




在上图中,阿宝哥简单分析了 “发送普通文本” 示例对应的数据帧格式。这里我们来进一步介绍一下 Payload length,因为在后面开发数据解析功能的时候,需要用到该知识点。


Payload length 表示以字节为单位的 “有效负载数据” 长度。它有以下几种情形:


如果值为 0-125,那么就表示负载数据的长度。

如果是 126,那么接下来的 2 个字节解释为 16 位的无符号整形作为负载数据的长度。

如果是 127,那么接下来的 8 个字节解释为一个 64 位的无符号整形(最高位的 bit 必须为 0)作为负载数据的长度。

多字节长度量以网络字节顺序表示,有效负载长度是指 “扩展数据” + “应用数据” 的长度。“扩展数据” 的长度可能为 0,那么有效负载长度就是 “应用数据” 的长度。


另外,除非协商过扩展,否则 “扩展数据” 长度为 0 字节。在握手协议中,任何扩展都必须指定 “扩展数据” 的长度,这个长度如何进行计算,以及这个扩展如何使用。如果存在扩展,那么这个 “扩展数据” 包含在总的有效负载长度中。


3.3.2 掩码算法

掩码字段是一个由客户端随机选择的 32 位的值。掩码值必须是不可被预测的。因此,掩码必须来自强大的熵源(entropy),并且给定的掩码不能让服务器或者代理能够很容易的预测到后续帧。掩码的不可预测性对于预防恶意应用的作者在网上暴露相关的字节数据至关重要。


掩码不影响数据荷载的长度,对数据进行掩码操作和对数据进行反掩码操作所涉及的步骤是相同的。掩码、反掩码操作都采用如下算法:


j = i MOD 4

transformed-octet-i = original-octet-i XOR masking-key-octet-j

original-octet-i:为原始数据的第 i 字节。

transformed-octet-i:为转换后的数据的第 i 字节。

masking-key-octet-j:为 mask key 第 j 字节。

为了让小伙伴们能够更好的理解上面掩码的计算过程,我们来对示例中 “我是阿宝哥” 数据进行掩码操作。这里 “我是阿宝哥” 对应的 UTF-8 编码如下所示:


E6 88 91 E6 98 AF E9 98 BF E5 AE 9D E5 93 A5

而对应的 Masking-Key 为 0x08f6efb1,根据上面的算法,我们可以这样进行掩码运算:


let uint8 = new Uint8Array([0xE6, 0x88, 0x91, 0xE6, 0x98, 0xAF, 0xE9, 0x98,

 0xBF, 0xE5, 0xAE, 0x9D, 0xE5, 0x93, 0xA5]);

let maskingKey = new Uint8Array([0x08, 0xf6, 0xef, 0xb1]);

let maskedUint8 = new Uint8Array(uint8.length);


for (let i = 0, j = 0; i < uint8.length; i++, j = i % 4) {

 maskedUint8[i] = uint8[i] ^ maskingKey[j];

}


console.log(Array.from(maskedUint8).map(num=>Number(num).toString(16)).join(' '));

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


ee 7e 7e 57 90 59 6 29 b7 13 41 2c ed 65 4a

上述结果与 WireShark 中的 Masked payload 对应的值是一致的,具体如下图所示:




在 WebSocket 协议中,数据掩码的作用是增强协议的安全性。但数据掩码并不是为了保护数据本身,因为算法本身是公开的,运算也不复杂。那么为什么还要引入数据掩码呢?引入数据掩码是为了防止早期版本的协议中存在的代理缓存污染攻击等问题。


了解完 WebSocket 掩码算法和数据掩码的作用之后,我们再来介绍一下数据分片的概念。


3.3.3 数据分片

WebSocket 的每条消息可能被切分成多个数据帧。当 WebSocket 的接收方收到一个数据帧时,会根据 FIN 的值来判断,是否已经收到消息的最后一个数据帧。


利用 FIN 和 Opcode,我们就可以跨帧发送消息。操作码告诉了帧应该做什么。如果是 0x1,有效载荷就是文本。如果是 0x2,有效载荷就是二进制数据。但是,如果是 0x0,则该帧是一个延续帧。这意味着服务器应该将帧的有效负载连接到从该客户机接收到的最后一个帧。


为了让大家能够更好地理解上述的内容,我们来看一个来自 MDN 上的示例:


Client: FIN=1, opcode=0x1, msg="hello"

Server: (process complete message immediately) Hi.

Client: FIN=0, opcode=0x1, msg="and a"

Server: (listening, new message containing text started)

Client: FIN=0, opcode=0x0, msg="happy new"

Server: (listening, payload concatenated to previous message)

Client: FIN=1, opcode=0x0, msg="year!"

Server: (process complete message) Happy new year to you too!

在以上示例中,客户端向服务器发送了两条消息。第一个消息在单个帧中发送,而第二个消息跨三个帧发送。


其中第一个消息是一个完整的消息(FIN=1 且 opcode != 0x0),因此服务器可以根据需要进行处理或响应。而第二个消息是文本消息(opcode=0x1)且 FIN=0,表示消息还没发送完成,还有后续的数据帧。该消息的所有剩余部分都用延续帧(opcode=0x0)发送,消息的最终帧用 FIN=1 标记。


好的,简单介绍了数据分片的相关内容。接下来,我们来开始实现消息通信功能。


3.4 实现消息通信功能

阿宝哥把实现消息通信功能,分解为消息解析与消息响应两个子功能,下面我们分别来介绍如何实现这两个子功能。


3.4.1 消息解析

利用消息通信基础环节中介绍的相关知识,阿宝哥实现了一个 parseMessage 函数,用来解析客户端传过来的 WebSocket 数据帧。出于简单考虑,这里只处理文本帧,具体代码如下所示:


function parseMessage(buffer) {

 // 第一个字节,包含了FIN位,opcode, 掩码位

 const firstByte = buffer.readUInt8(0);

 // [FIN, RSV, RSV, RSV, OPCODE, OPCODE, OPCODE, OPCODE];

 // 右移7位取首位,1位,表示是否是最后一帧数据

 const isFinalFrame = Boolean((firstByte >>> 7) & 0x01);

 console.log("isFIN: ", isFinalFrame);

 // 取出操作码,低四位

 /**

  * %x0:表示一个延续帧。当 Opcode 为 0 时,表示本次数据传输采用了数据分片,当前收到的数据帧为其中一个数据分片;

  * %x1:表示这是一个文本帧(text frame);

  * %x2:表示这是一个二进制帧(binary frame);

  * %x3-7:保留的操作代码,用于后续定义的非控制帧;

  * %x8:表示连接断开;

  * %x9:表示这是一个心跳请求(ping);

  * %xA:表示这是一个心跳响应(pong);

  * %xB-F:保留的操作代码,用于后续定义的控制帧。

  */

 const opcode = firstByte & 0x0f;

 if (opcode === 0x08) {

   // 连接关闭

   return;

 }

 if (opcode === 0x02) {

   // 二进制帧

   return;

 }

 if (opcode === 0x01) {

   // 目前只处理文本帧

   let offset = 1;

   const secondByte = buffer.readUInt8(offset);

   // MASK: 1位,表示是否使用了掩码,在发送给服务端的数据帧里必须使用掩码,而服务端返回时不需要掩码

   const useMask = Boolean((secondByte >>> 7) & 0x01);

   console.log("use MASK: ", useMask);

   const payloadLen = secondByte & 0x7f; // 低7位表示载荷字节长度

   offset += 1;

   // 四个字节的掩码

   let MASK = [];

   // 如果这个值在0-125之间,则后面的4个字节(32位)就应该被直接识别成掩码;

   if (payloadLen <= 0x7d) {

     // 载荷长度小于125

     MASK = buffer.slice(offset, 4 + offset);

     offset += 4;

     console.log("payload length: ", payloadLen);

   } else if (payloadLen === 0x7e) {

     // 如果这个值是126,则后面两个字节(16位)内容应该,被识别成一个16位的二进制数表示数据内容大小;

     console.log("payload length: ", buffer.readInt16BE(offset));

     // 长度是126, 则后面两个字节作为payload length,32位的掩码

     MASK = buffer.slice(offset + 2, offset + 2 + 4);

     offset += 6;

   } else {

     // 如果这个值是127,则后面的8个字节(64位)内容应该被识别成一个64位的二进制数表示数据内容大小

     MASK = buffer.slice(offset + 8, offset + 8 + 4);

     offset += 12;

   }

   // 开始读取后面的payload,与掩码计算,得到原来的字节内容

   const newBuffer = [];

   const dataBuffer = buffer.slice(offset);

   for (let i = 0, j = 0; i < dataBuffer.length; i++, j = i % 4) {

     const nextBuf = dataBuffer[i];

     newBuffer.push(nextBuf ^ MASK[j]);

   }

   return Buffer.from(newBuffer).toString();

 }

 return "";

}

创建完 parseMessage 函数,我们来更新一下之前创建的 WebSocket 服务器:


server.on("upgrade", function (req, socket) {

 socket.on("data", (buffer) => {

   const message = parseMessage(buffer);

   if (message) {

     console.log("Message from client:" + message);

   } else if (message === null) {

     console.log("WebSocket connection closed by the client.");

   }

 });

 if (req.headers["upgrade"] !== "websocket") {

   socket.end("HTTP/1.1 400 Bad Request");

   return;

 }

 // 省略已有代码

});

更新完成之后,我们重新启动服务器,然后继续使用 “发送普通文本” 的示例来测试消息解析功能。以下发送 “我是阿宝哥” 文本消息后,WebSocket 服务器输出的信息。


Server running at http://localhost:8888

isFIN:  true

use MASK:  true

payload length:  15

Message from client:我是阿宝哥

通过观察以上的输出信息,我们的 WebSocket 服务器已经可以成功解析客户端发送包含普通文本的数据帧,下一步我们来实现消息响应的功能。


3.4.2 消息响应

要把数据返回给客户端,我们的 WebSocket 服务器也得按照 WebSocket 数据帧的格式来封装数据。与前面介绍的 parseMessage 函数一样,阿宝哥也封装了一个 constructReply 函数用来封装返回的数据,该函数的具体代码如下:


function constructReply(data) {

 const json = JSON.stringify(data);

 const jsonByteLength = Buffer.byteLength(json);

 // 目前只支持小于65535字节的负载

 const lengthByteCount = jsonByteLength < 126 ? 0 : 2;

 const payloadLength = lengthByteCount === 0 ? jsonByteLength : 126;

 const buffer = Buffer.alloc(2 + lengthByteCount + jsonByteLength);

 // 设置数据帧首字节,设置opcode为1,表示文本帧

 buffer.writeUInt8(0b10000001, 0);

 buffer.writeUInt8(payloadLength, 1);

 // 如果payloadLength为126,则后面两个字节(16位)内容应该,被识别成一个16位的二进制数表示数据内容大小

 let payloadOffset = 2;

 if (lengthByteCount > 0) {

   buffer.writeUInt16BE(jsonByteLength, 2);

   payloadOffset += lengthByteCount;

 }

 // 把JSON数据写入到Buffer缓冲区中

 buffer.write(json, payloadOffset);

 return buffer;

}

创建完 constructReply 函数,我们再来更新一下之前创建的 WebSocket 服务器:


server.on("upgrade", function (req, socket) {

 socket.on("data", (buffer) => {

   const message = parseMessage(buffer);

   if (message) {

     console.log("Message from client:" + message);

     // 新增以下

停止犯下这5个JavaScript风格错误,使你的代码可读和可维护的快速提示

seo达人

使你的代码可读和可维护的快速提示。


有多少次,你打开一个旧的项目,发现混乱的代码,当你添加一些新的东西时,很容易崩溃?我们都有过这样的经历。


为了减少难以读懂的javascript的数量,我提供了以下示例。这些都是我过去所犯过的错误。


对具有多个返回值的函数使用数组解构

假设我们有一个返回多个值的函数。一种可能的实现是使用数组解构,如下所示:


const func = () => {

 const a = 1;

 const b = 2;

 const c = 3;

 const d = 4;

 return [a,b,c,d];

}

const [a,b,c,d] = func();

console.log(a,b,c,d); // 1,2,3,4

尽管上面的方法很好用,但确实引入了一些复杂性。


当我们调用函数并将值分配给 a,b,c,d 时,我们需要注意返回数据的顺序。这里的一个小错误可能会成为调试的噩梦。


此外,无法确切指定我们要从函数中获取哪些值,如果我们只需要 c 和 d 怎么办?


相反,我们可以使用对象解构。


const func = () => {

 const a = 1;

 const b = 2;

 const c = 3;

 const d = 4;

 return {a,b,c,d};

}

const {c,d} = func();

现在,我们可以轻松地从函数中选择所需的数据,这也为我们的代码提供了未来的保障,允许我们在不破坏东西的情况下增加额外的返回变量。


不对函数参数使用对象分解

假设我们有一个函数,该函数将一个对象作为参数并对该对象的属性执行一些操作。一种幼稚的方法可能看起来像这样:


// 不推荐

function getDaysRemaining(subscription) {

 const startDate = subscription.startDate;

 const endDate = subscription.endDate;

 return endDate - startDate;

}

上面的方法按预期工作,但是,我们创建了两个不必要的临时引用 startDate 和 endDate。


一种更好的实现是对 subscription 对象使用对象解构来在一行中获取 startDate 和 endDate。


// 推荐

function getDaysRemaining(subscription) {

 const { startDate, endDate } = subscription;

 return startDate - endDate;

}

我们可以更进一步,直接对参数执行对象析构。


// 更好

function getDaysRemaining({ startDate, endDate }) {

 return startDate - endDate;

}

更优雅,不是吗?


在不使用扩展运算符的情况下复制数组

使用 for循环遍历数组并将其元素复制到新数组是冗长且相当丑陋的。


可以以简洁明了的方式使用扩展运算符来达到相同的效果。


const stuff = [1,2,3];


// 不推荐

const stuffCopyBad = []

for(let i = 0; i < stuff.length; i++){

 stuffCopyBad[i] = stuff[i];

}


// 推荐

const stuffCopyGood = [...stuff];

使用var

使用 const 保证不能重新分配变量。这样可以减少我们代码中的错误,并使其更易于理解。


// 不推荐

var x = "badX";

var y = "baxY";


// 推荐

const x = "goodX";

const y = "goodX";

果你确实需要重新分配变量,请始终选择 let 而不是 var。


这是因为 let 是块作用域的,而 var 是函数作用域的。


块作用域告诉我们,只能在定义它的代码块内部访问变量,尝试访问块外部的变量会给我们提供ReferenceError。


for(let i = 0; i < 10; i++){

 //something

}

print(i) // ReferenceError: i is not defined

函数作用域告诉我们,只能在定义其的函数内部访问变量。


for(var i = 0; i < 10; i++){

 //something

}

console.log(i) // 10

let 和 const 都是块范围的。


不使用模板字面值

手动将字符串连接在一起相当麻烦,而且输入时可能会造成混淆。这是一个例子:


// 不推荐

function printStartAndEndDate({ startDate, endDate }) {

 console.log('StartDate:' + startDate + ',EndDate:' + endDate)

}

模板文字为我们提供了一种可读且简洁的语法,该语法支持字符串插值。


// 推荐

function printStartAndEndDate({ startDate, endDate }) {

 console.log(`StartDate: ${startDate}, EndDate: ${endDate}`)

}

模板文字也提供了嵌入新行的简便方法,你所需要做的就是照常按键盘上的Enter键。


// 两行打印

function printStartAndEndDate({ startDate, endDate }) {

 console.log(`StartDate: ${startDate}

 EndDate: ${endDate}`)

}

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

如何推进多方合作的A/B测试项目?

雪涛

在多方合作的项目中,我们需要规划项目的合理落地方案,并在执行过程中和各方有效沟通。结合近期羚珑商家合作测试项目的实践经历,聊聊我在项目中的心得体会。

项目背景

我们想了解京东首焦banner中,设计因素对点击效果的影响情况。做这件事最大的限制在于投放数据会受多种因素影响,不仅是一个设计因素的选择,还有比如人群、出价、品牌、类目等因素的不同,都能影响到最终的投放结果。因此,从总体样本中取样进行分析的意义不大。为了解决上述问题,我们招募了一些能控制其他因素的品牌广告主,与有能力支持投放的测试投放系统合作,共同完成A/B测试。

需求拆解与规划

不难发现这个需求会涉及到多个项目角色,意味着将有复杂的推进流程,需要提前拆解与规划,才能保证项目进度可控。

如何推进多方合作的A/B测试项目?来看京东的实战案例!

△ 测试项目流程与分工

我们可以分三步来进行:

1. 找到项目关键步骤

关键步骤是将一个项目的推进拆解成多个阶段,找到关键步骤,也就知道了每个阶段的主要任务,可以进一步分工来推进项目。

如何找到关键步骤?我们可以从项目目标中提取。测试项目的目标是找到设计因素与投放点击效果的影响关系。这个目标里提取重要的关键词为:设计因素、投放点击效果、影响关系。面对这些关键词我们会有疑问产生如「测试设计因素是什么?有哪些?」、「如何投放」、「如何判断影响关系」,再进行串联梳理,得到完整步骤流程。

2. 明确角色任务与产出

每个阶段的主要任务,可以拆解分工给项目角色,项目角色也就有了各自的任务,角色产出物则是任务执行的结果,将直接推动项目进入下一环节。所以为了顺利推进项目,我们需要根据项目实际情况评估角色任务分工是否合理,产出是否满足项目要求。

比如在「设计测试图片」这个步骤中,有两个分工方案,让广告主或我们自己设计图片。我们从项目时长、沟通成本、潜在风险等维度进行评估,判断广告主把控变量不严谨会导致测试数据无效,且外部合作沟通成本较高时间不够,所以最后将设计图片的任务分给了内部。

3. 预判项目风险

对流程规划得越详细,在后续合作过程中越容易把控项目的节奏,项目风险越低。

再如「设计测试图片」这个关键步骤,详细展开执行流程会发现涉及到交互设计师、两方视觉设计师、广告主四个角色。对于项目执行角色,产出物的质量及按时交付尤为重要,所以重点把控输出准确性与完成时间。对于项目决策角色,也就是决策最终投放哪些图片的广告主,沟通配合尤为重要。所以基于规避风险也对项目流程进行了优化,前置在项目之初,要求广告主提前准备交付内容,规定交付时间及格式,以及充分沟通说明图片设计产出之后不能进行较大改动。

结论推导的重要前提与方法

1. 保证数据有效

测试结论来源于测试数据,我们首先应该保证回收数据的有效性。测试项目的关键指标是点击率,即点击量与曝光量的比值。当图片本身曝光量低时,我们认为随着曝光量增加,点击率比值波动范围仍然较大,数据还未稳定在某个区间段,会影响结论准确性,判定为无效数据。发现类似这种问题,我们会和广告主商量继续投放,延长测试时间来增加曝光。

2. 充分测试

要想得到可靠普适的结论,需要对比多组样本的测试结果。对于某个设计因素,我们先进行了单一广告主的投放数据对比,可以找到投放效果最优和最差的设计水平。然后又将多个广告主的结果进行对比,会发现存在不一致的情况,验证了单组样本结论不能作为类目结论输出。如果多个广告主结果一致,或呈现某一趋势,则结论在该类目可以认为较为普适。

3. 差异分析

对于多组样本结果不一致的情况,可以从组间因素差异着手分析。

如何推进多方合作的A/B测试项目?来看京东的实战案例!

△ 背景设计因素测试结果

上图是两个广告主分别测试背景设计因素得到的结果。第一组的投放结果为实景设计效果更好,第二组则为普通平面设计效果更好。产生这样差异的原因是什么呢?我们先找出组间可能导致这一结果的因素,分别是颜色和产品。从颜色上看,是否平面+黑色效果更好呢?我们看了其他广告主结果,否定了这一猜想。再从产品图来看,两个产品的识别度是不同的,我们将其他广告主该因素测试图按产品是否容易识别分组,最终得出两组不同的结论:当产品图易识别时,背景设计对效果影响不大;当产品图不易识别时,实景图效果更好。这也就解释了上图结果不一致的原因。

回顾整个项目,我个人认为项目中最重要的工作是沟通。下面我来分别谈谈内外部沟通的经验。

双赢是外部合作沟通的基石

与外部团队、广告主合作推进项目,需要及时有效的沟通,什么是有效的沟通呢?

1. 围绕对方利益谈配合

广告主只看转化结果,我们如果只谈设计不谈转化,广告主是不会想要出钱参与项目的,那么项目也将停滞不前。所以在合作前,我们做了项目及团队包装,用真实的案例让广告主快速理解参与项目所能带来的价值,并用团队以往的作品、能力展示让广告主了解与我们合作的优势。广告主越认同项目价值及我们的专业度,配合度就会越高。

如何推进多方合作的A/B测试项目?来看京东的实战案例!

△ 招募PPT中的真实案例介绍

2. 围绕对方目标来「推销」方案

在推进项目这一点上,找到目标一致的点更容易促成各方意见达成一致。

我们合作的广告主很多会选择外包banner设计,所以广告主们习惯了做传说中的「甲方爸爸」,难免会对视觉设计方案有各种主观意见。比如,某电视广告主不喜欢红色图片设计,想要蓝色。对于这样的分歧,我个人喜欢用引导式的沟通方法。首先,不要急于表达自己或否定对方的意见,可以以疑问句式来猜测对方的目的。「喜欢蓝色是希望用沉稳的颜色来表现产品的高级感吗?」,如果猜测正确,对方会因自己的需求被理解而更容易沟通,这时再继续阐述用红色如何表达高级感;如果猜测不正确,对方会顺势说出原因,我们再围绕对方的目标进一步沟通。如果对方毫无理由「不喜欢这个设计」,而设计师并不觉得有设计问题,那么我们需要停止专业角度的沟通,从设计是否满足当前阶段项目需求的角度来沟通,方案则更容易被接受。

内部合作各尽所能发挥优势

内部合作分交互、视觉、用研三种不同的岗位角色,在项目中都发挥着自己重要的作用。

交互擅长统筹与拆解问题,团队的内部沟通能产生多种解题思路,对项目的推进至关重要;在遇到如需要统计学分析、样本设置等专业问题时,用研可以提供更专业的分析方法与帮助,为我们实现科学分析提供有力支持;视觉设计师对banner设计有丰富的经验,可以在各个环节中发挥优势。从项目之初就参与设计因素拆解、测试方案设置,可以补充视觉相关的设计因素,在优化测试方案、风险预期方面都可以提供重要的建议。在结论推导中,对图中的因素差异敏感度高,可以根据数据提出关键猜想。

文章来源:优设    作者:京东设计中心JDC

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

如何为你的UI制定一套色彩系统?

雪涛

色彩在UI设计中的作用:加深品牌印象与品牌感、引导用户视觉凹增加易读性、区分信息交互的状态、营造氛围传递热度……

前言

不管是做 UI 设计还是画插画,有很多同学觉得自己是因为天赋不够所以对色彩的敏感度不够,其实不然。一个可能是大家总结的太少,从来都是凭感觉和运气去配色,但配色都是有讲究的。

一个设计作品呈现到用户面前,第一眼进入眼帘的就是产品的视觉表现,而产品的色彩在其中起到了举足轻重的作用,毫无疑问色彩搭配对于设计师来说是非常重要的。那么具体到实际项目中该使用什么怎样的色彩,需要怎么做呢?

用户界面是一个设计师用理性思维解决用户感性需求的窗口。如果对色彩的运用不加以克制,界面可能会显得花哨而没有主次;但过于拘谨又容易使界面保守,难以激发用户情绪,下面以Bee express项目的实例来理性推导制定一套色彩系统。

切勿直奔主题

做过设计的同学应该都知道颜色模式:RGB、CMYK、Lab 等等,这里不做过多的解释了。另外每个颜色具有一定的性格特征和表达方式,而且都会有正反两面。虽然每种色彩都有正向性格特征,但是我们在定位主体色之前一定要知道所选择色彩的负面特征对企业是否会带来负面的影响,

开始之前我们需要了解在配色过程中需要避免出现的问题,如果你经常出现下列的问题,保证你在试用期内一次性就能拿到全部薪资,emmm……

  • 高饱和度的色彩会造成我们的视觉疲劳及视幻;
  • 灰部使用过多的配色会使界面有一种脏兮兮、雾蒙蒙的感觉,甚至心情低落;
  • 没有规律且过多的配色。如果你不是做五彩斑斓的黑,建议6、3、1的色彩配比,辅助色不超过3种;
  • 荧光色。使用这种色彩的,建议跟色彩对视,看谁坚持的更久,除非是你赢了;
  • 太轻柔的颜色-没有重点且轻飘飘的感觉;
  • 现在很火的新拟物化设计对于部分(没有绝对)产品可能会造成信息识别性很差;
  • 不要将对抗色重叠,否则你会很浮躁。

定位品牌色

虽然设计是相通的,但是在不同的设计领域进行配色时,依然会存在巨大的区别。更换品牌的主体色,都不会是因为设计师自己的决定,而是公司在商业策略上优先做出了调整,然后通过品牌视觉上的变更将这个信息传递给消费者。

Bee Express快递、速递柜业务为主,前期的主色及视觉形象以橙黄色为主,为了避免视觉跳跃性太大,以及后期IP形象(蜜蜂吉祥物)打造,本次品牌色彩升级在原有基础上优化了色调,以保证后期产品的易用性和延展性,并利用最科学、最适用的方式推导出辅助色,以提升应用视觉的丰富性和感官体验。

express原主色:

如何为你的UI制定一套色彩系统?来看这个实战案例!

为了不影响原有色调前期的视觉传播,即在原有主体色的基础上调整SHB的数值,让色彩更具视觉冲击力,在色彩衬托(字体、图标)更清晰。

  • H(Hue:色相)
  • S(Saturation:饱和度)
  • B(Brightness:明度)

如何为你的UI制定一套色彩系统?来看这个实战案例!

通过调整后的主体色也能看出,明亮清晰的主体色(品牌色)也更适合在界面中的运用,为信息传递、引导操作、品牌价值带来更大的提升。

  • 信息传递:产品的首要目的是传递用户所需要的信息,这就需要界面有清晰的层级关系,明确、舒适的阅读体验。
  • 引导操作:清晰合理的操作引导,让用户能够准确地根据引导进行下一步操作。
  • 品牌价值:很多同学会忽略这一点,导致产品的界面与品牌关联性差,整体界面没有品牌感。

根据主体色推理同色系

同色系为统一的色相,使用中可以加深品牌色的感知,可以让界面更有层次,同时可以让界面保持色彩上的一致性,整体感较强,产生低对比度的和谐美感,给人协调统一的感觉。

具体是指与品牌色 H(色相)一致,通过改变 S(饱和度)与 B(明度)变化产生的色组。分别往浅色/深色方向按均匀数据增减,各产生5个坐标值。

如何为你的UI制定一套色彩系统?来看这个实战案例!

综上能看出,使用同一色系即可完成一个项目,但是对于中大型项目来说实在是过于单调,没有太多的层次感,因此我们需要多色搭配为辅。多色的辅助颜色可设定不同的任务属性和情感表达,再搭配中性色黑白灰,能赋予更多的变化和层次。

提取24色-铺垫辅助色

根据主体色 H(色相)为基础,不断地递增、递减 15,在 0-360 之间可以得出 24 个颜色,也就是将 360° 色环分割为 24 份,(24份在360°色环上,每一个色相的角度为15°),最终得到下图24色。

如何为你的UI制定一套色彩系统?来看这个实战案例!

选取辅助色

辅助色需要满足的两个条件:

和品牌色有明显区分:避免所选辅助色感官上给用户视觉区别与品牌色差距不大,传递的调性太过一致;

不能过于突兀:根据色彩原理,互补色是最能与品牌色本色产生视觉感官对比的颜色,但可能会有些突兀。为了让颜色的辅助起到丰富画面的作用,而不是反而让整个版面显得不和谐,所以选择互补色的邻近色作为辅助色,避免直接使用互补色。

  • 邻近色:色相差值 15° 以内的颜色为邻近色;
  • 类似色:色相差值 30° 以内的颜色为类似色;
  • 互补色:色相差值 180° 的颜色为互补色。

基于品牌色可衍生出 3 个辅助色:一个与品牌色传递调性有明显区分的类似色;两个互补色的邻近色。

如何为你的UI制定一套色彩系统?来看这个实战案例!

类似色搭配:使用色相相近的颜色,页面元素不会相互冲突,更加协调有质感。

互补色搭配:选择使用互补色,最佳搭配是一种作为主色,另一种用于强调。它们有着非常强烈的对比度,用在需要特别强调某个元素时会非常有效。

视觉统一感官校准

每一种颜色都有自己的「感官明度」,也就是发光度。根据现有的使用场景,类似色和互补色大都用在同层级的信息展示上,而当我们将最终得到的辅助色摆放在一起之后发现,虽然我们提取出的辅助色明度色值都一致,因为颜色本身自带的感官明度属性有所区别,导致视觉上会有明显的明暗差别。需要通过发光度来进行最终的颜色校正。

校准方式:依次在辅助色上叠加一层纯黑图层,将该纯黑图层颜色模式调整为 Hue(色相),就可以通过无彩色系下的明度色值,进行对比,使色彩视觉感官保持一致(青色和蓝色属冷色调,固需加深)。

如何为你的UI制定一套色彩系统?来看这个实战案例!

全色系输出

根据上面同色系的明度、纯度对比规则,对所有定义的辅助色进行明度和纯度的辅助色彩输出,最终得到辅助色色板。H(色相)一致,通过改变 S(饱和度)与 B(明度)变化产生色组。分别往浅色/深色方向按均匀数据增减,各产生5个坐标值

如何为你的UI制定一套色彩系统?来看这个实战案例!

删除最左侧的3种同色系,因明度过低时,颜色已经非常接近于黑色,色相在肉眼上几乎已经趋于一致。最后得到基于品牌色推导出的全色系色阶色板。

如何为你的UI制定一套色彩系统?来看这个实战案例!

如何为你的UI制定一套色彩系统?来看这个实战案例!

总结

配色常常从确定主色开始,根据行业类型和视觉诉求的需要,选择一种居于支配的色彩作为主调色彩,构成画面的整体色彩倾向。然后选择辅色,添加点缀色,最后按照色彩组合的原则完成设计中的需求。

虽然有了以上的配色方式,但一套标准的色彩系统还会包含中性色规范、颜色的使用规范等等。相信解决了大部分的需求,剩下的工作也难不倒大家了。毕竟以上的方式只是给大家提供了一个理性科学的方法,如果想更加优秀,还需要进一步深入地去学习色彩理论知识,多看优秀的配色作品提升审美,总之要多看、多实践和多思考。

文章来源:优设    作者:能量眼球

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

日历

链接

blogger

蓝蓝 http://www.lanlanwork.com

存档