• 推荐
  • 评论
  • 收藏

原生 js 小工具 v1.1:自动生成博文目录,文内标题平滑跳转:欢迎园友试用!

2022-12-25    5次浏览

一、前言

  最近十来天都在学习原生 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 表示移动的速度。 
  其前 4 个函数都是辅助性的函数,最后一个才是用户需要调用的创建标题菜单的函数。下面是全部的源代码:
//获取元素位置
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

https://files.cnblogs.com/hustlzp/createContent.js

原文地址:https://www.cnblogs.com/Leo_wl/p/2152820.html