晚上在Google Code上闲逛,发现google gears从0.4版开始新增了一个获取当前地理位置的Geolocation API。试用了一下,居然准确地找到了我的当前位置,很神奇~401705e0-e662-44bc-9099-72c5e4ad7ed7

相信大家对Google Gears都已经很熟悉了,三大主要功能:本地存储DataBase、本地服务器LocalServer、任务池WorkerPool,外加一个桌面相关功能DeskTop。如果对Gears的这几个特性不是很了解,可以参看老六写的这篇文章。在我这篇文章里只讨论Geolocation API。

这个API使用起来很简单,下面简单的列一下:

var geo = factory.create('beta.geolocation'); //创建geolocation对象
var okCallback = function(d){
 
alert('当前位置(纬度,经度): ' + d.latitude + ',' + d.longitude);
};
var errorCallback = function(err){
 
alert(err.message);
};
geo.getCurrentPosition(okCallback , errorCallback);

基本就是这样了,在这之前创建factory以及获得permission的代码就不贴了,看下源码就OK了。

另外,随手抓包看了下这个API实现的原理,其实就是post当前用户的一些信息给google服务器(我这里,post的数据有wifi的ssid,信号强度啥的),服务器就会返回当前的位置,这个技术上没啥特别的,关键在于数据的丰富程度与是否精确。

request:
post http://www.google.com/loc/json
data { “access_token” : “2:FIcsRNHXKylSLYpZ:N7RPZxjyMIX4AiMb”, “host” : “test.*.*.com”, “radio_type” : “unknown”, “request_address” : false, “version” : “1.1.0″, “wifi_towers” : [ { "mac_address" : "00-*-*-*-*-ea", "signal_strength" : -73, "ssid" : "***" }, { "mac_address" : "00-*-*-*-*-aa", "signal_strength" : -80, "ssid" : "*-*" }, { "mac_address" : "00-*-*-*-*-ba", "signal_strength" : -44, "ssid" : "*-*" } ] }

response:
{“location”:{“latitude”:40.050772,”longitude”:116.308348,”accuracy”:150.0}}

大家有空也可以试一试,看Gears能不能找到你的位置(需要先安装Google Gears,Chrome已经内置了Gears)。点这里

什么是图片平均色呢?我也不知道它的准确定义,总之看下面这张图就明白了。

2009-09-02_014118算出图片平均色有用么?用来做马赛克拼图的时候肯定会用到。另外,网上也有不少依赖于这个的应用:

Multicolr Search Lab,一个能够根据所选颜色找到flickr图片的网站,计算图片平均色应该是其关键的步骤。

live照片的幻灯片播放界面,背景会随着图片的切换而变化为当前图片的平均色填充,看上去很是自然、和谐。

还有Opera Mini,优秀的手机浏览器,细心的同学可能会发现它显示未下载的图片时,会用一个色块代替,当然这个色块也是该图片的平均色。

那么,怎么能得到图片的平均色呢?这里有煎蛋提供的一个可以完成该功能的软件,这里还有个国外的网站也提供了类似的功能。这些桌面程序或者后端程序的实现不属于这篇文章的讨论范畴了,用flash在前端同样可以实现,比如说下面这个Demo。


我采用的方法是JK提出的。取到图片后,分别读取图片每一个像素R、G、B三种色值,累加起来再除以图片总像素数,也就是图片宽×高,得到R、G、B的平均值,继而得到平均颜色。核心的代码在下面:

var imgData:BitmapData = Bitmap(img).bitmapData;
var red:Number = 0, blue:Number = 0, green:Number = 0;
for (var i = 0 ; i < imgData.width; i++) {
    
for (var j = 0 ; j < imgData.height; j++) {
        
var pixel:Pixel = new Pixel(imgData.getPixel32(i, j));
        
red += pixel.getRed();
        
green += pixel.getGreen();
        
blue += pixel.getBlue();
    
}
}
var area:Number = imgData.width * imgData.height;
var c:ColorTransform = new ColorTransform(1, 1, 1, 1,red/area, green/area , blue/area, 0);///
 
trace(c.color);
 
//Pixel
package com {
    
public class Pixel {
        
private var value : uint;
        
public function Pixel(n1:uint) {
            
value = n1;
        
}
        
public function getRed():int {
            
return (value >> 16) & 0xFF;
        
}
        
public function getGreen():int {
            
return (value >> 8) & 0xFF;
        
}
        
public function getBlue():int {
            
return value & 0xFF;
        
}
    
}
}

原理很简单,代码也不多。关键是看应用场景了,用得好的话很容易就起到锦上添花的效果~

很早之前,写过一篇“跨浏览器“复制到剪贴板”的解决方案”,当时给出的解决方案是,IE使用window.clipboardData,firefox等其它浏览器使用flash来调用System.setClipboard方法。但是,随着Flash10安全策略更新,只允许在flash内部调用setClipboard方法,那篇文章给出的demo已经失效。我重新写了一个demo,见这里

新demo是在flash内部调用的setClipboard方法,原则上安装了flash的浏览器都可以用;另外,IE7及以上版本用js调用clipboardData会弹出选择是否允许的提示,往往初级用户看到这样提示还以为网站有病毒,所以这次一视同仁所有浏览器都用flash写剪切板。原理比较简单,大概说一下:

  • 页面上提供两个js方法getData和copySuccess供flash调用,getData返回需要复制的内容,copySuccess是复制成功的回调函数;
  • 往flash里添加一个任意的DisplayObject,例如TextField,注册Click事件,事件响应函数里先调用页面上的js方法getData得到粘贴内容,再用System.setClipboard写入剪切板,最后通知页面上的copySuccess。

完整的代码见这里。如果要个性化提示文字,打开clipboard.as,修改后编译即可。

刚开始学习flash as3编程,一些学习笔记也丢上来吧,现在还都是些初级的话题~

Flash已经提供了ExternalInterface接口与JavaScript通信,ExternalInterface有两个方法,call和addCallback,call的作用是让Flash调用js里的方法,addCallback是用来注册flash函数让js调用。下面是官方文档对call和addCallback的说明:

利用 ActionScript,可以在 HTML 页上执行以下操作:

  • 调用任何 JavaScript 函数。
  • 传递任意数量、具有任意名称的参数。
  • 传递各种数据类型(Boolean、Number、String 等等)。
  • 接收来自 JavaScript 函数的返回值。

通过在 HTML 页上使用 JavaScript,可以:

  • 调用 ActionScript 函数。
  • 使用标准的函数调用表示法传递参数。
  • 将值返回给 JavaScript 函数。

实际使用的时候,需要注意以下两点:

一、调用时机。js调用flash对象提供的函数时,可能swf还没有完全加载完,此时调用会失败。类似的,flash调用js函数时,也存在js函数还没load到的情况。所以adobe官方示例里采用了一种比较绕的逻辑来避免这种问题:

  • 页面上有一个变量_isJSReady,初始为false。还有一个isJSReady函数用来返回_isJSReady的值,供flash调用。在合适的时机(例如:window.onload),将_isJSReady设置为true,表示flash可以使用js里的函数了;
  • flash里有一个定时器,定期(例如:100ms)去调用页面上的isJSReady方法,直到isJSReady返回true,就可以addCallback,调用页面上的flashReadyHandler方法,通知页面可以跟flash交互了。

二、如何获取flash对象。将flash插入到页面有很多方法,例如swfobject.js或者AC_RunActiveContent.js类似的库。我们来看一种最原始的方法,直接在html插入标记来插入flash:

<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000"
codebase="http://download.macromedia.com/pub/shockwave/cabs
/flash/swflash.cab#version=10,0,0,0
" width="730" height="520" id="test" align="middle">
<param name="allowScriptAccess" value="sameDomain" />
<param name="allowFullScreen" value="false" />
<param name="movie" value="test.swf" />
<param name="quality" value="high" />
<param name="bgcolor" value="#ffffff" />
    
<embed src="test.swf" quality="high" bgcolor="#ffffff"
    
width="730" height="520" name="test" align="middle"
    
allowScriptAccess="always" allowFullScreen="false"
    
type="application/x-shockwave-flash"
    
pluginspage="http://www.adobe.com/go/getflashplayer_cn"
    
/>
</object>

假设test.swf提供了hello的方法,我们来在js里调用这个方法,代码如下:

document.getElementById("test").hello();

结果,除了IE之外,其他浏览器都不会工作,会提示找不到hello这个方法。这个问题困扰了我比较久。最后发现:在非IE浏览器里,flash提供的方法是加在embed上的,我们要得到object下的embed对象,调用embed上的方法才会成功!

官方示例是采用下面方法获取flash对象的:

function getFlashMovieObject(movieName){
 
if (window.document[movieName]){
    
return window.document[movieName];
 
}else if (navigator.appName.indexOf("Microsoft")==-1){
    
if (document.embeds && document.embeds[movieName])
    
return document.embeds[movieName];
 
}else{
    
return document.getElementById(movieName);
 
}
}

这里罗列出所有情况,当然不会有问题。其实,没必要弄得这么复杂,我们给object和embed取不同名称,例如一个test1,一个test2,如果是IE就getElementById(“test1”),其它浏览器getElementById(“test2”)就行了。另外,如果使用js插入swf的话,很可能js里就已经做过判断,根据不同浏览器来输出object和embed其中一种。总之,如果调用失败,首先检查得到的flash对象是不是[object HTMLEmbedElement]。

最后,放上一个例子,是我参照官方文档写的。

点击这里

补充一个细节:在傲游里,刷新页面后js调用flash里的方法可能会失败。这篇文章有提到这个问题,解决方法是给swf地址加上随机数,让浏览器每次都重新加载flash。不过这样swf就不能被浏览器缓存,很无语~下面是一段判断傲游的js代码,建议只针对傲游加随机数。

var isMaxthon = false;
try {
    
if (external.max_language_id != undefined){ 
        
isMaxthon = true;
    
}
}catch (e){}
alert(isMaxthon);

呃,好久没更新博客了,一来最近确实比较忙,二来人也变懒了,这样很不好~要改变!

今天要讨论的话题是多页面数据同步。顾名思义,就是更新A页面的内容,B页面也要及时反映出改变,当然A、B页面至少要是在同域下才有意义。放在WebIM这个应用场景中来,就是如果有多个页面开着webim,其中任何一个页面上发送或接收到新消息,要在其他所有的页面中同步显示出来。

使用短连接的WebIM要轮询服务器来获取最新的消息,如果多个页面一起轮询,那么对服务器的消耗还是很大的,使用页面数据同步能减少消耗:无论同时存在多少个聊天页面,同时仅有一个主页面A负责与服务器轮询;在主页面A轮询到服务器消息后,分发给其他页面B、C、D;如果A被关掉了,检测程序会马上检测到,并从剩下的页面中挑选新的页面充当主页面负责通讯;客户端向服务端提交数据不必通过主页面,直接向服务器提交即可,只是提交的数据也需要分发给其他页面,便于同步UI。

多页面数据同步的实现细化起来,大概就是下面这个流程:

1.有一套本地化存储方案,要求能本域下任何页面都可以读写数据,至少有set和get两个方法;

2.页面加载时实例化sync对象,可以指定页面代号(name)方便进行页面分组,系统自动给每个页面分类一个唯一标识符(clientId),并将这个页面client的相关信息加入clients列表,放进本地存储;

3.sync提供sendMsg方法,可根据clientID发给单一页面,或根据name发给一组页面,传递的数据(data)是一个JSON,sendMsg方法不关心data的实际结构。将消息的发送者clientID、接收者clientID以及data拼成一个消息JSON,并将多条JSON组成一个消息数组,序列化后放本地存储;

4.sync通过定时检查并解析本地存储获知是否有新消息,在收到发给自己(根据clientID来判断)的消息后,将这条消息置为已读,并调用onNewMsg方法,将data作为参数传递给它;

5.页面unload的时候,需要调用remove方法将自己从clients列表中去除。

另外,每个页面都必须有一个定时器,用来更新clients列表,检查新消息以及删掉已读消息。

新版本的webim应该会采用这种方案来实现多页面聊天,具体代码我也没写完,不过从下面的demo已经可以看到基本思想:打开多个页面,在文本框写些文字,点击同步按钮,其它页面会同步更新文本框。

var sync = new MsgSync("test2"); //test2是页面名称(name)
sync.onNewMsg = function(e){    //处理其它页面发过来的消息
  $
("txt").value = e.message;
};
$
("send").onclick = function(){ //向页面名称同为test2的其它页面发送消息
 
sync.sendMsg({
    
name:"test2",
    
data:{
      
message:$("txt").value
    
}
 
});
 
return false;
};

点这里打开Demo

该方案的让人头疼的是用什么做本地存储:userData需要页面完全一致才能使用;globalStorage只有firefox和ie8才支持;flash、google gear需要装插件,database storage是html5里的内容也没几家支持。这样看来似乎只有session cookie比较靠谱,但众所周知每次与服务器的交互都会带上cookie,cookie本身的容量也很有限。但是,相比较每个页面都轮询服务器而言,就算用cookie存也还是值得的。

今天,一朋友发我一网站,看了差点没让我吐血。。。那网站把我开源的webim拿去弄成英文版的,对我这个作者只字不提,还弄了官方网站、博客和BBS。相比之下,反倒弄得像我是冒牌货似的。

既然完全公开了源代码,其实我早就预料到这样事情的发生,尽管我在说明里写上“仅供学习和研究使用,严禁用于任何商业用途”,但是对于很多站长拿这个去做盈利网站我也就默认了,偶尔还会无偿提供一下技术支持。

只是,我一再强调,我这个webim只有前端是用心写过的,后端无论是asp还是php都是随便写的,如果要用在非测试环境中,一定要重写后端代码,否则服务器会吃不消,性能也是一个问题。但是这个骗子网站在未征求我的同意情况下拿我的demo去骗钱,收39$的服务费,这不能不让人愤慨~

真是林子大了什么鸟都有了。查了一下whois,好像是北京朝阳通州?

附上骗子网址:www.fleaim.com

update:估计这骗子订了我blog的rss,在写这篇文章的不久,刚刚网站已经被解析到其他网站了。幸好我已经将所有相关资料都截图了,不知道是不是换其他域名继续坑人,我会持续关注的。

PS:新建了一个QQ群,欢迎对我这个webim或者是前端技术有兴趣的同学加进来探讨,我会抽空解答大家的疑问。
QQ群号:87485493
51JS讨论贴:http://bbs.51js.com/viewthread.php?tid=77474&extra=page%3D1&page=1

今天无意中看到一个画面还不错的webgame,随手右键单击看是否用flash写的(我对flash的判断标准是看右键菜单有无About Adobe Flash Player…字样),但是点了居然不出任何菜单。记得之前看到要完全干掉flash右键菜单要用到一些很WS的方法,不知道这个webgame怎么实现的。看了一下它的代码,原来是在flash父容器里做文章:firefox下阻止mousedown默认事件及事件传播;IE下给父容器setCapture。摘录核心代码稍加改造就是下面这个样子:

function NoRightClick(pid){//pid:flash's parentNode id
 
var el = document.getElementById(pid);
 
if(el.addEventListener){
 
el.addEventListener("mousedown",function(event){
  
if(event.button == 2){
    
event.stopPropagation(); //for firefox
    
event.preventDefault()//for chrome
  
}
 
},true);
 
}else{
 
el.attachEvent("onmousedown",function(){
  
if(event.button == 2){
    
el.setCapture();
  
}
 
});
 
el.attachEvent("onmouseup",function(){
  
el.releaseCapture();
 
});
 
el.oncontextmenu = function(){
  
return false;
 
};
 
}
};
<body>
 
<div id="testContent" style="width:800px">
 
</div>
 
<script type="text/javascript">
  var so = new SWFObject("test.swf", "t1", "800", "550", "9", "#000000");
  so.addParam("quality", "high");
  so.addParam("name", "t1");
  so.addParam("id", "t1");
  so.addParam("algin", "middle");
  so.addParam("AllowScriptAccess", "sameDomain");
  so.addParam("menu", "false");
  so.addParam("wmode", "opaque");
  so.addParam("pluginspage", "http://www.adobe.com/go/getflashplayer");
  so.write("testContent");
 
  NoRightClick("testContent");
 
</script>
</body>

经过试验,该代码可以在IE、Firefox和Google Chrome里去掉flash的右键菜单,还是挺方便的。至于这样做有什么意义呢?我暂时还没有想到——但网上搜索一下,有这种需求的人还是不少的。

演示地址

很多情况下,我们需要跨域读取数据,或者是调用别人json格式的api,都要用到js callback这种机制。通常做法是页面上定义一个A方法,再调用第三方的url并且把回调函数名A传过去。这样做固然没什么问题,但有没有更好的方法呢?

用过jQuery的同学肯定都知道,jQuery有一个getJSON的方法,只需要两个参数(callback地址和匿名函数)就能正常工作。摘录官方示例如下:

$.getJSON("http://api.flickr.com/services/feeds/photos_public.gne?tags=cat&tagmode=any&format=json&jsoncallback=?",
 
function(data){
  $.
each(data.items, function(i,item){
   $
("<img/>").attr("src", item.media.m).appendTo("#images");
  
if ( i == 3 ) return false;
 
});
 
});

很神奇吧!在这个例子中,我们并不用指定callback函数名,回调也仅仅只是一个匿名函数而已,那么它是怎么完成函数调用的呢?去看下jQuery的源代码,你会发现其实原理很简单。如果懒得看太多代码,看看下面我写的山寨版loadJSON,也能明白。

function loadJSON(url,callback){
 
var cn = "jscallback" + (+new Date());
 
var s = document.createElement("script");
 
s.type = "text/javascript";
 
s.src = url + cn;
 
window[cn] = callback;
 
document.getElementsByTagName("head")[0].appendChild(s);
};
loadJSON("http://api.flickr.com/services/feeds/photos_public.gne?tags=cat&tagmode=any&format=json&jsoncallback=",
 
function(o){
 
for(var i=0;i<3;i++){
  
var img = document.createElement("img");
  
img.src = o.items[i].media.m;
  
document.body.appendChild(img);
 
}
 
});

演示地址

之前写过一篇文章跨浏览器“复制到剪贴板”的解决方案,里面讲到在Firefox下如果安装了Flash,就可以利用js调用flash的setClipboard方法来将一段文字写进系统剪切板。但是最近发现,在很多电脑上这种方法已经失灵了。研究了一下,发现是flash10更新了安全策略:新的策略只允许在flash内部调用setClipboard方法,利用js调用无效;当然,如果在flash里添加事件来执行setClipboard是在允许范围内的。

除了setClipboard的更新外,还有一个比较大的更新就是:FileReference.browse和FileReference.download将只能通过Flash 内容响应鼠标或键盘的操作来使用。也就是说类似于SWFUpload一类的通过js来打开文件选择框的应用将会无法工作!SWFUpload官方采用”在SWF中引入一个按钮,用户点击此按钮来触发文件选择对话框的显示”的方案解决的这一问题。

Flash的这次安全升级给我的启示:最好不要用一种语言来做本来不属于他做的事(例如利用window.name跨域传输数据等等),尽管这些Hack在很多情况下很好用,但也最容易因为某次更新升级而失效。

参考:
http://www.jeffothy.com/weblog/clipboard-copy#comment-123736
http://www.v-sky.com/blog/?p=227

在测试或者优化web应用时,经常需要替换一些静态资源,如css/image/js等。当然,这些工作是在开发环境来做,直接ftp替换也没什么问题。但有的时候仅仅是想调研一下而不想影响环境的稳定,或者想方便的对比两段代码效果,就可以利用一个小工具来完成工作——Fiddler。

先来简单的介绍下Fiddler(官网|需要.NET Framework v2.0|MicroSoft出品):一个集web性能分析、数据监测、自动响应、创建请求四大功能于一身,自带众多实用小工具,支持插件扩展的HTTP调试工具。通过简单的配置代理(IE中全自动、FF中需如下图手动配置),就可以开始使用Fiddler了。

2009-01-11_151753

这篇文章讲的只是利用fiddler来替换静态资源,利用的是它的自动响应功能。选择软件右侧的AutoResponder这个tab,点“Add”按钮来添加一条规则,在Rule Editor里的文本框填上要被替换资源的url,后面的文本框选择源文件就OK了。如下图:
2009-01-11_145020

2009-01-11_144953

搞定,就这么简单,现在只要请求被命中,就会被转发到指定的源文件了。修改代码后保存一下F5就能生效,既方便又不会影响到他人。这个功能挖掘下还可以干一些其它有意思的事情:配置Rule时,选择转发404之类的错误码,就可以用来测试Ajax的onError事件;把类似于Http://www.***.com/1.html这样的url转发到本地页面,就可以在本地代码里用Ajax请求www.***.com的内容。由于本地页面是通过Http://www.***.com/1.html来访问,不再有跨域问题了。这在做一个获取数据程序的时候很有用。虽说最后还是要用后端程序来解决跨域问题,但开发前期利用Fiiddler做转发,非常的高效!

关于我

JerryQu,当前从事前端开发,@中国北京
这里是我随便记录东西的地方~
需要找我,我的联系方式在这里 »
查找QGYWebim相关信息,请点这里 »

  • paper: 这个john很早就在它blog上写了。。。 [...]
  • mzlwjs: 永远支持你,继续努力。 就送给你我们社团的一句话吧:“翥雁暂居于此 [...]
  • yaya: 方式确实有点意思。但感觉意义不是很大啊~还要正则去替换 [...]
  • Jerry Qu: to jswsky: http://www.qgy18.com/lab/clipboard2/,这个demo有所需的所有文件,另存为 [...]
  • jswsky: 你好,好像需要swfobject.js和clipboard.swf这两个文件,能否提供一下?还有, [...]
  • nevermad: 我关注WebIM的服务器端设计,楼主这个是基于ajax吧,性能应该满足不了很大 [...]
  • 基于浏览器的客户跟踪技术概述 | 出家如初,成佛有余: [...] http://www.qgy18.com/2008/05/localstorage/ [... [...]
  • Cherry: 不错呀,你是在做前端吗,我也是湖北的[:13 [...]

共享