web端语法高亮器到底是什么时候开始流行的?

SyntaxHighlighter发表于2007年;SHJS的网站上写着copyright © 2007的字样;google-code-prettify的开源项目主页,最早的反馈亦是Mar 2007;就连jssc的雏形也是出生在2007年初的一堂《编译原理》实验课上。这一年,似乎成为高亮web代码的热潮期。

然而,一切仅仅是开始。随着Yahoo官方采用sh(SyntaxHighlighter),用js编写的它一夜成名。sh的确是目前所有已知web端语法高亮中最出色的一个,许多网站都在使用这家伙,它的地位可以称得上是霸主。不过,开源世界的代码永远是竞争激烈的,其它高亮器如雨后春笋般诞生,互相之间无不在攀比性能、功用、大小等等。记得在jssc 2发表的时候,还是个学生的我就把矛头直指sh,意欲一较高下。当然,结果就是另外一回事了。

时至今日,jssc历经5个版本,各方面都已发展至成熟。然而技术推动却一直只有我一个人,各种因素都有,技术门槛肯定是最重要的一个。于是,我决定开写一个系列文章来介绍web端语法高亮原理——不仅仅是帮助jssc的发展,更是为了共享经验、推动web端高亮技术的进步。本篇文章就是作为序言而写的。

以上即是简单的概念介绍,下面是系列文章的目录,链接不定时更新:

  1. web端语法高亮原理:走进jssc的世界(一)
  2. web端语法高亮原理:走进jssc的世界(二)
  3. web端语法高亮原理:走进jssc的世界(三)
  4. web端语法高亮原理:走进jssc的世界(四)
  5. web端语法高亮原理:走进jssc的世界(五)

十月 30th, 2009

修复了jssc 5 beta中的两个错误

No Comments, jssc, by army8735.

本着想写系列文章《web端语法高亮原理:走进jssc的世界》,介绍jssc的历史和核心算法的,结果无意间发现两个bug,真是丢脸啊。

一是数字高亮的bug,最终处理上居然一小步布尔逻辑写错了,造成长串数字的误认;二是ie下的复制按钮,定位的复制对象居然是“关于”,而不是源代码(少了个parentNode)。

这么明显的失误啊,真想跳楼谢罪了……

话归正题,系列文章会逐步出炉的,将jssc的一切毫无保留地叙述出来。这次失误的收获倒是知晓了其它几个web端的语法高亮器(以前只知道syntax highlighter,貌似也是最知名的),其中最吸引我的是prettify,貌似是google官方的东东。不愧是大佬啊!拿我常用的一段js代码测试了下,prettify是目前我所知js解析器中最为准确的!当然还有一点点小瑕疵,那就是正则的flag(i、m、g)没有跟随正则一起被高亮。不过最让我搞不懂的是,相对简单的flag没有解析正确,反而更难的跨行除法却做到了。

以下是我常用的测试代码,如果web端语法高亮器能解析到jssc的程度,才能够说明是正确了。

//javascript
function none(){
}
/** javascript
jscript
*/
var reg = /[\/][*]([\S\s]*?)(?:[*][/;\[\]]|$)|[\/][/g;](.*)|"((?:\\\\|\\"|[^"])*)"|'((?:\\\\|\\'|[^'])*)'/gm; ''; /\d/.test(1);
reg = 12;
a / b
/reg/;
var num1 = 0.541f;
function f(test) {
    return (test/*
    /* //
    ' "
    { ;
    \*/ && // /* // " ' { ; \
    test &&
    " /* // \
    \" ' \
    { ;" &&
    ' /* // \
    " \' \
    { ;' &&
    test);
}

十月 29th, 2009

获取元素的绝对位置

1 Comment, 前端开发, by army8735.

要说getPosition()的起源,可能没人说得清楚。但是网上广为流传的这段代码,却是许多人使用的:

function getPosition (el) {
    var left = 0, top = 0;
    do {
        left += el.offsetLeft || 0;
        top += el.offsetTop || 0;
    } while (el = el.offsetParent);
    return {x:left,y:top};
}

它真的正确吗?答案当然是否定的。至于为什么,看完Mootools的Element类的相关实现,一切就会大白于天下。

Element.implement({

	getScroll: function(){
		if (isBody(this)) return this.getWindow().getScroll();
		return {x: this.scrollLeft, y: this.scrollTop};
	},

	getScrolls: function(){
		var element = this, position = {x: 0, y: 0};
		while (element && !isBody(element)){
			position.x += element.scrollLeft;
			position.y += element.scrollTop;
			element = element.parentNode;
		}
		return position;
	},

	getOffsetParent: function(){
		var element = this;
		if (isBody(element)) return null;
		if (!Browser.Engine.trident) return element.offsetParent;
		while ((element = element.parentNode) && !isBody(element)){
			if (styleString(element, 'position') != 'static') return element;
		}
		return null;
	},

	getOffsets: function(){
		if (this.getBoundingClientRect){
			var bound = this.getBoundingClientRect(),
				html = document.id(this.getDocument().documentElement),
				htmlScroll = html.getScroll(),
				elemScrolls = this.getScrolls(),
				elemScroll = this.getScroll(),
				isFixed = (styleString(this, 'position') == 'fixed');

			return {
				x: bound.left.toInt() + elemScrolls.x - elemScroll.x + ((isFixed) ? 0 : htmlScroll.x) - html.clientLeft,
				y: bound.top.toInt()  + elemScrolls.y - elemScroll.y + ((isFixed) ? 0 : htmlScroll.y) - html.clientTop
			};
		}

		var element = this, position = {x: 0, y: 0};
		if (isBody(this)) return position;

		while (element && !isBody(element)){
			position.x += element.offsetLeft;
			position.y += element.offsetTop;

			if (Browser.Engine.gecko){
				if (!borderBox(element)){
					position.x += leftBorder(element);
					position.y += topBorder(element);
				}
				var parent = element.parentNode;
				if (parent && styleString(parent, 'overflow') != 'visible'){
					position.x += leftBorder(parent);
					position.y += topBorder(parent);
				}
			} else if (element != this && Browser.Engine.webkit){
				position.x += leftBorder(element);
				position.y += topBorder(element);
			}

			element = element.offsetParent;
		}
		if (Browser.Engine.gecko && !borderBox(this)){
			position.x -= leftBorder(this);
			position.y -= topBorder(this);
		}
		return position;
	},

	getPosition: function(relative){
		if (isBody(this)) return {x: 0, y: 0};
		var offset = this.getOffsets(),
				scroll = this.getScrolls();
		var position = {
			x: offset.x - scroll.x,
			y: offset.y - scroll.y
		};
		var relativePosition = (relative && (relative = document.id(relative))) ? relative.getPosition() : {x: 0, y: 0};
		return {x: position.x - relativePosition.x, y: position.y - relativePosition.y};
	}

});

var styleString = Element.getComputedStyle;

function styleNumber(element, style){
	return styleString(element, style).toInt() || 0;
};

function borderBox(element){
	return styleString(element, '-moz-box-sizing') == 'border-box';
};

function topBorder(element){
	return styleNumber(element, 'border-top-width');
};

function leftBorder(element){
	return styleNumber(element, 'border-left-width');
};

function isBody(element){
	return (/^(?:body|html)$/i).test(element.tagName);
};

以上即是摘抄的部分代码,其中每个方法我会逐步叙说一遍。至于牵扯到其它一些关于mt核心的内容的,只做简单介绍,看不懂的话就会比较吃力了(尤其是mt的OOP理念)。

  1. getScroll()
    这是最简单的方法,判断当前元素是否是Body元素。是的话返回window对象的scroll;否则返回元素自身的scroll值。
  2. getScrolls()
    多了一个s,直接从命名上就能猜出获取的是当前元素相对于顶级body的scroll值。相对于上一个方法来说,它只是多了一个循环而已。
  3. getOffsetParent()
    获取元素的父偏移节点。首先,需要判断当前元素是不是Body,如果是的话,那么它爸爸自然就是null了;其它情况下,循环向上找,直到position不是static的为止(不是static自然就是relative、fixed或者absolute)。
  4. getOffsets()
    获取总偏移量。这是最难的方法,也是为什么说开始的那段代码是错误的原因所在!仔细看看合模型就会发现,开头的代码在每次循环时会少计算元素的border宽度,但是这个在不同浏览器和版本中的表现却是不同的。比如IE8反而将其包括进来,不再少算border宽度。只是我们无需再用循环的办法来做,因为有更好的东西——getBoundingClientRect()。
    这里不会叙说历史,介绍getBoundingClientRect()如何如何(我也不知道),但是较新版本的浏览器(FF 3.0+和Opera 9.5+)都已开始支持这一方法,用它可以直接获取页面元素的绝对位置。看代码的第29~39行,逻辑很清楚:getBoundingClientRect()的值赋予bound;然后分别取left和top转换成数字;再减去自身的scroll()加上scrolls()——这两个方法刚刚介绍过;最后根据元素本身是否是fixed定位,来减去documentElement的scroll()值;得出的结果减去clientLeft或clientTop即可。
    getBoundingClientRect()是很好,那么那些低版本或者不支持的浏览器呢?没办法,老套路,循环了。循环前依然要判断是否是Body元素,那样就直接返回0。
    在循环体的内部,我们发现mt对不同内核的浏览器分开了处理。gecko核心(Firefox为代表)在51行有个borderBox判断,这是什么?94行的方法给出了解答——box-sizing的值是否为border-box(改变css2.1合模型组成模式,width=content,而不是border+padding+content,具体请翻阅手册),是的话无需多计算元素本身的border。随后的56行判断父元素的overflow属性是否为visible,否的话要包含计算父元素的border。
    对于webkit引擎的浏览器(Safari为代表),处理显得简单多了:只需包含计算父元素的border即可(不包括自己)。
    循环完毕后,结果就出来了。只是针对gecko还需做一步处理:box-sizing不是border-box时要减去元素自身的border值。
  5. getPosition()
    这个方法名字有点误导,实际上是需要传入一个元素参数的,返回当前节点相对于参数元素的偏移量。当然,Body元素还是会直接返回0。
    首先计算出自己的offset、scroll,两值相减得出position;然后计算参数元素的position(注意,同样使用了getPosition()方法,只是没有传入参数,这样82行就不会产生计算,直接返回offset – scroll);最后,将元素的position减去参数的position就是返回值了。

88行以下的一些方法是mt的内置方法,上面的介绍中可能用到,我一一简单例举出来,就不多做解释——当然它们也是非常简单的。搞定绝对位置这个烫手山芋之后,想要做出兼容的拖动效果,便是水到渠成了。

十月 26th, 2009

onlylady相册上线

3 Comments, 前端开发, by army8735.

俺的相册首页地址:http://album.onlylady.com/599063

3天alpha版,5天beta版,硬是赶出来的。其实本身并没有这个html版相册需求的,主要是原本半flash版相册一直延期,延了3个月。实在受不了了,10.1长假之后于是决定:基于原本的设计原型和永赞配合试着改写个html版吧。

结果就是目前反而作为正式版本上线了了……很多人不喜欢这配色的其实。

开通地址:http://album.onlylady.com/register

十月 23rd, 2009

未知高度垂直居中

No Comments, 前端开发, by army8735.

其实这个标题有歧义,它包含两种意思:1.已定容器高度下,要求未知高度内部行级元素垂直居中;2.未知容器高度下,要求已定高度内部行级元素垂直居中。

2007年,《淘宝UED招聘题解》给出了第一种情况的解答;最近,19楼UED在《“床”结构的写法》一文中提出一种另类方法。

先给出DEMO地址:/wp-content/uploads/2009/10/vertical_middle.html

第一种情况下,关键在于对IE的hack(不使用expression),使用了font-size,让容器/字体 = 1.14实现IE垂直居中,demo中第一个框就是如此。

第二种情况下,关键在于使用了writing-mode:tb-rl,可以看到demo中第二、三个框正确居中。但是这里主要有2点问题:1是多余的标签,多出了2个;2是文字变成垂直的了,把鼠标移上去就会发现光标旋转了90度(IE)。

想要第二种情况下文字水平?别急,综合前两种技巧就可以。demo中第四、五个框给我们展示了成果。