分类
nodejs

新版node.js安装教程

知道node.js这个东西后,一直想尝鲜。今天终于下手,先要安装环境,看了好多教程,没看太明白,似乎很难的样子。

最后按照官方文档自己尝试了才知道,原来现在安装node.js已经完全自动化了。我用的是Ubuntu 10.4,先更新一下源:

sudo apt-get update
sudo apt-get upgrade

然后安装GNU make和git,接着clone代码,最后make就ok了。

sudo apt-get install gcc
sudo apt-get install git-core
git clone git://github.com/joyent/node.git
cd node
./configure
make
make install

make的过程比较久,让它慢慢跑就是了,完成后,就可以在命令行里测试了。现在node.js已经是0.9.3-pre版了,看到版本号很高,心里很高兴呀~

node -v // v0.9.3-pre
node
> console.log('hello, world') // hello, world

今天先到这里,将来哪天开新项目的时候用node.js做后端吧。

分类
PhoneGap

解决 iOS webkit 使用CSS动画时闪烁的问题

这个,咱们必须承认版本管理不是万能的,尤其对于像我这样习惯不好的人来说,更是如此。比如,上次不知道改了什么东西,导致肉大师制作的杂志在 iOS 里突然就变卡了,而且不仅卡,还伴随黑块、切换图片的最后会闪一下。

开始我一直以为是性能问题,调啊调啊调啊,就是不见好。于是 Google “ios phonegap 闪烁”,然后发现一篇文章,内容是解决 iOS 下 Safari 渲染 Transition 时页面闪动的问题。说只要加一句 -webkit-backface-visibility: hidden; 就行,这样可以避免元素转换时显示元素背面。虽然听起来莫名其妙,不过我觉得有戏,但是文章里没有提应该加在哪儿,所以我想了想,在样式表里加了一句:

#container {
  -webkit-backface-visibility: hidden;
}

因为我用到 iScroll,而负责动画的CSS是加在 #container 上,所以我就理所当然的把这段代码加在这里。测试,没有效果。

于是我一发狠,把这段样式加在所有元素上,居然问题就解决了。

#viewport * {
  -webkit-backface-visibility: hidden;
}
分类
jQuery

使用jQuery托管事件对阻止冒泡的影响

列表范例
移动App中常见的列表

jQuery托管事件是个好东西,减少侦听器的数量,还能降低内存泄露的风险,尤其在列表类的应用比较常见。

想象一个如右图所示的下载列表,点击各列表项会展开详情,用户可以在里面查看详细信息;不展开的话也可以直接点击右边的下载按钮,就会直接下载应用。使用jQuery来写代码大概是这样的:

$('#app-list li').on('click', function (event) {
  // 显示/隐藏详情
  $(this).toggleClass('active');
});
$('#app-list .download-button').on('click', function (event) {
  // 下载通过超链进行
  // 这时为了不让详情展开,需要阻止事件冒泡
  event.stopPropagation();
});

假如列表里有20个选项,那么这样就会添加40个侦听器。在PC浏览器里这样做问题不大,但是移动设备内存比较少的话可能会引发问题。另外,如果我们还引入了“上拉读取更多”和“下拉刷新”的功能,那么就要为内容的更新添加更多的代码,以避免内存泄露。所以这时,就应当将事件托管到ul去处理。( 关于jQuery事件托管可以参考其文档(http://api.jquery.com/delegate/)。)

$('#app-list').on('click', 'li', function (event) {
  $(this).toggleClass('active');
});
$('#app-list').on('click', '.download-button', function (event) {
  event.stopPropagation();
});

可以看到,我没有修改事件处理函数。这样做,我们只添加了2个侦听器,并且不管#app-list里的内容如何变化,都不需要重新注册侦听器。这给代码减了不少负。

不过接下来我发现,点击按钮后,详情仍然会被展开收起,似乎阻止冒泡的代码没有生效。仔细一想,对了,我已经把事件注册到#app-list上了,所有的事件其实都是冒泡上来的,所以点击按钮,冒泡必然经过上层节点。于是继续修改代码,改变事件处理函数的逻辑:

$('#app-list').on('click', 'li', function (event) {
  // 如果事件始于下载按钮,则退出
  if ($(event.target).is('.download-button')) {
    return;
  }
  $(this).toggleClass('active');
});

这样便万事大吉了。

分类
js

JavaScript奇葩的参数机制

今天实习生遇到一个问题:有一个数组,想在一个函数里将它清空,结果办不到。代码大概是这样的:

function empty(arr) {
  arr = [];
}
var array = [1, 2];
empty(array);
console.log(array); // 预期 [],实际 [1, 2]

对于我这种半路出家根基不实的人来说面对这种问题总是很挠头。于是只有翻书,在《JavaScript高级程序设计(第三版)》上看到,原来JavaScript的函数参数设计这么奇葩。

首先,JS里的变量分为值类型和引用类型,这点我是知道的。基础类型只有NaN、null、undefined、String、Number这5个,其他都是引用类型,也就是赋值时传引用不复制值的类型。区分如下:

// 值类型
var a = 'test',
    b = a;
b = 'temp';
console.log(a); // 'test'

// 引用类型
var a = {id: 1},
    b = a;
b.id = 2;
console.log(a.id); // 2

但是在用在函数参数的时候,又会有所不同。参数并不是传递的引用,而是传递的引用的引用。所以即使用“===”判断,也会返回true,因为最终指向的对象是一样的。但是如果在参数中对参数重新赋值的话,就相当于改变了引用地址,重新创建了一个对象,也就无法操作外面的对象了。这可能也是“运行时环境对象”造成的结果吧。

分类
js

Google Map 和 Bootstrap 冲突的地方

今天遇到一个诡异的问题(其实以前就遇到了,被我想办法绕开了):

在Google Map里添加一个Marker,如果Marker能够被拖动(draggable),就显示不正常。表现为一个1/3宽度的图标带2个阴影,拖动后变成1/4大小的图标。

反复尝试都无法解决,去查看了Gmap的源码,发现大家是一样的,也没能解决。最后还是Google之!

原来是Bootstrap的问题,它为了不让图片撑开容器,在样式里规定img { max-width: 100%};,就是这句话导致图片被压缩(应该是默认占据更宽的位置,好显示阴影之类的东西)。

修改我的样式,增加.map-container img { max-width: none; }之后,就一切正常了。

分类
js

Backbone.js笔记

关于事件

  • 使用Backbone里,我们可以继承Backbone.View,并且侦听UI事件。这些操作是通过jQuery或者Zepto的事件委托实现的,所以很重要的一点就是:这些事件都是UI事件,loaderror这些事件是无法在events属性里注册并被侦听到的。
  • 因为是托管的事件,事件处理函数最好用event.currentTarget来寻到节点
  • model的事件都会被collection转发,所以可以直接侦听collection;同理,除非remove并等待垃圾回收的model,也不应简单的调用off(),因为这会使collection没法侦听到事件,漏掉一些处理。

路由解析规则

这点文档中说得不算太详尽,我摸索如下:

  1. 路径分析以#/为起始,所以链接应该如#/app/add
  2. /是很重要的分隔符,末尾的/会被认为有下一级参数,比如app/list/的规则就不适用于http://domain.com/#/app/list这样的路径
  3. 规则只匹配一次,不会多次执行
  4. 刷新页面的方法:
    Backbone.history.loadUrl(Backbone.history.fragment);

其它关于Backbone.js的文章

Backbone.js经验两则

重写Backbone.js的加载动作

分类
PhoneGap

Mobile Web开发笔记

这里记录使用Phonegap开发移动应用期间发现。

性能

使用jQuery托管事件对阻止冒泡的影响

表现

解决 iOS webkit 使用CSS动画时闪烁的问题

CSS 兼容性

CSS 属性 iOS Android IE 其它
border-radius 3.x -webkit- Firefox 3.6- -moz-
Safari 4- -webkit-
box-shadow 2.3 -webkit- Safari 5- -webkit-

网摘

Developing Better PhoneGap Apps: Float Mobile Learning

重点包括

  1. 使用tap取代click(这点我还需要进一步测试,我发现zepto+phonegap没法捕获tap事件,不知道是我不是敲键盘的姿势不对)
  2. 使用css动画取代js动画

随记

  1. “click”和“tap“不是一回事儿,“click”会延迟300~400ms才处理,时间上会接近taphold之类的事件,“tap”更快
  2. 给元素加上:active伪类,可以给用户更快的反馈
  3. 调试不太容易,debug.phonegap.com似乎已经关了,得想办法把自己的weinre服务搭起来。暂时可以在js里写console.log(),接受一个参数,会在log cat输出。
分类
js

HTML5的File API应用

HTML5新增了很多特性,其中File API是非常重要的部分。在肉大师中,我大量使用了HTML5的文件API,这样一来可以给予用户近乎桌面软件的体验,二来还能减少服务器和带宽的消耗。今天终于把最后几个问题解决了,在这里总结下HTML5 File API的使用。

随着用的越来越多,发现自己其实搞混了“File API和FileSystem API”两个东西。而且类写的也有问题。等到有空的时候把这篇文章重写一下好了。(2012-09-13)

用途

在W3C页面上,列出了File API可能用到的场合(以下为意译,可能有所偏颇,欢迎对比原文阅读):

  1. 断点续传
    • 上传时,先把目标文件复制到本地沙箱,然后分解逐块上传
    • 浏览器崩溃或者网络中断也没关系,因为恢复后可以续传
  2. 需要大量媒体素材的应用,比如视频游戏
    • 下载压缩包,在本地解压,就能恢复之前目录结构
    • 跨平台
    • 通过渐进式下载,进入新关卡或者开启新功能均无需等待,因为玩的时候所需素材已经通过后台下载完成了
    • 从本地缓存中直接读取素材,速度飞快
    • 二进制文件也不在话下
    • 使用压缩包可以大大减轻带宽和服务器消耗,也避免了频繁下载碎片文件带来的检索问题
  3. 离线图片/音频编辑器通
    • 不怕频繁读写大量数据
    • 只想重写文件的某些部分也能做到(比如修改ID3或者EXIF信息)
    • 创建目录组织项目后用起来舒服多了
    • 编辑完的文件还能被iTunes、Picasa之类的本地应用访问
  4. 离线视频播放器
    • 下载超过1G的大文件,将来想看再看
    • 可以在不同时间点间来回跳转播放
    • 能够给Video标签提供URL
    • 即便片子还没下完,也能把下载到的部分先睹为快
    • 还能任意截取一段视频交给Video标签播放
  5. 离线邮件客户端
    • 下载保存附件到本地自不必说
    • 断网的情况下,可以缓存用户要上传的附件,以后再上传
    • 需要时可以列出缓存里的附件,通过缩略图显示,预览后上传
    • 能像正常服务器那样触发标准的下载动作
    • 不仅能使用XHR一次性上传全部内容,还可以把邮件和附件拆解成小块依次发送

听起来都是些令人振奋的功能,实际用起来还是要踩点坑。下面就把我的经验分享一下。

分类
js

Windows 8 64bit,Chrome 18,display:none的<input type=”file” />无法通过JS手工触发click事件的方法来打开选择文件的窗口。添加到页面内也不行,不知道是不是所有不显示的标签都不会广播click事件,回头验证下。

分类
js

Backbone.js经验两则

使用HTML5,实现从桌面拖拽到网页

使用HTML5新增加的API,可以很方便的实现拖拽, 包括从桌面拖拽到网页上(部分浏览器比如Chrome还可以把东西从网页拖拽到桌面),这个操作不在今天的讨论之内,可以参考:NATIVE HTML5 DRAG AND DROP这篇文章,讲得足够详细了。

当我试图在Backbone框架上使用这个功能的时候,问题出现了。开始我没多想,直接这么写的:

var myView = Backbone.View.extend({
  events: {
    "drop img": "img_dropHandler"
  },
  img_dropHandler: function (event) {
    var reader = new FileReader();
    var img = event.target;
    reader.onload = function (event) {
      $(img).attr('src', event.target.result);
    }
    result.readAsDataURL(event.dataTransfer.files[0]);
  }
});

结果运行时提示我,event对象没有dataTransfer属性。我用的是最新版本的Chrome,理应是对HTML5支持最好的,而且文中也说代码在Firefox和Chrome下运行通过。后来检查了一下event,发现是f.Event,似乎原本应该是MouseEvent或者Event什么的。于是我把属性展开,看到了originalEvent这个属性,是MouseEvent;再展开originalEvent,就看到了dataTransfer属性,里面有期望中的所有属性。看来是Backbone并没有直接使用原始事件,而是封装了一层再广播。(更正)事件代理是通过jQuery来做的,jQuery在这里把原始事件封装了一层再进行转播,导致原始事件的属性没有完全复制。解决方法很简单,多写几个字母就行了:

// 其它地方都一样
result.readAsDataURL(event.originalEvent.dataTransfer.files[0]);

Model中数组的处理

先看一段代码:

var ModelClass = Backbone.Model.extend({
  defaults: {
    contents: []
  }
});
var model1 = new ModelClass();
var arr = model1.get('contents');
arr[0] = 'haha';
var model2 = new ModelClass();
console.log(model2.get('contents'));

大家猜猜结果是什么?竟然是“[‘haha’]”!这个我只能认为是Backbone.js的bug了。解决方法是先复制一个数组,对数据操作后再赋值回去,如下:

var model1 = new ModelClass();
var arr = model1.get('contents').concat();
arr[0] = 'haha';
model1.set('contents', arr);