QQ 游戏中心经过设计改版之后,我们重新设定了整体的世界观——多彩的游戏宇宙。并且对多个模块及内容进行了新的设计升级,其中也包括重要的图标图形。
1. 延展思考
因此基于目前较为完整的图标图形,希望可以拓展出更多不一样的设计内容,并且可以应用在不同的位置,例如空白页、运营内容、背景等等。
2. 问题分析
基于目前的图形可以很明显的得到 2 个问题:
3. 设计启发
3D作为 2020 的主流设计趋势之一,可以很好地表达设计氛围。因此想尝试跨次元的设计方式,从 3D 图形的角度去思考,尝试更多可能性。
下面主要是分享我在制作 3D 图标中的一些方法和流程,以及 2D 与 3D 图形设计中思考的差异性,希望可以跟大家互相学习,一起探讨这方面的设计。
虽然是 3D 的图标,但实际上使用到的软件包括有:Cinema 4D(C4D)、Sketch、Photoshop(PS)、illustrator(AI)。
整体的大概流程:
3D 与 2D 最大的差别在于多一个维度来表达图形,因此我们在 2D 向 3D 转化的过程中,需要思考一些基本的原则,并且结合这些规则,降低 3D 图标与 2D 图标违和感。在这次的 3D 图标中我总结了以下几条基本规则。
1. 圆变球
在 3D 软件中表达圆有 2 种方式:球体、圆柱体。在实际的设计中,我们需要根据实际情况判断是否变成球体或者圆柱体,这里建议单体呈现的圆形设计成球体,在这种情况下球体相比圆柱体更能表达圆形的视觉感受。
例如下面气泡的例子,球体化的表现比圆柱体化的表现更加饱满,光影效果更加丰富。
2. 方变块
与上面的规则比较接近。当我们在 2D 图标中使用矩形之类的图形,建议使用立方体来表达。优点:立方体可以增加图标上的细节表现;增加厚度更有利于光影的表达。
例如下面礼物的图标,我们在实际的 3D 场景下应该更贴合现实生活中的认知,设计成礼物盒子的效果。
3. 结合实际认知
除以上的 2 种建议之外,我们在实际建模的时候还需要结合实际认知而定。例如金币、游戏卡的设计应该是带有厚度的片形;钱包设计成折叠的效果。
4. 适当简化图形
2D 图标向 3D 图标的转换过程中,需要适当进行简化,一些不必要的内容可以适当进行删减。主要的目的是:
5. 增强空间思维
2D 的图标只有一个平面,因此大部分情况下是一种「纸片性」的思维,常规的 2D 向 3D 的转换思维是增加厚度,但实际上出来的效果并不理想。因此在转换的过程中,需要使用空间的思维去思考,在 3 维的空间中应该是怎么样的。例如下载和收件箱的图标,常规的思维可能是在 2D 的图标上增加厚度,但转换成空间思维就是让其具有立体感和空间感的形体。
6. 图标状态补充
在实际建模的中会发现,很多模型在静态下是可以进行简单处理的,但结合动态或实际认知,就需要相关细节状态补充。例如礼物和宝箱图标的开盖效果,因此把实际的盖子和盒子/箱子的模型做出来,以便于动画的实际表达。
在进入 C4D 之前,需要清楚不同图形可以使用什么方式建模,因此我们可以进行一个简单的分类,分为:常规图形和异形。两种图形在建模中的方式会有一些小差异,当然一个图标也可能包含这两种类型,因此实际操作中可以灵活处理。
1. 常规形:使用基础物体建模
部分简单的有规则的图形,可以直接使用 C4D 的基础物体(例如:立方体、球体、柱状体、锥体等),通过对基础物体的调整后得到模型,例如下面的图形。
案例展示
2. 异形:AI路径+挤出
在实际操作的过程中发现部分模型较难通过基础形调整得到,或是直接建模会比较耗时。因此我们可以导入 AI 路径再挤出的方式来得到我们的模型。例如下面的图形
案例展示
基于以上的以上 2 种类型的图形,这里分享一下制作的过程和心得。可能不够全面,但希望大家可以一起来补充互相学习。
1. 对齐中心点
基础建模对齐中心轴点是一切开始的重中之重,这里会涉及到很多后续的调整和其他命令的应用(例如挤出、对称等命令)。例如一些中高阶的人物建模也是非常依赖中心点对齐来实现对称命令的。
2. 结合图像
在 C4D 视图本身具有多视图,可以结合不同视图导入不同视角的平面结构进行制作,常见情况下的建模可以导入三视图(例如角色、人物之类的)。而图标相对来说是很简单的,所以这里只应用正面视图即可,其他的视角可以自行脑补后制作。
结合图像的好处:
操作流程:正视图下快捷键 shift+V 调出视图背景——选择背景——添加图像。或在视图选项中调出,然后配置即可。
3. 结合路径
如上图标类型中的描述,部分异形的图标如果直接在 C4D 中绘制会相对耗时,因此我们可以结合路径的方式,再使用挤出的命令来实现我们想要的模型,这样可以大大提升异形物体建模的效率。
C4D 中对 AI 的图层只会读取颜色的边缘,然后生成路径。因此在 AI 中编辑的路径,依据实际的情况选择填充或者描边,然后再拖拽进 C4D。如下产生的效果对比,左边为填充图形,右边为描边图形。
操作流程:使用 AI 导出 8.0 版本的路径——拖拽进 C4D——添加挤出命令——设置挤出高度及封顶样式。
4. 使用变形器
一些简单的形变可以通过变形器的应用,得到我们想要的造型。例如下面的案例,外星人脸是在圆形的基础上使用 FFD 进行调整,而宝箱则在方形的基础上使用锥化来达到圆弧的效果。
C4D 的渲染效果主要是依赖于材质和灯光的配合,熟练者往往可以依靠经验有效率的制作,但我们也可以通过锻炼总结出一些常用的材质参数或者布光的位置来提率。因此我也从这次的 3D 图标制作中总结了一套关于材质和布光的方法。
C4D 场景的主光源我们可以通过全局光照+天空的方式来营造整体的氛围,这组光的特点在于具有比较柔和的效果,并且模拟自然的环境光效。
全局光照开启后,需要依赖灯光、天空光来对物体进行照射,如果设定后未增加灯光或者天空,在渲染时只能渲染出一片黑色。(全局光照——主要是模拟真实的光照效果,通过光源投射到物体上再经过无数次的反射和折射出来的效果,因此也能解释为什么只加全局光照渲染不出来内容。)
操作流程:渲染设置——效果——全局光照
添加天空增加天空作为基础光照补充
操作流程:地面快捷入口——选择天空——添加材质球——勾选发光——添加 HDR 贴图。
下面通过一些案例对比来看看全局光照及天空的对比效果
全局光照-开和关的差异
从下面的案例我们可以明显看到差别,全局光照关闭后的图标相对暗淡一些,整体图标的光反射也相对减弱了许多。
有无天空的差异
天空有助于增强图标的光感,添加天空后整体图标的细节和质感也相对更加丰富。相对,无天空整体图标质感则有所下降。
天空是否增加HDR贴图的差异
添加 HDR 贴图可以增强场景内物体的环境反射,让物体材质更加丰富增强细节质感。在一些强反射的场景下非常依赖 HDR 贴图的使用。从以下案例对比,可以明显看到差异性。
整体添加三盏灯光来营造整体的场景氛围。主要分为:主光(最强)、补光(增强阴影面的亮度)、背光(补充背面环境的光源,增强环境光氛围,勾勒轮廓)。在实际的场景中可以根据实际的反射效果和氛围,调整灯光的位置、与物体的间距、明暗度。
灯光对于物体的作用会随着颜色的差异,产生的光亮度也会有所差异,因此在实际的使用过程中,对于灯光的位置、反射的细节都可以进行微调来达到最优的效果。
色相的对比:不同色相在同样的灯光作用下产生的效果具有稍微较小。
明暗的对比:深色和浅色在同样的灯光作用下产生的效果差别较大。
实际案例对比:从下面的实际案例对比可以明显看出同样灯光下不同色相的明显差别,绿色的两部产生过度曝光。因此可以通过调整灯光的距离或者亮度来解决这一问题(如上面灯光分布建议)。
3D 图标由于相对简单,材质上主要使用颜色和反射的配合就可以得到不错的质感。当然如果希望在质感表现上更加丰富,亦可考虑增加其他的内容项来进行补充
颜色的设定
图标的颜色基本上与原图标的颜色保持一致,但部分颜色但实际渲染效果会存在些许差异,因此我们在材质上也可以根据视觉效果进行微调,视觉上保持统一的颜色感受。例如礼物的图标,如果按原来的颜色,亮部会过渡曝光,因此适当提高了亮部颜色的饱和度。
颜色偏差
在 3D 的场景内是通过各种光与颜色的反射而成的,因此即便同样的颜色,在实际渲染出来的 3D 图标和 2D 图标也会存在一定颜色偏差。
反射是本次 3D 图标中材质非常重要的一环,基本的效果都是来源于对反射的设定。整体主要设定了反射的类型、粗糙度、反射强度、高光强度、层遮罩的颜色。由于图标的颜色并不完全一致,因此在粗糙度、反射强度、高光强度是一组动态的参数。
参数变化的对比
如下面的案例,针对不同颜色的图标在粗糙度、反射强度、高光强度上都有差异性,因此不是说设定好一组参数之后就那个完全适用所有的颜色,因此这里会根据实际情况调整,但整体的视觉效果保持一致。
层遮罩的设定差异
除了基础的反射类型及参数,还需要在层遮罩中添加菲涅耳来增强反射的丰富度。默认的菲涅尔是一组黑白的颜色材质,我们可以通过调整暗部的颜色来增强图标的颜色饱和度和丰富度,如下案例对比。
静态的 3D 图标显得精致,增加动效之后的 3D 图标则除精致外,还更加富有趣味性和新鲜感。3D 的动效与 2D 有着明显的差别,可以更多维度地思考物体的运动轨迹、变化方式。
首先我们需要根据不同造型对需要制作动效的图标进行简单的分类,这个分类的主要作用在于明确不同图标的动效设计方式,为动效的设计方式进行铺垫。根据已有的图标划分为:单体形、组合型、拼装形。
单体形
图标以单个或单组形体呈现,或者整体造型属于某个已存在的事物或者形体,整体图形内容具有不可切割性。
组合型
图标通过两组或两组以上的图形内容组合而成,图标由主形(图标实际的外轮廓造型)和点缀图形(用于图标表意或者提升图形内涵)组合而成的图标,图标可进行拆分或者重组后形成动效。
拼装形
图标本身可能在现实中不存在的事物或物体,通过创意思考而得到的图形,图标的动效更具有可发散性和可重塑性。
结合上面的类型差,在设计动效的时候也会稍稍不同。重点在于表达不同的图标具有的特性,因此我们可以根据这些特性去设计图标的动画方式。
自体运动
对应单体图形,图标动效通过自身的位移、旋转、形变而产生,这类图标的动效比较靠近现实生活中接触的感知或图形动效本身具有普适性认知。例如火箭升空、UFO 飞碟、放礼炮、开宝箱等。
组合运动
对应组合图形和拼装图形,多图形运动组合而成,图标的多个部件可从不同轴向开始进行不同的轨迹运动,最终进行完整的图标融合。各个部件本身可能也存在位移、旋转、形变等动效,可以更大程度丰富图标的动效表现。
部件运动
整体动效相比前面两种类型较为简单,通过某个图标上的部件运动来表达动效的内容,因此这个部件需要是图标上较为明显的图标特征,这样更能让人具有记忆点。
音效是这次 3D 图标设计的点睛之笔,结合音效可以更加丰富地表达图标动效的趣味性。不同的图标动画反馈出来的音效是不一样的,因此赋予对应的音效反馈才是更合理的表达。
1. 选择音效
在实际的配置音效的过程中发现,部分图标比较难找到相关联的音效。但我们可以通过较为类似或者可以表达出该图标动画过程中的声音反馈的音效。例如活动小礼炮用的是开葡萄酒塞的声音,开宝箱用的是开门的声音,飞碟(UFO)用的是一组电子音效等等,并且从相关声音中窃取其中一段需要的。
2. 组合音效
部分图标的动画效果很难通过一条音效进行表达,因此需要叠加 2 组或者 2 组以上的音效来丰富整体的感受。例如手柄人图标叠加了三组不同的音效来表达,游戏卡叠加 2 种不同的音效。
3D 的图标或 3D 类型的内容如何与 UI 结合?相信大家也时有思考这方面的内容。基于这次的 3D 图标设计,我也进行了初步的尝试,从几个方面来简单聊聊这方面的内容。
1. 3D图标对于UI设计的作用
在尝试之前,我们需要明确 3D 内容对于 UI 设计作用是什么?我简单总结了几个关键点:
2. 3D ICON X Tab bar
当我们设计 Tabbar 的时候,首先想到的表现方式往往是有趣的图标图形设计、结合动效之类的方式。但我们或许也可以考虑使用 3D 的图标+动画的方式来表达我们的设计。
3. 3D ICON X 运营内容
一些相对简单的运营内容,我们可以考虑将元素进行 3D 化设计,这样可以一定程度增强整体运营的视觉表现力。
4. 3D ICON X 空白页插图
3D 插图是 2020 的设计趋势之一,结合 3D 的插图让整体的设计更加具有氛围感。
5. 3D ICON X COVER
将背景中的某些元素结合 3D 图形进行设计,让整体的氛围更加具有空间感和立体感。
本次结合实际项目中的内容进行不同维度的设计尝试,并且希望,可以从中去寻找到更多设计的可能性和突破点。当然这只是系统化设计思考中的一步,但可以启发后续更加深入的 3D 设计探索。
文章来源:优设 作者:ID设计站
前言
前面几篇我们就 Redux 展开了几篇文章,这次我们来实现 react-thunk,就不是叫实现 redux-thunk 了,直接上源码,因为源码就11行。如果对 Redux 中间件还不理解的,可以看我写的 Redux 文章。
实现一个迷你Redux(基础版)
实现一个Redux(完善版)
浅谈React的Context API
带你实现 react-redux
为什么要用 redux-thunk
在使用 Redux 过程,通过 dispatch 方法派发一个 action 对象。当我们使用 redux-thunk 后,可以 dispatch 一个 function。redux-thunk会自动调用这个 function,并且传递 dispatch, getState 方法作为参数。这样一来,我们就能在这个 function 里面处理异步逻辑,处理复杂逻辑,这是原来 Redux 做不到的,因为原来就只能 dispatch 一个简单对象。
用法
redux-thunk 作为 redux 的中间件,主要用来处理异步请求,比如:
export function fetchData() {
return (dispatch, getState) => {
// to do ...
axios.get('https://jsonplaceholder.typicode.com/todos/1').then(res => {
console.log(res)
})
}
}
redux-thunk 源码
redux-thunk 的源码比较简洁,实际就11行。前几篇我们说到 redux 的中间件形式,
本质上是对 store.dispatch 方法进行了增强改造,基本是类似这种形式:
const middleware = (store) => next => action => {}
在这里就不详细解释了,可以看 实现一个Redux(完善版)
先给个缩水版的实现:
const thunk = ({ getState, dispatch }) => next => action => {
if (typeof action === 'function') {
return action(dispatch, getState)
}
return next(action)
}
export default thunk
原理:即当 action 为 function 的时候,就调用这个 function (传入 dispatch, getState)并返回;如果不是,就直接传给下一个中间件。
完整源码如下:
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => next => action => {
// 如果action是一个function,就返回action(dispatch, getState, extraArgument),否则返回next(action)。
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument)
}
// next为之前传入的store.dispatch,即改写前的dispatch
return next(action)
}
}
const thunk = createThunkMiddleware()
// 给thunk设置一个变量withExtraArgument,并且将createThunkMiddleware整个函数赋给它
thunk.withExtraArgument = createThunkMiddleware
export default thunk
我们发现其实还多了 extraArgument 传入,这个是自定义参数,如下用法:
const api = "https://jsonplaceholder.typicode.com/todos/1";
const whatever = 10;
const store = createStore(
reducer,
applyMiddleware(thunk.withExtraArgument({ api, whatever })),
);
// later
function fetchData() {
return (dispatch, getState, { api, whatever }) => {
// you can use api and something else here
};
}
总结
同 redux-thunk 非常流行的库 redux-saga 一样,都是在 redux 中做异步请求等副作用。Redux 相关的系列文章就暂时写到这部分为止,下次会写其他系列。
一、前言
前端的模块化规范包括 commonJS、AMD、CMD 和 ES6。其中 AMD 和 CMD 可以说是过渡期的产物,目前较为常见的是commonJS 和 ES6。在 TS 中这两种模块化方案的混用,往往会出现一些意想不到的问题。
二、import * as
考虑到兼容性,我们一般会将代码编译为 es5 标准,于是 tsconfig.json 会有以下配置:
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
}
}
代码编译后最终会以 commonJS 的形式输出。
使用 React 的时候,这种写法 import React from "react" 会收到一个莫名其妙的报错:
Module "react" has no default export
这时候你只能把代码改成这样:import * as React from "react"。
究其原因,React 是以 commonJS 的规范导出的,而 import React from "react" 这种写法会去找 React 模块中的 exports.default,而 React 并没有导出这个属性,于是就报了如上错误。而 import * as React 的写法会取 module.exports 中的值,这样使用起来就不会有任何问题。我们来看看 React 模块导出的代码到底是怎样的(精简过):
...
var React = {
Children: {
map: mapChildren,
forEach: forEachChildren,
count: countChildren,
toArray: toArray,
only: onlyChild
},
createRef: createRef,
Component: Component,
PureComponent: PureComponent,
...
}
module.exports = React;
可以看到,React 导出的是一个对象,自然也不会有 default 属性。
二、esModuleInterop
为了兼容这种这种情况,TS 提供了配置项 esModuleInterop 和 allowSyntheticDefaultImports,加上后就不会有报错了:
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true
}
}
其中 allowSyntheticDefaultImports 这个字段的作用只是在静态类型检查时,把 import 没有 exports.default 的报错忽略掉。
而 esModuleInterop 会真正的在编译的过程中生成兼容代码,使模块能正确的导入。还是开始的代码:
import React from "react";
现在 TS 编译后是这样的:
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
var react_1 = __importDefault(require("react"));
编译器帮我们生成了一个新的对象,将模块赋值给它的 default 属性,运行时就不会报错了。
三、Tree Shaking
如果把 TS 按照 ES6 规范编译,就不需要加上 esModuleInterop,只需要 allowSyntheticDefaultImports,防止静态类型检查时报错。
{
"compilerOptions": {
"module": "es6",
"target": "es6",
"allowSyntheticDefaultImports": true
}
}
什么情况下我们会考虑导出成 ES6 规范呢?多数情况是为了使用 webpack 的 tree shaking 特性,因为它只对 ES6 的代码生效。
顺便再发散一下,讲讲 babel-plugin-component。
import { Button, Select } from 'element-ui'
上面的代码经过编译后,是下面这样的:
var a = require('element-ui');
var Button = a.Button;
var Select = a.Select;
var a = require('element-ui') 会引入整个组件库,即使只用了其中的 2 个组件。
babel-plugin-component 的作用是将代码做如下转换:
// 转换前
import { Button, Select } from 'element-ui'
// 转换后
import Button from 'element-ui/lib/button'
import Select from 'element-ui/lib/select'
最终编译出来是这个样子,只会加载用到的组件:
var Button = require('element-ui/lib/button');
var Select = require('element-ui/lib/select');
四、总结
本文讲解了 TypeScript 是如何导入不同模块标准打包的代码的。无论你导入的是 commonJS 还是 ES6 的代码,万无一失的方式是把 esModuleInterop 和 allowSyntheticDefaultImports 都配置上。
初始化
使用 https://github.com/XYShaoKang... 作为基础模板
gatsby new gatsby-project-config https://github.com/XYShaoKang/gatsby-hello-world
Prettier 配置
安装 VSCode 扩展
按 Ctrl + P (MAC 下: Cmd + P) 输入以下命令,按回车安装
ext install esbenp.prettier-vscode
安装依赖
yarn add -D prettier
Prettier 配置文件.prettierrc.js
// .prettierrc.js
module.exports = {
trailingComma: 'es5',
tabWidth: 2,
semi: false,
singleQuote: true,
endOfLine: 'lf',
printWidth: 50,
arrowParens: 'avoid',
}
ESLint 配置
安装 VSCode 扩展
按 Ctrl + P (MAC 下: Cmd + P) 输入以下命令,按回车安装
ext install dbaeumer.vscode-eslint
安装 ESLint 依赖
yarn add -D eslint babel-eslint eslint-config-google eslint-plugin-react eslint-plugin-filenames
ESLint 配置文件.eslintrc.js
使用官方仓库的配置,之后在根据需要修改
// https://github.com/gatsbyjs/gatsby/blob/master/.eslintrc.js
// .eslintrc.js
module.exports = {
parser: 'babel-eslint',
extends: [
'google',
'eslint:recommended',
'plugin:react/recommended',
],
plugins: ['react', 'filenames'],
parserOptions: {
ecmaVersion: 2016,
sourceType: 'module',
ecmaFeatures: {
jsx: true,
},
},
env: {
browser: true,
es6: true,
node: true,
jest: true,
},
globals: {
before: true,
after: true,
spyOn: true,
__PATH_PREFIX__: true,
__BASE_PATH__: true,
__ASSET_PREFIX__: true,
},
rules: {
'arrow-body-style': [
'error',
'as-needed',
{ requireReturnForObjectLiteral: true },
],
'no-unused-expressions': [
'error',
{
allowTaggedTemplates: true,
},
],
'consistent-return': ['error'],
'filenames/match-regex': [
'error',
'^[a-z-\\d\\.]+$',
true,
],
'no-console': 'off',
'no-inner-declarations': 'off',
quotes: ['error', 'backtick'],
'react/display-name': 'off',
'react/jsx-key': 'warn',
'react/no-unescaped-entities': 'off',
'react/prop-types': 'off',
'require-jsdoc': 'off',
'valid-jsdoc': 'off',
},
settings: {
react: {
version: '16.4.2',
},
},
}
解决 Prettier ESLint 规则冲突
推荐配置
安装依赖
yarn add -D eslint-config-prettier eslint-plugin-prettier
在.eslintrc.js中的extends添加'plugin:prettier/recommended'
module.exports = {
extends: ['plugin:prettier/recommended'],
}
VSCode 中 Prettier 和 ESLint 协作
方式一:使用 ESLint 扩展来格式化代码
配置.vscode/settings.json
// .vscode/settings.json
{
"eslint.format.enable": true,
"[javascript]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
},
"[javascriptreact]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
}
}
ESLint 扩展会默认忽略.开头的文件,比如.eslintrc.js
如果需要格式化.开头的文件,可以在.eslintignore中添加一个否定忽略来启用对应文件的格式化功能.
!.eslintrc.js
或者直接使用!.*,这样可以开启所有点文件的格式化功能
方式二:使用 Prettier 扩展来格式化代码
在版prettier-vscode@v5.0.0中已经删除了直接对linter的集成,所以版没法像之前那样,通过prettier-eslint来集成ESLint的修复了(一定要这样用的话,可以通过降级到prettier-vscode@4来使用了).如果要使用Prettier来格式化的话,就只能按照官方指南中的说的集成方法,让Prettier来处理格式,通过配置在保存时使用ESlint自动修复代码.只是这样必须要保存文件时,才能触发ESLint的修复了.
配置 VSCode 使用 Prettier 来格式化 js 和 jsx 文件
在项目中新建文件.vscode/settings.json
// .vscode/settings.json
{
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[javascriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
}
}
说实话这个体验很糟糕,之前直接一键格式化代码并且修复 ESLint 错误,可以对比格式化之前和格式化之后的代码,如果感觉不对可以直接撤销更改就好了.现在必须要通过保存,才能触发修复 ESlint 错误.而在开发过程中,通过监听文件改变来触发热加载或者重新编译是很常见的操作.这样之后每次想要去修复 ESLint 错误,还是只是想看看修复错误之后的样子,都必须要去触发热加载或重新编译,每次操作的成本就太高了.
我更推荐第一种方式使用 ESLint 扩展来对代码进行格式化.
调试 Gatsby 配置
调试构建过程
添加配置文件.vscode/launch.json
// .vscode/launch.json
{
// 使用 IntelliSense 了解相关属性。
// 悬停以查看现有属性的描述。
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Gatsby develop",
"type": "node",
"request": "launch",
"protocol": "inspector",
"program": "${workspaceRoot}/node_modules/gatsby/dist/bin/gatsby",
"args": ["develop"],
"stopOnEntry": false,
"runtimeArgs": ["--nolazy"],
"sourceMaps": false,
"outputCapture": "std"
}
]
}
的gatsby@2.22.*版本中调试不能进到断点,解决办法是降级到2.21.*,yarn add gatsby@2.21.40,等待官方修复再使用版本的
调试客户端
需要安装 Debugger for Chrome 扩展
ext install msjsdiag.debugger-for-chrome
添加配置文件.vscode/launch.json
// .vscode/launch.json
{
// 使用 IntelliSense 了解相关属性。
// 悬停以查看现有属性的描述。
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "chrome",
"request": "launch",
"name": "Gatsby Client Debug",
"url": "http://localhost:8000",
"webRoot": "${workspaceFolder}"
}
]
}
先启动 Gatsby,yarn develop,然后按 F5 开始调试.
收集了一些工作中常用的工具。
如果你有好用的工具或者有意思的工具网站,要留言哦!
React是Facebook开发的一款JS库,那么Facebook为什么要建造React呢,主要为了解决什么问题,通过这个又是如何解决的?
从这几个问题出发我就在网上搜查了一下,有这样的解释。
Facebook认为MVC无法满足他们的扩展需求,由于他们非常巨大的代码库和庞大的组织,使得MVC很快变得非常复复杂,每当需要添加一项新的功能或特性时,系统的复杂度就成级数增长,致使代码变得脆弱和不可预测,结果导致他们的MVC正在土崩瓦解。认为MVC不适合大规模应用,当系统中有很多的模型和相应的视图时,其复杂度就会迅速扩大,非常难以理解和调试,特别是模型和视图间可能存在的双向数据流动。
解决这个问题需要“以某种方式组织代码,使其更加可预测”,这通过他们(Facebook)提出的Flux和React已经完成。
Flux
是一个系统架构,用于推进应用中的数据单向流动。React
是一个JavaScript框架,用于构建“可预期的”和“声明式的”Web用户界面,它已经使Facebook更快地开发Web应用
对于Flux,目前还没怎么研究,不怎么懂,这里就先把Flux的图放上来,有兴趣或者了解的可以再分享下,这里主要说下React。
那么React是解决什么问题的,在官网可以找到这样一句话:
We built React to solve one problem: building large applications with data that changes over time.
构建那些数据会随时间改变的大型应用,做这些,React有两个主要的特点:
另外在React官网上,通过《Why did we build React?》为什么我们要建造React的文档中还可以了解到以下四点:
Virtual DOM 虚拟DOM
传统的web应用,操作DOM一般是直接更新操作的,但是我们知道DOM更新通常是比较昂贵的。而React为了尽可能减少对DOM的操作,提供了一种不同的而又强大的方式来更新DOM,代替直接的DOM操作。就是Virtual DOM
,一个轻量级的虚拟的DOM,就是React抽象出来的一个对象,描述dom应该什么样子的,应该如何呈现。通过这个Virtual DOM去更新真实的DOM,由这个Virtual DOM管理真实DOM的更新。
为什么通过这多一层的Virtual DOM操作就能更快呢? 这是因为React有个diff算法,更新Virtual DOM并不保证马上影响真实的DOM,React会等到事件循环结束,然后利用这个diff算法,通过当前新的dom表述与之前的作比较,计算出最小的步骤更新真实的DOM。
component 的使用在 React 里极为重要, 因为 components 的存在让计算 DOM diff 更。
State 和 Render
React是如何呈现真实的DOM,如何渲染组件,什么时候渲染,怎么同步更新的,这就需要简单了解下State和Render了。state属性包含定义组件所需要的一些数据,当数据发生变化时,将会调用Render重现渲染,这里只能通过提供的setState方法更新数据。
好了,说了这么多,下面看写代码吧,先看一个官网上提供的Hello World
的示例:
<!DOCTYPE html> <html> <head> <script src="http://fb.me/react-0.12.1.js"></script> <script src="http://fb.me/JSXTransformer-0.12.1.js"></script> </head> <body> <div id="example"></div> <script type="text/jsx"> React.render( <h1>Hello, world!</h1>,
document.getElementById('example')
); </script> </body> </html>
这个很简单,浏览器访问,可以看到Hello, world!
字样。JSXTransformer.js
是支持解析JSX语法的,JSX是可以在Javascript中写html代码的一种语法。如果不喜欢,React也提供原生Javascript的方法。
再来看下另外一个例子:
<html> <head> <title>Hello React</title> <script src="http://fb.me/react-0.12.1.js"></script> <script src="http://fb.me/JSXTransformer-0.12.1.js"></script> <script src="http://code.jquery.com/jquery-1.10.0.min.js"></script> <script src="http://cdnjs.cloudflare.com/ajax/libs/showdown/0.3.1/showdown.min.js"></script> <style> #content{ width: 800px; margin: 0 auto; padding: 5px 10px; background-color:#eee; } .commentBox h1{ background-color: #bbb; } .commentList{ border: 1px solid yellow; padding:10px; } .commentList .comment{ border: 1px solid #bbb; padding-left: 10px; margin-bottom:10px; } .commentList .commentAuthor{ font-size: 20px; } .commentForm{ margin-top: 20px; border: 1px solid red; padding:10px; } .commentForm textarea{ width:100%; height:50px; margin:10px 0 10px 2px; } </style> </head> <body> <div id="content"></div> <script type="text/jsx"> var staticData = [ {author: "张飞", text: "我在写一条评论~!"}, {author: "关羽", text: "2货,都知道你在写的是一条评论。。"}, {author: "刘备", text: "哎,咋跟这俩逗逼结拜了!"} ]; var converter = new Showdown.converter();//markdown /** 组件结构: <CommentBox> <CommentList> <Comment /> </CommentList> <CommentForm /> </CommentBox> */ //评论内容组件 var Comment = React.createClass({ render: function (){ var rawMarkup = converter.makeHtml(this.props.children.toString()); return ( <div className="comment"> <h2 className="commentAuthor"> {this.props.author}: </h2> <span dangerouslySetInnerHTML={{__html: rawMarkup}} /> </div> ); } }); //评论列表组件 var CommentList = React.createClass({ render: function (){ var commentNodes = this.props.data.map(function (comment){ return ( <Comment author={comment.author}> {comment.text} </Comment> ); }); return ( <div className="commentList"> {commentNodes} </div> ); } }); //评论表单组件 var CommentForm = React.createClass({ handleSubmit: function (e){ e.preventDefault(); var author = this.refs.author.getDOMNode().value.trim(); var text = this.refs.text.getDOMNode().value.trim(); if(!author || !text){ return; } this.props.onCommentSubmit({author: author, text: text}); this.refs.author.getDOMNode().value = ''; this.refs.text.getDOMNode().value = ''; return; }, render: function (){ return ( <form className="commentForm" onSubmit={this.handleSubmit}> <input type="text" placeholder="Your name" ref="author" /><br/> <textarea type="text" placeholder="Say something..." ref="text" ></textarea><br/> <input type="submit" value="Post" /> </form> ); } }); //评论块组件 var CommentBox = React.createClass({ loadCommentsFromServer: function (){ this.setState({data: staticData}); /* 方便起见,这里就不走服务端了,可以自己尝试 $.ajax({ url: this.props.url + "?_t=" + new Date().valueOf(), dataType: 'json', success: function (data){ this.setState({data: data}); }.bind(this), error: function (xhr, status, err){ console.error(this.props.url, status, err.toString()); }.bind(this) }); */ }, handleCommentSubmit: function (comment){ //TODO: submit to the server and refresh the list var comments = this.state.data; var newComments = comments.concat([comment]); //这里也不向后端提交了 staticData = newComments; this.setState({data: newComments}); }, //初始化 相当于构造函数 getInitialState: function (){ return {data: []}; }, //组件添加的时候运行 componentDidMount: function (){ this.loadCommentsFromServer(); this.interval = setInterval(this.loadCommentsFromServer, this.props.pollInterval); }, //组件删除的时候运行 componentWillUnmount: function() { clearInterval(this.interval); }, //调用setState或者父级组件重新渲染不同的props时才会重新调用 render: function (){ return ( <div className="commentBox"> <h1>Comments</h1> <CommentList data={this.state.data}/> <CommentForm onCommentSubmit={this.handleCommentSubmit} /> </div> ); } }); //当前目录需要有comments.json文件 //这里定义属性,如url、pollInterval,包含在props属性中 React.render( <CommentBox url="comments.json" pollInterval="2000" />, document.getElementById("content") ); </script> </body> </html>
乍一看挺多,主要看脚本部分就可以了。方便起见,这里都没有走后端。定义了一个全局的变量staticData
,可权当是走服务端,通过浏览器的控制台改变staticData
的值,查看下效果,提交一条评论,查看下staticData的值的变化。
国外应用的较多,facebook、Yahoo、Reddit等。在github可以看到一个列表Sites-Using-React,国内的话,查了查,貌似比较少,目前知道的有一个杭州大搜车。大多技术要在国内应用起来一般是较慢的,不过React确实感觉比较特殊,特别是UI的组件化和Virtual DOM的思想,我个人比较看好,有兴趣继续研究研究。
和其他一些js框架相比,React怎样,比如Backbone、Angular等。
与传统PC桌面不同,手机屏幕的尺寸更加小巧操作,方式也已触控为主,APP界面设计不但要保证APP功能的完整性和合理性,又要保证APP的功能性和实用性,在保证其拥有流畅的操作感受的同时,满足人们的审美需求。
接下来为大家介绍几款手机appui界面设计
--手机appUI设计--
--手机appUI设计--
--手机appUI设计--
--手机appUI设计--
--手机appUI设计--
--手机appUI设计--
--手机appUI设计--
--手机appUI设计--
--手机appUI设计--
--手机appUI设计--
--手机appUI设计--
--手机appUI设计--
--手机appUI设计--
--手机appUI设计--
--手机appUI设计--
--手机appUI设计--
--手机appUI设计--
--手机appUI设计--
(以上图片均来源于网络)
蓝蓝设计( www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 、平面设计服务
更多精彩文章:
闭包是一个让初级JavaScript
使用者既熟悉又陌生的一个概念。因为闭包在我们书写JavaScript
代码时,随处可见,但是我们又不知道哪里用了闭包。
关于闭包的定义,网上(书上)的解释总是千奇百怪,我们也只能“取其精华去其糟粕”去总结一下。
ECMAScript中,闭包指的是:
从实践角度:一下才算是闭包:
闭包跟词法作用域,作用域链,执行上下文这几个JavaScript
中重要的概念都有关系,因此要想真的理解闭包,至少要对那几个概念不陌生。
闭包的优点:
闭包的缺点:
我们来一步一步引出闭包。
自执行函数也叫立即调用函数(IIFE),是一个在定义时就执行的函数。
var a=1;
(function() { console.log(a)
})()
上述代码是一个最简单的自执行函数。
在ES6之前,是没有块级作用域的,只有全局作用域和函数作用域,因此自执行函数还能在ES6之前实现块级作用域。
// ES6 块级作用域 var a = 1; if(true) { let a=111; console.log(a); // 111 } console.log(a); // 1
这里 if{} 中用let声明了一个 a。这个 a 就具有块级作用域,在这个 {} 中访问 a ,永远访问的都是 let 声明的a,跟全局作用域中的a没有关系。如果我们把 let 换成 var ,就会污染全局变量 a 。
如果用自执行函数来实现:
var a = 1;
(function() { if(true) { var a=111; console.log(a); // 111 }
})() console.log(a); // 1
为什么要在这里要引入自执行函数的概念呢?因为通常我们会用自执行函数来创建闭包,实现一定的效果。
来看一个基本上面试提问题:
for(var i=0;i<5;i++) {
setTimeout(function() { console.log(i);
},1000)
}
在理想状态下我们期望输出的是 0 ,1 ,2 ,3 ,4。但是实际上输出的是5 ,5 ,5 ,5 ,5。为什么是这样呢?其实这里不仅仅涉及到作用域,作用域链还涉及到Event Loop、微任务、宏任务。但是在这里不讲这些。
下面我们先解释它为什么会输出 5个5,然后再用自执行函数来修改它,以达到我们预期的结果。
提示:for 循环中,每一次的都声明一个同名变量,下一个变量的值为上一次循环执行完同名变量的值。
首先用var声明变量 for 是不会产生块级作用域的,所以在 () 中声明的 i 为全局变量。相当于:
// 伪代码 var i; for(i=0;i<5;i++) {
setTimeout(function() { console.log(i);
},1000)
}
setTimeout中的第一个参数为一个全局的匿名函数。相当于:
// 伪代码 var i; var f = function() { console.log(i);
} for(i=0;i<5;i++) {
setTimeout(f,1000)
}
由于setTimeout是在1秒之后执行的,这个时候for循环已经执行完毕,此时的全局变量 i 已经变成了 5 。1秒后5个setTimeout中的匿名函数会同时执行,也就是5个 f 函数执行。这个时候 f 函数使用的变量 i 根据作用域链的查找规则找到了全局作用域中的 i 。因此会输出 5 个5。
那我们怎样来修改它呢?
for(var i=0;i<5;i++) {
(function (){ setTimeout(function() { console.log(i);
},1000)
})();
}
上述例子会输出我们期望的值吗?答案是否。为什么呢?我们虽然把 setTimeout 包裹在一个匿名函数中了,但是当setTimeout中匿名函数执行时,首先去匿名函数中查找 i 的值,找不到还是会找到全局作用域中,最终 i 的值仍然是全局变量中的 i ,仍然为 5个5.
那我们把外层的匿名函数中声明一个变量 j 让setTimeout中的匿名函数访问这个 j 不就找不到全局变量中的变量了吗。
for(var i=0;i<5;i++) {
(function (){ var j = i;
setTimeout(function() { console.log(j);
},1000)
})();
}
这个时候才达到了我们预期的结果:0 1 2 3 4。
我们来优化一下:
for(var i=0;i<5;i++) {
(function (i){ setTimeout(function() { console.log(i);
},1000)
})(i);
}
*思路2:用 let 声明变量,产生块级作用域。
for(let i=0;i<5;i++) {
setTimeout(function() { console.log(i);
},1000)
}
这时for循环5次,产生 5 个块级作用域,也会声明 5 个具有块级作用域的变量 i ,因此setTimeout中的匿名函数每次执行时,访问的 i 都是当前块级作用域中的变量 i 。
什么是理论中的闭包?就是看似像闭包,其实并不是闭包。它只是类似于闭包。
function foo() { var a=2; function bar() { console.log(a); // 2 }
bar();
}
foo();
上述代码根据最上面我们对闭包的定义,它并不完全是闭包,虽然是一个函数可以访问另一个函数中的变量,但是被嵌套的函数是在当前词法作用域中被调用的。
我们怎样把上述代码foo 函数中的bar函数,在它所在的词法作用域外执行呢?
下面的代码就清晰的展示了闭包:
function foo() { var a=2; function bar() { console.log(a);
} return bar;
} var baz=foo();
baz(); // 2 —— 朋友,这就是闭包的效果。
上述代码中 bar 被当做 foo函数返回值。foo函数执行后把返回值也就是 bar函数 赋值给了全局变量 baz。当 baz 执行时,实际上也就是 bar 函数的执行。我们知道 foo 函数在执行后,foo 的内部作用域会被销毁,因为引擎有垃圾回收期来释放不再使用的内存空间。所以在bar函数执行时,实际上foo函数内部的作用域已经不存在了,理应来说 bar函数 内部再访问 a 变量时是找不到的。但是闭包的神奇之处就在这里。由于 bar 是在 foo 作用域中被声明的,所以 bar函数 会一直保存着对 foo 作用域的引用。这时就形成了闭包。
我们先看个例子:
var scope = "global scope"; function checkscope(){ var scope = "local scope"; function f(){ return scope;
} return f;
} var foo = checkscope();
foo();
我们用伪代码来解释JavaScript
引擎在执行上述代码时的步骤:
JavaScript
引擎遇到可执行代码时,就会进入一个执行上下文(环境)
但是我们想一个问题,checkscope函数执行完毕,它的执行上下文从栈中弹出,也就是销毁了不存在了,f 函数还能访问包裹函数的作用域中的变量(scope)吗?答案是可以。
理由是在第6步,我们说过当checkscope 执行函数执行完毕时,它的执行上下文会从栈中弹出,此时活动对象也会被回收,按理说当 f 在访问checkscope的活动对象时是访问不到的。
其实这里还有个概念,叫做作用域链:当 checkscope 函数被创建时,会创建对应的作用域链,里面值存放着包裹它的作用域对应执行上下文的变量对象,在这里只是全局执行上下文的变量对象,当checkscope执行时,此时的作用域链变化了 ,里面存放的是变量对象(活动对象)的集合,最顶端是当前函数的执行上下文的活动对象。端是全局执行上下文的变量对象。类似于:
checkscope.scopeChain = [
checkscope.AO
global.VO
]
当checkscope执行碰到了 f 函数的创建,因此 f 函数也会创建对应的作用域链,默认以包裹它的函数执行时对应的作用域链为基础。因此此时 f 函数创建时的作用域链如下:
checkscope.scopeChain = [
checkscope.AO
global.VO
]
当 f 函数执行时,此时的作用域链变化如下:
checkscope.scopeChain = [
f.AO
checkscope.AO
global.VO
]
当checkscope函数执行完毕,内部作用域会被回收,但是 f函数 的作用域链还是存在的,里面存放着 checkscope函数的活动对象,因此在f函数执行时会从作用域链中查找内部使用的 scope 标识符,从而在作用域链的第二位找到了,也就是在 checkscope.AO 找到了变量scope的值。
正是因为JavaScript
做到了这一点,因此才会有闭包的概念。还有人说闭包并不是为了拥有它采取设计它的,而是设计作用域链时的副作用产物。
闭包是JavaScript
中最难的点,也是平常面试中常问的问题,我们必须要真正的去理解它,如果只靠死记硬背是经不起考验的。
你知道吗?视力,听力和行动能力完全健康的人,可以轻松地读写,可以有效执行多任务,并且始终可以正常工作的人约占总人口的50%?其余的人都是戴着眼镜或有色盲,手腕或耳朵受伤,生活在嘈杂的环境中或网络信号质量差,忙碌或忙碌中,阅读障碍或有注意力障碍等。
这意味着大约一半的用户可能很难使用我们的产品或浏览我们的网站。因此,我们可能错过了提高用户满意度并扩大受众范围的机会。
不过在设计阶段实施一些简单的原则就可以改善交互和整体用户体验,极限设计可以为所有人带来价值,我们称之为“包容性设计”。
什么是包容性设计?包容性设计考虑了尽可能多的人的需求和能力,而不仅仅是针对残疾人。它认识到我们的需求会随着时间和环境的变化而变化,因此它会预测错误,挣扎和不同的交互方式。它的目的是在问题发生之前解决问题,提高标准并改变良好产品设计的标准。
包容的用户界面是善解人意,有意识且可访问的。年龄,性别,教育程度,财富和能力等不同特征,在不同环境中生活或工作,获得技术水平不同的不同人群可以舒适地使用它。
我们将使用POUR作为在用户与界面之间创建简单,轻松,快速交互的参考。
POUR代表
可以理解:数字内容可以轻松地以不同方式进行解释或处理吗?
可操作:数字产品能否轻松自如地进行功能和控制?
可以理解:用户可以理解界面的功能和内部信息吗?
健壮性:数字产品是否与不同的辅助技术和设备兼容?
设计师如何提供帮助
作为设计师,我们当然不能控制以上所有要求都能做到。但是我们应该承认,人们遇到的许多可访问性问题是由设计阶段未做过的决定引起的。因此,设计师有很多机会可以有所作为。仅通过做出更明智的设计决策,我们就可以影响(改进或协助)四种经验。
视觉体验:这包括形状,颜色,对比,文本样式-产品界面的所有图形元素。
听觉体验:这是指与产品互动时产生的声音,音量和清晰度。
认知经验:这描述了用户花费在解释界面上的时间,以及使用界面需要多少注意力和精力。
运动体验:这包括执行任务或与产品交互所需的所有动作和动作。
通常,可访问性被认为是对创造力的挑战;但是,如果我们认为这是一个创造性的挑战,那么我们会开辟全新的可能性领域。真正好的可访问性的诀窍不是在功能或功能上进行折衷,也不是在美学上取舍,而是使功能和创意体验也可以访问。
改善视觉体验
1.颜色
对比度对比度是亮度或颜色的差异,使物体从周围环境中脱颖而出,并可能对清晰度产生显着影响。高对比度使视觉元素从背景中脱颖而出,更加引人注目。
专家提示:纯粹的#000000黑白色会给眼睛带来强烈的对比度,甚至会影响阅读障碍者。这就是为什么我们倾向于避免使用它,而是选择深灰色的原因。
亮度
亮度描述从光源发出的照明水平或从表面反射的光量。明亮的颜色反射更多的光线,并会干扰我们阅读和处理信息的能力。
避免在背景或较大表面上使用鲜艳的颜色。请勿在文本上或文本附近使用鲜艳的颜色,以免干扰文本。如果品牌要求特定的高亮度颜色,请尝试建议使用饱和或较深的颜色。如果你绝对必须使用明亮的颜色,则应将其用于突出显示动作的方法最小化,并将其与较深的色相搭配以达到平衡和高对比度。
专家提示:任何含有超过50%黄色的颜色都会自然反射更多的光。这意味着黄色,橙色,绿色和蓝绿色是高风险颜色,应谨慎使用。
色盲
色盲是无法区分特定颜色(通常是红色和绿色,偶尔是蓝色)的一种,它比你想象的要常见。
专家提示:不要仅仅依靠颜色;颜色不应该是传达重要信息的唯一方法。您可以执行以下操作:
使用下划线表示链接和斜体,或使用粗体突出显示文本
将图标与文本一起使用可传达成功或失败的信息
使用纹理或图案作为图表
为按钮或明显的通知使用清晰的视觉样式,针对焦点或活动状态使用不同的字体样式
2.版式
字体选择
通信是每个数字产品的首要目标,可以借助印刷术及其正确应用来实现。内容应清晰易读,这意味着易于识别和解释,轻松阅读和处理。
简洁明了对于快速阅读和解释至关重要,请避免使用复杂的字体,因为它们只会增加视觉干扰。选择正确的字体家族,针对那些具有清晰定义和独特形状的字符,因为视力障碍或阅读障碍的人可能会因某些字符或其组合而感到困惑。
字体样式
字体样式还会影响弱视或阅读障碍者的阅读性能。我们应该注意并谨慎使用字体样式(如斜体,下划线和大写)的频率和位置。
根据“英国阅读障碍协会”的规定,应避免使用斜体,特别是对于较大的副本块或较小的字体。这是因为它们使字母倾斜,显得更加尖锐,因此更难以阅读。
正文也应避免使用带下划线的字体样式。给长的段落加下划线会增加视觉噪音,从而降低阅读性能,而给短的句子或单词加下划线会与活动链接相关联,并可能引起混乱。粗体是添加对比度和强调的更好选择。
尽管没有确凿的研究,但有一些证据支持也应避免主要针对正文使用大写字母。似乎所有大写字母的统一外观会降低单词形状的对比度,从而使扫描变得不那么容易。此外,大写看起来有点紧张,可能感觉好像有人在向您大喊大叫。
专家提示:平衡是关键。谨慎使用每个样式并赋予其含义甚至可以提高可读性。
字体大小
您知道绝大多数人戴眼镜或隐形眼镜吗?实际上,十分之六以上!此外,约有62%的人通过手机访问互联网,这还不包括应用程序的使用情况。当视力不佳的人在旅途中在小屏幕上使用技术时,可能会出什么问题?
使用较大的字体。通常,16px被认为是最具有包容性的,但是请注意,字体可以以不同的比例站立,并且字体的大小可以相差很大。切勿低于14px,事实上,大多数现代网站的正文都使用18px字体,而标签,标题或工具提示仅使用14px或16px。
专家提示:此外,避免使用薄而轻的字体,因为对于较小的字体或在明亮的光线下可能难以阅读。
段落格式
帮助人们轻松浏览内容应该是我们的首要目标,因为只有20%的人可以阅读内容,其中55%的人可以快速浏览内容。我们的工作是通过使用舒适的段落格式来尽可能地支持人们。
研究表明,用于支持可读性的平均在线行长(包括空格)约为70个字符。标题,字幕和项目符号点将有助于扫描,而左段对齐将使文本更易于阅读。
较长的文字墙使人们参与的机会大大减少。成功的段落长度不超过5到6个句子。
空格将帮助患有认知和注意力障碍的人,保持阅读重点。对于其余的内容,它只会使阅读更加愉快和流畅。根据WCAG,最佳做法是将行高(行之间的间距)设置为相对于该类型大小的1.5相对值。段落之间的间距也至少应比行间距大1.5倍,因此必须明确定义。
提示:行距不应超过2.0,因为它可能产生相反的效果并分散读者注意力。
复制版面
作为设计师,我们经常陷入过度设计布局的陷阱,以使它们看起来引人注目或独特,从而将可用性放在一边。这就是为什么我们看到诸如文本的一部分之类的趋势在彩色或带纹理的背景上重叠图像或文本的趋势。只要我们知道如何以及何时使用它们,我们仍然可以享受其中的一些趋势。
当在彩色或带纹理的背景上使用文本时,我们需要确保它们之间的色彩对比度足够高,同时在整个重叠区域都保持一致-意味着在副本下没有较浅和较暗的区域,也没有过多的细节干扰。较大的字体大小和较重的字体粗细也会提高对比度。
专家提示:一如既往地“了解您的用户”。时髦的布局并不适合所有人。
改善听觉体验
您可能在想,视觉设计如何影响听觉体验?因此,想象一下您正在与一个俱乐部的朋友交谈。我敢打赌,您只能听见她说的话的一半,但是您可以通过看着她的嘴唇移动,肢体语言和面部表情来保持对话的进行。由于视觉效果的支持增强了模棱两可的声音,因此您最终可以理解它们。
在用户界面中,声音对于不同的人可能意味着各种各样的事情。它们也很容易在嘈杂的背景中丢失,因此最好以视觉提示来支持它们。
我们的目标应该是提供听觉和视觉提示的反馈,支持错误,通知以及与相关和邻近图形元素的重大交互。我们还必须确保视觉线索保持足够长的活动时间,以使人们能够看到和阅读,同时又不隐藏任何重要的内容。
一个好的做法-不限于支持声音辅助技术,是在UI元素中添加描述性标签,并在图像中添加标题,以便于在屏幕阅读器中轻松导航。为视频使用字幕是改善听力体验的另一种方法,对非母语人士也有帮助。
最后,我们不应该忽略声音是问题的情况,这就是为什么我们需要视觉替代的原因。有些人可能对特定的声音敏感,或者处于声音可能引起干扰的情况下。然后,这是一个好习惯,让人们可以选择关闭声音而不必调低扬声器音量,从而使此功能清晰可见。
专家提示:避免使用不必要的自动播放声音和音乐,因为它们会打扰甚至惊吓别人。
改善认知体验
1.知觉
视觉清晰度
清晰度是用户界面中的第一个也是最重要的属性。成功的用户界面使用户能够识别和解释他们所看到的内容,了解产品的价值和所要采取的行动,预测使用产品时会发生什么以及与产品成功交互。
形式跟随功能是一项原则,指出对象应反映其预期的功能或目的。为了在用户界面中实现此目的,我们使用了附加功能,附加到UI的视觉提示/属性,以显示用户与其交互的可能方式。
支付能力取决于用户的身体能力,目标,过去的经验,当然还取决于他们认为可能的情况。按钮应该看起来像按钮,就像链接,菜单标签,表单等一样。使用清晰的符号/功能可以帮助用户识别或解释界面,并轻松进行交互。
在用户界面中使用熟悉的和已建立的设计解决方案将帮助用户预测结果并自信地采取行动。因此,使用设计模式来解决常见问题是一个好习惯,该设计模式是经过测试,优化和可重用的解决方案。
设计模式建立在过去的经验和可能性的基础上,并附加到特定的目标上。为避免眼前的问题,选择正确的设计模式应该是我们避免混淆或压力大的交互的第一要务。
建立一致的视觉语言是获得更全面界面的关键。具有相同功能和/或重要性的重复交互式UI组件应始终以相同的方式外观和操作。因此,导航,按钮,链接,标签,错误等元素应在整个产品中具有一致的样式,颜色和动画。
值得注意的是,一致的视觉语言不仅可以通过附加含义和减少视觉噪音来帮助互动,而且还可以增强产品的个性,提升品牌知名度,建立情感联系和信任。
层次结构
视觉层次结构是指图形元素的视觉重量及其排列方式,使用户可以轻松地探索和发现内容。通过为页面元素分配不同的视觉权重,我们可以对内容进行分组并影响人们感知信息和浏览产品的顺序。
颜色是第一大关注焦点。彩色元素将脱颖而出,因此在层次结构中位于较高位置。明亮的颜色会更加突出,因此,考虑到这一点,我们应该仔细安排和分配颜色,以将眼睛引导至正确的位置。
视觉元素的大小(例如印刷,按钮,图标和图像)在确定重要性方面几乎与颜色一样强大。较大的图形吸引了用户的注意,并且显得很重要。对于排版,明显不同的尺寸缩放比例可以帮助建立内容层次结构,并使内容扫描变得轻松而轻松。
辅助视觉层次结构的另一种方法是通过设计一致性和例外。一致对齐,外观相似,重复或相邻的元素给人的印象是它们是相关且同等重要的,而偏离元素以及不寻常的形状和有趣的纹理或样式将更加显着。太多的设计例外会引起人们的关注,并会增加复杂性,因此,谨慎使用它们是一个好习惯。
专家提示:研究格式塔原理及其在UI设计中的应用将有助于我们理解视觉感知和分组以改善视觉层次。
色彩应用
颜色不应该是传达信息或增加意义的唯一方法,但它仍然有用且很有影响力,因此不应将其视为装饰性元素。颜色具有含义,尽管没有硬性规定,但是太多的颜色会导致信息疲劳,并且不一致地使用颜色会导致混乱。
避免使用太多颜色。通常,三种颜色足以描述页面的所有重要视觉元素。60–30–10规则可以帮助我们建立完美的和谐。其中60%的彩色项目由原色组成,以创建统一的产品主题,具有30%的辅助颜色增强含义和/或创建引人注目的效果,以及10%的强调色,以补充和辅助主颜色和辅助颜色。
此外,我们需要确保为消息使用正确的色调。除了美学,颜色还可以创造情感和无意识的联系。特定阴影的含义会因我们所处的文化和环境而异,并且颜色通常具有不同的含义-在西方世界,错误是红色,成功是绿色,信息是蓝色等。
专家提示:可以将我们自己的含义分配给颜色,只要它们不与既定规范重叠,并且我们在整个产品中使它们保持一致。
符号学
符号学是对符号/图标及其含义的研究。它着重于人们如何形成和解释这些含义,这取决于人们所看到的上下文。在用户界面中,图标是可视语言的一部分,用于表示功能,功能或内容。符号学可以帮助我们设计立即被识别和理解的图像。
尽管这些年来,我们已经开发出具有大多数人接受和理解的含义的图标。用户还习惯于使用特定于平台的图标,并且可以轻松地进行解释。在可能的情况下,最好遵循这些既定的解决方案,以获得熟悉和流畅的体验。
当然,在某些情况下,我们需要设计具有特定功能的自定义产品特定图标。这些图标必须尽可能简单明了,以确保清晰度。它们还应该具有一致的视觉样式,以传达其功能或与其他非功能性元素区分开。
最后,我们不应该仅仅依靠视觉隐喻来传达含义,因为某些关联可能并不那么明显。如果图标需要标题来描述其含义,则可能不合适。如果不确定,请与实际用户一起测试我们的设计会有所帮助。
专家提示:图标不仅易于解释,而且还可以具有多种含义。因此,将标记与功能图标结合使用是一种很好的做法。
2.互动
记忆
许多心理学实验表明,健康个体的处理能力非常有限。在我们的短期记忆中,我们大多数人平均可以保留7项,具体取决于个人。我们的大脑并未针对数字产品所需的抽象思维和数据记忆进行优化,因此良好的设计会有所作为。
减少页面上可用选项和信息的数量,以及使用清晰的标题,面包屑和“后退”选项来访问以前的内容,将帮助用户记住或提醒自己他们在哪里,打算做什么或要做什么。是必需的。
交互元素上或附近的清晰可见副本将帮助用户在整个交互过程中保持知情和自信。例如,表单标签应始终可见,动作不应隐藏在悬停后面,按钮应提供目标位置的上下文,并且各节的标题应明确。
专家提示:通过称为“块”的过程可以增加我们的短期记忆和处理能力。这是我们在视觉上将项目分组以形成更容易记住的较大项目的地方。
改善运动体验
菲茨法
菲茨法则为人类的运动和互动提供了一个模型。它指出,将指针(光标或手指)快速移动到目标区域所需的时间是其距目标的距离除以目标大小的函数。意味着较小的目标会增加互动时间。
根据Fitts法则,我们旨在减小用户与目标之间的距离,同时增加其尺寸。该法律主要适用于导航和按钮。菜单和子菜单元素应在附近,而按钮,链接和分页应在较大区域上单击,以实现更快更准确的交互。
专家提示:根据可用性最佳实践,按钮/链接的最小尺寸为42x42像素(重击尺寸)。
奖励:提高绩效
到目前为止,我们已经建立了包容性设计,旨在让尽可能多的人访问并实现他们的目标或解决他们的问题,尽管他们有自己的情况。我们可能很幸运,可以使用进的设备或超高速互联网,但是当我们的信号不太好时,我们会感到挣扎。对于大多数人来说,老式设备和糟糕的互联网已成为常态,因此,为获得最佳性能而设计是一件大事。
极简主义是关键。如果我们打算创造一种可以被尽可能多的人使用的产品,那么我们就应该摆脱不必要的一切。图形,图像或动画是有价值的,还是增加了视觉噪音和加载时间?如果是的话,那就必须走了。
图像优化是帮助提高数字产品性能的另一个标准。通过将图像调整为合适的大小,然后通过诸如ImageOptim和TinyPNG之类的工具运行它们,可以节省宝贵的千字节和实际的加载时间。
开发人员通常使用的一种提高性能的技术是“延迟加载”模式,其中图像的加载是异步的,并延迟到需要时才加载。例如,如果您快速滚动到页面底部,则在网站完全加载之前,您可能会看到类似网站线框的内容。“渐进图像加载”的一种替代方法是“渐进图像加载”,其中我们显示一个空的占位符框<div>,然后用小的低质量模糊图像填充它,最后用所需的高质量图像替换它。
在每个数字产品中都遵循上述最佳实践,这是高可访问性标准的良好起点。但是总会有改进的余地,并且更好地了解我们的用户可以揭示提高无障碍标准的新方法。因此,有必要花费一些时间和金钱来更多地了解我们的不同类型的用户,因为他们可以教会我们很多有关使包容性体验成为现实的知识。
了解我们的用户将帮助我们练习同理心。“赋权”不是偶然的设计思维过程的第一步。在移情阶段,我们的目标是加深对我们正在设计的人员及其独特视角的了解,因此我们可以在进行任何设计决策时与他们认同并代表他们。
zhuanz
this
this是我们在书写代码时最常用的关键词之一,即使如此,它也是JavaScript最容易被最头疼的关键词。那么this到底是什么呢?
如果你了解执行上下文,那么你就会知道,其实this是执行上下文对象的一个属性:
executionContext = {
scopeChain:[ ... ],
VO:{
...
},
this: ?
}
执行上下文中有三个重要的属性,作用域链(scopeChain)、变量对象(VO)和this。
this是在进入执行上下文时确定的,也就是在函数执行时才确定,并且在运行期间不允许修改并且是永久不变的
在全局代码中的this
在全局代码中this 是不变的,this始终是全局对象本身。
var a = 10;
this.b = 20;
window.c = 30;
console.log(this.a);
console.log(b);
console.log(this.c);
console.log(this === window) // true
// 由于this就是全局对象window,所以上述 a ,b ,c 都相当于在全局对象上添加相应的属性
如果我们在代码运行期尝试修改this的值,就会抛出错误:
this = { a : 1 } ; // Uncaught SyntaxError: Invalid left-hand side in assignment
console.log(this === window) // true
函数代码中的this
在函数代码中使用this,才是令我们最容易困惑的,这里我们主要是对函数代码中的this进行分析。
我们在上面说过this的值是,进入当前执行上下文时确定的,也就是在函数执行时并且是执行前确定的。但是同一个函数,作用域中的this指向可能完全不同,但是不管怎样,函数在运行时的this的指向是不变的,而且不能被赋值。
function foo() {
console.log(this);
}
foo(); // window
var obj={
a: 1,
bar: foo,
}
obj.bar(); // obj
函数中this的指向丰富的多,它可以是全局对象、当前对象、或者是任意对象,当然这取决于函数的调用方式。在JavaScript中函数的调用方式有一下几种方式:作为函数调用、作为对象属性调用、作为构造函数调用、使用apply或call调用。下面我们将按照这几种调用方式一一讨论this的含义。
作为函数调用
什么是作为函数调用:就是独立的函数调用,不加任何修饰符。
function foo(){
console.log(this === window); // true
this.a = 1;
console.log(b); // 2
}
var b = 2;
foo();
console.log(a); // 1
上述代码中this绑定到了全局对象window。this.a相当于在全局对象上添加一个属性 a 。
在严格模式下,独立函数调用,this的绑定不再是window,而是undefined。
function foo() {
"use strict";
console.log(this===window); // false
console.log(this===undefined); // true
}
foo();
这里要注意,如果函数调用在严格模式下,而内部代码执行在非严格模式下,this 还是会默认绑定为 window。
function foo() {
console.log(this===window); // true
}
(function() {
"use strict";
foo();
})()
对于在函数内部的函数独立调用 this 又指向了谁呢?
function foo() {
function bar() {
this.a=1;
console.log(this===window); // true
}
bar()
}
foo();
console.log(a); // 1
上述代码中,在函数内部的函数独立调用,此时this还是被绑定到了window。
总结:当函数作为独立函数被调用时,内部this被默认绑定为(指向)全局对象window,但是在严格模式下会有区别,在严格模式下this被绑定为undefined。
作为对象属性调用
var a=1;
var obj={
a: 2,
foo: function() {
console.log(this===obj); // true
console.log(this.a); // 2
}
}
obj.foo();
上述代码中 foo属性的值为一个函数。这里称 foo 为 对象obj 的方法。foo的调用方式为 对象 . 方法 调用。此时 this 被绑定到当前调用方法的对象。在这里为 obj 对象。
再看一个例子:
var a=1;
var obj={
a: 2,
bar: {
a: 3,
foo: function() {
console.log(this===bar); // true
console.log(this.a); // 3
}
}
}
obj.bar.foo();
遵循上面说的规则 对象 . 属性 。这里的对象为 obj.bar 。此时 foo 内部this被绑定到了 obj.bar 。 因此 this.a 即为 obj.bar.a 。
再来看一个例子:
var a=1;
var obj={
a: 2,
foo: function() {
console.log(this===obj); // false
console.log(this===window); // true
console.log(this.a); // 1
}
}
var baz=obj.foo;
baz();
这里 foo 函数虽然作为对象obj 的方法。但是它被赋值给变量 baz 。当baz调用时,相当于 foo 函数独立调用,因此内部 this被绑定到 window。
使用apply或call调用
apply和call为函数原型上的方法。它可以更改函数内部this的指向。
var a=1;
function foo() {
console.log(this.a);
}
var obj1={
a: 2
}
var obj2={
a: 3
}
var obj3={
a: 4
}
var bar=foo.bind(obj1);
bar();// 2 this => obj1
foo(); // 1 this => window
foo.call(obj2); // 3 this => obj2
foo.call(obj3); // 4 this => obj3
当函数foo 作为独立函数调用时,this被绑定到了全局对象window,当使用bind、call或者apply方法调用时,this 被分别绑定到了不同的对象。
作为构造函数调用
var a=1;
function Person() {
this.a=2; // this => p;
}
var p=new Person();
console.log(p.a); // 2
上述代码中,构造函数 Person 内部的 this 被绑定为 Person的一个实例。
总结:
当我们要判断当前函数内部的this绑定,可以依照下面的原则:
函数是否在是通过 new 操作符调用?如果是,this 绑定为新创建的对象
var bar = new foo(); // this => bar;
函数是否通过call或者apply调用?如果是,this 绑定为指定的对象
foo.call(obj1); // this => obj1;
foo.apply(obj2); // this => obj2;
函数是否通过 对象 . 方法调用?如果是,this 绑定为当前对象
obj.foo(); // this => obj;
函数是否独立调用?如果是,this 绑定为全局对象。
foo(); // this => window
DOM事件处理函数中的this
1). 事件绑定
<button id="btn">点击我</button>
// 事件绑定
function handleClick(e) {
console.log(this); // <button id="btn">点击我</button>
}
document.getElementById('btn').addEventListener('click',handleClick,false); // <button id="btn">点击我</button>
document.getElementById('btn').onclick= handleClick; // <button id="btn">点击我</button>
根据上述代码我们可以得出:当通过事件绑定来给DOM元素添加事件,事件将被绑定为当前DOM对象。
2).内联事件
<button onclick="handleClick()" id="btn1">点击我</button>
<button onclick="console.log(this)" id="btn2">点击我</button>
function handleClick(e) {
console.log(this); // window
}
//第二个 button 打印的是 <button id="btn">点击我</button>
我认为内联事件可以这样理解:
//伪代码
<button onclick=function(){ handleClick() } id="btn1">点击我</button>
<button onclick=function() { console.log(this) } id="btn2">点击我</button>
这样我们就能理解上述代码中为什么内联事件一个指向window,一个指向当前DOM元素。(当然浏览器处理内联事件时并不是这样的)
定时器中的this
定时器中的 this 指向哪里呢?
function foo() {
setTimeout(function() {
console.log(this); // window
},1000)
}
foo();
再来看一个例子
var name="chen";
var obj={
name: "erdong",
foo: function() {
console.log(this.name); // erdong
setTimeout(function() {
console.log(this.name); // chen
},1000)
}
}
obj.foo();
到这里我们可以看到,函数 foo 内部this指向为调用它的对象,即:obj 。定时器中的this指向为 window。那么有什么办法让定时器中的this跟包裹它的函数绑定为同一个对象呢?
1). 利用闭包:
var name="chen";
var obj={
name: "erdong",
foo: function() {
console.log(this.name) // erdong
var that=this;
setTimeout(function() {
// that => obj
console.log(that.name); // erdong
},1000)
}
}
obj.foo();
利用闭包的特性,函数内部的函数可以访问含义访问当前词法作用域中的变量,此时定时器中的 that 即为包裹它的函数中的 this 绑定的对象。在下面我们会介绍利用 ES6的箭头函数实现这一功能。
当然这里也可以适用bind来实现:
var name="chen";
var obj={
name: "erdong",
foo: function() {
console.log(this.name); // erdong
setTimeout(function() {
// this => obj
console.log(this.name); // erdong
}.bind(this),1000)
}
}
obj.foo();
被忽略的this
如果你把 null 或者 undefined 作为 this 的绑定对象传入 call 、apply或者bind,这些值在调用时会被忽略,实例 this 被绑定为对应上述规则。
var a=1;
function foo() {
console.log(this.a); // 1 this => window
}
var obj={
a: 2
}
foo.call(null);
var a=1;
function foo() {
console.log(this.a); // 1 this => window
}
var obj={
a: 2
}
foo.apply(null);
var a=1;
function foo() {
console.log(this.a); // 1 this => window
}
var obj={
a: 2
}
var bar = foo.bind(null);
bar();
bind 也可以实现函数柯里化:
function foo(a,b) {
console.log(a,b); // 2 3
}
var bar=foo.bind(null,2);
bar(3);
更复杂的例子:
var foo={
bar: function() {
console.log(this);
}
};
foo.bar(); // foo
(foo.bar)(); // foo
(foo.bar=foo.bar)(); // window
(false||foo.bar)(); // window
(foo.bar,foo.bar)(); // window
上述代码中:
foo.bar()为对象的方法调用,因此 this 绑定为 foo 对象。
(foo.bar)() 前一个() 中的内容不计算,因此还是 foo.bar()
(foo.bar=foo.bar)() 前一个 () 中的内容计算后为 function() { console.log(this); } 所以这里为匿名函数自执行,因此 this 绑定为 全局对象 window
后面两个实例同上。
这样理解会比较好:
(foo.bar=foo.bar) 括号中的表达式执行为 先计算,再赋值,再返回值。
(false||foo.bar)() 括号中的表达式执行为 判断前者是否为 true ,若为true,不计算后者,若为false,计算后者并返回后者的值。
(foo.bar,foo.bar) 括号中的表达式之行为分别计算 “,” 操作符两边,然后返回 “,” 操作符后面的值。
箭头函数中的this
箭头函数时ES6新增的语法。
有两个作用:
更简洁的函数
本身不绑定this
代码格式为:
// 普通函数
function foo(a){
// ......
}
//箭头函数
var foo = a => {
// ......
}
//如果没有参数或者参数为多个
var foo = (a,b,c,d) => {
// ......
}
我们在使用普通函数之前对于函数的this绑定,需要根据这个函数如何被调用来确定其内部this的绑定对象。而且常常因为调用链的数量或者是找不到其真正的调用者对 this 的指向模糊不清。在箭头函数出现后其内部的 this 指向不需要再依靠调用的方式来确定。
箭头函数有几个特点(与普通函数的区别)
箭头函数不绑定 this 。它只会从作用域链的上一层继承 this。
箭头函数不绑定arguments,使用reset参数来获取实参的数量。
箭头函数是匿名函数,不能作为构造函数。
箭头函数没有prototype属性。
不能使用 yield 关键字,因此箭头函数不能作为函数生成器。
这里我们只讨论箭头函数中的this绑定。
用一个例子来对比普通函数与箭头函数中的this绑定:
var obj={
foo: function() {
console.log(this); // obj
},
bar: () => {
console.log(this); // window
}
}
obj.foo();
obj.bar();
上述代码中,同样是通过对象 . 方法调用一个函数,但是函数内部this绑定确是不同,只因一个数普通函数一个是箭头函数。
用一句话来总结箭头函数中的this绑定:
个人上面说的它会从作用域链的上一层继承 this ,说法并不是很正确。作用域中存放的是这个函数当前执行上下文与所有父级执行上下文的变量对象的集合。因此在作用域链中并不存在 this 。应该说是作用域链上一层对应的执行上下文中继承 this 。
箭头函数中的this继承于作用域链上一层对应的执行上下文中的this
var obj={
foo: function() {
console.log(this); // obj
},
bar: () => {
console.log(this); // window
}
}
obj.bar();
上述代码中obj.bar执行时的作用域链为:
scopeChain = [
obj.bar.AO,
global.VO
]
根据上面的规则,此时bar函数中的this指向为全局执行上下文中的this,即:window。
再来看一个例子:
var obj={
foo: function() {
console.log(this); // obj
var bar=() => {
console.log(this); // obj
}
bar();
}
}
obj.foo();
在普通函数中,bar 执行时内部this被绑定为全局对象,因为它是作为独立函数调用。但是在箭头函数中呢,它却绑定为 obj 。跟父级函数中的 this 绑定为同一对象。
此时它的作用域链为:
scopeChain = [
bar.AO,
obj.foo.AO,
global.VO
]
这个时候我们就差不多知道了箭头函数中的this绑定。
继续看例子:
var obj={
foo: () => {
console.log(this); // window
var bar=() => {
console.log(this); // window
}
bar();
}
}
obj.foo();
这个时候怎么又指向了window了呢?
我们还看当 bar 执行时的作用域链:
scopeChain = [
bar.AO,
obj.foo.AO,
global.VO
]
当我们找bar函数中的this绑定时,就会去找foo函数中的this绑定。因为它是继承于它的。这时 foo 函数也是箭头函数,此时foo中的this绑定为window而不是调用它的obj对象。因此 bar函数中的this绑定也为全局对象window。
我们在回头看上面关于定时器中的this的例子:
var name="chen";
var obj={
name: "erdong",
foo: function() {
console.log(this.name); // erdong
setTimeout(function() {
console.log(this); // chen
},1000)
}
}
obj.foo();
这时我们就可以很简单的让定时器中的this与foo中的this绑定为同一对象:
var name="chen";
var obj={
name: "erdong",
foo: function() {
// this => obj
console.log(this.name); // erdong
setTimeout(() => {
// this => foo中的this => obj
console.log(this.name); // erdong
},1000)
}
}
obj.foo();
蓝蓝设计的小编 http://www.lanlanwork.com