TAG | javascript
location 对象,一个不太起眼的对象。
对于这个 URL “http://localhost:8000/lab/index.html?key=1#fly”,它包含了下面的属性。
hash: #fly host: localhost:8000 hostname: localhost href: http://localhost:8000/lab/index.html?key=1#fly pathname: /lab/index.html port: 8000 protocol: http: search: ?key=1
这篇文档里面有相关属性的说明。不过还有一点不太起眼的地方,hash 和 search 的先后顺序。
作为对比,有“http://localhost:8000/lab/index.html#fly?key=1”
hash: #fly?key=1 search:
各个浏览器上表现都是一样的,hash 先出现的话会吃掉 search 的,这让我有点费解,这个顺序还真那么重要吗?
当我们点击一个锚点的时候浏览器会在整个 URL 之后加上 hash的,这样似乎也挺好理解的。
只是如果需要手工拼接 URL,那就需要注意一下这个顺序了。
分析新的需求,确实需要增强 jscombiner 了。之前写了一半,时间不够,大家急着看效果。今天把另外一半补上。再修补一下,可以升级到 0.4-SNAPSHOT 了。工具,好用是最实在的。
这次主要是增强了 classpath 的概念。原先使用时不需要设置根类路径,根据文件中的 @class 声明来反向定义根类路径。相应地,入口调整为 buildHierarchy(String className)。外表看来变动很小,而且实现着一样的效果,但是里面整个寻址方式都变了。
代码本身没什么值得深究的。不过结构变了。几番修改,有些共通点:
- 重要的功能需要规划
- 实现的进度是看需求的
- 不到最后不得不修改结构,不会乱动
- 测试用例需要有一定的抽象
- 重构很重要
- 结构发生变化,重写比重构快
java · javascript · jscombiner · tools
为自己写个工具 Jscombiner,除了帮助自己高效完成任务,可以自娱自乐,还可以开源,和别人分享。
这个工具前一个版本是命令行方式的,一方面技术所限,另一方面是一直坚持认为前台和后台开发是可以而且应该分开进行的。
当然思考的结果是会变的,该分开的还是分开,但有时后台也应该为前台开发提供一些帮助,而单纯的apache httpd提供不了,也有想过用asp, php,但是按照目前的态势,java应该更熟手一点。而且我主要是想和公司的其它同事分享这种开发方式,他们的机器上大多有tomcat而没有apache。
Jscombiner 的基本目标就是减少程序员开发 javascript 项目时所需要投入的管理成本。让 javascript 脚本采取自描述的方式来方便进行管理。因此,开发者可以在开发过程中,随心所欲地更新脚本,而无需担心重构或者脚本规模增长所带来的管理成本。是不是已经有同样的东西存在,我不确定,有是正常的,多个选择,多个竞争,才会有进步的。也许是有开发者像我这样不善推广的。
现在的状况是基本功能以及有了,自己一直在用,我期望的使用感受是感觉不到它的存在。
不过,不是为了做这个东西而做的,还有其它的目标任务,而且只有我一个人在瞎搞,而且主要的更新都是针对自己的实际应用需求的(我相信我所遇到的问题,其它人也会在某个时刻遇到),所以更新会比较慢。
等当前任务完成了,再做一个example出来。不过其实想想也很简单的,mvn archetype:generate一个项目出来,加入依赖,将脚本丢进去,然后mvn jetty:run,立马就可以着手开发了。
关于这个项目的改进计划会在Issue里面发起。目前想到的一个就是先实现前一版本中的一个功能,合并大脚本,这个应该不难,只是怎么提供出来,会更实用,更有趣呢?敬请期待啦!
java · javascript · opensource · tools
在一些应用中,可能会有需要知道某一个控件在页面中的位置,在网上比较容易找到下面这个解决方法。
在页面中有一个按钮:
<input id=a type="button"
value="click me to get position"
onclick="getPos(this)"/>
在脚本中响应点击事件的是这个函数:
function getPos(e){
var t=e.offsetTop;
var l=e.offsetLeft;
while(e=e.offsetParent){
t+=e.offsetTop;
l+=e.offsetLeft;
}
alert("top="+t+"\nleft="+l);
}
这个方法的实现相当精炼,意思就是先取得自己的相对位置,再叠加其最近的相对位置容器(offsetParent,它不一定是其父节点)的相对位置,直至顶层位置容器(一般就是body),从而得出该节点的相对位置。
不过,很快就发现这个函数也许并不够用,因为我在页面里有可能使用了一些CSS来使得本来平铺的画面变成一个滚动区域,例如设置了父节点的height为某个值,并且其overflow设为auto或者scroll。这时,上面的方法因为没有计算其滚动偏移,所以所得的值不一定是元素当前的绝对位置,所以我对上面的方法进行了一些小改动,实际上就是加入了对其滚动偏移量的计算。
function getPosition(sender) {
var e=sender,E=e;
var x=e.offsetLeft;
var y=e.offsetTop;
while (e=e.offsetParent) {
var P=e.parentNode;
while (P!=(E=E.parentNode)) {
x-=E.scrollLeft;
y-=E.scrollTop;
}
x+=e.offsetLeft;
y+=e.offsetTop;
E=e;
}
return {"x":x,"y":y};
//alert("top="+y+"\nleft="+x);
}
稍微测试了一下,基本上在IE和FF中都能正常运转。
在运用方面,由于使用到往上遍历,如果节点树结构过于复杂,而且有不会有滚动出现的话,那么还是用第一个方法就足够了。
注:又是炒冷饭 -_-! 原作发表于 2007-09-08
不经意地被一位同事问起在javascript里面如何检测右键事件,并且屏蔽原来的右键菜单,上网查找一翻之后发现一些比较简单的方法。
如设置onmousedown,检查其event.button的值是不是2(代表右键)。
这个方法在FF和IE中都可用,但是在Maxthon中event.button却是0,这让我有点困惑,Maxthon不是IE内核的吗?
我只能设想Maxthon这个壳是做过手脚的。然而如果设置onmouseup,其event.button值就是2了。
所以如果检测右键的话,是设置其onmouseup即可。
document.getElementById("test").onmouseup=function(oEvent) {
if (!oEvent) oEvent=window.event;
if (oEvent.button==2) {
//-- do something for user right click
// alert("Mouse up");
}
}
但是如果还需要屏蔽右键的话,还是用oncontextmenu比较简单,但这时就不是检测右键,而是检测是否弹出上下文菜单。
屏蔽的方法跟屏蔽其他默认行为的方法是一样的,一般来说都是有效的,不过因为某些浏览器有禁止禁止弹出右键菜单的功能,所以如果需要在用户点击右键时做点事情,最好还是不要放在oncontextmenu中,而是放在onmouseup中并检测右键,附加oncontextmenu来屏蔽原来的菜单。
document.getElementById("test").oncontextmenu=function(event) {
//-- do something here
// alert("ContextMenu Popup");
//-- prevent the default behavior
if (document.all) window.event.returnValue = false;// for IE
else event.preventDefault();
};
通过一些简单的测试,可以发现在FF和在IE存在着有趣的区别。
在onmouseup和oncontextmenu事件处理中都使用alert,可以看出来是先执行onmouseup事件再到 oncontextmenu的,在IE中,两者会非常连贯的在一起执行,(均认为是发生在test元素上的事件),而在FF里面则不是(前提是test元素所占区域比较小,当alert弹出时需要移动鼠标才能点击’OK’的情况下),它会先执行onmouseup,alert出来之后,移动鼠标点击 ‘OK’,这时还是会弹出菜单的,但是如果不移开鼠标,而是直接按Enter确认的话,这时它就会认为是在test元素上触发的事件了。可以理解为是IE 和 Firefox中的事件机制的细节区别。当然我们很少会应用到连续事件的,就无须注意到这点区别了,把需要的事件处理完整的写在一个处理方法里面就是最简单有效的解决方案了。
PS: 这post是很久之前(2007-09-06)发布在旧blog里头的,现在拿出来翻炒一下~

Bezier Curve in Canvas
只是靠直线和弧线来画图是不够的,面对一些更加复杂但是有规律的图形时,我们需要另外一个更加强而有力的工具——贝塞尔曲线。
在 canvas 里,贝塞尔曲线有二次方和三次方两种形式,区别就在于控制点的数目,二次方的只有一个控制点,而三次方的则有两个。
quadraticCurveTo(cp1x, cp1y, x, y) bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)
这里的贝塞尔曲线的用法同样的很简单的,但是却不像在 Photoshop 或者 Illustrator 里画曲线那么轻巧了,它是非常需要触觉和耐心的。所以上面强调了有规律这个关键词。
实际上是什么感觉,大家亲身体验吧,看看下面的示例。
var colors = ["ec460c", "1e951e", "2692f7", "ea62f6"];
var cp1 = 60;
var cp2 = 160;
var y1 = 150;
for (var i=0; i< colors.length; i++) {
ctx.beginPath();
ctx.moveTo(25,75);
ctx.bezierCurveTo(85,75,cp1,y1,100,y1);
ctx.bezierCurveTo(cp2,y1,120,35,190,35);
cp1 -= 10; // 使控制点 1 横坐标左移 10 像素
cp2 += 10; // 使控制点 2 横坐标右移 10 像素
y1 += 20; // 是曲线低点下降 20 像素
ctx.strokeStyle = "#" + colors[i];
// 设置线条颜色
ctx.stroke();
}
如图,我比较懒,里面用了循环画了几条曲线,一阵颜色一条,不同在于曲线的底端位置与曲线口径的不同。
你觉得像个什么呢?
这里并没有用到二次方形式的贝塞尔曲线,但是三次方的都会用了,二次方的还不行吗?
PS: 以上代码是主体部分,要运行上面的例子,请参考 进驻Canvas
在 canvas 里绘制弧线也是非常简单的。
arc(x, y, radius, startAngle, endAngle, anticlockwise)
x, y 是圆心坐标,radius 是圆弧的半径,startAngle,endAngle分别是弧线起点和终点的弧度,注意,是弧度,而不是度,它们之间可以通过
function radian(degrees) {
return (Math.PI/180)*degrees
}
来进行转换。最后 anticlockwise 用于指示弧线绘制的方向,true 时表示逆时针方向。
var canvas = document.getElementById('myCanvas');
var size = 220;
canvas.width = canvas.height = size;
if (canvas.getContext) {
var ctx = canvas.getContext('2d');
var X = size/2;
var Y = size/2;
var N = 12;
var R = 80;
for (var i=1; i<=12; i++) {
var r = 20;
var startAngle = 0 - Math.PI/2;
var endAngle = radian(360*(i/N)) - Math.PI/2;
var x = X + R*Math.cos(endAngle);
var y = Y + R*Math.sin(endAngle);
var anticlockwise = false;
ctx.beginPath();
ctx.arc(x, y, r, startAngle, endAngle, anticlockwise);
Math.cos(endAngle + Math.PI/2) > 0 ? ctx.stroke(): ctx.fill();
}
}

Arc
这样一个按顺时针变化的大圆就完成了。
完成上面的例子大概需要一点基础数学知识。
可以随意改变里面的圆的半径和起始、结束弧度又或者最后的控制填充与描边的算式来感受一下那种变化。
在了解 如何绘制 canvas 里唯一的基本形状——矩形 之后,现在可以转入使用路径了。路径不仅仅是线条,只是习惯性的会从线条开始,而直线可算是最简单的线条了。
使用路径,是有套路的。
创建、绘制、关闭和填充或者勾勒形状。
beginPath() // actual drawing closePath() stroke() fill()
两点确定一条直线,在 canvas 里也不例外的。
lineTo(x, y)
x, y 只是终点的坐标,起点坐标是隐含的,并且取决于前一操作。简单来说,上一操作的终点就是当前直线的起点。新建 canvas 时起点就是原点。另外还可以通过这个方法:
moveTo(x, y)
来移动起始坐标。moveTo 顾名思义就是移动坐标用的,它一般用于重设起始坐标,大多数情况下路径都不是起于原点的,而且在绘制一些不连续的路径时非常有用。
var x = 60; var y = 20; var r = 40; var dy = Math.sqrt((r*2)*(r*2)-r*r); ctx.beginPath(); ctx.moveTo(x, y); ctx.lineTo(x-r, y+dy); ctx.lineTo(x+r, y+dy); ctx.fill(); var y = y + 2*(Math.sqrt(4/3)*r); ctx.beginPath(); ctx.moveTo(x, y); ctx.lineTo(x-r, y-dy); ctx.lineTo(x+r, y-dy); ctx.closePath(); ctx.stroke();

2-triangles
上面的代码绘制出六角星,moveTo 这里是移动起始坐标又用于创建两条不连续的三角路径。
三角几何的内容就不解析了,重点是使用填充和描边的区别。填充是会触发路径的自动闭合的,而描边则不会。
这里可以看出在这个套路里 closePath 不是必须的。如果描边之前不闭合路径,所勾画出来的将会是开放的路径,在这个案例里就是只画两边。
canvas · html · javascript
在show图片的时候经常需要用到它,大概是大家都比较懒去为每个图调整大小,用Photoshop的批处理是可以省掉很多功夫了,但为求更省事,用脚本啦。
<img id="inu" src="your-image-path.jpg" onload = "resize(this)" />
在页面里插一行就可以了(resize函数体在下面),如果图像是动态装载的
var img = document.createElement('img');
img.onload = resize;
img.src = "http://farm4.static.flickr.com/3575/3343323074_499a81709c_o.png";
var MAX_IMAGE_SIZE = {
x: 400,
y: 400
};
function resize(obj) {
var img = obj || this;
var size = MAX_IMAGE_SIZE;
var rate = img.offsetWidth / img.offsetHeight;
if (img.offsetWidth > size.x) {
img.style.width = size.x + "px";
img.style.height = size.x / rate + "px";
}
if (img.offsetHeight > size.y) {
img.style.width = size.y * rate + "px";
img.style.height = size.y + "px";
}
// 如果有需要居中的话,用CSS也可以实现
reCenter(img, size);
}
function reCenter(img, size) {
if (img.offsetHeight < size.y) {
img.style.marginTop = (size.y - img.offsetHeight)/2 + "px";
}
if (img.offsetWidth < size.x) {
img.style.marginLeft = (size.x - img.offsetWidth)/2 + "px";
}
}
网上也有不少说onload事件在不同浏览器里的不同现象,Netscape(国内没什么人用了吧) 和 IE不是每次都触发onload事件,只有从网上下载时才执行,从缓存里装载后不执行。
那应该是因为设置onload在设置src之后,设置src后浏览器随即从缓存里装载好了,那时再指定onload为时已晚了,但在Firefox里在前在后都始终如一。最稳是在src之前设置onload属性。
html · img · javascript

