一、前言
最近十来天都在学习原生 javascript,参考的是 《DOM Scripting》 这本英文原版书,写得确实非常不错,适合 js 基础非常不牢的小白~ 学的时候基本上是看一章就写点读书笔记发表在博客上,但却基本没动过手写代码。
正好自己最近在写技术博文时遇到了一点小需求,就打算用 js 做一个小工具解决掉。
二、情景
技术人员写博客时,有一个很常见的方式就是写系列博客(或者称作主题博文),即围绕某一个中心技术点,循序渐进,由浅入深地论述其方方面面。园子里这种现象非常常见,就拿排在前列的几位老大说说,例如 Artech 的 “深入剖析授权在 WCF 中的实现[共14篇]”,例如 李永京 的 “NHibernate 之旅系列文章”,如 小洋(燕洋天) 的 “浅谈ASP.NET 的内部机制” 等等,都是其中的典范之作。
为什么高手们似乎都有写系列博客的倾向?个人认为有以下几点好处:
- 长时间专注于某一具体技术点,不断地深入探索
- 当你把技术心得说给他人听时,会强迫你去重新整理自己的思路,把其中的一些盲点暴露出来,然后弄清楚
- 对系列博文的布局安排,要求你将所学知识整理为框架、体系,对知识结构的构建非常有益
- 好的系列博文,条理分明,非常方便以后个人参考
呵呵,总之,系列博文的写作非常有利于个人知识的深化和整理,可以说是从小工到专家的一个比较有效的方法之一。
我们在写系列博文时,不仅要对文章前后的安排顺序仔细斟酌,而且每一篇文章的谋篇布局也是需要花一番头脑的。随便看看上面提到的高手的系列博 文,都会感觉逻辑之分明,条理之清晰。就拿永京老大的 “NHibernate 之旅” 系列文章为例,作者在每篇博文中都用到了大量的标题,主标题下还有次级标题,这样一来把整篇文章的内容结构很好地规划出来,同时为提高读者的阅读体验,还 在每篇文章的开篇列下了文章的标题结构。不多说,上图:
呵呵,读者一打开这篇文章,映入眼帘的就是整篇文章的框架结构,非常清晰并富有逻辑性,有助于把握整篇文章的脉络。从这一点可以看出,为一篇技术博文划分逻辑层次,使用主标题(mainTitle)+ 次级标题(subTitle)的形式是极其常见和实用的。
笔者的第一个原生 js 小工具的目的,就是为了增强这种 主标题 + 次级标题 的用户体验效果。
三、功能描述
核心功能只有两个:
自动生成博文目录
如永京老大的博文,写完文章后把大大小小的标题抽出来放在文首固然是个好方法,但总觉得这样做比较麻烦。那么能否通过代码自动抽取标题,然后再包装一下插入文档中呢?
小工具的第一个核心功能,就是为了解决上述的需求。基本实现原理:首先要求博主在写博文的时候,将主标题和次级标题用 HTML 标签中的 title tag(如:h1、h2、h3...)包起来;然后通过 JS 代码遍历整个包含博文正文的 <div>,过滤出这些标签,再把它们组装成 自定义列表 的形式,再插入到 HTML 文档中,如下:
<dl>
<dt>NHibernate中的查询方法</dt>
<dt>条件查询</dt>
<dd>创建ICriteria实例</dd>
<dd>结果集限制</dd>
<dd>结果集排序</dd>
<dd>一些说明</dd>
<dt>根据示例查询</dt>
<dt>实例分析</dt>
<dt>结语</dt>
</dl>
如果不加任何样式,它在浏览器中的默认显示效果如下:
呵呵,不过我们当然可以手动为其添加 CSS 代码,后面会给出来的。
文内标题平滑跳转
现在假设我们已经构造出了标题的菜单,插入到了文档中。如果我想点击其中的某一项,页面就会定位到相应位置的话,这样会方便很多(特别是在以后 再次浏览的时候)。对 HTML 比较熟的童鞋可能马上想到:用锚!对,我们可以把 <a name="title"></a> 放置到想要跳转的位置,然后再用 <a href="#title"></a> 来填充菜单,那么只要点击后者,浏览器窗口就会立即切换到前者所处的位置。
这是一个不错的解决方案,但是缺点就在于这种跳转是瞬间的,而且目的地是未知的(不知道向下跳转还是向上跳转),这样会造成非常糟糕的用户体验,会打乱读者在浏览一篇文章时所形成的连贯性。所以我们不打算使用锚,而是自己来使用 JS 实现一种平滑跳转的效果。
实现的原理也不难:首先通过调用 DOM 方法,判断出浏览器滚动条(scroll bar)的当前位置,记为 currentPos;然后计算出目标标题(target title)的距页面顶端的距离,记为 finalPos;最后通过一定的算法实现平滑过度。
四、源代码
下面给出 JS 源代码和一些必要的 CSS 样式:
JS 源代码 createContent.js
一共有 5 个函数:
- getPos(e):获取元素位置,即距浏览器左边界的距离(left)和距浏览器上边界的距离(top);
- getScroll():获取滚动条当前位置;
- moveWindow(finalpos, internal):移动滚动条,finalPos 为目的位置,internal 为移动速度;
- moveFixElement(elementID, finalTop, finalRight, internal):移动绝对定位(absolutely、fixed)元素,elementID 表示元素 ID,finalTop 和 finalRight 表示最终位置,internal 表示移动速度
- createContent(id, mt, st, interval):创建标题菜单,id 表示包含博文正文的 div 容器的 id,mt 和 st 分别表示主标题和次级标题的标签名称(如 H2、H3,需大写!),interval 表示移动的速度。
//获取元素位置
function getPos(e) {
var t = l = 0;
while (e)
{
t += e.offsetTop;
l += e.offsetLeft;
e = e.offsetParent;
}
return {top:t, left:l};
}
//获取滚动条信息
function getScroll() {
return document.body.scrollTop | document.documentElement.scrollTop;
}
//移动窗体
function moveWindow(finalpos, interval) {
//若不支持此方法,则退出
if(!window.scrollTo) return false;
//窗体滚动时,禁用鼠标滚轮
window.onmousewheel = function(){
return false;
};
//清除计时
if (document.body.movement) {
clearTimeout(document.body.movement);
}
var currentpos = getScroll(); //获取滚动条信息
var dist = 0;
if (currentpos == finalpos) { //到达预定位置,则解禁鼠标滚轮,并退出
window.onmousewheel = function(){
return true;
}
return true;
}
if (currentpos < finalpos) { //未到达,则计算下一步所要移动的距离
dist = Math.ceil((finalpos - currentpos)/10);
currentpos += dist;
}
if (currentpos > finalpos) {
dist = Math.ceil((currentpos - finalpos)/10);
currentpos -= dist;
}
var scrTop = getScroll(); //获取滚动条信息
window.scrollTo(0, currentpos); //移动窗口
if(getScroll() == scrTop) //若已到底部,则解禁鼠标滚轮,并退出
{
window.onmousewheel = function(){
return true;
}
return true;
}
//进行下一步移动
var repeat = "moveWindow(" + finalpos + "," + interval + ")";
document.body.movement = setTimeout(repeat, interval);
}
//移动绝对定位元素
function moveFixElement(elementID, finalTop, finalRight, interval)
{
var elem = document.getElementById(elementID);
if(elem.movement){
clearTimeout(elem.movement);
}
finalTop = parseInt(finalTop);
finalRight = parseInt(finalRight);
var xpos = parseInt(elem.style.right);
var ypos = parseInt(elem.style.top);
var dist = 0;
if(xpos == finalRight && ypos == finalTop){
return true;
}
if(xpos < finalRight){
dist = Math.ceil((finalRight - xpos) / 10);
xpos += dist;
}
if(xpos > finalRight){
dist = Math.ceil((xpos - finalRight) / 10);
xpos -= dist;
}
if(ypos < finalTop){
dist = Math.ceil((finalTop - ypos) / 10);
ypos += dist;
}
if(ypos > finalTop){
dist = Math.ceil((ypos - finalTop) / 10);
ypos -= dist;
}
elem.style.right = xpos + "px";
elem.style.top = ypos + "px";
var repeat = "moveFixElement('" + elementID + "'," + finalTop + "," + finalRight + "," + interval + ")";
elem.movement = setTimeout(repeat, interval);
}
//创建菜单
function createContent(id, mt, st, interval)
{
//获取博文正文div容器
var elem = document.getElementById(id);
if(!elem) return false;
//获取div中所有元素结点
var nodes = elem.getElementsByTagName("*");
//创建自定义列表
var dlist = document.createElement("dl");
//创建div容器
var blogContent = document.createElement("div");
blogContent.setAttribute("id","blogContent");
var title = document.createElement("div");
title.setAttribute("id","contentTitle");
var dldiv = document.createElement("div");
dldiv.setAttribute("id", "dldiv");
var num = 0;
//遍历所有元素结点
for(var i=0; i<nodes.length; i++)
{
if(nodes[i].nodeName == mt || nodes[i].nodeName == st)
{
//获取标题文本
var nodetext = nodes[i].firstChild.nodeValue;
//插入锚
nodes[i].setAttribute("id", "blogTitle" + num);
switch(nodes[i].nodeName)
{
case mt: //若为主标题
var item = document.createElement("dt");
break;
case st: //若为子标题
var item = document.createElement("dd");
break;
}
//创建锚链接
var itemtext = document.createTextNode(nodetext);
item.appendChild(itemtext);
item.setAttribute("name", num);
item.onclick = function(){ //添加鼠标点击触发函数
var pos = getPos(document.getElementById("blogTitle" + this.getAttribute("name")));
if(!moveWindow(pos.top, interval)) return false;
};
//将自定义表项加入自定义列表中
dlist.appendChild(item);
num++;
}
}
if(num == 0) return false;
//将自定义列表加入文档中
dldiv.appendChild(dlist);
blogContent.appendChild(title);
blogContent.appendChild(dldiv);
document.body.appendChild(blogContent);
//设置初始化位置
blogContent.style.right = 0 - dldiv.offsetWidth + "px";
blogContent.style.top = (window.innerHeight - title.offsetHeight) / 2 + "px";
//点击 title,目录平滑伸缩
title.onclick = function(){
var blogContent = document.getElementById("blogContent");
var dldiv = document.getElementById("dldiv");
if(parseInt(blogContent.style.right) == 0){
moveFixElement("blogContent", blogContent.style.top, -dldiv.offsetWidth, 20);
}
else{
moveFixElement("blogContent", blogContent.style.top, 0, 20);
}
};
//添加窗口 resize 事件响应
window.onresize= function(){
var title = document.getElementById("contentTitle");
blogContent.style.top = (window.innerHeight - title.offsetHeight) / 2 + "px";
}
}
CSS 样式代码
最终生成的标题菜单(content of titles)的 HTML 结构图如下所示:
所以我们可为其编写如下的 CSS 代码:
/*三个div容器的样式*/
#blogContent{
position:fixed;
}
#contentTitle{
background-image: url(title.png);
background-position:-46px 0px;
width:46px;
height:148px;
float:left;
cursor:pointer;
}
#dldiv{
float:left;
font-family:'微软雅黑';
color:#3191B4;
background-color:#F1FAFB;
border:1px dotted #3191B4;
border-radius:5px;
}
/*自定义列表样式*/
#dldiv dl{
margin:8px 8px 8px 12px;
}
#dldiv dd,dt{
cursor:pointer;
}
#dldiv dd:hover, dt:hover{
color:#A7995A;
}
#dldiv dt{
font-weight:bold;
margin-top:10px;
font-size:15px;
}
#dldiv dd{
margin:4px 15px;
font-size:12px;
}
五、使用方法
写博
最重要的当然是写博啦,如果不写博,那么这个工具就毫无存在的价值了。在写博添加标题时,首先用鼠标选定标题文字,然后点击编辑栏的样式设置菜单,选择 标题1-标题6 其中的一项。如下图所示:
当然,主标题要比次级标题设得大一些,对我来说,一般选择 h2 作为主标题,h3 作为次级标题。
添加 JS 引用
首先在 博客后台管理->设置->公告 中添加对 js 文件的引用,如下:
调用函数
然后,在博文编辑器中点击 HTML 功能键,在博文的 HTML 源码中添加如下 js 代码:
方法 createContent 即为生成标题目录的 js 函数,它有四个参数:
"cnblogs_post_body":表示包含博文正文的 div 容器的 id;
"H2":表示主标题的 HTML tag(需大写!);
"H3":表示次级标题的 HTML tag(需大写!);
20:表示平滑跳转时的速度,数值越大速度越快。
一般来说第一个参数不用改,后面三个根据具体情况而定。
添加 CSS 样式
最后,我们需要对生成的标题构成的目录设置样式,包括位置、底色、字体等等,我们在 后台管理->设置->通过 CSS 设置页面样式 中添加上面列出的 CSS 代码:
最终的效果,你应该在浏览本篇博文时已经看到了。
六、结语
这是笔者初学原生 javascript 的第一个小作品,欢迎园友试用,也欢迎提出改进建议!
http://www.cnblogs.com/hustlzp/archive/2011/08/22/a-js-tool-for-auto-blog-title-content-generate.html