2014年3月3日星期一

读《瞬间之美》 - peace-lee

本邮件内容由第三方提供,如果您不想继续收到该邮件,可 点此退订
读《瞬间之美》 - peace-lee  阅读原文»

《瞬间之美》是一本Web用户体验设计的书籍,作者从自己的实战经验用小案例的方式阐述了交互设计之道, 如作者所言用户每个使用瞬间的感受,都会对整个用户提体验产生重要影响,而书中作者也从“瞬间”给出了自己一些经验之谈。

作为一个开发人员,在看书的时候还是会时不时的想这些功能怎么实现,之前做开发的时候很少从用户的角度去思考,只是接到任务就按着需求去开发了,功能实现完了之后直接提交、发布交给测试了,测试没有问题了就ok了,本应该有更多的角度来看问题。

如果提炼下这本书的内容可以用另外一本书名来概括中心“Don’t make me think”,提前为用户想周全了,作者所描述的那些小案例都是如何为了用户最好的体验,当然有了用户你的产品才有继续活下去的可能。

良好的用户体验,全在于那些完美的瞬间,该书的开篇第一句话。每一次在网站或Web应用上的发出的操作得到反馈的瞬间,都会影响到的用户对网站或应用的看法,自己也遇到过不少谈不上有用户体验的网站,用起来十分不爽,直接就没有了下次再访问的欲望了,当然也会爆粗口,估计自己开发的东西也被骂过。

下面是摘自书中的一些亮点:

1,若想完成优秀的设计,我们就需要时刻自问:在这个瞬间我们希望用户做什么,界面应该如何鼓励他们完成这个任务?(喜欢这里的原因在于,之前并没有想到过可以去诱导用户来完成想要用户完成的任务,但诱导的关键在于用户并不觉得是被诱导,要确保用户在这个瞬间自我感觉得心应手,这里是考验设计师的水平之处了)

2,古腾堡图表:古腾堡图表中,左上角是第一视觉落点区,而右下角则是最终视觉落点区,与之相对,右上角和左下角则是视觉盲点;因此,采用对角线平衡通常都是一个好设计所需要考虑的因素,因为设计师遵从了用户习惯性的眼动规律。(这里的亮点在于知道一个新的知识点,而书中作者那个小案例利用古腾堡图表和颜色一起实现的效果确实看着舒服)

3,统一风格,形成积极的印象(这个之前自己开发时候没有注意这点,后来发现改成统一风格才发现效果更好)

4,软件并不是为了我们能单击那些按钮而存在,它是为了我们能够完成任务而存在。(作者在随后所说的人类不喜欢感觉自己正在按软件的要求去做事情,喜欢在做决定的那个瞬间自己才是主人的感觉;动宾词组是动词-名词的简单组合,以动词开头以名词结尾,通过这种形式我们告诉软件希望做哪种动作,以及动作的针对对象是什么,我们是领导者,而不是被领导者)

5,小日本“环境的提示”,日本的城际铁路上,每一个站台会在列车进站后播放自己独特的音乐旋律。(未做考证,不过这个思路到是新鲜,每一个站台、独特的旋律,乘客脑海里形成一张独特的虚拟路线图,避免坐过站,不错的解决思路。)

6,在这个页面中,其实我们是在要求用户为了使用我的软件,他们必须知道一些只有我们才知道的事情。我们要求用户先揣摩我们的心思。如果用户失败了,我们还经常因此责怪他们,这难道不是很搞笑吗?(这是作者在第七章里面的一段文字,中枪了,所以摘出来了,有时候犯过这样的错误,总是觉得用户怎么那么笨呢,但是用户并没有我们作为开发者的那种思维,周鸿�把自己变成小白的能力来测试产品是值得学习的,站在用户的角度去思考下你的软件是否真的那么易懂,易操作)

7,使用视频来表述想法(书中这个小案例吸引了我,在我所浏览过的网站上似乎没有专门用视频来表述一个想法的呢,更多的是广告,吐槽下新浪博客的广告啊…也可能有我忘了,这个想法终究还是不错)

8,关于分页,遵守那些默认的行业标准,会更容易被大众接受。(Right)

9,无浏览器自测试,抛开浏览器以及站点导航的工具栏等,只依靠网站本身来引导用户。(这个点也从来没有想到过,以后浏览网站时候关注下)

10,注册,取消的那个左边常规按键,右边超链接的按钮设计的方式,我感觉不错,但是很少看见过这样的,大都是一样的两个按键,一样的样式。

11,注意每一处细节,这将会为用户创造出一个平滑顺畅的瞬间,而每一个这样的瞬间都会对整体的用户体验做出贡献。(Right)

12,多玩玩新的产品、竞品,取长补短;多关注有创意的产品,学习设计思路;(学会从别的东西中吸取利于自己的东西)

13,用诚恳、真实的声音与客户对话,将能快速增加你的吸引力。(客户至上)

14,人们并不关心技术本身,而是关心自己能得到什么。

15,失败也要优雅的离开。这个Sign out时候的这个案例,作者弄的不矫揉造作。

书中所有案例,归纳到一个点就是站在用户的角度,为用户想好了,给客户很赞的用户体验,你就有机会了。同理心,是最近常常触及的一个词汇。维基百科上“同理心”的定义:“同理心(Empathy),又叫做换位思考、神入、共情,指站在对方立场设身处地思考的一种方式,即于人际交往过程中,能够体会他人的情绪和想法、理解他人的立场和感受,并站在他人的角度思考和处理问题。主要体现在情绪自控、换位思考、倾听能力以及表达尊重等与情商相关的方面。”似乎同理心是个很高大上的词,其实就是换位思考而已,但说总是比做更省力一些。

书中角度都是站在客户至上的角度来写的,但是现实中并不都是这样的情况,总是有一些牛逼哄哄的公司或者niubility的人对用户不屑一顾,Who TM care ? 之前构思时候想在这里写搬家时候,我爱我家那“顾客至上”的“完美”服务的,但是当写到这里的时候却懒的写了,把时间花在自己喜欢的人和事上会有更大的好处。

狄更斯在双城记里面写到:It was the best of times, it was the worst of times, it was the age of wisdom, it was the age of foolishness, it was the epoch of belief, it was the epoch of incredulity, it was the season of Light, it was the season of Darkness, it was the spring of hope, it was the winter of despair. We had everything before us, we had nothing before us, we were all going direct to Heaven, we were all going direct the other way--in short.(这是最好的时代,这是最坏的时代;这是智慧的年代,这是愚蠢的年代;这是信仰的时期,这是怀疑的时期;这是光明的季节,这是黑暗的季节;这是希望之春,这是绝望之冬;我们的前途拥有一切,我们的前途一无所有;我们正走向天堂,我们也正直下地狱)

社会中存在这抱怨,存在着不满,那是上天给�丝们逆袭的一个机会;当你理解了世界的本来面目后,却依然热爱这个世界,你的生命就会是美丽的。

人在做,天在看。当你能力不足以支撑你野心的时候,请学习。


本文链接:http://www.cnblogs.com/peace-lee/p/3579730.html,转载请注明。

knockout的监控数组实现 - 司徒正美  阅读原文»

knockout应该是博客园群体中使用最广的MVVM框架,但鲜有介绍其监控数组的实现。最近试图升级avalon的监控数组,决定好好研究它一番,看有没有可借鉴之外。


ko.observableArray = function(initialValues) {
initialValues = initialValues || [];

if (typeof initialValues != 'object' || !('length' in initialValues))
throw new Error("The argument passed when initializing an observable array must be an array, or null, or undefined.");

var result = ko.observable(initialValues);
ko.utils.extend(result, ko.observableArray['fn']);
return result.extend({'trackArrayChanges': true});
};

这是knockout监控数组的工厂方法,不需要使用new关键字,直接转换一个普通数组为一个监控数组。你也可以什么也不会,得到一个空的监控数组。



var myObservableArray = ko.observableArray(); // Initially an empty array
myObservableArray.push('Some value'); // Adds the value and notifies obs

// This observable array initially contains three objects
var anotherObservableArray = ko.observableArray([
{ name: "Bungle", type: "Bear" },
{ name: "George", type: "Hippo" },
{ name: "Zippy", type: "Unknown" }
]);
console.log(typeof anotherObservableArray)//function

虽说是监控数组,但它的类型其实是一个函数。这正是knockout令人不爽的地方,将原本是字符串,数字,布尔,数组等东西都转换为函数才行使用。

这里有一个ko.utils.extend方法,比不上jQuery的同名方法,只是一个浅拷贝,将一个对象的属性循环复制到另一个之上。


extend: function(target, source) {
if (source) {
for (var prop in source) {
if (source.hasOwnProperty(prop)) {
target[prop] = source[prop];
}
}
}
return target;
},

result 是要返回的函数,它会被挂上许多方法与属性。首先是 ko.observableArray['fn']扩展包,第二个扩展其实可以简化为


result.trackArrayChanges = true

我们来看一下 ko.observableArray['fn']扩展包,其中最难的是pop,push,shift等方法的实现


ko.observableArray['fn'] = {
'remove': function(valueOrPredicate) {//值可以是原始数组或一个监控函数
var underlyingArray = this.peek();//得到原始数组
var removedValues = [];
var predicate = typeof valueOrPredicate == "function" && !ko.isObservable(valueOrPredicate) ? valueOrPredicate : function(value) {
return value === valueOrPredicate;
};//确保转换为一个函数
for (var i = 0; i < underlyingArray.length; i++) {
var value = underlyingArray;
if (predicate(value)) {
if (removedValues.length === 0) {
this.valueWillMutate();//开始变动
}
removedValues.push(value);
underlyingArray.splice(i, 1);//移除元素
i--;
}
}
if (removedValues.length) {//如果不为空,说明发生移除,就调用valueHasMutated
this.valueHasMutated();
}
return removedValues;//返回被移除的元素
},
'removeAll': function(arrayOfValues) {
// If you passed zero args, we remove everything
if (arrayOfValues === undefined) {//如果什么也不传,则清空数组
var underlyingArray = this.peek();
var allValues = underlyingArray.slice(0);
this.valueWillMutate();
underlyingArray.splice(0, underlyingArray.length);
this.valueHasMutated();
return allValues;
}
//如果是传入空字符串,null, NaN
if (!arrayOfValues)
return [];
return this['remove'](function(value) {//否则调用上面的remove方法
return ko.utils.arrayIndexOf(arrayOfValues, value) >= 0;
});
},
'destroy': function(valueOrPredicate) {//remove方法的优化版,不立即移除元素,只是标记一下
var underlyingArray = this.peek();
var predicate = typeof valueOrPredicate == "function" && !ko.isObservable(valueOrPredicate) ? valueOrPredicate : function(value) {
return value === valueOrPredicate;
};
this.valueWillMutate();
for (var i = underlyingArray.length - 1; i >= 0; i--) {
var value = underlyingArray;
if (predicate(value))
underlyingArray["_destroy"] = true;
}
this.valueHasMutated();
},
'destroyAll': function(arrayOfValues) {//removeAll方法的优化版,不立即移除元素,只是标记一下
if (arrayOfValues === undefined)//不传就全部标记为destroy
return this['destroy'](function() {
return true
});

// If you passed an arg, we interpret it as an array of entries to destroy
if (!arrayOfValues)
return [];
return this['destroy'](function(value) {
return ko.utils.arrayIndexOf(arrayOfValues, value) >= 0;
});
},
'indexOf': function(item) {//返回索引值
var underlyingArray = this();
return ko.utils.arrayIndexOf(underlyingArray, item);
},
'replace': function(oldItem, newItem) {//替换某一位置的元素
var index = this['indexOf'](oldItem);
if (index >= 0) {
this.valueWillMutate();
this.peek() = newItem;
this.valueHasMutated();
}
}
};

//添加一系列与原生数组同名的方法
ko.utils.arrayForEach(["pop", "push", "reverse", "shift", "sort", "splice", "unshift"], function(methodName) {
ko.observableArray['fn'][methodName] = function() {
var underlyingArray = this.peek();
this.valueWillMutate();
this.cacheDiffForKnownOperation(underlyingArray, methodName, arguments);
var methodCallResult = underlyingArray[methodName].apply(underlyingArray, arguments);
this.valueHasMutated();
return methodCallResult;
};
});

//返回一个真正的数组
ko.utils.arrayForEach(["slice"], function(methodName) {
ko.observableArray['fn'][methodName] = function() {
var underlyingArray = this();
return underlyingArray[methodName].apply(underlyingArray, arguments);
};
});

cacheDiffForKnownOperation 会记录如何对元素进行操作


target.cacheDiffForKnownOperation = function(rawArray, operationName, args) {
// Only run if we're currently tracking changes for this observable array
// and there aren't any pending deferred notifications.
if (!trackingChanges || pendingNotifications) {
return;
}
var diff = [],
arrayLength = rawArray.length,
argsLength = args.length,
offset = 0;

function pushDiff(status, value, index) {
return diff[diff.length] = {'status': status, 'value': value, 'index': index};
}
switch (operationName) {
case 'push':
offset = arrayLength;
case 'unshift':
for (var index = 0; index < argsLength; index++) {
pushDiff('added', args, offset + index);
}
break;

case 'pop':
offset = arrayLength - 1;
case 'shift':
if (arrayLength) {
pushDiff('deleted', rawArray[offset], offset);
}
break;

case 'splice':
// Negative start index means 'from end of array'. After that we clamp to [0...arrayLength].
// See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice
var startIndex = Math.min(Math.max(0, args[0] < 0 ? arrayLength + args[0] : args[0]), arrayLength),
endDeleteIndex = argsLength === 1 ? arrayLength : Math.min(startIndex + (args[1] || 0), arrayLength),
endAddIndex = startIndex + argsLength - 2,
endIndex = Math.max(endDeleteIndex, endAddIndex),
additions = [], deletions = [];
for (var index = startIndex, argsIndex = 2; index < endIndex; ++index, ++argsIndex) {
if (index < endDeleteIndex)
deletions.push(pushDiff('deleted', rawArray, index));
if (index < endAddIndex)
additions.push(pushDiff('added', args[argsIndex], index));
}
ko.utils.findMovesInArrayComparison(deletions, additions);
break;

default:
return;
}
cachedDiff = diff;
};
};

ko.utils.findMovesInA

阅读更多内容

没有评论:

发表评论