RETINA时代的前端优化

RETINA时代的前端优化

原文链接:http://www.haipi8.com/css/477

一,什么是Retina?

“Retina”“视网膜屏”是苹果为最新的双密度的屏幕起得名字,其他厂商也有类似的屏幕,只是不叫这个名字。最新的iPhone,iPad,MacBook Pro等设备用的就是这类屏幕。

二,Retina屏幕带来了什么问题?

以MacBook Pro with Retina Display[3]为例,工作时显卡渲染出2880×1800个像素,其中每四个像素一组,输出原来屏幕的一个像素显示的大小区域内的图像。这样一来,用户所看到的图标与文字的大小与原来的1440×900分辨率显示屏相同,但精细度是原来的4倍…由四个像素代替原来一个像素。

wikipedia

这样就会造成普通图片在Retina屏幕上显示模糊

Non-Retina display and retina display

三,这个问题会影响哪些地方?

1,Favicon

2,Web Clip

3,图片

4,图标

四,怎样解决这个问题?

将原来的文件增大为原来的4倍(宽高都X2)

Read More

CSS 长度单位

CSS 长度单位

原文: https://hacks.mozilla.org/2013/09/css-length-explained/
中文:http://www.haipi8.com/news/469 

大家都知道 CSS中的 英寸(in)在屏幕上并不是真正的英寸, 1像素(px)也不是真的1像素,你知道这些CSS单位是怎样在屏幕中展示的吗?
工业上的1英寸 = 2.54 厘米 = 0.0254 米
设备像素
电脑屏幕上显示东西,以像素为单位。显示屏上可以显示全部颜色的一个物理“光点”,在本文中称为“设备像素”(区别与CSS中的“像素”)
DPI,每英寸点数(dots per inch);PPI,每英寸像素(pixels per inch)
一个设备上的“设备像素”来自于制造商给出的像素密度(单位是 DPI 或者 PPI),DPI 最常用,但是 PPI 更准确。大多数液晶显示器上显示一像素的所有颜色,需要红绿蓝三个点,有些显示器为了节省空间或者增加有效像素密度,像素之间还会共享点。
CSS像素(px)
一个CSS像素的尺寸是一个肉眼可以比较舒适的看出来的点,没有小到需要眯眼睛,也不会大到可以看清像素内结构。(眼睛看到的大小,取决于可视角度。而可视角度取决于物体的实际大小以及物体与眼睛的距离。10米远处一个1米见方的东西,与1米远处的10厘米见方的东西,看上去的大小差不多是一样的,所谓一叶障目不见泰山,讲的就是这个常识。 )W3C CSS 规范根据视角定义了“参考像素”
1参考像素即为从一臂之遥看解析度为96DPI的设备输出(即1英寸96点)时,1点(即1/96英寸)的视角。
请注意这个差别——CSS规范定义的参考像素并不是1/96英寸,而是1/96英寸在一臂之遥的看起来的视角。通常认为常人臂长为28英寸,所以其视角可以计算出来是0.0213度。(即(1/96)in / (28in * 2 * PI / 360deg) )
w3c css pixel

w3c css pixel

上图中,左边的屏幕(可以认为是电脑屏幕)的典型视觉距离是71厘米即28英寸,其1px对应了0.28mm;
而右边的屏幕(可以认为是你的42寸高清电视)的典型视觉距离是3.5米即120英寸,其1px对应1.3mm。42寸的1080p电视,分辨率是1920*1080,则其物理像素只有0.5mm左右,可见确实是高清哦。
96dpi是参考20世纪的标准,21世纪的常用设备上有不同参考建议
reference pixel

reference pixel

综上,px是一个相对单位,而且在特定设备上总是一个近似值(原则是尽量接近参考像素)。
CSS英寸(in)
在电脑显示器上,CSS英寸与实际的英寸没有关系, 而是等于 96 CSS像素。这样就造成一个尴尬,不能用 CSS 单位在显示器上画出一个准确的尺子,CSS单位是为了在不同的设备上都让用户感到舒适。
不过真的需要尺子的话,可以打印到纸上,打印机会把CSS英寸映射为正确的物理尺寸(打印机要关闭缩放选项)。
设备像素比,DPPX(Device pixel ratio)
时下许多智能手机都有高密度显示屏。为了确保CSS像素大小一致,需要将多个设备像素映射到一个CSS像素。DPPX 就是 CSS 像素与设备像素的比例。
用 iPhone4 举个例子,它有 326 DPI 显示屏,根据上表,智能手机的典型观看距离大概16.8英寸,基准像素为 160 DPI。所以要显示一个 CSS 像素,苹果选择将像素比设置为2,所以看起来就和 163 DPI 手机中显示的一样了。
看上面的数字,为什么不做的更好,把像素比设置为 326/160 = 2.0375,做一个与参考像素完全相同的尺寸。 事实上,这样的比率将导致更严重的后果,每个 CSS 像素都不是用一个完整的设备像素显示,一些图片,边框都需要完整的像素才能正确显示,不然看起来会发虚。还有,163 DPI 正好是上一代的 iPhone 的像素密度。
CSS point(pt)
点是一种印刷行业中常用的单位,继承自活字印刷中的单位,当这个行业进化到用桌面排版后,pt 被重新定义为 1/72 英寸。 CSS 遵循了相同的约定,映射 1pt = 1/72 * 1in = 96/72 CSS像素
所以 pt 和其他传统单位一样,必须打印出来才和物理单位一致。
CSS PICA(PC),CSS厘米(cm),CSS毫米(mm)
与英寸(in)类似,同时保持相对关系。
viewport meta标签
虽然智能手机可以很方便的在手掌上显示让眼睛舒适的大小,但是固定宽度的网站,不能只显示一部分,也不能不顾 CSS 规则显示成别的样式(貌似很多手机浏览器会重新排版……)
viewport 标签解决了这个问题,可以让开发人员控制在桌面版显示完整版,手机上显示缩放后的页面。
总结
浏览器常上在竞争的同时,也意识到努力保持稳定的Web平台,通过一个标准组织协调功能集。新的特性和 API 在所有场景中都测试后才宣布其作为一个标准。 CSS 像素的定义,从一开始就是这样的。新的特性必须保持向后兼容,而不是改变旧的行为(咳咳,xHTML,呵呵),所以很多特性(设备像素,viewport 标签等)都是作为新的内容加入,旧页面就会忽略这些特性,自动有了“向后兼容”的能力。
参考链接:
A quick PSA on “dots” versus “pixels” in LCDs
CSS 长度单位
像素(px)到底是个什么单位
1996 W3C CSS 像素定义
Read More

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

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

本文链接: 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