VPS上部署 nodejs 应用并和现有应用共存

最近,nodejs 很火,我也尝试用 nodejs 写了一个小应用,主要用到了 express framework 和 socket.io ,数据库使用的 mongodb.

我有一台Linode的VPS ,自然是想把应用部署在自己的VPS上面,为什么没有选择云服务呢?一方面是省钱,我已经为Linode付过钱了,一方面是更加自由,可以随心配置。这台Linode上已经部署了标准的 LAMP 环境,跑了几个 wordpress 。那这次部署自然是不能影响到现有应用。

我的部署方案主要要达到的目的有:

  1. nodejs 应用可以像一个守护进程(daemon)一样运行
  2. 提供一对简单的命令来 start / stop nodejs 应用
  3. 提供监控机制,当应用崩溃,可以及时重启它
  4. 不影响现有 PHP 应用,但是也要用 80 端口访问
  5. socket.io 提供的 websocket 功能正常使用

下面分享下我的部署经验,不一定好,我也是第一次部署,欢迎交流。

1,在 VPS 上安装 nodejs ,我 VPS 跑的是 Ubuntu 12.04 , 最简单的方式当然是通过 apt-get 来安装,但是直接 apt-get install 的话,安装的版本比较旧,建议采用 https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager这个页面提供的方法,先添加源,再来安装,就可以安装到最新的稳定版了。这里还有其他主要 Linux 发行版的安装方式可以参考。下面的步骤默认使用的 OS 是 Ubuntu 12.04。

2,安装 mongodb (如果使用的不是 mongodb ,请忽略此步) ,http://www.mongodb.org/downloads#packages 这里同样有包管理器的安装方式,照着做就好了。

3,把你的代码放到 VPS 上来,然后以开发模式运行一次试试,不出意外的话,应该是可以运行了,如果有其他依赖需要解决,请自行解决。

4,把 nodejs 应用变成一个 daemon 。 我使用 upstart 来完成此任务 ,高版本的 ubuntu 已经自带,如果没有,直接 apt-get install upstart 来安装。然后新建一个文件在 /etc/init/yourapp.conf, 文件内容如下

#!upstart
description "node.js server"
author      "joe"

start on startup
stop on shutdown

script
    # We found $HOME is needed. Without it, we ran into problems
    export HOME="/var/www"

    echo $$ > /var/run/yourapp.pid
    exec sudo -u username NODE_ENV=production /usr/bin/node /where/yourapp.js >> /var/log/yourprogram.sys.log 2>&1
end script

pre-start script
    # Date format same as (new Date()).toISOString() for consistency
    echo "[`date -u +%Y-%m-%dT%T.%3NZ`] (sys) Starting" >> /var/log/yourapp.sys.log
end script

pre-stop script
    rm /var/run/yourprogram.pid
    echo "[`date -u +%Y-%m-%dT%T.%3NZ`] (sys) Stopping" >> /var/log/yourapp.sys.log
end script

你需要修改上述文件的 username 为你想要运行应用的用户名,推荐 www-data ,不要使用 root ,比较危险。如果你不使用 www-data 来运行你的应用,记得把export HOME=""改成对应的 HOME 目录。/where/yourapp.js 是你应用的路径。/var/run/yourapp.pid是你应用的pid文件,/var/log/yourapp.sys.log是你应用的日志文件。这些内容都需要根据你的情况修改。NODE_ENV=production 是让你的 nodejs 应用运行在生产环境的环境变量,你可以在代码中通过 process.env.NODE_ENV来获取。

配置文件创建好后,你就可以使用下面命令来开启/关闭你的应用了。

#!sh
start yourapp
stop yourapp

5,监控你的应用。我们不能保证我们的代码总是运行的很好,总会有崩溃的可能,所以我们需要监控它的运行,当它挂掉后,重启它。这里使用 monit 来解决这个问题,安装方法自己去这个网站找吧。

安装好后,新建一个文件在/etc/init/monit/conf.d/yourapp.conf,文件内容如下:

#!monit
set logfile /var/log/monit.log

check process nodejs with pidfile "/var/run/yourapp.pid"
    start program = "/sbin/start yourapp"
    stop program  = "/sbin/stop yourapp"
    if failed port 3000 protocol HTTP
        request /
        with timeout 10 seconds
        then restart

需要替换 /var/run/yourapp.pid 为你刚才设置的 pid 文件,yourapp 替换成你刚才在 upstart 设置的。port 3000 设置成你的 nodejs 应用实际监听的端口号,timeout 时间,可以根据你的实际情况调整。这几行配置文件很容易看懂,就不解释了。

然后确保 /etc/monit/monitrc 文件存在 include /etc/monit/conf.d/* 这行, 同时,你也可以在这个文件里修改监控频率,根据你的实际情况调整。然后重启 monit: service restart monit让刚才的配置文件生效。

upstart 和 monit 的配置我是参考 http://howtonode.org/deploying-node-upstart-monit 这篇博客的,你也可以来这里看看。

6,配置 apache 。我们需要在 80 端口来访问 nodejs 应用,现在又存在 apache ,那最简单的方式就使用反向代理来解决这个问题了。首先,需要安装 mod_proxy 和 mod_proxy_http ,你可以现在 /etc/apache2/mods-enabled/ 这个目录查看是否已经安装了,如果没有安装,你可以通过 a2enmod 命令来快速安装。
然后,新建一个 virtualhost ,按照你的习惯,新建一个文件 include 进去,或者一起写在某个文件。virtualhost 配置如下:

    
<VirtualHost *:80>
    ServerAdmin webmaster@localhost
    ServerName yourhost.com

    ProxyRequests off

    <Proxy *>
        Order deny,allow
        Allow from all
    </Proxy>

    <Location />
        ProxyPass http://localhost:3000/
        ProxyPassReverse http://localhost:3000/
    </Location>
</VirtualHost>

如果你的应用不是跑在 3000 端口上,记得修改配置, ServeName 记得改成你的域名,然后把你的域名解析到你 VPS 的 IP 上来。 ProxyPass 的作用是用 yourhost.com 来代理 localhost:3000 。ProxyPassReverse 的作用是处理 30X 跳转的情况,会把 Location:中的 http://localhost:3000 替换成 http://yourhost.com 。现在开启你的应用,重启 apache , 访问试试看。

如果都配置对了,到这里就应该能正常访问了。

使用反向代理时,要特别注意一些事情。页面中 a 标签的 url 尽量使用省略域名的形式,例如 href="/login",因为如果你处理不好,写上了 http://localhost:3000 这个后端的域名加端口,点击后就打不开了。对于 30* 跳转,Location 的值如果是 “//localhost:3000/login” 这种省略了协议的形式,apache 的反向代理也是无能为力的,我在 express 某个非正式版发现了这个问题。建议 Location 也使用省略域名的形式,这样虽然不符合 HTTP 1.1 的规范,但是各个浏览器都能兼容,可以省去这样的麻烦。

等等,如果你和我一样,也使用了 socket.io 来提供 websocket 服务,你会发现,现在长链接虽然可以用,但是却不是走 websocekt 模式了,这是因为 websocket 是无法通过 mod_proxy 来反向代理的,那就要另外想办法了。我使用的办法是:直接让 websocket 的请求去连接应用监听的后端真实端口。

7, 如果你使用了 websocket ,可以在页面中埋点,把带有你应用真实监听端口的 URI 埋入页面,然后前端 js 获取此 URI ,再来 io.connect( server ) 就可以让 websocket 正常运行了。需要注意的是,这个带端口的 URI 的域名需要和你访问页面的域名保持一致,否则取不到 cookie ,就没法获取 session 了。

如果你有更好的方式,请在下面留言交流,或者新浪微博 @allenm 来交流

localStorage in HTML5(2)

上一篇博客简单的介绍了下 localStorage,并且对各个浏览器的存储空间大小做了简单的测试。在上篇博客到这篇博客期间,世界也发生了很多变化,IE9 和 firefox4 正式版都发布了。于是我也对这两个新的浏览器进行了测试,关于 localStorage 的, IE9 和 IE8 表现一致,FF4 和 FF3.6 表现一致,和上篇博客中介绍的一样,FF4 在 Mac 下和 WIN 下表现仍然不一致,参考上篇博客。同时,给出我写的测试存储空间大小的页面,你可以自己来试试:http://lab.allenm.me/html5/storage/maxtest.html

除了最基本的和使用 cookie 一样使用 localStorage,我们还能用它来做什么呢?

如果你看过 localStorage 的文档,你应该注意到了我们在改变 localStorage 中存储的数据的时候,会触发一个 storage 事件:

interface StorageEvent : Event {
  readonly attribute DOMString key;
  readonly attribute any oldValue;
  readonly attribute any newValue;
  readonly attribute DOMString url;
  readonly attribute Storage storageArea;
  void initStorageEvent(in DOMString typeArg, in boolean canBubbleArg, in boolean cancelableArg, in DOMString keyArg, in any oldValueArg, in any newValueArg, in DOMString urlArg, in Storage storageAreaArg);
};

 

既然我们可以知道此“源”下的localStorage 中的某个字段的值发生了变化,那我们就可以在前端做到跨页面通信,这个是有意义的。 你想想看,在我们目前的WEB应用中,经常会发生这样的事情:在淘宝或者其他电商网站购物,点击购买某个物品,会在页面上购物车中显示。然后在新的 tab 页中打开其他物品的页面,也点击购买,新的页面当然会显示你购买的所有物品,但是旧的页面中的购物车却并不会主动更新这个数据。以前的技术并不是不可以实现这个,只是相对来说成本较大,没有必要。利用 localStorage ,我们就可以只使用前端技术来同步这个数据,成本极小。

我写了一个简单的利用 storage 事件来做前端层面的夸页面数据同步,请在支持 localStorage 的浏览器中打开两个此页面:http://lab.allenm.me/html5/storage/ ,然后在任意一个中输入任意字符,再看看另外一个页面中有什么变化。

我们知道 localStorage 中,我们只可以保存字符串。但是有些时候,我们需要保存一些结构比较复杂的数据,字符串不适合做这种工作,自己再制定一种特殊的规则来利用字符串保存这些数据,也是不明智的选择。因为我们已经有了 JSON 这个事实上的标准了啊。要让 localStorage 支持 JSON 的存储也比较容易,我们只需要自己再封装一层就OK了。现代浏览器已经原生支持 JSON 的解析,JSON.parse() 用来把字符串解析成 JSON 对象,JSON.stringify() 用来把 JSON 对象编码成字符串格式。有了这两个方法,我们只需要在存储的时候,编码,获取数据的时候,解码就可以实现用 JSON 来存储数据了。具体封装这里就不给出代码了。

即使有些不够现代的浏览器不支持 JSON.parse() 和 JSON.stringify() 也没关系,因为我们还有开源的 js 实现的 JSON 的解析器和编码器:老道的 JSON-js

localStorage 还有一个孪生兄弟,叫 sessionStorage,他们的用法基本一致,同属于 WEB Storage, 只是保存的数据的生命周期有区别,详情请阅读这里

localStorage in HTML5(1)

HTML5 中的 localStorage 我相信 web developer 们,都听说过了吧。目前 chrome,firefox,opera,safari, IE8 都支持此属性,注意 IE8 也支持,那就是说,如果你的网站用户不是太低端,起码有一半以上的用户的浏览器是支持 localStorage 特性的。各个浏览器分配给每个“源(region)”的 localStorage 空间至少为 5M(具体数值请看稍后讨论),对于想开始使用 HTML5 的人们来说,这个是个不错的开始。

localStorage 使用方式也非常简单,有 setItem, getItem, removeItem,key, clear 5个方法,和 length 一个属性。定义如下:

interface Storage {
  readonly attribute unsigned long length;
  DOMString key(in unsigned long index);
  getter any getItem(in DOMString key);
  setter creator void setItem(in DOMString key, in any value);
  deleter void removeItem(in DOMString key);
  void clear();
};

localStorage 中都是以 key/value 的形式来存储数据的,存储的数据类型都是字符串,如果需要其他类型,需要自行转换。我们可以使用 setItem 方法来存储数据,例如 localStorage.setItem(‘foo’,’test data’); 这样来存储一个 key 为 foo 的数据,然后可以使用 getItem 来读取 value 值,即 localStorage.getItem(‘foo’); 如果 setItem 的时候,key 已经存在,则修改保存的数据为最新的数据,如果不存在,则新建 key。执行 removeItem() 可以删除已经存储的数据,比如localStorage.removeItem(‘foo’) 可以删除 key 为 foo 的数据。执行 clear() 会清除此“源”下的所有数据。key() 方法可以获取第 n 个数据的 key 值,例如 localStorage.key(0) 可以获取到第 0 个 key/value 对 的 key 值。这个配合 length 属性可以遍历出所有存储的值。length 属性返回已经存储的数据的条数。

上面简单的介绍了下 localStorage 的用法,然后我们来讨论各个浏览器分配了多少空间给 localStorage。

在各个浏览器中,只有 opera 可以清楚的看到用了多少空间,当这个容量到达 5M 的时候,浏览器就会提示用户是否增加容量,如果用户点击了拒绝,那此后就不会再提示,如果不删除这些数据,将不能再存储,抛出错误。如果用户点击了允许,那将获得以前两倍的空间,等到达了新的上限后,又会弹出提示,以 5M -> 10M -> 20M -> 40M 这样递增。

另外 IE8 的 localStorage 对象,有个 remainingSpace 的属性,可以查看剩余的可用空间,在 IE 中执行 alert(localStorage.remainingSpace) 可以看到 IE8 有5M 的最大存储空间。如果达到最大值,会抛出错误,不清除数据前将不能继续存储。

其他浏览器我还没找到办法查看最大容量,但是我用同样的脚本在不同的浏览器里跑,等到达最大值后,查看存储的键值对的数量,结果如下:opera 可以存储 1482 条记录,chrome 和 safari 可以存储1986条,MAC下的 firefox 3.6  可以存储3970条,IE8 下可以存储 3786条。WIN 下的 firefox 3.6 一直没得到结果,一直没有抛出错误,好像是无限的一样。(以上浏览器都是使用的截止到今天的最新的正式版)。 win 下的 firefox 3.6 的问题还有待验证,我使用的环境是虚拟机下的 windows7 系统。

从 IE8 的 remainingSpace 属性可以看出默认最大容量也是 5M ,和 opear 相同,但是存储的条目数量却相差很大,猜测可能是使用了压缩算法,因为存储的都是字符串,压缩率还是非常可观的。chrome 和 safari 具体的最大容量,从这里也没办法推断出来,不过既然 opera 存储的条目数最少,那我们就认为至少可以存储 5M 的数据吧。

有了 5M 这个数量,我们就可以判断哪些数据适合用这个来存储,哪些不适合。毕竟现在如果存储满了后,还没有什么好办法来清除一些不再使用的数据,因为我们从代码里不容易判断哪些是用户很少用到的。最好使用这个 feature 来实现一些用户体验加强的东西,而不是来实现关键性的东西。

另外在 IE8 以下的版本,我们可以使用其他技术来代替,比如 IE 特有的 userData,或者 flash store 。可以考虑封装一下,暴露出统一的接口给用户( developer)使用。

chrome.tabs.create 的 callback 不执行?

在写 chrome 插件的过程中,不知道你有没有遇到使用 chrome.tabs.create 创建一个新的 tab,但是这个方法的 callback 却不执行,具体说来,在调试的时候是可以执行的,关掉 chrome  的 DEBUG 工具后,就不可以了。

前几天我也遇到了这个麻烦,可能出现这种问题的情况为:在点击浏览器上插件的图标后,打开一个 popup 的页面,在这个页面里点击某按钮后执行 chrome.tabs.create 方法新建一个新的 tab 页,关掉 DEBUG 工具后,此方法的 callback 函数就无法执行。

我们先来观察这种 popup 的页面的行为,随便找一个会 popup 页面的扩展,点击,让页面弹出,然后你切换下浏览器上已经打开的 tab 页,看看发生了什么?是不是这个 popup 出来的页面会关闭掉?然后在那个扩展上右键,选择“审查弹出内容”,再切换下 tab,发现了什么?是不是这个弹出的页面依然打开?

这就是问题所在了,默认情况下,chrome.tabs.create 创建新的 tab 后,会自动切换到那个新建的 tab,在不打开 DEBUG 工具的情况下,原先弹出的页面会关闭,也就是被 unload 了,页面中 js 的宿主已经不在了,当然也就不会执行 callback 了。那应该怎么办呢?很简单,不切换 tab 就OK了。在 chrome.tabs.create 的第一个参数中,是有个 selected 的可选属性的,把这个属性置为 false,就不会自动切换到新窗口去了。例如下面的代码:

chrome.tabs.create( { url:chrome.extension.getURL('./html/testy.html'), index:(S.tab.index+1), selected:false }, function(tab){
				setTimeout(function(){_send2Spliter(tab); },50 );
});

我的 callback 中,使用了 setTimeout,是因为我遇到的另外一个问题,我需要在新的 tab 打开后,向新的 tab 发送一点数据,但是大部分时候会失败。经过测试,发送数据的程序是执行了,但是 tab 中接收数据的 callback 未能执行。于是猜测,是新的 tab 中的  chrome.extension.onRequest.addListener 还没来得及执行造成的,于是就稍稍延迟下,再发送数据,这样就OK了。

如果你遇到向新建的 tab 中发送数据失败的情况,不妨也试试这个方法。

chrome extension的content script 无法读取页面中的iframe内容的解决方法

chrome插件中的 content script 是运行在一个被称为isolated world 的运行环境里,和页面上的脚本互不干扰,因为不在一个运行环境里,所以也无法调用页面上脚本定义的方法了,当然google也给出了解决方法:http://code.google.com/chrome/extensions/content_scripts.html

除了这些以外,在最近写插件的过程中,发现content scirpt 也不能读取到页面中的 iframe 内的内容(同域),这个被人报告为BUG,也许会在以后的版本中解决。当然,你可以通过设置manifest 中的 all_frames = true,来让脚本在 iframe里也运行。不过更简单的方法是,载入外部脚本到页面。

chrome插件有权限更改页面的DOM(如果这都没权限,这个插件系统也就没什么用处了),所以我们只要往页面中插入script节点,引入自己写的脚本,这个脚本的运行环境就和页面中的脚本是同一个环境了,于是页面中脚本有权限做的事情,我们自己写的脚本也有权限做,当然这样做一定要把自己的脚本放在一个大的闭包中,以防和页面上的脚本冲突。这样插件中的脚本就相当于一个加载器,只用做在页面中加载自己写的脚本就可以了。

这样做还有一个好处是,我们可以轻松移植这样的插件到 firefox 下的 greasemonkey 的 user script 中,也可以轻松制作成 Bookmarklets,也就是可以很方便移植到各种浏览器下。 :)

SyntaxHighlighter 3.0.8 重复加载 brush 的解决方法

我使用 syntaxhighlighter 来做我的博客代码高亮,使用 autoloader 来自动加载需要的 brush。但是使用后,发现,如果页面中存在两个地方使用相同的 brush,这个 brush 文件会被加载两次。如果电脑中有缓存只会加载一次,但是第一次访问的用户,是会加载两次的。这个应该算是BUG吧,等会儿我尝试反馈给作者试试看。

于是我稍微修改了下 autoloader.js 的源码,现在不会加载两次了。源码放在 http://api.allenm.me/syntaxhighlighter/src/shAutoloader.js ,需要的来拿。注意,处了这个文件在 src 下面放着,其他文件有使用 scripts 文件夹下的,要不然会报错。

中秋节的3天假期马上就过去了,我算是一直在玩,一直在放松。第一天去看了山楂树之恋,打了电玩,吃了火锅。第二天约了朋友来我这里做饭吃。第三天就在家宅着,蛮不错的,明天上班应该精神十足,然后再等待十一假期。目前还没有十一假期的安排。

零碎知识记录–计算字符串宽度–随机打乱数组

一、计算字符串宽度

最近的一个项目中有一个部分,需要计算一个字符串在浏览器中显示所占的宽度,因为可能的字符串不仅仅只是汉字,还有可能是字母,另外还是用来微软雅黑字体,在没有雅黑的机器上,会降级用黑体显示或者其他字体,而且要测宽度的字符串中还有三个字号,所以,用工具先量一个字会占多宽,是不靠谱的,只能通过程序来计算在用户的浏览器上显示的时候占多宽。

其实思路很简单,记得我以前看到过类似的例子,所以很快就知道要怎么来做这个东西了。我们可以把这个字符串放到页面中,让它具有真正显示的区域同样的 style ,也就是相当于复制一份这个字符串包括样式一起,到相同页面的另外一个位置去,但是这个地方,要让用户看不见才行。我们可以用一个绝对定位,z-index设置为一个负数,然后用visibility:hidden 把这个隐藏掉。总之让用户既看见,也点不着,也不影响页面的长度就行了。注意这个隐藏这个元素的方法一定要用 visibility 这个属性来隐藏,而不能用 display:none。因为如果用 display:none 的话,这个元素才尺寸就是0了。用 visibility的话,元素还是具有原来的尺寸,只是不显示出来而已。这个接着用 js 获取这个元素的 offsetWidth 属性就可以了。

二、随机打乱一个数组

今天还需要打乱一个数组中元素的顺序,但是在 js 手册里没找到相关的直接方法,但是想到了一个简单的方法,效果不一定特别好,但是够我用了。看代码:

function randomSort(a,b){
    return 0.5-Math.random();
}
function randomArr(arr){
    return arr.sort(randomSort);
}
var a = ['我','是','前','端','开','发','工','程','师'];
var b = randomArr(a);
console.dir(b);

大家可以复制代码到 firebug中运行试试看。就是利用了数组的排序函数,然后给了个随机的排序规则函数。如果谁有更好的方法,麻烦赐教,谢谢!

','

Android WEB app 跨域 AJAX

前段时间做毕设的时候,使用 PhoneGap打包web app的方式做开发,我使用的 PhoneGap版本比较老,没找到和服务器端通信的直接方法,因为html是在本地,所以使用标准的XHR是跨域的,当然也就不能用了,于是自己写了一可以跨域加载的小东东,使用动态 script 节点的方式,不是常见的jsonp的方式,因为写后台的老师比较土,AJAX都不知道,更不晓得让他每次我传递个函数名字过去,他包装数据了。就像YUI的 getScript函数一样,后台可以返回 var foo=2; 这样的数据,然后成功后,就可以利用 foo 这个全局变量了,写的过程中参考了 YUI和 jQuery 的实现方式,没有兼容其他浏览器,只兼容 Android。

代码如下,很简单,可能还有点小BUG,本来是打算接着完善的,但是现在好像没时间来折腾这个了,所以就先发上来,加上这段时间找 phoneGap找到我博客的人蛮多的,或许对大家有点帮助吧。

/**
*  By Allen.M http://allenm.cn  email: i@allenm.me 2010-05-22
*  @method getScript
*   url : string, the script's url;
*    options: object (options)
*          onSucess: function , it will be execute when the script load sucessful.
 onFailure: function, it will be execute when the script load failure, the URL is wrong, OR the Internet problem .
 onTimeout: function, it will be execute when the script is timeout.
 erase: Boolean. The default value is true; if it's true, the script will be remove when the onload event fire.
 timeout: number.(millisecond).  The default value is 3000
 charset: string. set the scirpt's charset. The default value is utf-8
*/

//function getData(url,onSuccess,onFailure,onTimeout,erase,timeout){
function getScript(url,options){
 var script=document.createElement("script"), T, Now=+new Date(), timeout=options.timeout||3000, erase=options.erase||true,
 head=document.getElementsByTagName("head")[0], isTimeout=false;
 script.charset=options.charset||"utf-8";
 if(url.indexOf('?')==-1){ // avoid the browser cache the data.
 script.src=url+'?t='+(+new Date())+'&emp='+emp;
 }else{
 script.src=url+'&t='+(+new Date())+'&emp='+emp;
 }    

 script.onload=function(){ // Handle the onload event
 if(options.onSuccess&&(!isTimeout)){
 options.onSuccess();
 }
 if(erase&&(!isTimeout)){
 head.removeChild(script);
 }
 clearTimeout(T);
 };
 script.onerror = function(){ // Handle the onerror event
 if(options.onFailure){
 options.onFailure();
 }
 clearTimeout(T);
 head.removeChild(script);
 };
 head.appendChild(script);
 T =  setTimeout (function(){ //Handle the timeout event
 if(options.onTimeout){
 options.onTimeout();
 }
 isTimeout=true;
 head.removeChild(script);
 },timeout);
}

比较简单,看看就懂了,我就不解释了,有什么意见和建议可以联系我,在 About Me 页面可以找到我的联系方式。

移动互联网终端的touch事件和click事件

昨天晚上学习并分享了手持设备浏览器的 touchstart, touchend, touchmove 事件,突然又想到这个事件和 click 事件在应用的过程中是否会有冲突呢?

如果我们允许用户在页面上用类似桌面浏览器鼠标手势的方式来控制WEB APP,这个页面上肯定是有很多可点击区域的,如果用户触摸到了那些可点击区域怎么办呢?

带着这些疑问,我测试了我的 Android 1.5,发现系统已经很好的帮我们处理好了,具体说来,当明显的手指在屏幕上滑动,是不会触发 click 事件的,当明显的点击的时候,同时出发 click 事件和 touch类事件。有了这些我们已经很方便的控制程序来做我们想做的事情了,我们可以通过 touchstart事件和 touchend 事件的 pageX,pageY属性来判断用户到底是想做什么操作了,如果偏移值很大,很明显的就是滑动操作了,如果偏移很短,就不做操作,这个时候很有可能是会触发click事件了。

测试例子在:http://lab.allenm.me/touch_click.html,和上一篇文章一样,你可以使用你的移动手持设备去访问,测试。

touchstart, touchend, touchmove 与移动互联网开发

你为移动互联网准备好了吗?

大家都可以感受到iPhone,Android,iPad带来了移动互联网革命,前端程序员们,大家准备好了吗?移动互联网早已不是那个WAP的时代,WAP基本被淘汰了,新一代的智能移动终端,都具有了标准的HTML,CSS,JS的解析能力,但是又和桌面不同,比如这样的终端是没有光标的,也就是我们平时经常用到的 mouseover这些光标相关的东西,在这些终端上是没有作用的。同时,这些终端又具有一些他们自己的特色,比如 touch 事件,因为操作全是 touch ,所以这个事件如果好好利用,在移动互联网开发中会发挥大用处的。

正好现在手里有个 Android 设备,做毕设用的,现在也想为毕设搞点新鲜内容,所以就做了对 Android 浏览器的 touch 事件的测试。说了这么多,是因为这个东西很简单,不说点废话撑下门面,那这个博文就太短了。

touth相关的事件有 touchstart,touchend,touchmove。这三个事件最重要的属性是 pageX和 pageY,表示X,Y坐标。

其中 touchstart 在开始触摸的时间激发, touchend 在触摸结束的时间激发, touchmove 这个事件比较奇怪,按道理在触摸到过程中不断激发这个事件才对,但是在我的 Android 1.5 中,在 touchstart 激发后激发一次,然后剩余的都和 touchend 差不多同时激发。

这三个事件都都有一个 timeStamp 的属性,查看 timeStamp 属性,可以看到顺序是 touchstart -> touchmove ->touchmove -> … -> touchmove ->touchend,但是在我这里测试实际看到的确是上一段中提到的那样。

现在 Android 官方也没有一个好的关于 WEB APP 的文档,不过有了 touchstart, touchend 这两个事件的 pageX,pageY,timeStamp 属性已经可以开发出好玩的东西了。比如我想让用户通过划屏幕,来做 tab 切换。

关于 touch 事件的测试,你可以用你的 iPhone,Android,iPad访问 http://lab.allenm.me/touch.html,然后触摸屏幕,看结果。注意由于为了完整展示效果,所以对屏幕宽度有要求,最好横批浏览。同时,预告一下,我以后会用 allenm.me 这个域名,等有空了做迁移。

另外附上apple 官方关于这个事件的文档.有兴趣的可以去看看,iPhone 可是支持多点触摸的,在这个文档里,我们也可以看到处理多点触摸的方法。