使你的代码可读和可维护的快速提示。
有多少次,你打开一个旧的项目,发现混乱的代码,当你添加一些新的东西时,很容易崩溃?我们都有过这样的经历。
为了减少难以读懂的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界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 、平面设计服务
在多方合作的项目中,我们需要规划项目的合理落地方案,并在执行过程中和各方有效沟通。结合近期羚珑商家合作测试项目的实践经历,聊聊我在项目中的心得体会。
我们想了解京东首焦banner中,设计因素对点击效果的影响情况。做这件事最大的限制在于投放数据会受多种因素影响,不仅是一个设计因素的选择,还有比如人群、出价、品牌、类目等因素的不同,都能影响到最终的投放结果。因此,从总体样本中取样进行分析的意义不大。为了解决上述问题,我们招募了一些能控制其他因素的品牌广告主,与有能力支持投放的测试投放系统合作,共同完成A/B测试。
不难发现这个需求会涉及到多个项目角色,意味着将有复杂的推进流程,需要提前拆解与规划,才能保证项目进度可控。
△ 测试项目流程与分工
我们可以分三步来进行:
1. 找到项目关键步骤
关键步骤是将一个项目的推进拆解成多个阶段,找到关键步骤,也就知道了每个阶段的主要任务,可以进一步分工来推进项目。
如何找到关键步骤?我们可以从项目目标中提取。测试项目的目标是找到设计因素与投放点击效果的影响关系。这个目标里提取重要的关键词为:设计因素、投放点击效果、影响关系。面对这些关键词我们会有疑问产生如「测试设计因素是什么?有哪些?」、「如何投放」、「如何判断影响关系」,再进行串联梳理,得到完整步骤流程。
2. 明确角色任务与产出
每个阶段的主要任务,可以拆解分工给项目角色,项目角色也就有了各自的任务,角色产出物则是任务执行的结果,将直接推动项目进入下一环节。所以为了顺利推进项目,我们需要根据项目实际情况评估角色任务分工是否合理,产出是否满足项目要求。
比如在「设计测试图片」这个步骤中,有两个分工方案,让广告主或我们自己设计图片。我们从项目时长、沟通成本、潜在风险等维度进行评估,判断广告主把控变量不严谨会导致测试数据无效,且外部合作沟通成本较高时间不够,所以最后将设计图片的任务分给了内部。
3. 预判项目风险
对流程规划得越详细,在后续合作过程中越容易把控项目的节奏,项目风险越低。
再如「设计测试图片」这个关键步骤,详细展开执行流程会发现涉及到交互设计师、两方视觉设计师、广告主四个角色。对于项目执行角色,产出物的质量及按时交付尤为重要,所以重点把控输出准确性与完成时间。对于项目决策角色,也就是决策最终投放哪些图片的广告主,沟通配合尤为重要。所以基于规避风险也对项目流程进行了优化,前置在项目之初,要求广告主提前准备交付内容,规定交付时间及格式,以及充分沟通说明图片设计产出之后不能进行较大改动。
1. 保证数据有效
测试结论来源于测试数据,我们首先应该保证回收数据的有效性。测试项目的关键指标是点击率,即点击量与曝光量的比值。当图片本身曝光量低时,我们认为随着曝光量增加,点击率比值波动范围仍然较大,数据还未稳定在某个区间段,会影响结论准确性,判定为无效数据。发现类似这种问题,我们会和广告主商量继续投放,延长测试时间来增加曝光。
2. 充分测试
要想得到可靠普适的结论,需要对比多组样本的测试结果。对于某个设计因素,我们先进行了单一广告主的投放数据对比,可以找到投放效果最优和最差的设计水平。然后又将多个广告主的结果进行对比,会发现存在不一致的情况,验证了单组样本结论不能作为类目结论输出。如果多个广告主结果一致,或呈现某一趋势,则结论在该类目可以认为较为普适。
3. 差异分析
对于多组样本结果不一致的情况,可以从组间因素差异着手分析。
△ 背景设计因素测试结果
上图是两个广告主分别测试背景设计因素得到的结果。第一组的投放结果为实景设计效果更好,第二组则为普通平面设计效果更好。产生这样差异的原因是什么呢?我们先找出组间可能导致这一结果的因素,分别是颜色和产品。从颜色上看,是否平面+黑色效果更好呢?我们看了其他广告主结果,否定了这一猜想。再从产品图来看,两个产品的识别度是不同的,我们将其他广告主该因素测试图按产品是否容易识别分组,最终得出两组不同的结论:当产品图易识别时,背景设计对效果影响不大;当产品图不易识别时,实景图效果更好。这也就解释了上图结果不一致的原因。
回顾整个项目,我个人认为项目中最重要的工作是沟通。下面我来分别谈谈内外部沟通的经验。
与外部团队、广告主合作推进项目,需要及时有效的沟通,什么是有效的沟通呢?
1. 围绕对方利益谈配合
广告主只看转化结果,我们如果只谈设计不谈转化,广告主是不会想要出钱参与项目的,那么项目也将停滞不前。所以在合作前,我们做了项目及团队包装,用真实的案例让广告主快速理解参与项目所能带来的价值,并用团队以往的作品、能力展示让广告主了解与我们合作的优势。广告主越认同项目价值及我们的专业度,配合度就会越高。
△ 招募PPT中的真实案例介绍
2. 围绕对方目标来「推销」方案
在推进项目这一点上,找到目标一致的点更容易促成各方意见达成一致。
我们合作的广告主很多会选择外包banner设计,所以广告主们习惯了做传说中的「甲方爸爸」,难免会对视觉设计方案有各种主观意见。比如,某电视广告主不喜欢红色图片设计,想要蓝色。对于这样的分歧,我个人喜欢用引导式的沟通方法。首先,不要急于表达自己或否定对方的意见,可以以疑问句式来猜测对方的目的。「喜欢蓝色是希望用沉稳的颜色来表现产品的高级感吗?」,如果猜测正确,对方会因自己的需求被理解而更容易沟通,这时再继续阐述用红色如何表达高级感;如果猜测不正确,对方会顺势说出原因,我们再围绕对方的目标进一步沟通。如果对方毫无理由「不喜欢这个设计」,而设计师并不觉得有设计问题,那么我们需要停止专业角度的沟通,从设计是否满足当前阶段项目需求的角度来沟通,方案则更容易被接受。
内部合作分交互、视觉、用研三种不同的岗位角色,在项目中都发挥着自己重要的作用。
交互擅长统筹与拆解问题,团队的内部沟通能产生多种解题思路,对项目的推进至关重要;在遇到如需要统计学分析、样本设置等专业问题时,用研可以提供更专业的分析方法与帮助,为我们实现科学分析提供有力支持;视觉设计师对banner设计有丰富的经验,可以在各个环节中发挥优势。从项目之初就参与设计因素拆解、测试方案设置,可以补充视觉相关的设计因素,在优化测试方案、风险预期方面都可以提供重要的建议。在结论推导中,对图中的因素差异敏感度高,可以根据数据提出关键猜想。
文章来源:优设 作者:京东设计中心JDC
蓝蓝设计( www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 、平面设计服务
色彩在UI设计中的作用:加深品牌印象与品牌感、引导用户视觉凹增加易读性、区分信息交互的状态、营造氛围传递热度……
不管是做 UI 设计还是画插画,有很多同学觉得自己是因为天赋不够所以对色彩的敏感度不够,其实不然。一个可能是大家总结的太少,从来都是凭感觉和运气去配色,但配色都是有讲究的。
一个设计作品呈现到用户面前,第一眼进入眼帘的就是产品的视觉表现,而产品的色彩在其中起到了举足轻重的作用,毫无疑问色彩搭配对于设计师来说是非常重要的。那么具体到实际项目中该使用什么怎样的色彩,需要怎么做呢?
用户界面是一个设计师用理性思维解决用户感性需求的窗口。如果对色彩的运用不加以克制,界面可能会显得花哨而没有主次;但过于拘谨又容易使界面保守,难以激发用户情绪,下面以Bee express项目的实例来理性推导制定一套色彩系统。
做过设计的同学应该都知道颜色模式:RGB、CMYK、Lab 等等,这里不做过多的解释了。另外每个颜色具有一定的性格特征和表达方式,而且都会有正反两面。虽然每种色彩都有正向性格特征,但是我们在定位主体色之前一定要知道所选择色彩的负面特征对企业是否会带来负面的影响,
开始之前我们需要了解在配色过程中需要避免出现的问题,如果你经常出现下列的问题,保证你在试用期内一次性就能拿到全部薪资,emmm……
虽然设计是相通的,但是在不同的设计领域进行配色时,依然会存在巨大的区别。更换品牌的主体色,都不会是因为设计师自己的决定,而是公司在商业策略上优先做出了调整,然后通过品牌视觉上的变更将这个信息传递给消费者。
Bee Express快递、速递柜业务为主,前期的主色及视觉形象以橙黄色为主,为了避免视觉跳跃性太大,以及后期IP形象(蜜蜂吉祥物)打造,本次品牌色彩升级在原有基础上优化了色调,以保证后期产品的易用性和延展性,并利用最科学、最适用的方式推导出辅助色,以提升应用视觉的丰富性和感官体验。
express原主色:
为了不影响原有色调前期的视觉传播,即在原有主体色的基础上调整SHB的数值,让色彩更具视觉冲击力,在色彩衬托(字体、图标)更清晰。
通过调整后的主体色也能看出,明亮清晰的主体色(品牌色)也更适合在界面中的运用,为信息传递、引导操作、品牌价值带来更大的提升。
同色系为统一的色相,使用中可以加深品牌色的感知,可以让界面更有层次,同时可以让界面保持色彩上的一致性,整体感较强,产生低对比度的和谐美感,给人协调统一的感觉。
具体是指与品牌色 H(色相)一致,通过改变 S(饱和度)与 B(明度)变化产生的色组。分别往浅色/深色方向按均匀数据增减,各产生5个坐标值。
综上能看出,使用同一色系即可完成一个项目,但是对于中大型项目来说实在是过于单调,没有太多的层次感,因此我们需要多色搭配为辅。多色的辅助颜色可设定不同的任务属性和情感表达,再搭配中性色黑白灰,能赋予更多的变化和层次。
根据主体色 H(色相)为基础,不断地递增、递减 15,在 0-360 之间可以得出 24 个颜色,也就是将 360° 色环分割为 24 份,(24份在360°色环上,每一个色相的角度为15°),最终得到下图24色。
辅助色需要满足的两个条件:
和品牌色有明显区分:避免所选辅助色感官上给用户视觉区别与品牌色差距不大,传递的调性太过一致;
不能过于突兀:根据色彩原理,互补色是最能与品牌色本色产生视觉感官对比的颜色,但可能会有些突兀。为了让颜色的辅助起到丰富画面的作用,而不是反而让整个版面显得不和谐,所以选择互补色的邻近色作为辅助色,避免直接使用互补色。
基于品牌色可衍生出 3 个辅助色:一个与品牌色传递调性有明显区分的类似色;两个互补色的邻近色。
类似色搭配:使用色相相近的颜色,页面元素不会相互冲突,更加协调有质感。
互补色搭配:选择使用互补色,最佳搭配是一种作为主色,另一种用于强调。它们有着非常强烈的对比度,用在需要特别强调某个元素时会非常有效。
每一种颜色都有自己的「感官明度」,也就是发光度。根据现有的使用场景,类似色和互补色大都用在同层级的信息展示上,而当我们将最终得到的辅助色摆放在一起之后发现,虽然我们提取出的辅助色明度色值都一致,因为颜色本身自带的感官明度属性有所区别,导致视觉上会有明显的明暗差别。需要通过发光度来进行最终的颜色校正。
校准方式:依次在辅助色上叠加一层纯黑图层,将该纯黑图层颜色模式调整为 Hue(色相),就可以通过无彩色系下的明度色值,进行对比,使色彩视觉感官保持一致(青色和蓝色属冷色调,固需加深)。
根据上面同色系的明度、纯度对比规则,对所有定义的辅助色进行明度和纯度的辅助色彩输出,最终得到辅助色色板。H(色相)一致,通过改变 S(饱和度)与 B(明度)变化产生色组。分别往浅色/深色方向按均匀数据增减,各产生5个坐标值
删除最左侧的3种同色系,因明度过低时,颜色已经非常接近于黑色,色相在肉眼上几乎已经趋于一致。最后得到基于品牌色推导出的全色系色阶色板。
配色常常从确定主色开始,根据行业类型和视觉诉求的需要,选择一种居于支配的色彩作为主调色彩,构成画面的整体色彩倾向。然后选择辅色,添加点缀色,最后按照色彩组合的原则完成设计中的需求。
虽然有了以上的配色方式,但一套标准的色彩系统还会包含中性色规范、颜色的使用规范等等。相信解决了大部分的需求,剩下的工作也难不倒大家了。毕竟以上的方式只是给大家提供了一个理性科学的方法,如果想更加优秀,还需要进一步深入地去学习色彩理论知识,多看优秀的配色作品提升审美,总之要多看、多实践和多思考。
文章来源:优设 作者:能量眼球
蓝蓝设计( www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 、平面设计服务
没有 switch 就没有复杂的代码块
switch很方便:给定一个表达式,我们可以检查它是否与一堆case子句中的其他表达式匹配。 考虑以下示例:
const name = "Juliana";
switch (name) {
case "Juliana":
console.log("She's Juliana");
break;
case "Tom":
console.log("She's not Juliana");
break;
}
当 name 为“Juliana”时,我们将打印一条消息,并立即中断退出该块。 在switch函数内部时,直接在 case 块使用 return,就可以省略break。
当没有匹配项时,可以使用 default 选项:
const name = "Kris";
switch (name) {
case "Juliana":
console.log("She's Juliana");
break;
case "Tom":
console.log("She's not Juliana");
break;
default:
console.log("Sorry, no match");
}
switch在 Redux reducers 中也大量使用(尽管Redux Toolkit简化了样板),以避免产生大量的if。 考虑以下示例:
const LOGIN_SUCCESS = "LOGIN_SUCCESS";
const LOGIN_FAILED = "LOGIN_FAILED";
const authState = {
token: "",
error: "",
};
function authReducer(state = authState, action) {
switch (action.type) {
case LOGIN_SUCCESS:
return { ...state, token: action.payload };
case LOGIN_FAILED:
return { ...state, error: action.payload };
default:
return state;
}
}
这有什么问题吗?几乎没有。但是有没有更好的选择呢?
从 Python 获得的启示
来自 Telmo 的这条 Tweet引起了我的注意。 他展示了两种“switch”风格,其中一种非常接近Python中的模式。
Python 没有开关,它给我们一个更好的替代方法。 首先让我们将代码从 JavaScript 移植到Python:
LOGIN_SUCCESS = "LOGIN_SUCCESS"
LOGIN_FAILED = "LOGIN_FAILED"
auth_state = {"token": "", "error": ""}
def auth_reducer(state=auth_state, action={}):
mapping = {
LOGIN_SUCCESS: {**state, "token": action["payload"]},
LOGIN_FAILED: {**state, "error": action["payload"]},
}
return mapping.get(action["type"], state)
在 Python 中,我们可以使用字典来模拟switch 。 dict.get() 可以用来表示 switch 的 default 语句。
当访问不存在的key时,Python 会触发一个 KeyError 错误:
>>> my_dict = {
"name": "John",
"city": "Rome",
"age": 44
}
>>> my_dict["not_here"]
# Output: KeyError: 'not_here'
.get()方法是一种更安全方法,因为它不会引发错误,并且可以为不存在的key指定默认值:
>>> my_dict = {
"name": "John",
"city": "Rome",
"age": 44
}
>>> my_dict.get("not_here", "not found")
# Output: 'not found'
因此,Pytho n中的这一行:
return mapping.get(action["type"], state)
等价于 JavaScript中的:
function authReducer(state = authState, action) {
...
default:
return state;
...
}
使用字典的方式替换 switch
再次思考前面的示例:
const LOGIN_SUCCESS = "LOGIN_SUCCESS";
const LOGIN_FAILED = "LOGIN_FAILED";
const authState = {
token: "",
error: "",
};
function authReducer(state = authState, action) {
switch (action.type) {
case LOGIN_SUCCESS:
return { ...state, token: action.payload };
case LOGIN_FAILED:
return { ...state, error: action.payload };
default:
return state;
}
}
如果不使用 switch 我们可以这样做:
function authReducer(state = authState, action) {
const mapping = {
[LOGIN_SUCCESS]: { ...state, token: action.payload },
[LOGIN_FAILED]: { ...state, error: action.payload }
};
return mapping[action.type] || state;
}
这里我们使用 ES6 中的计算属性,此处,mapping的属性是根据两个常量即时计算的:LOGIN_SUCCESS 和 LOGIN_FAILED。
属性对应的值,我们这里使用的是对象解构,这里 ES9((ECMAScript 2018)) 出来的。
const mapping = {
[LOGIN_SUCCESS]: { ...state, token: action.payload },
[LOGIN_FAILED]: { ...state, error: action.payload }
}
你如何看待这种方法?它对 switch 来说可能还能一些限制,但对于 reducer 来说可能是一种更好的方案。
但是,此代码的性能如何?
性能怎么样?
switch 的性能优于字典的写法。我们可以使用下面的事例测试一下:
console.time("sample");
for (let i = 0; i < 2000000; i++) {
const nextState = authReducer(authState, {
type: LOGIN_SUCCESS,
payload: "some_token"
});
}
console.timeEnd("sample");
测量它们十次左右,
for t in {1..10}; do node switch.js >> switch.txt;done
for t in {1..10}; do node map.js >> map.txt;done
clipboard.png
人才们的 【三连】 就是小智不断分享的最大动力,如果本篇博客有任何错误和建议,欢迎人才们留言,最后,谢谢大家的观看。
原文:https://codeburst.io/alternat...
代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug。
蓝蓝设计( www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 、平面设计服务
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。Vuex 也集成到 Vue 的官方调试工具 devtools extension,提供了诸如零配置的 time-travel 调试、状态快照导入导出等高级调试功能。采用了全局单例模式,将组件的共享状态抽离出来管理,使得组件树中每一个位置都可以获取共享的状态或者触发行为。
那么什么是状态呢?我把状态理解为在没有使用vuex时,在当前组件中data内需要共用的数据为状态。
vuex使得状态或行为成为了共享的状态,所共享的状态或行为可以在各个组件中都可以访问到,省去了子父或子子之间传递变量,提高了开发效率。
当我们不使用vuex时,对于组件之间传递信息会较为麻烦。
App.vue文件中:
<template>
<div id="app">
<Fruits :fruitList="fruitList"/>
</div>
</template>
<script> import Goods from './components/Goods'; export default { name: 'App',
components:{
Fruits,
Goods
}, data(){
return{ goodList:[
{
name:'doll',
price:12 },
{ name:'glass',
price:10 }
],
}
}
}
</script>
<style>
</style>
Good.vue文件中:
<template>
<div class="hello">
<ul>
<li v-for="(good,index) in goodList" :key="index"> name:{{good.name}} number: {{good.number}} {{index}}
</li>
</ul>
</div>
</template>
<script> export default { props:['goodList'],
}
</script>
<style>
</style>
首先先创建一个js文件作为两兄弟之间传输的纽扣,这里起名为msg.js
//创建并暴露vue import Vue from 'vue';
export default new Vue
兄弟组件Goods:
<template>
<div>
<button @click="deliver">点击</button>
</div>
</template>
<script> import MSG from '../msg';
export default {
data(){ return{
msg:'hahah' }
},
methods:{
deliver() {
MSG.$emit('showMsg',this.msg)
}
}
}
</script>
<style>
</style>
兄弟组件Fruits:
<template>
<div>
<div class="fruit">
{{text}}
</div>
</div>
</template>
<script> import MSG from '../msg';
export default {
data(){ return{
text:'' }
},
created(){ this.getMsg()
},
methods:{
getMsg(){
MSG.$on('showMsg',(message)=>{ this.text = message
})
}
}
}
</script>
<style>
</style>
在App组件中的代码:
点击按钮:
蓝蓝设计( www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 、平面设计服务
一套 UI 界面当中,核心的 APP 图标是用户每天都要接触、经常使用的视觉组件和交互对象。设计图标的时候,要用到大家最熟悉的元素才能贴合用户认知,要醒目、统一,也要足够「隐形」,避免喧宾夺主。今天这篇文章,来自著名的 Tubik Studio 团队,他们为华为旗下的 EMUI 10 系统设计了核心的图标系统,来看看他们的设计过程吧。
我们总不会低估一个操作系统基础图标,对于产品的可用性和合意性的影响。基础图标虽然小巧,但是对于整个操作系统而言,总是极具影响力的,因为他们是用户界面的核心元素,帮助用户快速直观地在系统中定位、浏览、导航。但是,对于设计师而言,图标的设计始终是挑战,它看起来最为简单,但实则为最为艰难的工作。
图标需要,让人一目了然,但是也要具备良好的可识别性,在传统和创新之间达到平衡。这一次,Tubik Studio 设计团队将要给华为的 EMUI 10 来设计图标,而这篇文章将会为你揭示背后的设计过程。
这次的项目主要是由 Sergii Valiukh 、Arthur Avakyan 和 Polina Taran 来负责。这次的设计项目从最初的探索构思入手,逐渐开始绘制草图,探索样式,一直到最后打磨,完成设计。
为华为 EMUI 10 系统的用户界面设计基础的图标
我们在 2019 年夏季,收到了来自华为的邀约,这次的项目要重新设计 EMUI 这套基于 Android 系统的用户界面基础图标,这套图标会用来适配华为旗下的旗舰手机,这些图标将会随着新版的系统部署到这些手机产品当中。我们的任务,是创建总计 54 款符合当下潮流趋势的图标,这些图标要能够贴合品牌和已有用户的偏好,并且能够吸引新的用户。
这个图标设计项目如今已经在当下的华为旗下手机产品中应用且部署好了,最早使用这套图标的智能手机型号为 Mate 30 ,紧接其后的是 P40。
在整个操作系统中,这些图标是始终位于用户视野以内、最基础的最核心的交互元素,通常用户每天都会同这些核心的基础 APP 进行交互,有时候一天多达几十次,因此它们应当具备良好的功能性,还应该足够美观,给用户带来满足感。同时,这套图标的设计,也应当足够统一,以协调的体验切入到整个 EMUI 的设计系统当中,贴合整体的样式特征。
所以,我们使用了一整套图标网格系统,应对不同需求,在设计的过程中,这套网格有助于确保所有图标外部尺寸的一致性,也保证了内部元素的统一性。
为了发掘全新的视角,我们决定从传统的手绘图标开始,寻找重新塑造图标设计的方法。这些图标所涉及到的元素,早已为全球数百万用户所熟知,要重新设计图标外观,并且还要成套且统一,这本身就是一个巨大的挑战。
比如「电话」图标所对应的听筒元素、「信息」图标所对应的气泡对话框这样的元素,是不可能完全抛弃重新创造的,所以我们的真正的切入点是在形态和色彩上寻求解决方案。
越是简单的东西,重塑起来就越难。
在实际的设计过程中,我们尝试了数以百计的造型变体,从完全放飞、非常规的的外部造型,到极其常规,大家熟知的造型解决方案,我们都逐一试过。而在色彩上的尝试相对会显得更加具有实验性:我们尝试使用明亮的紫罗兰色、栗色和浅绿色来进行混搭。
当然,我们很清楚,这样的实验性的工作是探索过程中的一小步,但是它是必须的,是创造新设计的种子,是寻求正确答案的必经之路。
在设计过程在,有一件有趣的事情发生在设计「相机」图标的过程中。我们尝试过很多不同的图标造型,不同的元素,不同的样式,但是其中始终有一个细节是不变的,那就是右上角的小红点。这是为了暗示用户,华为的摄像头模组来自徕卡,这个红色的小点就是向其致敬的标识。
而下面的概念设计,则是强化了图标之间的几何轮廓的差异,从而提升图标在智能手机屏幕上的对比度和识别度。
下一步,我们基于几何图形外观差异性,设计了多种造型不同但同样优雅的图标。在基于这种风格概念进行延伸的过程中,我们会优先考虑圆形的元素。而「天气」图标明显既不适合圆形也不适合方形来呈现,所以我们使用的是云和太阳两种元素的组合。「钱包」图标使用的是矩形,然后搭配卡片的组合。尽管造型整体上是相对自由的,但是所有的图标都是遵循图标网格,并且在视觉权重上尽可能统一。
在色彩和样式上,我们则更加倾向于渐变。没有色彩渐变,纯扁平的图标显得过于幼稚和「古早」,没有足够的品质感,所以,我们在新的图标设计上,开始加入渐变色彩,提升质感。
不过,在最终版本当中,我们还是保留了图标外部的圆角矩形的外轮廓,然后将图标元素的内径进行了适当地缩减,渐变和核心的简约几何特征依然是整套设计的最高优先级。
这套设计方案展现了在整套 UI 界面中,图标这个小元素的设计上所需要投入的精力和潜在的难度。想要图标足够协调、美观、易用还要贴合品牌特征、还要区别于以往,是一件相当不容易的事情。
细节里藏着魔鬼,任何细小的元素、线条轮廓、色彩的变化都可能会在屏幕上放大、被感知到。
文章来源:优设 作者:Tubik Studio蓝蓝设计( www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 、平面设计服务
作为公民,我们在用我们擅长的设计去服务社会,在这个过程中形成自己的积淀。这是不可用金钱来衡量的价值。
作者:蓝蓝
蓝蓝设计( www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 、平面设计服务
It is ultra experience
ISUX Design Trend Report
——————————
身为用户体验设计师,无时无刻不被世界上的新事物冲刷着认知——互联网红利下降带来变化莫测的商业动向、循着摩尔定律野蛮生长日新月异的新技术、各类亚文化群体催生出多元复杂的圈层文化、脑洞口味越来越独特的年轻人,甚至眼下席卷全球的黑天鹅事件......
任何一个新事物的悄悄冒头,都有可能在未知的将来影响着用户体验设计师。我们能做的是,在起初感受到微微震幅时,便沿着震感逐步寻找源头,并思考未来的发展走向。赶在变化降临前先拥抱变化。
本文通过研究近一两年科技、社会文化以及自身用户体验领域的变化,从用户体验领域关键的用户、媒介(设备与应用)、交互行为、信息与场景的五个角度出发,探索用户体验设计未来的趋势,希望能带来启发。
随着人工时代到来,过去机械的单向交互方式逐渐被打破,机器渐渐演化成了会主动“观察”真实场景,“感受”用户情感,预判用户意图并自动完成任务的贴心小棉袄。机器如何为人们提供更智能便捷的服务,未来还有非常大的想象空间。
随着人工时代到来,过去机械的单向交互方式逐渐被打破,机器渐渐演化成了会主动“观察”真实场景,“感受”用户情感,预判用户意图并自动完成任务的贴心小棉袄。机器如何为人们提供更智能便捷的服务,未来还有非常大的想象空间。
随着AI技术的发展,智能设备可以越来越无缝地将数字世界和物理世界嫁接起来,主动感知用户所处情境并智能提供相应服务。
在2019的 Google I/O 大会上,Google Lens 展示的AR点菜功能可以智能识别用户扫描的菜单并将美食网站上的相关推荐直接呈现在屏幕上。
当用 Google Lens 识别到小票信息时,可快速提取小票上的金额,且可自动弹起计算器快速帮助用户计算人均消费,节省人工计算的时间成本。
随着信息入口从数字空间延伸到周围的物理空间中,未来万物皆可为用户体验的媒介,设计师未来在设计的时候需要注意:
寻找适合的打通真实世界的切入点:在陌生语言、信息复杂或者难以处理等苛刻的环境下,充分发挥智能设备对信息智能读取、批量识别与翻译等强大能力,帮助用户完成任务;
将用户旅程的上下游串联:根据生活常识和经验预判用户行为目的,前置推荐服务;
更加系统细心地考量干扰因素:真实场景是动态变化的,需要更全方位考虑光线的强弱、多源的噪音、实体的可视性、人员和事件的打断等因素。
为了完成一项任务,用户往往需要借助多个应用来回切换配合,使用起来琐碎麻烦。如今应用越做越强大也越复杂,过去仅仅解决单一场景的解决方案不再适应于用户对于完成任务的诉求。
Google Assistant 的新能力 Duplex on the web 可以通过自动跨应用任务处理来简化用户旅程。只需要用户发出语音指令“预定一辆去某地的车”,助手便可自动跨邮件、日历、付款等应用调取信息、自动根据使用习惯做选择,并自动填写信息,而用户全程需要的只是在关键节点轻敲“确认”即可。
2019年随着 iOS 13 的更新,“快捷指令”推出了“自动化”能力,用户通过“if...then...”语法便可为自己的App设计一套程序,实现如:“当我回到公司时提醒我打卡”、“每天早上10点给我的女朋友发送一条表白短信”等能力,将不相关的场景动作串联字一起自动化执行,大大节省人工操作成本。
提升使用效率是用户体验设计孜孜不倦努力的方向之一。在利用新技术进一步简化用户旅程时,设计师可以充分利用以下因素:
借助语音输入:比起界面触控操作,语音交互的直达性可以“穿透”复杂界面,让设备第一时间明确用户目标;
基于用户行为形成习惯记忆:对用户长期重复的行为做分析处理,构建用户习惯模型并主动提供服务;
适当考虑专家级用户:随着部分用户的智能设备使用水平越来越高,可以考虑为专家用户提供自定义操作脚本,满足其自身的独特需求。
随着人脸识别、表情识别、肢体跟踪等技术的提升,机器逐渐学会感性语言,主动感知用户内在情感和心理需求。
2019年1月的CES展上起亚亮相的互动式“情感驾驶空间”技术,可通过传感器读取用户的面部表情、心率等反应,调整驾驶空间内的灯光、影片类型、音乐风格等,舒缓舱内乘客心情,由此提供更人性化的出行体验。
用户总是会期待更贴心的服务,设计师未来对同理心的情感嗅觉更加敏锐:
利用感性线索定位用户情绪:需要通过面部表情、特殊时间节点或者识别到的关键词,对用户情绪进行理解和定位,判断用户情感理解用户内心诉求是自由探索、趣味娱乐、或者静谧修行并提供符合用户当下心境的服务。
综合使用感性元素进行设计:通过使用线条、色彩、声音和动作等传达并唤起相对应的情感,提供更加人性化的体验。
更智能的服务提供方式会让人们生活拥有更多可能性,但一旦火候把握不得当,可能就会造成对人们生活的野蛮入侵。关于如何让科技更好造福于人们,早在上个世纪,施乐帕克研究中心提出了宁静技术(Calm Technology)的愿景,认为影响最深远的技术应该是隐匿不见的,它们如纤维般融入日常生活,丝丝入扣,直至不可分辨。
随着科技的发展,设计师对新技术不应是不加克制地应用,而应该润物细无声般地提供服务,帮助人们从繁杂喧嚣的数字世界中解脱出来,将宝贵的注意力资源投放在让生活更美好的事物上。
回顾人类和机器的交流语言,从命令行界面、图形用户界面到自然用户界面,人机交互方式越来越贴合人与人之间更自然的交流方式,其背后是心智模型与实现模型的高度拟合的趋势。
在自然用户界面中,为满足新形态智能硬件对新接口的需求,以及人们对更丰富强大的交互方式的自然诉求,越来越多的自然用户界面被开发出来。语音交互和隔空手势交互便是近几年迅速发展并落地的两种交互方式。
为了让机器更好地读懂用户的身体语言,能够感知深度信息的摄像头走进了日常手机。2019年国内外手机厂商的发布大会上,LG 手机 G8 ThinQ 以及华为发布 Mate 30 系列推出的隔空手势,可实现一些简单的诸如滑动、切歌、截屏等效果。
除此以外,隔空手势支持更加细微的手势,如旋转、揉搓等,可以更直观、更灵活的方式操控界面,让用户获得一种像魔术师用意念控制事物运作的快感。
对于隔空手势操作网上的言论褒贬不一,其中争议性最大的就是隔空手势宛如“杀鸡用牛刀”,明明可以用更加精准的手势触控,为什么还要用看似很酷炫其实精准度更低的隔空手势操作?
隔空手势并不是要替代触控手势成为主流的人机交互方式,更多是对情境式障碍场景的补充。在某些场景下,用户使用设备的条件可能是充满干扰的。想想看当你边看手机食谱边炒菜的时候、边煲剧边剥小龙虾的时候、疫情期间出门佩戴橡胶手套无法正常触控手机屏幕时.....隔空手势是不是特别好用?
每个人在特殊的场景下都有可能面临感官障碍,未来的设计也应该更多地考虑情境式障碍的场景,让用户无论身处何时何地依旧能一如既往无障碍地使用设备。
语音交互作为更趋近于人与人之间最自然的交流方式,近些年有许多发展的突破点。
在发展主线上,语音交互趋向更自然、更人性化、更个性化。过去反人类的一些沟通方式慢慢被“调教”。此外,多人会话场景下的技术方案日渐增多。
2019的 Google I/O 大会展示了一个视频片段,视频中的两位嘉宾相继吐槽,经常出现针锋相对难以听清的时候,这时用户可以调节音源音量选择性增强自己关注的人物声音,让另一个人“静音”。
滑动选择音源
此外,语音交互除了在智能音箱领域广泛应用以外,也逐渐应用在广告等更多的传播媒介中,刷新人们日常使用体验。2020年2月索尼提交了一项广告播放新专利。当用户在观看电视节目时,如果出现广告,只要站起来大喊广告中对应品牌的名字,便可直接跳过这个广告。
设计师在语音交互场景下,需要留意以下几个比较容易被忽视的因素:
用户语音交互习惯培养:如今还处于培养用户语音交互使用习惯阶段,设计师需要更多地考虑应用的语音交互规则如何才能更趋近于人们日常的沟通习惯,并进一步为人们的社会习俗所接纳。
真实场景下的多人音源:在现实情境中, 在多人对话场景下将面临音源不清、穿插停顿、噪音过多等影响体验的情况,由于计算机听觉分析能力开始从单人音源拓宽到了多人音源,多人对话解决方案上还有很大想象空间。
改变传统的视听体验:在使用场景上,语音交互接口也将逐渐运用到更多的媒介上,更全面地刷新用户体验。
人类拥有双手、眼睛、耳朵和发声的嘴巴,但是并不总是在每个使用场景下都能自如地使用:在安静的自习室下声音收到限制,在驾驶场景下注意力受到限制,在双手拎着东西场景下双手受到限制......但目前许多产品设计都建立在用户能完整使用感官功能这一理想化的基础上。
未来的发展趋势倾向于将视、听、触、嗅等多通道信息完美整合起来,综合使用多种输入通道和输出通道,根据用户使用场景用最恰当的方式传递服务,满足用户多方位的需求。
尽管乔布斯曾断言3.5英寸是手机的黄金尺寸,但作为人们日常内容消费与娱乐的窗口,手机屏幕毫无疑问地变得越来越大,甚至超出传统物理限制。人们对大屏享受的追求与设备携带便捷性之间的矛盾由来已久,硬件形态的变化对旧有的用户体验设计思路带来的新的挑战。
屏幕横纵比越来越大,而人类的手部具有先天限制,曾经惯用的界面布局方式在高横纵比的屏幕上可能无法被大拇指无障碍全覆盖,使得越来越多的设计更加重视利用移动屏幕下半部分。
操作与信息进一步下移:
高德地图、苹果地图的搜索框下移,方便单手操作用户快速激活输入框;
影视资讯平台IMDB强化底部标签栏功能,双击“搜索”tab即可激活输入框,无须艰难地触摸顶部。
即时战斗类手游皇室战争的说明卡片主要展示在下半部分,方便用户进行卡片上的相关操作。
底部导航被赋予更多能力:
Pocket的底部标签栏现在兼任汉堡菜单功能,在激活状态下再次点击主页icon可选择主页上须展示的内容。
利用下滑手势代替点击:
Snapchat的许多表示前后进退关系的页面都不是”返回“按钮,而是向下箭头,用户可下滑退出当前页面。
为了解决设备形态和人类手部先天限制之间的矛盾,折叠屏诞生浏览并颠覆旧有的界面设计方式。
更灵活的信息布局
过去在单屏设计下,考虑到用户注意力由上到下纵向衰减,因此信息布局更多是按照优先级从上往下排序。而折叠屏中,屏幕展开后便可以开辟出更大的可利用空间,将次级页面或者较为重要的内容曝光在第二屏,对信息的布局将带来全新的变化。设计师为保证大小屏下顺畅的阅读体验,需要对信息模块在不同空间布局下的流动性有更强的把控能力。
更便捷的多任务操作
在过去的单屏体验中,用户只能将注意力完全集中在当前的界面中,一次只做一件事。但在实际生活中,用户面临的情景往往是主线任务和支线任务的频繁交错,并且根据会任务不同的性质自由调动自己的注意力重心,如边看视频边聊天、边看直播边逛街等等。在折叠屏中,设计师可以探索更多主线和支线交错进行的场景,利用折叠屏带来的更大的屏幕空间,可以让用户在不离开主线场景的基础上进行支线任务的处理,大大节约了在不同App上来回切换的操作成本。
更直观的拖拽交互
此外,随着多任务处理越来越广泛使用,拖拽交互将成为重要的交互模式之一。文本、表情包、图片、视频等交互对象,不再需要经过复杂的分享转发流程才能在不同App中流转,通过拖拽的方式可以更直观地进行交互。
双面屏互动玩法
外折叠屏在折叠状态下可转为双面屏,等于是给用户增加多一个观看视角。例如华为 Mate X 的镜像拍摄可以让被拍摄者即时获知自己的镜头影像是否满意,这一拍女友神器有望成为直男拍摄终结者。在未来更多的多人观看和互动玩法将被开拓出来。
华为Mate X 的镜像拍摄
未来随着5G通讯技术的成长,越来越多的设备可以同时加入物联网,人们的生活将被各种智能设备围绕,设计师需要参与更多屏幕外的设计,让不同设备串联在一起协同合作,让用户能更加自在地享受科技的便利。
席卷全球的新冠疫情让数十亿用户乖乖待在家里。过去需要花费大量精力去教育的用户使用习惯因为疫情纷纷转变。云购物、云蹦迪、云赏樱、云监工......人们足不出户便可还原许多线下场景。随着用户线上和线下生活的界限进一步模糊,用户对于应用的效率和情感诉求也发生了变化。
疫情让远程办公学习需求剧增,多人协作场景越来越频繁,许多企业随之升级了电话、视频会议、文档制作等多人协作效率软件。过去仅仅考虑少人场景协作的方式不适用,设计师需要比以往更多地考虑多人协作场景下,如何对海量密集的信息进行分析处理和展示。
在学习方式上,由于线下学习转移至线上,学生群体对于娱乐向软件也有了效率诉求。为了顺应用户诉求变化,2020年5月QQ推出学习模式,屏蔽娱乐性的内容推送,让学生更专注在学习上。
除了效率诉求急剧提升以外,随着长时间的线上学习与办公所产生社交疏离感和缺失感,人们对于线上学习工作的情感化诉求也进一步增强。
2020年推出的plagi远程办公软件支持设置每个人的avartar形象,让大家在远程办公时依旧能时刻感受到彼此的存在。在完成任务时还可以放鞭炮庆祝,让员工能感受到亲密无间的线上办公体验。
设计师需要更加关注如何让线上生活进一步与现实生活圈和时间线接轨,通过拓展真实社交下的更多伴生行为让线上也能还原线下的真实场景细节和互动体验,以弥补用户对真实社交的缺失感。
疫情的发生加速了人与信息之间的连接。人们越来越习惯将自身的身体资料、心情状态等信息沉淀在智能设备上。
为了做好广大市民群众的健康监测服务,辅助疫情防控工作,微信和支付宝在2020年年初都上线了健康码服务,不同颜色的健康码代表人们不同的健康情况,市民出入特定场所都需初始健康码。
随着人的数据化越来越深入,个人身份信息的线上化在各平台上将成为更加通用的能力。设计师需要考虑如何更自然更低成本地将线下动态变化的资料信息线上化,更有效地对用户信息进行加工处理,以及记忆用户的使用习惯和行为,以便帮助用户更地完成任务。
疫情的出现加速了线下生活线上化,短短时间内我们看到日常习以为常的应用为响应疫情下的特殊需求纷纷出现改造,钉钉、QQ群被改造成上网课、批改作业的地方,医疗卫生公众号开辟了实时疫情播报与辟谣通道,无接触设计和服务需求异常突出......这也启发了设计师需要保持对突发事件的敏感力以及应急能力,在日常生活中留心思考,为日后突发事件提供充足的场景支撑。
在汹涌的资本语境下,互联网设计师裹挟在商业驱动的结果导向中狂奔,对设计的伦理和责任鲜有发声,但伴随着互联网红利退潮,发展放缓,狂奔之下的人本问题也逐渐浮出水面。在大趋势下,UX设计师需要培养自身设计对伦理和责任的敏感度,在满足商业目的外,重拾节操,为多群体,为大社会设计,更加注重“以人为本”。
包容性设计师指在做设计产品的时候,考虑到各类用户的诉求,输出具有包容性的设计方案。包容性设计依旧是2020年设计主题之一,伴随着互联网产品全球化,在通用性和包容性上也提出了新的要求。
为身障人士设计
三星在2019年针对东南亚市场推出了一款让聋盲人士和健全人实时交流的app:Good Vibes,盲聋人轻击屏幕输入摩斯电码,预先连线好的另一台手机就会显示从盲聋人发来的短信。健全人用普通的文字输入回复,在盲聋人这一端就会翻译成摩斯电码、以手机振动的方式读出短信内容。
GOOD VIBES宣传视频
饿了么:在饿了么送货骑手中,约8%受色盲色弱的困扰(全国男性群体中红绿色盲色弱占比达8%-9%,饿了么骑手男性占比90%),为此饿了么设计团队在2019年对app的进行了重新设计,包括使用WCAG无障碍色彩对比度,以及无障碍色盘,以及调整字阶,使用辅助图形等设计手段来解决部分骑手在送货途中使用APP的痛点问题。
饿了么UED:《为骑士创造平等 — 配送 App 的包容性设计》
跨年龄段设计
谷歌助手礼貌功能 ( Google Pretty Please ) :开启谷歌助手礼貌功能后,如果使用者在下达指令的语句中包括“Please”,谷歌助手会对礼貌的请求表示感谢,以此培养孩子的礼貌言行。
Google Pretty Please功能宣传
Swift Playground:当10后小学生VITA君的编程课被“可敬的发量”刷满弹幕时,Swift playgrounds功不可没,这款为儿童新手学习编程的软件,用趣味的游戏方式为4岁以上低龄用户提供了一个学习编程的低门槛平台。
为性别平等而设计
苹果emoji:回看历年苹果emoji的更新,从肤色平等,到性别、性向平等,再到为残疾人设计,2020年再为跨性别者增加新表情,性别平等依旧是包容性设计中重要一环。
Airbnb插画:爱彼迎在插画系统中,也为不同肤色,不同职业,不同性别,以及身障人士进行了人物的绘制。
2019是互联网科技隐私问题沉浮的一年,国外有Facebook因泄露隐私收到史上最大罚单,国内则打响了“人脸识别第一案”。笼罩在隐私信任危机下,个人信息和数据立法突飞猛进,美国推动《加州消费者隐私法案》,国内也将在2020年出台《个人信息保护法》和《数据安全法》。
MIUI12推出隐匿面具功能
Android开放生态导致的权限隐私问题一直被用户所诟病,某些APP存在用户不授权就无法使用情况,针对这一情况,MIUI12推出了隐匿面具功能。当用户在开启某些APP要求授权权限时,可以选择空白通行证进行授权,从而保护用户真实信息。
在MIUI12的更新中,还推出了照明弹、拦截网两项隐私保护功能
iOS 14剪贴板提醒
在iOS 14的更新中,保护用户隐私方面进一步升级。
其中剪贴板提醒设计很贴心,当用户打开应用,如果该应用读取了你剪贴板的内容,会在系统顶部弹出提示,用户能在第一时间意识到剪贴板内容被读取,帮助用户更好的保护自己的隐私内容。
科技的发展是一把双刃剑,互联网产品的发展给用户带来便捷和沉浸体验的同时,也使得用户沉溺于科技所带来的惰性和投食之下,逐渐丧失了对真实生活的把控权,被科技绑架。
数字福祉(digital wellbeing)近年被频频提起,指科技产品需要权衡好数码产品和真实生活之间的平衡,防止数码产品过渡分散用户的注意力而影响生活质量。
Android Q 专注模式 Google Android Q Focus Mode
Android Q的更新加入了专注模式,用户在专注模式下,可以在系统层面快捷地关闭使你分心的应用,让你聚焦于更重要的事情。
防沉迷系统升级
推荐技术的进步,产品体验的升级,给用户带来了更合胃口的菜式和沉浸体验,但同时也被冠上了“电子海洛因”的称号。游戏或者内容产品的防沉迷系统依旧会是数字福祉下不可避免的趋势。
王者荣耀在2020年升级防沉迷系统,对青少年的娱乐时间和点券充值的限制进行了进一步升级。承接话。B站在2019年推出青少年模式,在该模式下,使用时长和内容推荐等做了定制化处理。
2020年的UI设计趋势,一方面是对往年风格的衍变和细化,另一方面,在扁平克制的界面风格盛行后,设计师们向往更自由、更突破的视觉表达。
2019年iOS 13深色模式姗姗来迟,紧接着大厂APP相继推出此功能。在2020年,深色模式会继续普及外,也会在可视性和实现成本方面有更多细节打磨和研究。
设计趋势的发展是螺旋式上升的,在扁平化设计流行之后,对物体的拟真再一次回归设计圈,新拟态以一种对旧拟物风格的再创新,重新流行起来。
新拟物风格(Neumorphism)缘起于设计师Alexander Plyuto发布在dribbble的一组作品,以投影重新对扁平界面进行了塑造,模仿出类似浮雕的视觉效果,感受耳目一新,引起大量设计师相尽模仿。
新拟态的实用性和可落地性有待商榷,但是作为一种新的风格受到设计师拥趸,也不失为下一波风潮到来前的灵感缪斯。
WWDC2020对mac OS的更新也重新定义了新拟态设计语言,在mac OS新系统Big Sur中,图标的设计增添了轻微的渐变、投影、高光,以此来营造图标内元素之间的纵深关系。
在扁平简洁UI风格盛行之后,丰富的色彩依旧是设计趋势之一,大面积色块,碰撞配色,带来更具冲击感的视觉体验。
UI界面逐渐扁平,色块图标弱化,为突出页面重心和内容,iOS 11在界面标题上使用更大的字号,更粗的字重。近年在大标题的风格衍变下,文字在传达信息外,也开始有了装饰性作用,采用超大字体,成为页面排版美化的一部分。
大圆角的风格会继续延续,相较以往,卡片的处理圆角会更大,随之带来的是多的留白处理,结合大字号,带来更透气通透的视觉感受。
Mac OS Big Sur的界面相对旧版本采用了更大的圆角;系统图标的设计统一成圆角矩形。
UI插图的丰富体现在样式和内容上,样式上开始3D化,内容上更注重插图叙事的表达。
3D插图
3D图形往年更多运用在动态影像或运营类设计中,随着3D的普及运用,UI插图也会迎来3D化,给用户带来更立体,更新鲜的视觉感受。
讲求叙事表意
相较于往年追求形式的UI插图,新趋势下的插图更讲求功能性,每一副插图都承载一定的作用——传达功能信息或透传品牌情感;同时插图更讲求画面表意和情节,给用户叙事性的视觉体验,增进用户和产品之间的情感联系。
插图组件化
插画的流行,随之而来的是成本的水涨船高——一套系列插图为保持风格统一,往往由唯一设计师绘制,同时为兼容各类场景,设计师往往要绘制多张。
为解决插图的成本和效率,插图开始以组件化的方式进行绘制——插图设计师将插画进行拆分绘制——不同人物,不同场景,不同物件等,再通过组件化的拼接合成,使用组件的设计师可以根据需求场景自由组合,也避免了风格不统一问题。
设计师Pablo Stanley将日常绘制的插画制成一套矢量插图组件库,将人物分为:半身、全身和坐姿3大类。通过不同表情、发型和服装可自由搭配出近60万种组合。
Pablo Stanley人物插画系统
新趋势下,动画一方面回溯复古线描手绘风格,另一方面追求更三维的体验,同时帧率进一步提升,追求更流畅的视觉感受。
手绘动画
手绘插图是往年的热门,其随性自然的笔触,能给用户带来亲切的感受,在新的趋势下,动画的加入赋予手绘插图一份灵性和趣味。
3D运动
Google Material Design通过卡片投影层级和二维动画规律,赋予扁平界面Z轴的纵深感。随着3D的普及流行,新趋势下的界面,界面的运动从二维走向三维,表现出3D场景下透视感。
高帧率动画
高帧率影视从线下电影院移步到线上流媒体,手机高帧率屏幕从90Hz到120Hz逐步升级,用户对画面流畅的定义一再刷新,UI动画的帧率升级也会是新的一轮趋势。
Telegram的表情采用了高帧率动画,给用户更流畅的视觉感受。
体验的持续升级,产品的高速迭代,对UX设计师的设计师效率提出了更高的要求。的设计方式是一个永恒的趋势。
传统的文件交接方式效率低下,导致设计师之间信息不对称,最终影响产品的一致性体验。近些年在线设计协同工具发展迅速,从UI设计、 设计交付以及组件协同等环节上给设计师提供更加实时的协作体验,获得大量UX设计师的簇拥。在2019 uxtool的设计工具调研中,在线设计协同工具佼佼者figma以其协作和性能优势,大有追赶sketch之势。
随着团队对设计效率要求的提高,设计文档从本地走向云端协作是不可逆趋势。不过设计工具的迭代是需要成本的,尤其在大型设计团队,设计工具需要渡过阵痛期来完成迭代,进而提升设计效率和体验一致性。
UX的发展,从早期的静态规范到当下的动态设计系统,是为解决产品迭代增速后带来的设计效率和产品体验问题。商业驱动下的产品迭代速度有增无减,设计系统依旧会是未来几年的设计趋势之一。
这里说的设计系统不是广义上的设计系统,而是在互联网设计的发展中,对组件化设计逐步迭代升华的一套设计协作方法:
“设计系统(Design systems)是一组为了共同目标而服务的内在相互联系的设计模式和多人协同执行的方法。”(引自《Design systems》,Alla Kholmatova,C7210翻译)。
设计系统历程衍变
组件化的发展历经规范文档到UI组件,再到设计系统,形态从最初对设计一致性的指导规范,到对产品研发流程的规范,以及产品设计价值观的输出,当下的设计系统以集大成者形式影响整个产品的设计形态。
设计系统的结构见下图:
设计系统的求同存异
设计系统并非一成不变的,他是一个动态进化的系统,会根据团队性质、产品特性在内容上有所区分——比如大团队更应该大而全,小团队更倾向小而精;成熟产品的设计系统更倾向于打造完整闭环的合作流程机制,新产品的设计系统应该以小为始,快速迭代……
随着产品的垂直化,细分化,设计系统的趋势会是在趋势大同之下找到适合产品和团队自身的形态和节奏。
Material Design是一个包含了指导规范、组件,以及设计开发工具的自适应性设计系统。
它作为平台型性设计系统,更为大而全的规范了整个生态系统的设计风格,以及提供工具让研发者能快速产出符合规范的产品。
Google生态庞大繁杂,Material Design更为全面
Ant Design作为一个为to B产品提供解决方案的平台,更多从设计可用性和完整性考虑设计系统的搭建。
Ant Design通过模块化解决方案,降低冗余的生产成本,让设计者专注于更好的用户体验
QQ作为一款面向95后的2C社交产品,其设计系统Q语言从风格调性上对设计进行规范,同时给予设计师一定的自由度;也考虑到QQ内兼顾多个产品,以及界面主题样式,对基础组件的使用场景和代码进行了规范,方便设计和开发敏捷开发。
Q语言,给予产品的自由调性之外,也针对主题和基础组件进行了规范
每个产品和团队都有自身的特征,设计系统的建设也应该有的放矢,没有可照搬的标准答案,在大方向下找到适合自身的解决方案才是的可行之道,将效率最大化。
科学有效的优化迭代
组件是设计系统中的重要组成部分,但是以往静态的、孤立的协作方式使得组件的更新迭代滞后和阻塞。随着设计系统的发展,设计师组件化思维的普及,组件的更新需要更科学的方式进行管理。
Figma在2019年推出的Design System Analytics功能,组件设计师可以借此查看组件的使用情况,包括引用次数,解组次数等,并可以生成组件使用情况的曲线趋势图,以数据的形式,科学地推动组件的优化迭代。
1.选择分析的时间段;2.组件使用的次数曲线图;3.团队使用情况;4.所有组件使用情况
未来的用户体验会出现什么新趋势?人工智能等算法的发展、5G技术普及、新的智能设备形态、新的信息处理技术、新一代用户的喜好和口味......这些往后或将影响用户体验发展的走向。未来用户对体验的要求只会越来越高。
用户体验设计师需要了解更多的技术动向,但安身立命之本还是让用户真正受益:立足于用户真实使用场景,在理性价值层面上,打造可用、易用、的设计;在感性需求层上赋予情感上的愉悦性,在反思层面赋予意义价值。
文章来源:站酷 作者:百度MEUX
蓝蓝设计( www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 、平面设计服务
用户隐私安全在产品设计中是很重要的一个环节,本文从用户体验角度切入,从匿名模式、减少永久性和减少公开性三个方面展开分析。
我们先看⼀组来⾃⽪优的2019年6⽉的调研数据:70%的美国⼈认为,与5年前相⽐个⼈信息变得更不安全。尤其是⾼学历⾼收⼊群体。由此可⻅⽤户对个⼈信息数据的隐私担忧⽐以往更甚。
⽤户隐私安全很重要,涉及的范围和⻆度也很多。本次的分析从⽤户体验⻆度切⼊,涉及如下三个⽅⾯:
Incognito Mode匿名模式;
Reducing Permanence减少永久性;
Reducing Publicity减少公开性。
下图是Google系App(Google AppChromeYouTubeandGoogle Map)匿名模式切换,从交互体验上来说有如下⼏个特点:
统⼀在右上⻆;
可以便捷切换⾄匿名模式,反过来也很容易切回登陆状态;
匿名模式的状态提示,例如YouTube 在匿名模式下在界⾯底部有⼩字提示“您 当前处于匿名模式”。
匿名模式不是最近才流⾏的功能,最早提供隐匿模式的是苹果safari浏览器,早在 2005年就⽀持了匿名模式。Chrome浏览器在2008年就开始⽀持此模式。虽然由来已久,为什么到了2020年,匿名模式依然是国外互联⽹⼀⼤趋势呢?
我们看⼀组数据:
这是来⾃DuckDuckGo 2019年9⽉的调研(DuckDuckGo是美国的⼀款不记录⽤户⾏为保护⽤户隐私的搜索引擎)。样本来⾃美国、英国、德国和澳⼤利亚的成年⼈⽤户,共计3,411⼈的调研得出。各国⽤户对使⽤搜索引擎的个⼈隐私安全⾮常在意(是否搜集个⼈的数据和记录搜索⾏为)。
2020年5⽉DuckDuckGo⽇均搜索次数为6200万。对⽐看2019年11⽉底⽇均搜索次数4900万,2018年10⽉是2900万。
最近⼏年的持续活跃度⾼幅增⻓证明了不记录个⼈隐私的搜素引擎越来越受到⽤户的⻘睐。
国内,头条、UC浏览器在搜索框输⼊状态也提供了“⽆痕浏览”⼊⼝。
不仅是搜索引擎领域,保护⽤户隐私也成为Facebook最重要的战略⽅向之⼀。Facebook CEO Mark Zuckerberg在2019年 F8开发者⼤会上喊出“THE FUTURE IS PRIVATE”。2019年3⽉Mark Zuckerberg发⽂,主题就是《聚焦于保护隐私的社交⽹络》。
我们先看国外社交媒体Stories(⼩故事)产品形态的流⾏。
⼈们总是对于所分享的内容永远记录在⽹上感到担忧。Stories24⼩时消失缓解了⼈们的隐私顾虑,这让⽤户更安⼼地⾃然分享。
Stories由Snapchat⾸创,由 Facebook发扬光⼤。早在2019年4⽉,Facebook+Messenger Stories, Instagram Stories⽇活⽤户数就突破5亿。 2020年2-3⽉LinkedIn,Twitter也先后宣布将上线类似功能。
来⾃⽪优的调研报告:41%的美国⼈经历过⽹络骚扰,最常⻅的就是在社交媒体上。23%的⽤户最近经历的⽹络骚扰来⾃评论区的评论内容。27%的⽤户经历⽹络骚扰后决定不再发布任何内容。
我们以限定评论互动的公开性为例:
2020年5⽉Twitter上线了新的评论功能,可以限定谁可以回复帖⼦的功能,提供了三种选项:谁都可以评论,只有被关注者可以评论,只有被提及者可以评论三种公开度的限定。
Instagram也在测试“评论限制”新功能,批量屏蔽/限制评论。⽬前已经上线的⼀个例⼦:⽤户(评论发布者)如果发布的评论含有攻击性敏感词,发布前伴有提示,提醒评论含有攻击性敏感词是否真的要发布。
注重隐私提供仅好友可⻅/仅⾃⼰可⻅/仅作者可⻅/等多重维度的隐私设定有助于⽤户更安⼼地参与互动。
另外⼀个例⼦是付费频道会员:付费频道会员-限定频道的公开性让内容创作者减轻隐私顾虑不仅能获得⼴告收⼊,也能得到来⾃会员、会费的收⼊,形成“忠实粉丝”社区,有助于内容⽣态的社区化建设。
我们主要看YouTube的频道会员案例:
YouTube有两种会员模式。⼀种是YouTube整个平台的付费会员,去⼴告,看原创美剧影视,消费⾳乐,可下载内容的模式。第⼆种模式是Youtuber个⼈频道付费会员,吸引忠实粉丝加⼊。我想说的就是第⼆种。
为什么⼤V⽹红有意愿开通频道会员?
除了获得忠实粉丝收⼊变现的商业价值以及付费频道会员可以为忠实粉丝提供各种专属功能,背后也和⽹红⼤V对个⼈隐私顾虑有关。
⽹红⼤V在完全公开的社交⽹络上需要始终保持⾜够克制谨慎,避免引起争议。但在忠实粉丝付费频道专属会员群中,⽹红⼤V会减轻隐私顾虑,更加回归⾃我。
⽐如在频道会员中发布更多与个⼈⽣活相关的内容,表达更多不便在完全公开的社交⽹络中的想法和感受等,因为忠实粉丝通常更具包容度,更不容易引起争议。
YouTube频道会员费⽤可以从三种会费(按⽉)区间选择,⽀持多选:
低阶 Low Levels $0.99~3.99;
中阶 Medium Levels $4.99~14.99;
⾼阶 High Levels $19.99~49.99;
频道会员功能在2018年开始测试,⾯向粉丝数过10万的YouTuber开放。
以上综述,我们可以说:
1.匿名模式:
虽然匿名模式由来已久,但仍然是当前的⼀⼤基本⽤户体验设计趋势,尤其是匿名模式的切换便捷性⾮常重要。
2.减少永久性:
Stories⼩故事24⼩时消失缓解了⼈们的隐私顾虑,这让⽤户更安⼼地⾃然分享,已经成为国外社交媒体平台的必备功能,Facebook, Instagram平台的最主要、最具影响⼒的功能之⼀。
3.减少公开性:
⽤户总是对在社交媒体平台发表评论有所顾忌,限定评论的公开性能够有助于促进⽤户发帖表达,其他⽤户也可以更安⼼地参与互动。
付费频道会员可以限定频道的公开性,让内容创作者减轻隐私顾虑不仅能获得⼴告收⼊,也能得到来⾃会员会费的收⼊,形成“忠实粉丝”社区,有助于内容⽣态的社区化建设。
从UE⻆度,我们可以为频道会员提供专属身份设计例如专属徽章,专属表情等。
THE FUTURE IS PRIVATE, 注重⽤户隐私的体验设计越来越重要!
文章来源:站酷 作者:百度MEUX
蓝蓝设计( www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 、平面设计服务
凡是要知其然知其所以然
文件上传相信很多朋友都有遇到过,那或许你也遇到过当上传大文件时,上传时间较长,且经常失败的困扰,并且失败后,又得重新上传很是烦人。那我们先了解下失败的原因吧!
据我了解大概有以下原因:
服务器配置:例如在PHP中默认的文件上传大小为8M【post_max_size = 8m】,若你在一个请求体中放入8M以上的内容时,便会出现异常
请求超时:当你设置了接口的超时时间为10s,那么上传大文件时,一个接口响应时间超过10s,那么便会被Faild掉。
网络波动:这个就属于不可控因素,也是较常见的问题。
基于以上原因,聪明的人们就想到了,将文件拆分多个小文件,依次上传,不就解决以上1,2问题嘛,这便是分片上传。 网络波动这个实在不可控,也许一阵大风刮来,就断网了呢。那这样好了,既然断网无法控制,那我可以控制只上传已经上传的文件内容,不就好了,这样大大加快了重新上传的速度。所以便有了“断点续传”一说。此时,人群中有人插了一嘴,有些文件我已经上传一遍了,为啥还要在上传,能不能不浪费我流量和时间。喔...这个嘛,简单,每次上传时判断下是否存在这个文件,若存在就不重新上传便可,于是又有了“秒传”一说。从此这"三兄弟" 便自行CP,统治了整个文件界。”
注意文中的代码并非实际代码,请移步至github查看代码
https://github.com/pseudo-god...
分片上传
HTML
原生INPUT样式较丑,这里通过样式叠加的方式,放一个Button.
<div class="btns">
<el-button-group>
<el-button :disabled="changeDisabled">
<i class="el-icon-upload2 el-icon--left" size="mini"></i>选择文件
<input
v-if="!changeDisabled"
type="file"
:multiple="multiple"
class="select-file-input"
:accept="accept"
@change="handleFileChange"
/>
</el-button>
<el-button :disabled="uploadDisabled" @click="handleUpload()"><i class="el-icon-upload el-icon--left" size="mini"></i>上传</el-button>
<el-button :disabled="pauseDisabled" @click="handlePause"><i class="el-icon-video-pause el-icon--left" size="mini"></i>暂停</el-button>
<el-button :disabled="resumeDisabled" @click="handleResume"><i class="el-icon-video-play el-icon--left" size="mini"></i>恢复</el-button>
<el-button :disabled="clearDisabled" @click="clearFiles"><i class="el-icon-video-play el-icon--left" size="mini"></i>清空</el-button>
</el-button-group>
<slot
//data 数据
var chunkSize = 10 * 1024 * 1024; // 切片大小
var fileIndex = 0; // 当前正在被遍历的文件下标
data: () => ({
container: {
files: null
},
tempFilesArr: [], // 存储files信息
cancels: [], // 存储要取消的请求
tempThreads: 3,
// 默认状态
status: Status.wait
}),
一个稍微好看的UI就出来了。
选择文件
选择文件过程中,需要对外暴露出几个钩子,熟悉elementUi的同学应该很眼熟,这几个钩子基本与其一致。onExceed:文件超出个数限制时的钩子、beforeUpload:文件上传之前
fileIndex 这个很重要,因为是多文件上传,所以定位当前正在被上传的文件就很重要,基本都靠它
handleFileChange(e) {
const files = e.target.files;
if (!files) return;
Object.assign(this.$data, this.$options.data()); // 重置data所有数据
fileIndex = 0; // 重置文件下标
this.container.files = files;
// 判断文件选择的个数
if (this.limit && this.container.files.length > this.limit) {
this.onExceed && this.onExceed(files);
return;
}
// 因filelist不可编辑,故拷贝filelist 对象
var index = 0; // 所选文件的下标,主要用于剔除文件后,原文件list与临时文件list不对应的情况
for (const key in this.container.files) {
if (this.container.files.hasOwnProperty(key)) {
const file = this.container.files[key];
if (this.beforeUpload) {
const before = this.beforeUpload(file);
if (before) {
this.pushTempFile(file, index);
}
}
if (!this.beforeUpload) {
this.pushTempFile(file, index);
}
index++;
}
}
},
// 存入 tempFilesArr,为了上面的钩子,所以将代码做了拆分
pushTempFile(file, index) {
// 额外的初始值
const obj = {
status: fileStatus.wait,
chunkList: [],
uploadProgress: 0,
hashProgress: 0,
index
};
for (const k in file) {
obj[k] = file[k];
}
console.log('pushTempFile -> obj', obj);
this.tempFilesArr.push(obj);
}
分片上传
创建切片,循环分解文件即可
createFileChunk(file, size = chunkSize) {
const fileChunkList = [];
var count = 0;
while (count < file.size) {
fileChunkList.push({
file: file.slice(count, count + size)
});
count += size;
}
return fileChunkList;
}
循环创建切片,既然咱们做的是多文件,所以这里就有循环去处理,依次创建文件切片,及切片的上传。
async handleUpload(resume) {
if (!this.container.files) return;
this.status = Status.uploading;
const filesArr = this.container.files;
var tempFilesArr = this.tempFilesArr;
for (let i = 0; i < tempFilesArr.length; i++) {
fileIndex = i;
//创建切片
const fileChunkList = this.createFileChunk(
filesArr[tempFilesArr[i].index]
);
tempFilesArr[i].fileHash ='xxxx'; // 先不用看这个,后面会讲,占个位置
tempFilesArr[i].chunkList = fileChunkList.map(({ file }, index) => ({
fileHash: tempFilesArr[i].hash,
fileName: tempFilesArr[i].name,
index,
hash: tempFilesArr[i].hash + '-' + index,
chunk: file,
size: file.size,
uploaded: false,
progress: 0, // 每个块的上传进度
status: 'wait' // 上传状态,用作进度状态显示
}));
//上传切片
await this.uploadChunks(this.tempFilesArr[i]);
}
}
上传切片,这个里需要考虑的问题较多,也算是核心吧,uploadChunks方法只负责构造传递给后端的数据,核心上传功能放到sendRequest方法中
async uploadChunks(data) {
var chunkData = data.chunkList;
const requestDataList = chunkData
.map(({ fileHash, chunk, fileName, index }) => {
const formData = new FormData();
formData.append('md5', fileHash);
formData.append('file', chunk);
formData.append('fileName', index); // 文件名使用切片的下标
return { formData, index, fileName };
});
try {
await this.sendRequest(requestDataList, chunkData);
} catch (error) {
// 上传有被reject的
this.$message.error('亲 上传失败了,考虑重试下呦' + error);
return;
}
// 合并切片
const isUpload = chunkData.some(item => item.uploaded === false);
console.log('created -> isUpload', isUpload);
if (isUpload) {
alert('存在失败的切片');
} else {
// 执行合并
await this.mergeRequest(data);
}
}
sendReques。上传这是最重要的地方,也是容易失败的地方,假设有10个分片,那我们若是直接发10个请求的话,很容易达到浏览器的瓶颈,所以需要对请求进行并发处理。
并发处理:这里我使用for循环控制并发的初始并发数,然后在 handler 函数里调用自己,这样就控制了并发。在handler中,通过数组API.shift模拟队列的效果,来上传切片。
重试: retryArr 数组存储每个切片文件请求的重试次数,做累加。比如[1,0,2],就是第0个文件切片报错1次,第2个报错2次。为保证能与文件做对应,const index = formInfo.index; 我们直接从数据中拿之前定义好的index。 若失败后,将失败的请求重新加入队列即可。
关于并发及重试我写了一个小Demo,若不理解可以自己在研究下,文件地址:https://github.com/pseudo-god... , 重试代码好像被我弄丢了,大家要是有需求,我再补吧!
// 并发处理
sendRequest(forms, chunkData) {
var finished = 0;
const total = forms.length;
const that = this;
const retryArr = []; // 数组存储每个文件hash请求的重试次数,做累加 比如[1,0,2],就是第0个文件切片报错1次,第2个报错2次
return new Promise((resolve, reject) => {
const handler = () => {
if (forms.length) {
// 出栈
const formInfo = forms.shift();
const formData = formInfo.formData;
const index = formInfo.index;
instance.post('fileChunk', formData, {
onUploadProgress: that.createProgresshandler(chunkData[index]),
cancelToken: new CancelToken(c => this.cancels.push(c)),
timeout: 0
}).then(res => {
console.log('handler -> res', res);
// 更改状态
chunkData[index].uploaded = true;
chunkData[index].status = 'success';
finished++;
handler();
})
.catch(e => {
// 若暂停,则禁止重试
if (this.status === Status.pause) return;
if (typeof retryArr[index] !== 'number') {
retryArr[index] = 0;
}
// 更新状态
chunkData[index].status = 'warning';
// 累加错误次数
retryArr[index]++;
// 重试3次
if (retryArr[index] >= this.chunkRetry) {
return reject('重试失败', retryArr);
}
this.tempThreads++; // 释放当前占用的通道
// 将失败的重新加入队列
forms.push(formInfo);
handler();
});
}
if (finished >= total) {
resolve('done');
}
};
// 控制并发
for (let i = 0; i < this.tempThreads; i++) {
handler();
}
});
}
切片的上传进度,通过axios的onUploadProgress事件,结合createProgresshandler方法进行维护
// 切片上传进度
createProgresshandler(item) {
return p => {
item.progress = parseInt(String((p.loaded / p.total) * 100));
this.fileProgress();
};
}
Hash计算
其实就是算一个文件的MD5值,MD5在整个项目中用到的地方也就几点。
秒传,需要通过MD5值判断文件是否已存在。
续传:需要用到MD5作为key值,当唯一值使用。
本项目主要使用worker处理,性能及速度都会有很大提升.
由于是多文件,所以HASH的计算进度也要体现在每个文件上,所以这里使用全局变量fileIndex来定位当前正在被上传的文件
执行计算hash
正在上传文件
// 生成文件 hash(web-worker)
calculateHash(fileChunkList) {
return new Promise(resolve => {
this.container.worker = new Worker('./hash.js');
this.container.worker.postMessage({ fileChunkList });
this.container.worker.onmessage = e => {
const { percentage, hash } = e.data;
if (this.tempFilesArr[fileIndex]) {
this.tempFilesArr[fileIndex].hashProgress = Number(
percentage.toFixed(0)
);
}
if (hash) {
resolve(hash);
}
};
});
}
因使用worker,所以我们不能直接使用NPM包方式使用MD5。需要单独去下载spark-md5.js文件,并引入
//hash.js
self.importScripts("/spark-md5.min.js"); // 导入脚本
// 生成文件 hash
self.onmessage = e => {
const { fileChunkList } = e.data;
const spark = new self.SparkMD5.ArrayBuffer();
let percentage = 0;
let count = 0;
const loadNext = index => {
const reader = new FileReader();
reader.readAsArrayBuffer(fileChunkList[index].file);
reader.onload = e => {
count++;
spark.append(e.target.result);
if (count === fileChunkList.length) {
self.postMessage({
percentage: 100,
hash: spark.end()
});
self.close();
} else {
percentage += 100 / fileChunkList.length;
self.postMessage({
percentage
});
loadNext(count);
}
};
};
loadNext(0);
};
文件合并
当我们的切片全部上传完毕后,就需要进行文件的合并,这里我们只需要请求接口即可
mergeRequest(data) {
const obj = {
md5: data.fileHash,
fileName: data.name,
fileChunkNum: data.chunkList.length
};
instance.post('fileChunk/merge', obj,
{
timeout: 0
})
.then((res) => {
this.$message.success('上传成功');
});
}
Done: 至此一个分片上传的功能便已完成
断点续传
顾名思义,就是从那断的就从那开始,明确思路就很简单了。一般有2种方式,一种为服务器端返回,告知我从那开始,还有一种是浏览器端自行处理。2种方案各有优缺点。本项目使用第二种。
思路:已文件HASH为key值,每个切片上传成功后,记录下来便可。若需要续传时,直接跳过记录中已存在的便可。本项目将使用Localstorage进行存储,这里我已提前封装好addChunkStorage、getChunkStorage方法。
存储在Stroage的数据
缓存处理
在切片上传的axios成功回调中,存储已上传成功的切片
instance.post('fileChunk', formData, )
.then(res => {
// 存储已上传的切片下标
+ this.addChunkStorage(chunkData[index].fileHash, index);
handler();
})
在切片上传前,先看下localstorage中是否存在已上传的切片,并修改uploaded
async handleUpload(resume) {
+ const getChunkStorage = this.getChunkStorage(tempFilesArr[i].hash);
tempFilesArr[i].chunkList = fileChunkList.map(({ file }, index) => ({
+ uploaded: getChunkStorage && getChunkStorage.includes(index), // 标识:是否已完成上传
+ progress: getChunkStorage && getChunkStorage.includes(index) ? 100 : 0,
+ status: getChunkStorage && getChunkStorage.includes(index)? 'success'
+ : 'wait' // 上传状态,用作进度状态显示
}));
}
构造切片数据时,过滤掉uploaded为true的
async uploadChunks(data) {
var chunkData = data.chunkList;
const requestDataList = chunkData
+ .filter(({ uploaded }) => !uploaded)
.map(({ fileHash, chunk, fileName, index }) => {
const formData = new FormData();
formData.append('md5', fileHash);
formData.append('file', chunk);
formData.append('fileName', index); // 文件名使用切片的下标
return { formData, index, fileName };
})
}
垃圾文件清理
随着上传文件的增多,相应的垃圾文件也会增多,比如有些时候上传一半就不再继续,或上传失败,碎片文件就会增多。解决方案我目前想了2种
前端在localstorage设置缓存时间,超过时间就发送请求通知后端清理碎片文件,同时前端也要清理缓存。
前后端都约定好,每个缓存从生成开始,只能存储12小时,12小时后自动清理
以上2中方案似乎都有点问题,极有可能造成前后端因时间差,引发切片上传异常的问题,后面想到合适的解决方案再来更新吧。
Done: 续传到这里也就完成了。
秒传
这算是最简单的,只是听起来很厉害的样子。原理:计算整个文件的HASH,在执行上传操作前,向服务端发送请求,传递MD5值,后端进行文件检索。若服务器中已存在该文件,便不进行后续的任何操作,上传也便直接结束。大家一看就明白
async handleUpload(resume) {
if (!this.container.files) return;
const filesArr = this.container.files;
var tempFilesArr = this.tempFilesArr;
for (let i = 0; i < tempFilesArr.length; i++) {
const fileChunkList = this.createFileChunk(
filesArr[tempFilesArr[i].index]
);
// hash校验,是否为秒传
+ tempFilesArr[i].hash = await this.calculateHash(fileChunkList);
+ const verifyRes = await this.verifyUpload(
+ tempFilesArr[i].name,
+ tempFilesArr[i].hash
+ );
+ if (verifyRes.data.presence) {
+ tempFilesArr[i].status = fileStatus.secondPass;
+ tempFilesArr[i].uploadProgress = 100;
+ } else {
console.log('开始上传切片文件----》', tempFilesArr[i].name);
await this.uploadChunks(this.tempFilesArr[i]);
}
}
}
// 文件上传之前的校验: 校验文件是否已存在
verifyUpload(fileName, fileHash) {
return new Promise(resolve => {
const obj = {
md5: fileHash,
fileName,
...this.uploadArguments //传递其他参数
};
instance
.post('fileChunk/presence', obj)
.then(res => {
resolve(res.data);
})
.catch(err => {
console.log('verifyUpload -> err', err);
});
});
}
Done: 秒传到这里也就完成了。
后端处理
文章好像有点长了,具体代码逻辑就先不贴了,除非有人留言要求,嘻嘻,有时间再更新
Node版
请前往 https://github.com/pseudo-god... 查看
JAVA版
下周应该会更新处理
PHP版
1年多没写PHP了,抽空我会慢慢补上来
待完善
切片的大小:这个后面会做出动态计算的。需要根据当前所上传文件的大小,自动计算合适的切片大小。避免出现切片过多的情况。
文件追加:目前上传文件过程中,不能继续选择文件加入队列。(这个没想好应该怎么处理。)
更新记录
组件已经运行一段时间了,期间也测试出几个问题,本来以为没BUG的,看起来BUG都挺严重
BUG-1:当同时上传多个内容相同但是文件名称不同的文件时,出现上传失败的问题。
预期结果:第一个上传成功后,后面相同的问文件应该直接秒传
实际结果:第一个上传成功后,其余相同的文件都失败,错误信息,块数不对。
原因:当第一个文件块上传完毕后,便立即进行了下一个文件的循环,导致无法及时获取文件是否已秒传的状态,从而导致失败。
解决方案:在当前文件分片上传完毕并且请求合并接口完毕后,再进行下一次循环。
将子方法都改为同步方式,mergeRequest 和 uploadChunks 方法
BUG-2: 当每次选择相同的文件并触发beforeUpload方法时,若第二次也选择了相同的文件,beforeUpload方法失效,从而导致整个流程失效。
原因:之前每次选择文件时,没有清空上次所选input文件的数据,相同数据的情况下,是不会触发input的change事件。
解决方案:每次点击input时,清空数据即可。我顺带优化了下其他的代码,具体看提交记录吧。
<input
v-if="!changeDisabled"
type="file"
:multiple="multiple"
class="select-file-input"
:accept="accept"
+ οnclick="f.outerHTML=f.outerHTML"
@change="handleFileChange"/>
重写了暂停和恢复的功能,实际上,主要是增加了暂停和恢复的状态
之前的处理逻辑太简单粗暴,存在诸多问题。现在将状态定位在每一个文件之上,这样恢复上传时,直接跳过即可
封装组件
写了一大堆,其实以上代码你直接复制也无法使用,这里我将此封装了一个组件。大家可以去github下载文件,里面有使用案例 ,若有用记得随手给个star,谢谢!
偷个懒,具体封装组件的代码就不列出来了,大家直接去下载文件查看,若有不明白的,可留言。
组件文档
Attribute
参数 类型 说明 默认 备注
headers Object 设置请求头
before-upload Function 上传文件前的钩子,返回false则停止上传
accept String 接受上传的文件类型
upload-arguments Object 上传文件时携带的参数
with-credentials Boolean 是否传递Cookie false
limit Number 最大允许上传个数 0 0为不限制
on-exceed Function 文件超出个数限制时的钩子
multiple Boolean 是否为多选模式 true
base-url String 由于本组件为内置的AXIOS,若你需要走代理,可以直接在这里配置你的基础路径
chunk-size Number 每个切片的大小 10M
threads Number 请求的并发数 3 并发数越高,对服务器的性能要求越高,尽可能用默认值即可
chunk-retry Number 错误重试次数 3 分片请求的错误重试次数
Slot
方法名 说明 参数 备注
header 按钮区域 无
tip 提示说明文字 无
后端接口文档:按文档实现即可
蓝蓝设计( www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 、平面设计服务
蓝蓝设计的小编 http://www.lanlanwork.com