<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>前端实战库</title>
	<atom:link href="http://www.butong.net/feed" rel="self" type="application/rss+xml" />
	<link>http://www.butong.net</link>
	<description>品味web前端技术</description>
	<lastBuildDate>Thu, 02 Feb 2012 13:21:04 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.1</generator>
		<item>
		<title>倒计时</title>
		<link>http://www.butong.net/archives/115</link>
		<comments>http://www.butong.net/archives/115#comments</comments>
		<pubDate>Sun, 01 Jan 2012 15:00:54 +0000</pubDate>
		<dc:creator>picheng</dc:creator>
				<category><![CDATA[工具]]></category>

		<guid isPermaLink="false">http://www.butong.net/?p=115</guid>
		<description><![CDATA[<h2>需求</h2>
<p>倒计时组件，要求如下 ：<br />
可以定义开始时间、结束时间；<br />
可以定义HTML模板；<br />
可以定义结束时的回调函数。</p>]]></description>
			<content:encoded><![CDATA[<h2>需求</h2>
<p>倒计时组件，要求如下 ：<br />
可以定义开始时间、结束时间；<br />
可以定义HTML模板；<br />
可以定义结束时的回调函数。</p>
<h2>方案</h2>
<h3 class="title-code">JavaScript</h3>
<div class="code">
<pre class="brush: js; gutter: false;">/** 工具函数 */
// 检查一个对象是否为方法
function isFunction(fn) {
	return fn &#038;&#038; Object.prototype.toString.call(fn) === '[object Function]';
}
// 根据ID值查找元素节点
function $get(id) {
	return document.getElementById(id);
}
// 创建元素节点
function $createElement(name) {
	return document.createElement(name);
}
// 创建文本节点
function $createTextNode(s) {
	return document.createTextNode(s);
}

/** Class: Task 在一个时间段内根据指定的频率执行回调函数
 * interface Task {
 *     var timed; // 已经开始的时间，单位：毫秒
 *     var length; // 总时长，单位：毫秒
 *     function stop(); // 结束任务
 * }
 *
 * 构造函数参数
 *   @ sets
 *     { Object } - 配置
 *         current
 *           { String || Date } - 可选，当前时间，默认为用户的系统时间
 *         start
 *           { Number || String || Date } - 可选，开始时间，默认为立即开始
 *         end
 *           { Number || String || Date } - 必填，结束时间
 *         unit
 *           { Number } - 可选，执行回调的频率，默认为1000毫秒
 *         callback
 *           { Function } - 回调函数
 *         ended
 *           { Function } - 结束时的回调函数
 *
 * 参数说明
 *   @ 时间
 *     { String } 例: '2011/12/31 12:00:00'
 *     { Number } 例: 10000 单位为毫秒，表示多少毫秒后开始或结束
 *     { Date } 例: new Date('2011/12/31 12:00:00')
 *   @ 回调函数
 *     函数内的this指针会指向当前Task实例，可以在函数内调用Task的公开方法和属性
 */
function Task(sets) {
	var isCallback = isFunction(sets.callback), isEndCallback = isFunction(sets.ended);
	if (!isCallback &#038;&#038; !isEndCallback) { return; }
	if (isCallback) {
		this.callback = sets.callback;
	}
	if (isEndCallback) {
		this.endCallback = sets.ended;
	}
	this.unit = typeof sets.unit === 'number' ? sets.unit : 1000;
	this.loop = isCallback ? this.loopForRunning : this.loopForEnding;
	this.setTime(sets.current, sets.start, sets.end);
	this.launch();
}

Task.createTime = function(d) {
	return d instanceof Date ? d : (typeof d === 'string' ? new Date(d) : new Date());
};

Task.prototype = {
	setTime: function(current, start, end) {
		var cTime = Task.createTime(current), sTime, sTimed;
		if (start) {
			sTime = typeof start === 'number' ? new Date(cTime.getTime() + start) : Task.createTime(start);
			if (sTime &gt; cTime) {
				this.delay = sTime - cTime;
				sTimed = 0;
			} else {
				sTimed = cTime - sTime;
			}
		} else {
			sTime = cTime;
			sTimed = 0;
		}
		this.timed = sTimed;
		this.length = end ? (typeof end === 'number' ? end : Task.createTime(end) - sTime) : 86400000;
	},
	launch: function() {
		if (this.length &lt; 1) { return; }
		if (this.delay) {
			var that = this;
			setTimeout(function() {
				that.start();
			}, this.delay);
		} else {
			this.start();
		}
	},
	start: function() {
		var that = this, fn = function() {
			that.loop();
		};
		this.loop();
		this.timer = setInterval(fn, this.unit);
	},
	loopForRunning: function() {
		if (this.timed &lt; this.length) {
			this.callback();
		} else if (this.timed === this.length) {
			this.callback();
			this.ended();
		} else {
			this.ended();
		}
		this.timed += this.unit;
	},
	loopForEnding: function() {
		if (this.timed &gt;= this.length) {
			this.ended();
		}
		this.timed += this.unit;
	},
	ended: function() {
		this.stop();
		if (this.endCallback) {
			this.endCallback();
		}
	},
	stop: function() {
		clearInterval(this.timer);
	}
};

/** Class: Countdown 倒计时
 * 构造函数参数
 *   @ sets
 *     { Object } - 配置
 *         current
 *           { String || Date } - 可选，当前时间，默认为用户的系统时间
 *         start
 *           { Number || String || Date } - 可选，开始时间，默认为立即开始
 *         end
 *           { Number || String || Date } - 必填，结束时间
 *         wrap
 *           { DOM 元素节点 } - 必填，倒计时的包装容器，生成的HTML会插入到容器中
 *         tmpl
 *           { String } - 必填，自定义的HTML模板，生成的HTML将由模板决定
 *         ended
 *           { Function } - 可选，结束时的回调函数
 *
 * 参数说明
 *   @ 时间
 *     { String } 例: '2011/12/31 12:00:00'
 *     { Number } 例: 10000 单位为毫秒，表示多少毫秒后开始或结束
 *     { Date } 例: new Date('2011/12/31 12:00:00')
 *   @ tmpl
 *     HTML模板中有4个可用变量
 *       {{d}} 天数值 (最大单位)
 *       {{h}} 小时值
 *       {{m}} 分钟值
 *       {{s}} 秒数值 (最小单位)
 *     单个变量输出的HTML结构
 *       &lt;span class="countdown-time countdown-m"&gt; // class名随变量名而变，countdown-m 表示输入的内容为分钟值
 *         &lt;span class="countdown-num-wrap countdown-m2"&gt; // class名随变量名和数位而变，countdown-m2 表示分钟值里的十位数
 *           &lt;span class="countdown-num n5"&gt;5&lt;/span&gt; // class名随时内容而变，n5 表示数字值为5
 *         &lt;/span&gt;
 *         &lt;span class="countdown-num-wrap countdown-m1"&gt; // countdown-m1 表示分钟值里的个位数
 *           &lt;span class="countdown-num n2"&gt;2&lt;/span&gt;
 *         &lt;/span&gt;
 *       &lt;/span&gt;
 *       可以根据以上结构写CSS样式，如：不想显示分钟值十位数上的0，可以写如下CSS隐藏掉
 *       .countdown-m2 .n0 { display: none; }
 *
 * 使用示例
 *   new Countdown({
 *     end: '2012/01/23',
 *     wrap: $get('countdownDemo1'),
 *     tmpl: '距离2012年春节还有：{{d}}天{{h}}时{{m}}分{{s}}秒',
 *     ended: function() {
 *       alert('春节快乐！');
 *   }
});
 */
function Countdown(sets) {
	if (sets.end &#038;&#038; sets.wrap &#038;&#038; sets.wrap.nodeType === 1 &#038;&#038; typeof sets.tmpl === 'string') {
		this.sets = sets;
		this.init();
	}
}

Countdown.prototype = {
	DATA: {
		TIME_UNITS: {
			d: 86400,
			h: 3600,
			m: 60,
			s: 1
		},
		NUMERAL_STSTEMS: {
			h: 2,
			m: 6,
			s: 6
		},
		TIME_UNITS_ORDER: ['d', 'h', 'm', 's']
	},
	init: function() {
		this.config();
		this.createTask();
	},
	config: function() {
		var sets = this.sets, tmplData = this.parseTemplate(sets.tmpl), tmplVars = tmplData.vars;
		this.template = tmplData;
		this.$wrap = sets.wrap;
		sets.minUnit = tmplVars[tmplVars.length - 1];
	},
	parseTemplate: function(template) {
		var TIME_UNITS = this.DATA.TIME_UNITS, TIME_UNITS_ORDER = this.DATA.TIME_UNITS_ORDER;
		var tmplVars = [], temp = {}, tmplVer = new Date().getTime();
		var htmlCode = template.replace(/{{(w+)}}/g, function($0, $1) {
			if (TIME_UNITS[$1]) {
				temp[$1] = true;
				return '&lt;span class="countdown-time" id="' + $1 + tmplVer + '"&gt;&lt;/span&gt;';
			}
		});
		var i, len = TIME_UNITS_ORDER.length;
		for (i = 0; i &lt; len; i++) {
			if (temp[TIME_UNITS_ORDER[i]]) {
				tmplVars.push(TIME_UNITS_ORDER[i]);
			}
		}
		return {
			htmlCode: htmlCode,
			vars: tmplVars,
			version: tmplVer
		};
	},
	createTask: function() {
		var that = this, sets = this.sets;
		this.taskClass = new Task({
			current: sets.current,
			start: sets.start,
			end: sets.end,
			unit: this.DATA.TIME_UNITS[sets.minUnit] * 1000,
			ended: sets.ended,
			callback: function() {
				if (that.isFormatted) {
					that.descending(0, 0);
				} else {
					that.formatting((this.length - this.timed) / 1000);
				}
			}
		});
	},
	formatting: function(secondNum) {
		if (!this.isFormatted) {
			var unitValues = this.countUnitValues(secondNum, this.template.vars);
			this.timeTree = this.createTimeTree(unitValues);
			this.renderTimeTree();
			this.isFormatted = true;
		}
	},
	countUnitValues: function(secondNum, units) {
		var unitValues = [], TIME_UNITS = this.DATA.TIME_UNITS, timeUnit;
		var i = 0, len = units.length;
		for (i = 0; i &lt; len; i++) {
			timeUnit = TIME_UNITS[units[i]];
			if (timeUnit) {
				unitValues.push([units[i], Math.floor(secondNum / timeUnit)]);
				secondNum = secondNum % timeUnit;
			}
		}
		return unitValues;
	},
	createTimeTree: function(unitValues) {
		var supNodes = [], len = unitValues.length, NUMERAL_STSTEMS = this.DATA.NUMERAL_STSTEMS, value;
		while (len--) {
			value = unitValues[len];
			supNodes.push({
				unit: value[0],
				system: NUMERAL_STSTEMS[value[0]],
				nodes: this.createSubNodes(value[0], value[1]),
				isHigh: len ? true : false
			});
		}
		return supNodes;
	},
	createSubNodes: function(unit, value) {
		var subNodes = [], s = value &lt; 10 ? '0' + value.toString() : value.toString(), len = s.length, i = len;
		var eWrap, eChild, tNode, digitValue;
		while (i--) {
			digitValue = s.charAt(i);
			eWrap = $createElement('span');
			eWrap.className = 'countdown-num-wrap countdown-' + unit + (len - i);
			eChild = $createElement('span');
			eChild.className = 'countdown-num n' + digitValue;
			tNode = $createTextNode(digitValue);
			eChild.appendChild(tNode);
			eWrap.appendChild(eChild);
			subNodes.push({
				wrap: eWrap,
				elem: eChild,
				text: tNode,
				value: Number(digitValue),
				isHigh: i ? true : false
			});
		}
		return subNodes;
	},
	renderTimeTree: function() {
		var timeTree = this.timeTree, treeLen = timeTree.length, tmplData = this.template, tmplVer = tmplData.version;
		var supNode, subNodes, subLen, supElem, eReplate;
		this.$wrap.innerHTML = tmplData.htmlCode;
		while (treeLen--) {
			supNode = timeTree[treeLen];
			subNodes = supNode.nodes;
			subLen = subNodes.length;
			supElem = $createElement('span');
			supElem.className = 'countdown-time countdown-' + supNode.unit;
			while (subLen--) {
				supElem.appendChild(subNodes[subLen].wrap);
			}
			eReplate = $get(supNode.unit + tmplVer);
			eReplate.parentNode.replaceChild(supElem, eReplate);
		}
	},
	descending: function(supDigit, subDigit) {
		var supNode = this.timeTree[supDigit], subNodes = supNode.nodes, node = subNodes[subDigit], value = node.value, status = true;
		if (value) {
			value--;
		} else if (node.isHigh) {
			if (this.descending(supDigit, subDigit + 1)) {
				value = 9;
			} else {
				status = false;
				node.isHigh = false;
			}
		} else if (supNode.isHigh) {
			if (this.descending(supDigit + 1, 0)) {
				value = supNode.system - 1;
			} else {
				status = false;
				supNode.isHigh = false;
			}
		}
		if (status) {
			node.value = value;
			node.text.nodeValue = value;
			node.elem.className = 'countdown-num n' + value;
		}
		return status;
	}
};</pre>
</div>
<p><a href="http://www.butong.net/demo/20120101/">查看demo</a></p>
<h2>分析</h2>
<p>倒计时多用于购物网站的促销活动，如：离某某活动开始还有多少时间、某某活动还有多少时间结束，用来制造时间上的紧迫感，激励用户采取行动。</p>
<p>它要做的是，在一个时间段内，不断的给予用户反馈，距离未来的某一期限还有多少时间。</p>
<p>功能的实现可以分为两个部分：</p>
<p>计算执行：在一个时间段内根据指定的频率执行某些操作；</p>
<p>反馈：将一个时间值格式化后呈现到页面上。</p>
<p>上面的实现方案中写了两个类：Task和Countdown，Task用于实现计算执行的部分，它会根据设置的参数（开始时间、结束时间&#8230;）计算得到一个时间段，在这个时间段内根据指定的频率执行回调，同时会将总时长和已经开始的时间保存到两个属性上，可以在回调函数内读取这两个属性值。</p>
<p>Countdown用于实现反馈的部分，这里需要的只是数字形式的反馈，格式化要做的就是对DOM的操作。</p>
<p>比较简单的一种实现方式是，根据指定的格式将时间值解析成HTML代码，然后innerHTML插入到指定的位置。但这样做是有问题的，innerHTML会导致重绘和重排，频繁的执行这种操作非常影响性能，而且它在IE中还有个Bug，如果innerHTML中的元素有背景图片，每一次innerHTML都会让IE重复的向服务器请求图片。</p>
<p>解析时间值 → 生成HTML代码 → 插入到页面，这一过程其实只需要做一次，接下来要做的只是一个递减的操作，要操作的是时间值中最小的数位对应的DOM节点，当然还要作一个判断，如果数位上的值为0，则需要向高位借位，其实就一个简单的减法计算，只是每一次计算的结果会反馈到DOM节点上。</p>
<p>Countdown类中实现格式化和递减操作的方法是formatting和descending，Countdown依赖于Task，它会创建一个Task实例并传入一个回调函数，回调函数在第一次执行时会调用formatting方法格式化时间值，接下来每次执行只是调用descending方法来进行时间值的递减操作。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.butong.net/archives/115/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>简洁相册模块</title>
		<link>http://www.butong.net/archives/28</link>
		<comments>http://www.butong.net/archives/28#comments</comments>
		<pubDate>Sun, 11 Dec 2011 09:50:51 +0000</pubDate>
		<dc:creator>picheng</dc:creator>
				<category><![CDATA[Tab]]></category>

		<guid isPermaLink="false">http://www.butong.net/?p=28</guid>
		<description><![CDATA[<h2>需求</h2>
<p>如图所示相册模块<br />
<a href="/imgs/20101223.jpg" target="_blank" title="点击查看大图"><img src="/imgs/20101223.jpg" alt="相册模块设计稿" style="width:600px;" /></a></p>
<p>交互逻辑如下：<br />
点击缩略图，显示对应大图；<br />
分类的第一个缩略图处于选中状态，已选中的缩略图加上边框；<br />
图片导航区域只有一个分类处于展开状态，默认展开第一个分类，点击标题可切换分类，分类展开时加载当前分类中已选中的图片；<br />
左右箭头可在当前分类的图片列表中顺序切换图片。</p>]]></description>
			<content:encoded><![CDATA[<h2>需求</h2>
<p>如图所示相册模块<br />
<a href="/imgs/20101223.jpg" target="_blank" title="点击查看大图"><img src="/imgs/20101223.jpg" alt="相册模块设计稿" style="width:600px;" /></a></p>
<p>交互逻辑如下：<br />
点击缩略图，显示对应大图；<br />
分类的第一个缩略图处于选中状态，已选中的缩略图加上边框；<br />
图片导航区域只有一个分类处于展开状态，默认展开第一个分类，点击标题可切换分类，分类展开时加载当前分类中已选中的图片；<br />
左右箭头可在当前分类的图片列表中顺序切换图片。</p>
<h2>方案</h2>
<h3 class="title-code">html</h3>
<div class="code">
<pre class="brush: html; gutter: false;">&lt;div id=&quot;gallery&quot;&gt;
	&lt;div id=&quot;photoCats&quot;&gt;
		&lt;div class=&quot;section open&quot;&gt;
			&lt;h2&gt;创意设计图片&lt;/h2&gt;
			&lt;div class=&quot;photos&quot;&gt;
				&lt;a href=&quot;01/01.jpg&quot;&gt;&lt;img src=&quot;01/small-01.jpg&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;
				&lt;a href=&quot;01/02.jpg&quot;&gt;&lt;img src=&quot;01/small-02.jpg&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;
				&lt;a href=&quot;01/03.jpg&quot;&gt;&lt;img src=&quot;01/small-03.jpg&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;
				&lt;!-- 更多 --&gt;
			&lt;/div&gt;
		&lt;/div&gt;
		&lt;div class=&quot;section&quot;&gt;
			&lt;h2&gt;黑白街景&lt;/h2&gt;
			&lt;div class=&quot;photos&quot;&gt;
				&lt;script type=&quot;text/temp&quot;&gt;
					&lt;a href=&quot;02/01.jpg&quot;&gt;&lt;img src=&quot;02/small-01.jpg&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;
					&lt;a href=&quot;02/02.jpg&quot;&gt;&lt;img src=&quot;02/small-02.jpg&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;
					&lt;a href=&quot;02/03.jpg&quot;&gt;&lt;img src=&quot;02/small-03.jpg&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;
					&lt;!-- 更多 --&gt;
				&lt;/script&gt;
			&lt;/div&gt;
		&lt;/div&gt;
		&lt;div class=&quot;section&quot;&gt;
			&lt;h2&gt;数字绘画：龙&lt;/h2&gt;
			&lt;div class=&quot;photos&quot;&gt;
				&lt;script type=&quot;text/temp&quot;&gt;
					&lt;a href=&quot;02/01.jpg&quot;&gt;&lt;img src=&quot;02/small-01.jpg&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;
					&lt;a href=&quot;02/02.jpg&quot;&gt;&lt;img src=&quot;02/small-02.jpg&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;
					&lt;a href=&quot;02/03.jpg&quot;&gt;&lt;img src=&quot;02/small-03.jpg&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;
					&lt;!-- 更多 --&gt;
				&lt;/script&gt;
			&lt;/div&gt;
		&lt;/div&gt;
		&lt;!-- 更多 --&gt;
	&lt;/div&gt;
	&lt;div id=&quot;photoFrame&quot;&gt;
		&lt;img src=&quot;&quot; alt=&quot;&quot; /&gt;
		&lt;div class=&quot;exterDiv&quot;&gt;&lt;/div&gt;
	&lt;/div&gt;
&lt;/div&gt;</pre>
</div>
<h3 class="title-code">css</h3>
<div class="code">
<pre class="brush: css; gutter: false;">@charset "utf-8";

img {
	border: 0;
}

/* =S Photo Catalogs */
#gallery {
	position: relative;
	width: 836px;
	padding-top: 44px;
}
#photoCats {
	float: left;
	width: 326px;
	margin-right: 10px;
}
#photoCats .section {
	border: 1px solid #666;
	margin-bottom: 10px;
}
#photoCats h2 {
	margin: 0;
	padding: 0.6em;
	background: #DDD;
	font-size: 14px;
	cursor: pointer;
}
#photoCats .photos {
	display: none;
	overflow: hidden;
	padding: 3px;
	_zoom: 1;
}
#photoCats .open .photos {
	display: block;
}
#photoCats .open h2 {
	background: #BBB;
	cursor: default;
}
#photoCats .photos img {
	float: left;
	width: 100px;
	height: 60px;
	padding: 3px;
}
#photoCats a.active img {
	padding: 1px;
	border: 2px solid #C96500;
}
/* =E Photo Catalogs */

/* =S Photo Frame */
#photoFrame {
	position: relative;
	float: left;
	width: 500px;
	min-height: 280px;
}
#photoFrame img {
	width: 500px;
	vertical-align: top;
}
#photoFrame .exterDiv {
	display: none;
	position: absolute;
	left: 0;
	top: 0;
	width: 100%;
	height: 100%;
	background: #000 url("loading.gif") 50% 50% no-repeat;
	opacity: 0.7;
	filter: alpha(opacity=70);
	_height: 58px;
}
#photoFrame.loading .exterDiv {
	display: block;
}
/* =E Photo Frame */

/* =S Photo Action */
#gallery .control {
	clear: both;
	position: absolute;
	top: 12px;
	left: 336px;
	width: 500px;
	height: 20px;
	text-align: center;
}
#gallery .control a {
	display: inline-block; *zoom: 1;
	width: 0;
	height: 0;
	overflow: hidden;
	margin: 0 5px;
	border: 10px solid #FFF;
	border-width: 10px 18px;
	vertical-align: top;
	outline: none;
	cursor: pointer;
}
#gallery .control .prev {
	border-right-color: #BBB;
}
#gallery .control .next {
	border-left-color: #BBB;
}</pre>
</div>
<h3 class="title-code">javascript</h3>
<div class="code">
<pre class="brush: js; gutter: false;">/** 工具方法 **/
// 跨浏览器的事件处理程序与事件对象
var EventUtil = {
	addHandler: function(element, type, handler) {
		if (element.addEventListener) {
			element.addEventListener(type, handler, false);
		} else if (element.attachEvent) {
			var fn = function() { handler.call(element); };
			element.attachEvent('on' + type, fn);
		} else {
			element['on' + type] = handler;
		}
	},
	getEvent: function(event) {
		return event || window.event;
	},
	getTarget: function(event) {
		return event.target || event.srcElement;
	},
	preventDefault: function(event) {
		if (event.preventDefault) {
			event.preventDefault();
		} else {
			event.returnValue = false;
		}
	}
};
// 判断一个对象是否为方法
function isFunction(fn) {
	return Object.prototype.toString.call(fn) === '[object Function]';
}
// 根据id获取DOM节点
function $get(id) {
	return document.getElementById(id);
}

/** Class：相框
 * interface PhotoFrame {
 *    function load(src);
 * }
 *
 * 参数
 *   @ eWrap
 *     { DOM节点 } - 图片的包装容器
 *   @ eImg
 *     { DOM节点 } - 显示图片的img标签
 *   @ oConfig
 *     { Object } - 配置项，可选
 *         src
 *         { String } - 初始化时默认加载的图片
 *         callback
 *         { Function } - 图片加载完毕后的回调函数
 */
function PhotoFrame(eWrap, eImg, oConfig) {
	if (eWrap.nodeType !== 1 || eImg.nodeName !== 'IMG') { return {}; }
	if (oConfig) {
		this.src = oConfig.src;
		if (oConfig.callback &amp;&amp; isFunction(oConfig.callback)) {
			this.callback = oConfig.callback;
		}
	}
	this.$img = eImg;
	this.$wrap = eWrap;
	this.init();
}

PhotoFrame.prototype = {
	regex: new RegExp('\b' + 'loading' + '\b'),
	init: function() {
		this.createImage();
		if (this.src) {
			this.load(this.src);
		}
	},
	createImage: function() {
		var that = this, image = new Image();
		image.onload = function() {
			that.show(this.src);
		};
		this.classImage = image;
	},
	load: function(src) {
		if (typeof src !== 'string') { return; }
		if (!this.isLoading) {
			this.isLoading = true;
			this.$wrap.className += ' loading';
		}
		this.classImage.src = src;
	},
	show: function(src) {
		if (typeof src !== 'string') { return; }
		this.$img.src = src;
		this.isLoading = false;
		this.$wrap.className = this.$wrap.className.replace(this.regex, '');
		if (this.callback) {
			this.callback();
		}
	}
};

/** Class：相片列表
 * interface PhotoList {
 *    function loadPhoto();
 *    function switchTo(index);
 *    function prev();
 *    function next();
 * }
 *
 * 参数
 *   @ eListWrap
 *     { DOM节点 } - 相片列表的包装容器
 *   @ oPhotoFrame
 *     { PhotoFrame实例 } - 用来加载图片的PhotoFrame实例
 *   @ oConfig
 *     { Object } - 配置项，可选
 *   @ index
 *     { Number } - 初始化时默认加载第几张图片
 */
function PhotoList(eListWrap, oPhotoFrame, oConfig) {
	if (eListWrap.nodeType !== 1 || !isFunction(oPhotoFrame.load)) { return {}; }
	this.$wrap = eListWrap;
	this.classPhoto = oPhotoFrame;
	this.config = oConfig;
	this.init();
}

PhotoList.prototype = {
	regex: new RegExp('\b' + 'active' + '\b'),
	init: function() {
		var config = this.config, index = config &amp;&amp; config.index ? config.index : 0;
		this.createIndexes();
		this.bind();
		this.switchTo(index);
	},
	createIndexes: function() {
		var $list = this.$wrap.getElementsByTagName('a'), len = $list.length, i = len, urls = [], item;
		while (i--) {
			item = $list[i];
			urls[i] = item.href;
			item.dataIndex = i;
		}
		this.$list = $list;
		this.length = len;
		this.urls = urls;
	},
	bind: function() {
		var that = this;
		EventUtil.addHandler(this.$wrap, 'click', function(e) {
			var event = EventUtil.getEvent(e), target = EventUtil.getTarget(event), index;
			EventUtil.preventDefault(event);
			if (target.nodeName === 'IMG') {
				index = target.parentNode.dataIndex;
			} else if (target.nodeName === 'A') {
				index = target.dataIndex;
			}
			if (index !== undefined) {
				that.switchTo(index);
			}
		});
	},
	switchTo: function(index) {
		if (index === this.current) { return; }
		var $target = this.$list[index], $current;
		if ($target) {
			$current = this.$list[this.current];
			if ($current) {
				$current.className = $current.className.replace(this.regex, '');
			}
			$target.className += ' active';
			this.current = index;
			this.loadPhoto();
		}
	},
	loadPhoto: function() {
		this.classPhoto.load(this.urls[this.current]);
	},
	prev: function() {
		var target = this.current - 1;
		this.switchTo(target &gt; -1 ? target : this.length - 1);
	},
	next: function() {
		var target = this.current + 1;
		this.switchTo(target &lt; this.length ? target : 0);
	}
};

/** Gallery */
var gallery = {
	regex: new RegExp('\b' + 'open' + '\b'),
	classLists: [],
	isCatRendered: [],
	init: function() {
		this.current = 0;
		this.frameInit();
		this.getElements();
		this.bind();
		this.nthClassListInit(0, this.$lists[0]);
		this.createControl();
	},
	frameInit: function() {
		var $frame = $get('photoFrame'), $img = $frame.getElementsByTagName('img')[0];
		this.classPhoto = new PhotoFrame($frame, $img);
	},
	getElements: function() {
		var $catWrap = $get('photoCats'), $cats = $catWrap.children, $titles = [], $lists = [], i = $cats.length, $catChild;
		while (i--) {
			$catChild = $cats[i].children;
			$titles[i] = $catChild[0];
			$lists[i] = $catChild[1];
		}
		this.$wrap = $get('gallery');
		this.$cats = $cats;
		this.$titles = $titles;
		this.$lists = $lists;
	},
	bind: function() {
		var $titles = this.$titles, i = $titles.length;
		while (i--) {
			this.catsHandler($titles[i], i);
		}
	},
	catsHandler: function(e, i) {
		var that = this;
		EventUtil.addHandler(e, 'click', (function(i) {
			var fn = function() {
				that.switchCatTo(i);
			};
			return fn;
		}(i)));
	},
	createControl: function() {
		var $control = document.createElement('div'), $prev = document.createElement('a'), $next = $prev.cloneNode(false);
		$prev.appendChild(document.createTextNode('上一张'));
		$next.appendChild(document.createTextNode('下一张'));
		$control.appendChild($prev);
		$control.appendChild($next);
		$prev.href = '#prev';
		$prev.className = 'prev';
		$next.href = '#next';
		$next.className = 'next';
		$control.className = 'control';
		this.$wrap.appendChild($control);
		this.controlHandler($prev, $next);
	},
	controlHandler: function(ePrev, eNext) {
		var that = this;
		EventUtil.addHandler(ePrev, 'click', function(e) {
			EventUtil.preventDefault(EventUtil.getEvent(e));
			var gallery = that, classList = gallery.classLists;
			if (classList) {
				classList.prev();
			}
		});
		EventUtil.addHandler(eNext, 'click', function(e) {
			EventUtil.preventDefault(EventUtil.getEvent(e));
			var gallery = that, classList = gallery.classLists;
			if (classList) {
				classList.next();
			}
		});
	},
	switchCatTo: function(index) {
		var $target = this.$cats[index], $current = this.$cats[this.current];
		if ($target) {
			$current.className = $current.className.replace(this.regex, '');
			$target.className += ' open';
			if (!this.isCatRendered[index]) {
				this.nthCatInit(index);
			}
			if (this.classLists[index]) {
				this.classLists[index].loadPhoto();
			}
			this.current = index;
		}
	},
	nthCatInit: function(index) {
		var $list = this.$lists[index], $temp = $list.getElementsByTagName('script')[0];
		if ($list &amp;&amp; $temp) {
			$list.innerHTML = $temp.innerHTML;
			if ($list.getElementsByTagName('a').length &gt; 0) {
				this.nthClassListInit(index, $list);
			}
			this.isCatRendered[index] = true;
		}
	},
	nthClassListInit: function(index, eWrap) {
		this.classLists[index] = new PhotoList(eWrap, this.classPhoto);
	}
};

window.onload = function() {
	gallery.init();
};</pre>
</div>
<p><a href="http://www.butong.net/demo/20101223/">查看demo</a></p>
<h2>分析</h2>
<p>所谓相册，就是存放相片的物品。网上相册也是一样，供用户存储图片，方便翻阅、搜索，或再提供一些旋转、剪裁之类的功能。不过这里需要的只是简洁相册，从设计中可以看到，只需要基本的功能：翻阅。</p>
<p>有两种翻阅的方式：在缩略图中选择性浏览，或使用左右箭头在一组图片中顺序浏览。</p>
<p>这里需要打造的是一种交互体验：所有图片的浏览在当前页面中进行。</p>
<p>相册分为两部分：相框、相片导航。</p>
<p>相框的职责是：提供加载图片的方法，在图片加载过程中应给予反馈（loading）。</p>
<p>相片导航的职责是：提供切换分类的方法，对图片列表进行分类；提供选择图片的方法，用户选择缩略图时调用相框对象提供的方法加载大图；提供顺序切换图片的方法，用户可以在一组图片中顺序浏览。</p>
<p>除了实现以上功能外，还应考虑页面性能。从需求中可以看到，相片导航中只有一个分类处于展开状态，那么在页面加载时，处于关闭状态的分类缩略图不需要加载，甚至连HTML都不需要解析到页面上。</p>
<p>在实现方案中，将处于关闭状态的HTML代码包裹在一个type属性值为“text/temp”的script标签中，这样浏览器不会把它当作JavaScript代码执行，也不会去解析里面的HTML代码。当用户切换到某个分类时，再将其列表渲染到页面上，以及对图片列表的实例化。</p>
<p>上面的实现方案未使用JavaScript类库，所以额外写了一些工具方法。在实际项目中大多会基于某个JavaScript类库来开发，那么上面方案中处理事件以及DOM操作之类的代码就大可不必这样写，但实现的思路大致如此。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.butong.net/archives/28/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>getChildElements</title>
		<link>http://www.butong.net/archives/64</link>
		<comments>http://www.butong.net/archives/64#comments</comments>
		<pubDate>Sun, 29 May 2011 06:56:42 +0000</pubDate>
		<dc:creator>picheng</dc:creator>
				<category><![CDATA[工具]]></category>

		<guid isPermaLink="false">http://www.butong.net/?p=64</guid>
		<description><![CDATA[<h2>需求</h2>
<p>写一个查找子元素的工具函数，要求如下：<br />
可以查找给定元素的所有子元素；<br />
可以查找给定元素的某类子元素（根据标签名、class值）。</p>]]></description>
			<content:encoded><![CDATA[<h2>需求</h2>
<p>写一个查找子元素的工具函数，要求如下：<br />
可以查找给定元素的所有子元素；<br />
可以查找给定元素的某类子元素（根据标签名、class值）。</p>
<h2>方案</h2>
<h3 class="title-code">javascript</h3>
<div class="code">
<pre class="brush: js; gutter: false;">function getChildElements(parent,selector) {
	// Get All Child Elements
	var nodes = parent.childNodes;
	var elements = new Array();
	for (var i=0,n=nodes.length; i&lt;n; i++) {
		if (nodes[i].nodeType === 1) {
			elements.push(nodes[i]);
		}
	}
	if (!selector) return elements;
	// Selector Filter
	var filtered = new Array();
	var selectors = selector.split(&quot;.&quot;);
	var eName = selectors[0].toUpperCase();
	if (!selectors[1]) {
		for (var i=0,n=elements.length; i&lt;n; i++) {
			if (elements[i].nodeName === eName) {
				filtered.push(elements[i]);
			}
		}
	} else {
		var re = new RegExp(&quot;\b&quot; + selectors[1] + &quot;\b&quot;);
		for (var i=0,n=elements.length; i&lt;n; i++) {
			var cName = elements[i].className;
			if (elements[i].nodeName === eName && cName && re.test(cName)) {
				filtered.push(elements[i]);
			}
		}
	}
	return filtered;
}

// Demo
var e1 = getChildElements(document.body);
var e2 = getChildElements(document.body,&quot;div&quot;);
var e3 = getChildElements(document.body,&quot;div.section&quot;);</pre>
</div>
<h2>分析</h2>
<p>查找子元素，所要做的就是先找到所有子节点(childNodes)，再筛选出需要的元素，其实就是一个for、if的过程。</p>
<p>要考虑的是怎么筛选更高效，那么在此之前就要先看看想怎么筛选，从需求可以分析出有下面4种筛选方式：</p>
<ul>
	<li>E &gt; *</li>
	<li>E &gt; F</li>
	<li>E &gt; .className</li>
	<li>E &gt; F.className</li>
</ul>
<p>这4种筛选方式也就确定了有4种筛选条件。</p>
<p>我想到的筛选方案有两种。对所有子节点只for一次，每次循环时判断筛选方式是什么，再给出不同的条件进行匹配，比如：</p>
<div class="code">
<pre class="brush: js; gutter: false;">function getChildElements(parent,elename,classname) {
	var nodes = parent.childNodes;
	var elements = new Array();
	for (var i=0,n=nodes.length; i&lt;n; i++) }
		if (!elename && !classname) {
			// E &gt; *
		} else if (!elename) {
			// E &gt; .className
		} else if (!classname) {
			// E &gt; F
		} else {
			// E &gt; F.className
		}
	}
	return elements;
}</pre>
</div>
<p>这个方案可以进一步优化，对筛选方式的判断不写在for里面，而是在for之前根据筛选方式写一个匹配函数，比如：</p>
<div class="code">
<pre class="brush: js; gutter: false;">function getChildElements(parent,elename,classname) {
	var nodes = parent.childNodes;
	var elements = new Array();
	if (!elename && !classname) {
		var filter = function(node) {}
	} else if (!elename) {
		var filter = function(node) {}
	} else if (!classname) {
		var filter = function(node) {}
	} else {
		var filter = function(node) {}
	}
	for (var i=0,n=nodes.length; i&lt;n; i++) }
		if (filter(node)) {
		}
	}
	return elements;
}</pre>
</div>
<p>第2种方案是，for多次，每次只根据一个筛选条件来匹配。先找到所有子元素，存在一个筛选条件则把已找到的元素进一步筛选匹配，比如：</p>
<div class="code">
<pre class="brush: js; gutter: false;">function getChildElements(parent,elename,classname) {
	var nodes = parent.childNodes;
	var elements = new Array();
	for (var i=0,n=nodes.length; i&lt;n; i++) }
		// E &gt; *
	}
	if (elename) {
		for (var i=0,n=elements.length; i&lt;n; i++) {
			// E &gt; F
		}
	}
	if (classname) {
		for (var i=0,n=elements.length; i&lt;n; i++) {
			E &gt; .className
		}
	}
	return elements;
}</pre>
</div>
<p>这两种方案各有利弊，前一种方案在优化后，执行效率从理论上来说更高，但代码明显冗余，而且易读性自我感觉较差。第二种方案在执行效率方面，因筛选方式而异，如果筛选方式是E &gt; F.className，那就需要for三次，代码依然有冗余，而且感觉比较啰嗦。</p>
<p>鱼与熊掌不可兼得，对执行效率的极致追求或是对代码量、可读性的极致追求是不合适的，需要的是在这之间取得一个合适的平衡。</p>
<p>回过头再看看，E &gt; .className和E &gt; F.className这两种匹配方式是否都需要，class的作用是对元素的进一步分类，那么是否存在有一组子元素当中，class值相同而元素类型不同的情况，肯定有这么写的，但我觉得这种结构的合理性是值得怀疑的，这样的极少数情况是否需要包含在内，我觉得没有必要（至少对于这个工具函数来说，当然，极少数情况就得特殊对待，可以在此基础上扩展）。</p>
<p>再次重写后的代码见方案区，这是我在各个因素中所取的一个平衡点，或许并无特别大的优势，但我更倾向于这个方案。</p>]]></content:encoded>
			<wfw:commentRss>http://www.butong.net/archives/64/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>鼠标触发显示内容，Tab效果一例</title>
		<link>http://www.butong.net/archives/22</link>
		<comments>http://www.butong.net/archives/22#comments</comments>
		<pubDate>Tue, 14 Dec 2010 10:38:21 +0000</pubDate>
		<dc:creator>picheng</dc:creator>
				<category><![CDATA[Tab]]></category>

		<guid isPermaLink="false">http://www.butong.net/?p=22</guid>
		<description><![CDATA[<h2>需求</h2>
<p>鼠标悬停在标签上则显示内容层；<br />
鼠标离开标签及内容层区域则隐藏内容层。</p>]]></description>
			<content:encoded><![CDATA[<h2>需求</h2>
<p>鼠标悬停在标签上则显示内容层；<br />
鼠标离开标签及内容层区域则隐藏内容层。</p>
<h2>方案</h2>
<h3 class="title-code">html</h3>
<div class="code">
<pre class="brush: html; gutter: false;">&lt;div id=&quot;label&quot;&gt;Label&lt;/div&gt;
&lt;div id=&quot;content&quot;&gt;Content&lt;/div&gt;</pre>
</div>
<h3 class="title-code">javascript</h3>
<div class="code">
<pre class="brush: js; gutter: false;">/** Class: SingleTab
 * 构造函数参数
 *   @ label
 *     { DOM节点 } - 内容标签
 *   @ content
 *     { DOM节点 } - 内容块
 *   @ delay
 *     { Number } - 标签响应鼠标事件的延迟时间
 *
 * 使用示例
 *   new SingleTab(document.getElementById('label'), document.getElementById('content'), 100);
 */

function SingleTab(label, content, delay) {
	if (label.nodeType !== 1 || content.nodeType !== 1) { return; }
	this.label = label;
	this.content = content;
	this.delay = delay &gt; 0 ? delay : 50;
	this.init();
}
SingleTab.prototype = {
	regex: new RegExp('\b' + 'active' + '\b'),
	init: function() {
		this.hide();
		this.bind();
	},
	bind: function() {
		var that = this;
		this.label.onmouseover = function() {
			that.cursorStatus = true;
			that.run();
		};
		this.label.onmouseout = function() {
			that.cursorStatus = false;
			that.run();
		};
		this.content.onmouseover = function() {
			that.cursorStatus = true;
			that.run();
		};
		this.content.onmouseout = function() {
			that.cursorStatus = false;
			that.run();
		};
	},
	run: function() {
		var that = this;
		clearTimeout(this.timer);
		this.timer = setTimeout(function() {
			that.toggle();
		}, this.delay);
	},
	toggle: function() {
		if (this.cursorStatus) {
			if (!this.contentStatus) {
				this.show();
			}
		} else {
			if (this.contentStatus) {
				this.hide();
			}
		}
	},
	show: function() {
		this.contentStatus = true;
		this.content.style.display = 'block';
		this.label.className += ' active';
	},
	hide: function() {
		this.contentStatus = false;
		this.content.style.display = 'none';
		this.label.className = this.label.className.replace(this.regex, '');
	}
};</pre>
</div>
<p><a href="http://www.butong.net/demo/20101214/">查看demo</a></p>
<h2>分析</h2>
<p>根据鼠标行为来显示内容，是比较常见的交互效果，这里涉及两个元素：标签#label、内容#content。#label用于响应鼠标事件，#content用于展示内容。#content有两种状态：显示、隐藏，默认为隐藏，由隐藏状态变为显示状态必须满足一个条件：鼠标悬停在#label上，由显示状态变为隐藏状态必须满足两个条件：鼠标未悬停在#label上，也未悬停在#content上。</p>
<p>可以看到这里的关键是状态，所以我定义了一个属性cursorStatus，根据鼠标行为改变状态，并在一定的延时后执行toggle方法，toggle的职责很明确，根据状态调用show方法或hide方法。这里还增加了一个状态的判断：contentStatus，之所以增加这个状态码，是为了避免不必要的DOM操作。</p>
<p>下面是一些比较常见的实现方法：</p>
<h3>方法1. CSS伪类hover，如：</h3>
<pre>&lt;div id=&quot;box&quot;&gt;
	&lt;div id=&quot;label&quot;&gt;Label&lt;/div&gt;
	&lt;div id=&quot;content&quot;&gt;Content&lt;/div&gt;
&lt;/div&gt;</pre>
<p>为#box定义hover伪类</p>
<pre>#box:hover > #content { display: block; }</pre>
<p>这里有一个兼容性问题，IE6不支持非A元素上的hover伪类，通常会只针对IE6写一段JavaScript来实现。</p>
<p>使用这种方法时有几个地方需要注意：</p>
<p>必须依赖#label和#content的父元素；<br />在CSS布局上#content元素与其父元素之间不能存在间距（如当其位于父元素之外时）；<br />hover伪类是即时响应鼠标行为的，响应太快可能会降低用户体验（如鼠标无意中划过#label时）。</p>
<h3>方法2. 为#label和#content的父元素绑定mouseover和mouseout事件，如：</h3>
<pre>&lt;div id=&quot;box&quot;&gt;
	&lt;div id=&quot;label&quot;&gt;Label&lt;/div&gt;
	&lt;div id=&quot;content&quot;&gt;Content&lt;/div&gt;
&lt;/div&gt;
var eBox = document.getElementById(&quot;box&quot;);
eBox.onmouseover = function() {
	// 显示#content
}
eBox.onmouseout = function() {
	// 隐藏#content
}</pre>
<p>上面这种写法相比方法1几乎没什么优势，通常会写一个定时器，当触发鼠标事件时不会即时执行代码，而是有一个延时，这样即能避免响应太快的情况，也能适当允许#content与父元素在CSS布局上存在一些间距。</p>
<p>使用这种方法同样须依赖#label和#content的父元素。</p>
<h3>方法3. 为#label绑定mouseover事件，为#content绑定mouseout事件，如：</h3>
<pre>&lt;div id=&quot;label&quot;&gt;Label&lt;/div&gt;
&lt;div id=&quot;content&quot;&gt;Content&lt;/div&gt;
var eLabel = document.getElementById(&quot;label&quot;);
var eContent = document.getElementById(&quot;content&quot;);
eLabel.onmouseover = function() {
	// 显示#content
}
eContent.onmouseout = function() {
	// 隐藏#content
}
</pre>
<p>与方法2类似，使用这种方法对结构没有依赖，但对CSS布局有要求，#label必须浮在#content之上。</p>
<p>前面方案中所写的代码对结构和样式方面没有特别的依赖，可以自定义延迟响应时间。考虑到其可复用性，代码的编写组合使用构造函数模式和原型模式。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.butong.net/archives/22/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>标题过长时则隐藏标题，日期完整显示</title>
		<link>http://www.butong.net/archives/15</link>
		<comments>http://www.butong.net/archives/15#comments</comments>
		<pubDate>Tue, 03 Aug 2010 14:31:11 +0000</pubDate>
		<dc:creator>picheng</dc:creator>
				<category><![CDATA[列表]]></category>

		<guid isPermaLink="false">http://www.butong.net/?p=15</guid>
		<description><![CDATA[<h2>需求</h2>
<p>如下图所示文章列表<br />
<img src="/imgs/20100803.png" alt="设计稿" /></p>
<p>列表宽220px，单行高度20px；<br />
日期紧随标题之后，距离标题10px；<br />
标题过长时，隐藏标题，但日期必须显示完整；</p>]]></description>
			<content:encoded><![CDATA[<h2>需求</h2>
<p>如下图所示文章列表<br />
<img src="/imgs/20100803.png" alt="设计稿" /></p>
<p>列表宽220px，单行高度20px；<br />
日期紧随标题之后，距离标题10px；<br />
标题过长时，隐藏标题，但日期必须显示完整；</p>
<h2>方案</h2>
<h3 class="title-code">html</h3>
<div class="code">
<pre class="brush: html; gutter: false;">&lt;ul class="list"&gt;
	&lt;li&gt;&lt;a href="#"&gt;前端工程师应该如何学习JS&lt;/a&gt; &lt;span class="date"&gt;2010-07-31&lt;/span&gt;&lt;/li&gt;
	&lt;li&gt;&lt;a href="#"&gt;前端工程师的知识收集与管理&lt;/a&gt; &lt;span class="date"&gt;2010-06-26&lt;/span&gt;&lt;/li&gt;
	&lt;li&gt;&lt;a href="#"&gt;分享你在开发中的经验&lt;/a&gt; &lt;span class="date"&gt;2010-05-29&lt;/span&gt;&lt;/li&gt;
	&lt;li&gt;&lt;a href="#"&gt;前端开发团队现状调查和未来期望&lt;/a&gt; &lt;span class="date"&gt;2010-04-24&lt;/span&gt;&lt;/li&gt;
	&lt;li&gt;&lt;a href="#"&gt;前端开发在研发流程中与其他岗位协作效率的提升&lt;/a&gt; &lt;span class="date"&gt;2010-03-27&lt;/span&gt;&lt;/li&gt;
	&lt;li&gt;&lt;a href="#"&gt;页面重构合理化讨论&lt;/a&gt; &lt;span class="date"&gt;2010-01-30&lt;/span&gt;&lt;/li&gt;
	&lt;li&gt;&lt;a href="#"&gt;页面重构合理化讨论&lt;/a&gt; &lt;span class="date"&gt;2009-12-26&lt;/span&gt;&lt;/li&gt;
	&lt;li&gt;&lt;a href="#"&gt;css sprites应用&lt;/a&gt; &lt;span class="date"&gt;2009-11-28&lt;/span&gt;&lt;/li&gt;
	&lt;li&gt;&lt;a href="#"&gt;网站重构中的文件组织&lt;/a&gt; &lt;span class="date"&gt;2009-10-31&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;</pre>
</div>
<h3 class="title-code">css</h3>
<div class="code">
<pre class="brush: css; gutter: false;">.list {
	list-style: none;
	width: 220px;
	margin: 0;
	padding: 0;
	font: 12px/20px Arial;
}
.list li {
	height: 20px;
	overflow: hidden;
}
.list a {
	float: left;
	padding-right: 64px;
	color: #000;
	text-decoration: none;
}
.list a:hover {
	text-decoration: underline;
}
.list .date {
	float: left;
	margin-left: -54px;
	color: #ACACAC;
	font-size: 10px;
}</pre>
</div>
<p><a href="http://www.butong.net/demo/20100803/">查看demo</a></p>
<h2>分析</h2>
<p>标题过长时隐藏标题，那么这里必定要用到overflow:hidden（内容溢出隐藏）。</p>
<p>上面html结构中的标题和日期都是inline元素，在列表宽度固定的情况下，标题过长时日期会紧随其后而转行，转行的文本会因li元素的overflow属性为hidden而被隐藏。但这里需要的只是隐藏过长的标题内容，而日期始终完整显示，所以日期不能随标题而转行。</p>
<p>怎样才能让日期紧随标题又不因标题过长而转行呢？</p>
<p>让日期（span）的元素框与标题（a）的元素框重叠，日期格式是固定的，其宽度也就确定了，为a元素定义一个padding-right值，再为span元素定义一个同等的margin-left负值，这样span元素就始终重叠于a元素的padding-right区域之上，不会受a元素内容的影响而转行了。当然这么做之前需要让a和span都浮动，因为它们默认都是inline元素，在没浮动时span元素还是会和a元素中转行的文本处于同一行框之中。</p>
<p>如不需考虑IE6的话，首先想到的自然是设置a元素的max-width属性，标题过长则隐藏就是要限制其最大宽度。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.butong.net/archives/15/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>侧栏定宽，主栏宽度自适应且优先载入</title>
		<link>http://www.butong.net/archives/9</link>
		<comments>http://www.butong.net/archives/9#comments</comments>
		<pubDate>Mon, 12 Jul 2010 15:29:47 +0000</pubDate>
		<dc:creator>picheng</dc:creator>
				<category><![CDATA[布局]]></category>

		<guid isPermaLink="false">http://pi-cheng.com/?p=9</guid>
		<description><![CDATA[<h2>需求</h2>
<p>两列布局，左栏宽度固定为200px，右栏宽度自适应且优先载入；<br />
两列间距为10px；<br />
两列高度不确定。</p>]]></description>
			<content:encoded><![CDATA[<h2>需求</h2>
<p>两列布局，左栏宽度固定为200px，右栏宽度自适应且优先载入；<br />
两列间距为10px；<br />
两列高度不确定。</p>
<h2>方案1</h2>
<h3 class="title-code">html</h2>
<div class="code">
<pre class="brush: html; gutter: false;">&lt;div id=&quot;content&quot;&gt;
	&lt;div id=&quot;main&quot;&gt;主栏&lt;/div&gt;
	&lt;div id=&quot;sidebar&quot;&gt;侧栏&lt;/div&gt;
&lt;/div&gt;</pre>
</div>
<h3 class="title-code">css</h3>
<div class="code">
<pre class="brush: css gutter: false;">#content {
	padding-left: 210px;
}
#main {
	float: right;
	width: 100%;
}
#sidebar {
	float: left; _display: inline; /* IE6 Bug */
	width: 200px;
	margin-left: -210px;
}</pre>
</div>
<p><a href="http://www.butong.net/demo/20100712/">查看demo</a></p>
<h2>方案2</h2>
<div class="code">
<pre class="brush: css; gutter: false;">#content {
	display: box;
	box-direction: reverse;
}
#main {
	box-flex: 1;
}
#sidebar {
	width: 200px;
	margin-right: 10px;
}</pre>
</div>
<p>注：实际应用中需要加上-moz和-webkit前缀，在此省略，以便示例代码的清晰。</p>
<h2>分析</h2>
<p>主栏需要优先载入，那么在html中两块内容的顺序就确定了，main要放置于sidebar前面。</p>
<p>两块内容并列放置，通常会用浮动或绝对定位来实现，绝对定位实现很方便，需要固定宽度的侧栏绝对定位，主栏写一个margin-left就OK。但这个需求中有一个“两列高度不确定”，有可能sidebar比main更高，如果sidebar绝对定位了，它就会脱离普通文档流，其后的内容不会随着它而流动。“两列高度不确定”也就意味着它们都要处于普通文档流当中。</p>
<p>如果用浮动呢？元素浮动后其宽度会自动收缩到最小，那么main浮动后，需要使其宽度自适应就只能依赖于父元素了，所以这里添加一个额外的div（#content），并设置其padding-left值为侧栏宽度以限制main的宽度范围，接下来定义sidebar的margin-left为负值让其与main并列显示。</p>
<p>对于这类弹性布局，CSS3的Flexible Box Layout当然是更好的选择，如方案二，Gecko和WebKit已提供了对它的支持。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.butong.net/archives/9/feed</wfw:commentRss>
		<slash:comments>7</slash:comments>
		</item>
	</channel>
</rss>

