为什么你应该把按钮左移三个像素

为什么你应该把按钮左移三个像素

本文链接: http://www.haipi8.com/news/452
原文:Why you should move that button 3px to the left

3px

每次当产品即将发布的时候,我就变成了完美主义者,每个元素的错位或蹩脚的交互都让我芒刺在背。 每次都会有十几个细节实现上的小错误,它们就像在嘲讽我,觉得一切都不对劲。

但对于团队中的其他人来说,我们的产品很好啊,功能正常啊。 他们会问“把这个按钮向左移动三像素,真的能改善我们的产品吗?”然后接着说 “上次我们修复一个很小的设计问题后,没有感觉到有什么区别啊” ,然后团队就扭头转向下一个“big idea”和下一组新功能了。

如果你和我一样是个较真的人,遇到这样的情况,你也会感到很挫败。作为设计师,我们要为用户体验的整体质量负责, 但是我们能设计出来漂亮、复杂、愉悦的细节,却拿团队其他人没有办法,我们没法儿控制队友制作、测试、发布的过程。

到底应该怎样说服我们的工程师和产品同行同样关注设计的整体细节呢,纠结了很长时间后,以下是我的几点体会:

Read More

读懂Underscore.js【2】

另一个基础函数

_bind .bind(function, object, arguments])

_.bind = function(func, context) {
    var args, bound;
    if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));//【1】【2】
    if (!_.isFunction(func)) throw new TypeError;
    args = slice.call(arguments, 2);
    return bound = function() {//【3】
        if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments)));
        ctor.prototype = func.prototype;
        var self = new ctor;
        ctor.prototype = null;
        var result = func.apply(self, args.concat(slice.call(arguments)));
        if (Object(result) === result) return result;
        return self;
    };
};

【1】Function.prototype.apply Function.prototype.apply (thisArg, argArray)
apply 和 call 功能类似,只不过只接受两个参数,第二个参数 argArray 是一个数组,作为函数的参数传入。

【2】这一句的意思是,如果支持原生的bind() Function.prototype.bind (thisArg , arg1 , arg2, …), 就调用原生的,并把当前的参数传入,第一个参数是func,之后的参数依次传入。
slice = Array.prototype.slice.call Array.prototype.slice.call
call将指定函数Function作为thisArg对象的方法来调用,将参数args传递给Function,返回值为Function的返回值。
这句是取第一位以后的arguments,这个arguments并不是数组,但是有个length属性,所以能用Array.prototype.slice调用
具体到这里,伪代码可以简略解释如下:

Array.prototype.slice = function(start,end){
	var result = new Array();
	//注释部分是处理参数为负数的情况,可以掠过
	//len = this.length;
	//start = start < 0 ? max(len + start) : min(start,len);
	//end = end ? len : end;
	//end = end < 0 ? max((len + end),0) : min(end,len);
	for(var i = start; i < end; i++){
		result.push(this[i]);
	}
	return result;
}

这里与内部实现有出入,不要深究细节,明白意思就行了。总之就是将当前函数的agruments转成数组

【3】这段代码有点复杂
先上一个简单版本,绑定一个函数到一个对象上:

_.bind = function(func, obj) {
    var aArgs = slice.call(arguments, 2);
    return function() {
        return func.apply(obj, args.concat(slice.call(arguments)));
    }
};

功能就是把bind()时传入的参数与原来的参数合并,返回一个函数,更详细的解释这里有
更多阅读参考partial application, 还有 JavaScript currying

Github上根据这个Issue, 为了更像原生的bind(), 需要支持new一个被绑定的函数。 在MDN的建议下,Pull request #282让这段代码变成这么复杂了

_.bind = function(func, obj) {
    if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
    var aArgs = slice.call(arguments, 2),
        fNOP = function () {},
        fBound = function () {
            return func.apply(this instanceof fNOP ? this : obj, aArgs.concat(slice.call(arguments)));
        };

    fNOP.prototype = func.prototype;
    fBound.prototype = new fNOP();
    return fBound;
};

最终版本:

var ctor = function(){};

_.bind = function bind(func, context) {
    var bound, args;

    // 如果原生支持,就用原生的.bind()
    if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));

    // 如果没有传入function参数,抛出异常
    if (!_.isFunction(func)) throw new TypeError;

    // 支持绑定 function 和 context 后面的参数 (所以用 .slice(2))
    args = slice.call(arguments, 2);

    // 返回绑定后的函数
    return bound = function() {

        // 如果没有用new关键字,(this instanceof bound)就为假
        // 此时为正常的调用 bound(),bound 函数中的 `this` 和 arguments
        // 都已经绑定,传入参数,直接调用函数就可以了。
        if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments)));

        // 如果使用了new关键字,`new bound()` 
        // (this instanceof bound)为真,此时模拟函数的构造函数
        // (JavaScript中通过 new 关键字方式调用的函数都被认为是构造函数。)
        // 具体步骤就是:创建一个对象A,将A的prototype指向原函数的prototype
        // 执行原函数
        // 如果原函数没有显式的return一个对象,则隐式的返回A对象的实例
        ctor.prototype = func.prototype;

        // ctor是空函数,不进行任何操作
        // 只创建一个实例对象
        var self = new ctor; 

        // 用这个新创建的实例作为 `this` 调用原函数,并传入相应的参数
        // 原函数作为新实例的构造函数执行
        var result = func.apply(self, args.concat(slice.call(arguments)));

        // 执行的结果是 object 就返回,不是的话返回这个实例对象
        // 因为标准规定 `new xxx` 操作必须返回对象
        if (Object(result) === result) return result;
        return self;
    };
};

扩展阅读:
underscore functions 测试用例
Understanding the code of _.bind
underscore中的function类函数解析
深入理解JavaScript系列(2):揭秘命名函数表达式
深入理解JavaScript系列(5):强大的原型和原型链
JavaScript 秘密花园 构造函数
深入理解JavaScript系列(26):设计模式之构造函数模式
深入理解JavaScript系列(18):面向对象编程之ECMAScript实现

updated:2013年1月6日 19:03:00 星期日

Read More

读懂Underscore.js【1】

Underscore是一个JavaScript函数库,在不修改原生对象的基础上提供了很多支持函数式编程的工具。官方文档已经详细的介绍了使用方法,还有大量的栗子。还有加了注释的源代码

Underscore有80多个函数,使用Backbone.js时发现了这个精巧的函数库。这里的javascript templating可以单独拿出来用到别的小项目中,快速生成前端页面。其它还有forEachmapindexOf等Array操作中常用但是原生js中没有的功能。最近的新版本中还增加了函数绑定等新功能。

阅读源码时发现我的js基础不是一般的差,下面就是记录我看不懂到看懂的过程。

each _.each(list, iterator, [context]) Alias: forEach

遍历List,用其中的每个值生成一个iterator对象。如果传入了context,则把iterator绑定到context对象上。每次调用iterator对象都会传入三个参数:(element, index, list),如果List是一个JavaScript对象,则传入参数:(value, key, list)。如果有原生的forEach(ECMAScript 5)方法,则会调用原生方法。

  var each = _.each = _.forEach = function(obj, iterator, context) {
    if (obj == null) return;
    if (nativeForEach && obj.forEach === nativeForEach) { //【1】
      obj.forEach(iterator, context);
    } else if (obj.length === +obj.length) { // 【2】【3】
      for (var i = 0, l = obj.length; i < l; i++) {
        if (iterator.call(context, obj[i], i, obj) === breaker) return; // 【4】【5】
      }
    } else {
      for (var key in obj) {
        if (_.has(obj, key)) {
          if (iterator.call(context, obj[key], key, obj) === breaker) return;
        }
      }
    }
  };

【1】//nativeForEach = Array.prototype.forEach

【2】 一元运算符+

1. Let expr be the result of evaluating UnaryExpression.
2. Return ToNumber(GetValue(expr)).

【3】这里只检查length属性,这样会有问题的吧,不只是Array,argument才有length属性,如果用户自己构造了一个带length属性的对象,
这样的结果会很意外吧:

function show(val,key,o){
    console.log(val);
    console.log(key);
    console.log(o);
    console.log('----');
}

var obj = {
    "length":3,
    "var1":"one",
    "var4":"four"
};
_.each(obj,show);

执行结果见jsbin

undefined
0
Object {length: 3, var1: "one", var4: "four"}
----
undefined
1
Object {length: 3, var1: "one", var4: "four"}
----
undefined
2
Object {length: 3, var1: "one", var4: "four"}
----

【4】Function.prototype.call
Function.prototype.call(thisArg [ , arg1 [ , arg2, … ] ] )

call将指定函数Function作为thisArg对象的方法来调用,将参数args传递给Function,返回值为Function的返回值。

【5】神秘的breaker

关于underscore.js中断枚举Github有好长的讨论
Stackoverflow有个回答是简略版

用一个秘密的变量来中断each循环,这个变量是underscore的内部变量。
不暴露在外的原因是原生方法中(目前)没有这个特性,如果这样做了,
就会导致(用户写的代码中的)中断特性只有在原生函数不支持的时候才能用

不过看源码是先尝试用原生的forEach方法,既然原生不支持,那执行到这里的时候就不能中断了?

待续……

Read More

CSS文件中的中文字体编码

CSS文件中的中文字体编码

—-

simsun – 微软宋体 TrueType 字型。
simsun 是微软向 ZHONGYI Electronic Co. 购买的中文简体字型。

1. 使用`font-family:”宋体”;` 如果CSS编码和浏览器解析不一致时,”宋体”两个字就会出现乱码,系统就会寻找替代字体显示。
1. SimSun 是宋体的英文名。Firefox 的较早版本和 Opera 不支持 SimSun 的写法。
1. `”\\5b8b\\4f53″` 是 “宋体”用 unicode 表示。这个规则对所有浏览器有效。
1. 可以写成`{font-family:”\\5B8B\\4F53″,SimSun;}` 保留 SimSun 是为了起到注释作用,方便阅读代码。

测试代码 :

body黑体,每行单独设置字体。

Opera 11.10 Win32 Windows 7 不识别Simsun

_图片 “Opera 11.10 Win32 Windows 7 不识别Simsun”_

**建议** 所有中文字体在CSS中都用unicode写法,为了方便阅读可以保留英文写法,起到注释作用。

http://blog.csdn.net/yilanyoumeng3/article/details/7904403 ”一般用css设置中文字体的Unicode编码”
http://frully.org/archives/jiang-zi-ti-ming-zhuan-huan-wei-unicode-bian-ma ”将字体名转换为 Unicode 编码”
http://old.unicac.cn/?p=1501 ”CSS中文字体对照表”
http://zh.wikipedia.org/zh-cn/%E5%AE%8B%E4%BD%93 ”宋体”
http://zh.wikipedia.org/wiki/Simsun ”中易宋体”
http://jsbin.com/iteyev/1/edit ”opera 中演示”
Read More

使用负值margin

什么是负值margin

简单点说就是

content{margin-right:-10px;}

  • 因为负值是CSS2中定义的,W3C特意加了一句Negative values for margin properties are allowed,…这里
  • 兼容性很强,所有现代浏览器都支持【IE6也在大部分情况下都支持】
  • 在margin属性中一共有两类参考线,top和left的参考线属于一类,right和bottom的参考线属于另一类。top和left是以外元素为参考,right和bottom是以元素本身为参考。当margin四个值都为正数值的话,那么margin按照正常逻辑同周围元素产生边距。当元素margin的top和left是负值时会引起元素的向上或向左位置移动。而当元素margin的bottom和right是负值时会影响右边和下边相邻元素的参考线。

使用负值margin

margin-motion

static元素就是遵从Normal flow布局的元素

  1. 当static元素在上/左方向设置负值margin以后,该元素就会被拉到相应位置,例如,

    /* 把元素向上移动10px */
    #div1{margin-top:-10px;}

  2. 当static元素在下/右方向设置的时候,则不会移动元素,只会把后面的元素拉向该元素里面,并且会重叠覆盖。
    /* #div1下面的所有元素都被向上移动10px,#div1本身不移动 */
    #div1{margin-bottom:-10px;}
  3. 如果没有设置宽度,在左右方向设置负值margin,会把该元素向左右两个方向拉伸,跟padding类似。
    常用来做[一像素的模拟按钮][4]

IE6 中的bug

当负值margin用在float元素上时,在IE6中可能会:

  • 链接不能点击
  • 文字不容易选中
  • Tab键不能选中链接
  • 一些内容会消失

解决方法就是加上position:relative;

一些参考资料:
[1]: http://www.w3.org/TR/CSS2/box.html#margin-properties ”W3C CSS2 margin定义”
[2]: http://media.smashingmagazine.com/wp-content/uploads/images/css-negative-margins/margin-motion.gif  ”NEGATIVE MARGINS ON STATIC ELEMENTS”
[3]: http://www.w3.org/TR/CSS2/visuren.html#positioning-scheme ”W3C CSS2 Positioning”
[4]: http://jsbin.com/ayomas/2/edit ”负值margin模拟圆角按钮”
[5]: http://www.hicss.net/i-know-you-do-not-know-the-negative-margin/ ”我知道你不知道的负Margin”
[6]: http://www.hicss.net/do-not-tell-me-you-understand-margin/ ”不要告诉我你懂margin”
[7]: http://www.planabc.net/2007/03/18/css_attribute_margin/ ”由浅入深漫谈margin属性”

Read More