详解自动生成博客目录案例


Posted in Javascript onDecember 09, 2016

前面的话

有朋友在博客下面留言,询问博客目录是如何生成的。接下来就详细介绍实现过程

操作说明

关于博客目录自动生成,已经封装成catalog.js文件,只要引用该文件即可

    //默认地,为页面上所有的h3标签生成目录
    <script src="http://files.cnblogs.com/files/xiaohuochai/catalog.js"></script>
    //或者,为页面上所有class="test"的标签生成目录
    <script src="http://files.cnblogs.com/files/xiaohuochai/catalog.js" data-seletor=".test"></script>
 如下图所示,打开HTML源代码编辑器,在最后引入js即可

详解自动生成博客目录案例

【功能简要说明】

   1、点击目录项,对应章节标题将显示在可视区上方

   2、滚动滚轮,目录项会对应章节标题的变化而相应地变化

   3、点击目录右上角的关闭按钮,可以将目录缩小为"显示目录"四个字,双击缩小后的目录,可恢复默认状态

   4、目录可以拖拽至任意地方

目录参照

首先,要确定的是,基于什么生成目录。是文章中的<h3>标签,还是文章中的class="list"的标签。所以,更人性化的做法是,将其作为参数,默认参数为<h3>标签

由于博客园的博文除了自己生成的博客内容外,博客园还会添加诸如评论、公告、广告等元素。所以,第一步要先定位博文

博文最终都处于id="cnblogs_post_body"的div中

详解自动生成博客目录案例

//DOM结构稳定后再操作
window.onload = function(){
 /*设置章节标题函数*/
 function setCatalog(){
 //获取页面中所有的script标题
 var aEle = document.getElementsByTagName('script');
 //设置sel变量,用于保存其选择符的字符串值
 var sel;
 //获取script标签上的data-selector值
 Array.prototype.forEach.call(aEle,function(item,index,array){
 sel = item.getAttribute('data-selector');
 if(sel) return;
 })
 //默认参数为h3标签
 if(sel == undefined){
 sel ='h3';
 }
 //选取文章中所有的章节标题
 var tempArray = document.querySelectorAll(sel);
};

目录连接

目录如何与章节进行对应呢,最常用的就是使用锚点。以基于文章中的<h3>标签生成目录为例,为每一个<h3>标签按照顺序添加锚点(#anchor1,#anchor2...)

//为每一个章节标题顺序添加锚点标识
Array.prototype.forEach.call(tempArray, function(item, index, array) {
 item.setAttribute('id','anchor' + (1+index));
});

目录显示

在文章左侧显示目录,目录显示的内容就是对应章节的题目

//设置全局变量Atitle保存添加锚点标识的标题项
 var aTitle = setCatalog();
 /*生成目录*/
 function buildCatalog(arr){
 //由于每个部件的创建过程都类似,所以写成一个函数进行服用
 function buildPart(json){
 var oPart = document.createElement(json.selector);
 if(json.id){oPart.setAttribute('id',json.id);}
 if(json.className){oPart.className = json.className;}
 if(json.innerHTML){oPart.innerHTML = json.innerHTML;}
 if(json.href){oPart.setAttribute('href',json.href);}
 if(json.appendToBox){
 oBox.appendChild(oPart);
 }
 return oPart;
 }
 //取得章节标题的个数
 len = arr.length;
 //创建最外层div
 var oBox = buildPart({
 selector:'div',
 id:'box',
 className:'box'
 });
 //创建关闭按钮
 buildPart({
 selector:'span',
 id:'boxQuit',
 className:'box-quit',
 innerHTML:'×',
 appendToBox:true
 });
 //创建目录标题
 buildPart({
 selector:'h6',
 className:'box-title',
 innerHTML:'目录',
 appendToBox:true
 });
 //创建目录项
 for(var i = 0; i < len; i++){
 buildPart({
 selector:'a',
 className:'box-anchor',
 href:'#anchor' + (1+i),
 innerHTML:'['+(i+1)+']'+arr[i].innerHTML,
 appendToBox:true
 });
 }
 //将目录加入文档中
 document.body.appendChild(oBox);
 }
 buildCatalog(aTitle);

目录样式

为目录设置样式,最外层div设置最小宽度和最大宽度。当目录项太宽时,显示...。由于最终要封装为一个js文件,所以样式采用动态样式的形式

/*动态样式*/
function loadStyles(str){
 loadStyles.mark = 'load';
 var style = document.createElement("style");
 style.type = "text/css";
 try{
 style.innerHTML = str;
 }catch(ex){
 style.styleSheet.cssText = str;
 }
 var head = document.getElementsByTagName('head')[0];
 head.appendChild(style); 
}
if(loadStyles.mark != 'load'){
 loadStyles("h6{margin:0;padding:0;}\
 .box{position: fixed; left: 10px;top: 60px;font:16px/30px '宋体'; border: 2px solid #ccc;padding: 4px; border-radius:5px;min-width:80px;max-width:118px;overflow:hidden;cursor:default;}\
 .boxHide{border:none;width:60px;height:30px;padding:0;}\
 .box-title{text-align:center;font-size:20px;color:#ccc;}\
 .box-quit{position: absolute; right: 0;top: 4px;cursor:pointer;font-weight:bold;}\
 .box-anchor{display:block;text-decoration:none;color:black; border-left: 3px solid transparent;padding:0 3px;margin-bottom: 3px;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;}\
 .box-anchor:hover{color:#3399ff;}\
 .box-anchorActive{color:#3399ff;text-decoration:underline;border-color:#2175bc};"); 
};

点击事件

为各目录项增加点击事件,使用事件代理,增加性能

//由于点击事件和滚轮事件都需要将目录项发生样式变化,所以声明锚点激活函数
function anchorActive(obj){
 var parent = obj.parentNode;
 var aAnchor = parent.getElementsByTagName('a');
 //将所有目录项样式设置为默认状态
 Array.prototype.forEach.call(aAnchor,function(item,index,array){
 item.className = 'box-anchor';
 })
 //将当前目录项样式设置为点击状态
 obj.className = 'box-anchor box-anchorActive';
}
var oBox = document.getElementById('box');
//设置目录内各组件的点击事件
oBox.onclick = function(e){
 e = e || event;
 var target = e.target || e.srcElement;
 //获取target的href值
 var sHref = target.getAttribute('href');
 //设置目录项的点击事件
 if(/anchor/.test(sHref)){
 anchorActive(target);
 }
}

隐藏功能

目录有时是有用的,但有时又是碍事的。所以,为目录添加一个关闭按钮,使其隐藏,目录内容全部消失,关闭按钮变成“显示目录”四个字。再次点击则完全显示

由于后续的拖拽功能需要使用点击事件。所以,重新显示目录的事件使用双击实现

var oBox = document.getElementById('box');
//设置目录内各组件的点击事件
oBox.onclick = function(e){
 e = e || event;
 var target = e.target || e.srcElement;
 //设置关闭按钮的点击事件
 if(target.id == 'boxQuit'){
 target.innerHTML = '显示目录';
 target.style.background = '#3399ff';
 this.className = 'box boxHide';
 }
} 
//设置关闭按钮的双击事件
var oBoxQuit = document.getElementById('boxQuit');
oBoxQuit.ondblclick = function(){
 this.innerHTML = '×';
 this.style.background = '';
 this.parentNode.className = 'box'; 
}

滚轮功能

当使用滚轮时,触发滚轮事件,当前目录对应可视区内相应的文章内容

//设置滚轮事件
var wheel = function(e){
 //获取列表项
 var aAnchor = oBox.getElementsByTagName('a');
 //获取章节题目项
 aTitle.forEach(function(item,index,array){
 //获取当前章节题目离可视区上侧的距离
 var iTop = item.getBoundingClientRect().top;
 //获取下一个章节题目
 var oNext = array[index+1];
 //如果存在下一个章节题目,则获取下一个章节题目离可视区上侧的距离
 if(oNext){
 var iNextTop = array[index+1].getBoundingClientRect().top;
 }
 //当前章节题目离可视区上侧的距离小于10时
 if(iTop <= 10){
 //当下一个章节题目不存在, 或下一个章节题目离可视区上侧的距离大于10时,设置当前章节题目对应的目录项为激活态
 if(iNextTop > 10 || !oNext){
 anchorActive(aAnchor[index]);
 }
 }
 });
}
document.body.onmousewheel = wheel;
document.body.addEventListener('DOMMouseScroll',wheel,false);

拖拽功能

由于不同计算机的分辨率不同,所以目录的显示位置也不同。为目录增加一个拖拽功能,可以把其放在任意合适的地方

//拖拽实现
oBox.onmousedown = function(e){
 e = e || event;
 //获取元素距离定位父级的x轴及y轴距离
 var x0 = this.offsetLeft;
 var y0 = this.offsetTop;
 //获取此时鼠标距离视口左上角的x轴及y轴距离
 var x1 = e.clientX;
 var y1 = e.clientY;
 document.onmousemove = function(e){
 e = e || event;
 //获取此时鼠标距离视口左上角的x轴及y轴距离
 x2 = e.clientX;
 y2 = e.clientY; 
 //计算此时元素应该距离视口左上角的x轴及y轴距离
 var X = x0 + (x2 - x1);
 var Y = y0 + (y2 - y1);
 //将X和Y的值赋给left和top,使元素移动到相应位置
 oBox.style.left = X + 'px';
 oBox.style.top = Y + 'px';
 }
 document.onmouseup = function(e){
 //当鼠标抬起时,拖拽结束,则将onmousemove赋值为null即可
 document.onmousemove = null;
 //释放全局捕获
 if(oBox.releaseCapture){
 oBox.releaseCapture();
 }
 }
 //阻止默认行为
 return false;
 //IE8-浏览器阻止默认行为
 if(oBox.setCapture){
 oBox.setCapture();
 }
}

代码展示

//DOM结构稳定后,再操作
window.onload = function(){
 /*动态样式*/
 function loadStyles(str){
 loadStyles.mark = 'load';
 var style = document.createElement("style");
 style.type = "text/css";
 try{
 style.innerHTML = str;
 }catch(ex){
 style.styleSheet.cssText = str;
 }
 var head = document.getElementsByTagName('head')[0];
 head.appendChild(style); 
 }
 if(loadStyles.mark != 'load'){
 loadStyles("h6{margin:0;padding:0;}\
 .box{position: fixed; left: 10px;top: 60px;font:16px/30px '宋体'; border: 2px solid #ccc;padding: 4px; border-radius:5px;min-width:80px;max-width:118px;overflow:hidden;cursor:default;background:rgba(0,0,0,0.1);}\
 .boxHide{border:none;width:60px;height:30px;padding:0;}\
 .box-title{text-align:center;font-size:20px;color:#444;}\
 .box-quit{position: absolute;text-align:center; right: 0;top: 4px;cursor:pointer;font-weight:bold;}\
 .box-quitAnother{background:#3399ff;left:0;top:0;}\
 a.box-anchor{display:block;text-decoration:none;color:black; border-left: 3px solid transparent;padding:0 3px;margin-bottom: 3px;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;}\
 a.box-anchor:hover{color:#3399ff;}\
 a.box-anchorActive{color:#3399ff;text-decoration:underline;border-color:#2175bc};"); 
 };
 /*设置章节标题函数*/
 function setCatalog(){
 //获取页面中所有的script标题
 var aEle = document.getElementsByTagName('script');
 //设置sel变量,用于保存其选择符的字符串值
 var sel;
 //获取script标签上的data-selector值
 Array.prototype.forEach.call(aEle,function(item,index,array){
 sel = item.getAttribute('data-selector');
 if(sel) return;
 })
 //默认参数为h3标签
 if(sel == undefined){
 sel ='h3';
 }
 //选取博文
 var article = document.getElementById('cnblogs_post_body');
 //选取文章中所有的章节标题
 var tempArray = article.querySelectorAll(sel);
 //为每一个章节标题顺序添加锚点标识
 Array.prototype.forEach.call(tempArray, function(item, index, array) {
 item.setAttribute('id','anchor' + (1+index));
 });
 //返回章节标题这个类数组
 return tempArray;
 }
 //设置全局变量Atitle保存添加锚点标识的标题项
 var aTitle = setCatalog();

 /*生成目录*/
 function buildCatalog(arr){
 //由于每个部件的创建过程都类似,所以写成一个函数进行服用
 function buildPart(json){
 var oPart = document.createElement(json.selector);
 if(json.id){oPart.setAttribute('id',json.id);}
 if(json.className){oPart.className = json.className;}
 if(json.innerHTML){oPart.innerHTML = json.innerHTML;}
 if(json.href){oPart.setAttribute('href',json.href);}
 if(json.appendToBox){
 oBox.appendChild(oPart);
 }
 return oPart;
 }
 //取得章节标题的个数
 len = arr.length;
 //创建最外层div
 var oBox = buildPart({
 selector:'div',
 id:'box',
 className:'box'
 });
 //创建关闭按钮
 buildPart({
 selector:'span',
 id:'boxQuit',
 className:'box-quit',
 innerHTML:'×',
 appendToBox:true
 });
 //创建目录标题
 buildPart({
 selector:'h6',
 className:'box-title',
 innerHTML:'目录',
 appendToBox:true
 });
 //创建目录项
 for(var i = 0; i < len; i++){
 buildPart({
 selector:'a',
 className:'box-anchor',
 href:'#anchor' + (1+i),
 innerHTML:'['+(i+1)+']'+arr[i].innerHTML,
 appendToBox:true
 });
 }
 //将目录加入文档中
 document.body.appendChild(oBox);
 }
 buildCatalog(aTitle);
 /*事件部分*/
 (function(){
 var oBox = document.getElementById('box');
 //设置目录内各组件的点击事件
 oBox.onclick = function(e){
 e = e || event;
 var target = e.target || e.srcElement;
 //设置关闭按钮的点击事件
 if(target.id == 'boxQuit'){
 target.innerHTML = '显示目录';
 target.className = 'box-quit box-quitAnother'
 this.className = 'box boxHide';
 }
 //获取target的href值
 var sHref = target.getAttribute('href');
 //设置目录项的点击事件
 if(/anchor/.test(sHref)){
 anchorActive(target);
 }
 } 
 /*设置关闭按钮的双击事件*/
 var oBoxQuit = document.getElementById('boxQuit');
 oBoxQuit.ondblclick = function(){
 this.innerHTML = '×';
 this.className = 'box-quit';
 this.parentNode.className = 'box'; 
 }
 //由于点击事件和滚轮事件都需要将目录项发生样式变化,所以声明锚点激活函数
 function anchorActive(obj){
 var parent = obj.parentNode;
 var aAnchor = parent.getElementsByTagName('a');
 //将所有目录项样式设置为默认状态
 Array.prototype.forEach.call(aAnchor,function(item,index,array){
 item.className = 'box-anchor';
 })
 //将当前目录项样式设置为点击状态
 obj.className = 'box-anchor box-anchorActive';
 }
 //设置滚轮事件
 var wheel = function(e){
 //获取列表项
 var aAnchor = oBox.getElementsByTagName('a');
 //获取章节题目项
 aTitle.forEach(function(item,index,array){
 //获取当前章节题目离可视区上侧的距离
 var iTop = item.getBoundingClientRect().top;
 //获取下一个章节题目
 var oNext = array[index+1];
 //如果存在下一个章节题目,则获取下一个章节题目离可视区上侧的距离
 if(oNext){
 var iNextTop = array[index+1].getBoundingClientRect().top;
 }
 //当前章节题目离可视区上侧的距离小于10时
 if(iTop <= 10){
 //当下一个章节题目不存在, 或下一个章节题目离可视区上侧的距离大于10时,设置当前章节题目对应的目录项为激活态
 if(iNextTop > 10 || !oNext){
 anchorActive(aAnchor[index]);
 }
 }
 });
 }
 document.body.onmousewheel = wheel;
 document.body.addEventListener('DOMMouseScroll',wheel,false);
 //拖拽实现
 oBox.onmousedown = function(e){
 e = e || event;
 //获取元素距离定位父级的x轴及y轴距离
 var x0 = this.offsetLeft;
 var y0 = this.offsetTop;
 //获取此时鼠标距离视口左上角的x轴及y轴距离
 var x1 = e.clientX;
 var y1 = e.clientY;
 document.onmousemove = function(e){
 e = e || event;
 //获取此时鼠标距离视口左上角的x轴及y轴距离
 x2 = e.clientX;
 y2 = e.clientY; 
 //计算此时元素应该距离视口左上角的x轴及y轴距离
 var X = x0 + (x2 - x1);
 var Y = y0 + (y2 - y1);
 //将X和Y的值赋给left和top,使元素移动到相应位置
 oBox.style.left = X + 'px';
 oBox.style.top = Y + 'px';
 }
 document.onmouseup = function(e){
 //当鼠标抬起时,拖拽结束,则将onmousemove赋值为null即可
 document.onmousemove = null;
 //释放全局捕获
 if(oBox.releaseCapture){
 oBox.releaseCapture();
 }
 }
 //阻止默认行为
 return false;
 //IE8-浏览器阻止默认行为
 if(oBox.setCapture){
 oBox.setCapture();
 }
 } 
 })(); 
};

最后

 如果有自己的需求,可以把代码下载下来,进行相应参数的修改

 如果点击右键,会出现自定义右键菜单,包括回到顶部、点赞、评论这三个功能;如果按住ctrl键,再点击右键,则出现原生的右键菜单。

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持三水点靠木!

Javascript 相关文章推荐
JQuery 初体验(建议学习jquery)
Apr 25 Javascript
JS+CSS实现模仿浏览器网页字符查找功能的方法
Feb 26 Javascript
Javascript writable特性介绍
Feb 27 Javascript
JavaScript面对国际化编程时的一些建议
Jun 24 Javascript
js实现iPhone界面风格的单选框和复选框按钮实例
Aug 18 Javascript
jQuery表格行上移下移和置顶的实现方法
Oct 08 Javascript
基于javascript实现九宫格大转盘效果
May 28 Javascript
遍历json获得数据的几种方法小结
Jan 21 Javascript
JS+canvas绘制的动态机械表动画效果
Sep 12 Javascript
node 版本切换的实现
Feb 02 Javascript
jQuery 移除事件的方法
Jun 20 jQuery
解决Nuxt使用axios跨域问题
Jul 06 Javascript
微信小程序之仿微信漂流瓶实例
Dec 09 #Javascript
JS判断是否手机或pad访问实现方法
Dec 09 #Javascript
js实现一个可以兼容PC端和移动端的div拖动效果实例
Dec 09 #Javascript
利用JS实现页面删除并重新排序功能
Dec 09 #Javascript
Bootstrap table使用方法详细介绍
Dec 09 #Javascript
jQuery Validate设置onkeyup验证的实例代码
Dec 09 #Javascript
任意Json转成无序列表的方法示例
Dec 09 #Javascript
You might like
PHP中for循环语句的几种变型
2006/11/26 PHP
php中的注释、变量、数组、常量、函数应用介绍
2012/11/16 PHP
php CI框架插入一条或多条sql记录示例
2014/07/29 PHP
老生常谈PHP中的数据结构:DS扩展
2017/07/17 PHP
nodejs的require模块(文件模块/核心模块)及路径介绍
2013/01/14 NodeJs
一个页面元素appendchild追加到另一个页面元素的问题
2013/01/27 Javascript
javascript正则匹配汉字、数字、字母、下划线
2014/04/10 Javascript
使用node.js 获取客户端信息代码分享
2014/11/26 Javascript
jquery比较简洁的软键盘特效实现方法
2015/03/19 Javascript
jquery实现点击弹出带标题栏的弹出层(从右上角飞入)效果
2015/09/19 Javascript
Nodejs的express使用教程
2015/11/23 NodeJs
Bootstrap下拉菜单Dropdowns的实现代码
2017/03/17 Javascript
详谈commonjs模块与es6模块的区别
2017/10/18 Javascript
vue中如何创建多个ueditor实例教程
2017/11/14 Javascript
AngularJS实现与后台服务器进行交互的示例讲解
2018/08/13 Javascript
了解Javascript中函数作为对象的魅力
2019/06/19 Javascript
tracking.js实现前端人脸识别功能
2020/04/16 Javascript
js+canvas实现刮刮奖功能
2020/09/13 Javascript
[08:47]DOTA2每周TOP10 精彩击杀集锦vol.6
2014/06/25 DOTA
Python判断变量是否已经定义的方法
2014/08/18 Python
python 出现SyntaxError: non-keyword arg after keyword arg错误解决办法
2017/02/14 Python
python编程实现12306的一个小爬虫实例
2017/12/27 Python
python机器学习案例教程——K最近邻算法的实现
2017/12/28 Python
使用Python通过win32 COM实现Word文档的写入与保存方法
2018/05/08 Python
wxpython+pymysql实现用户登陆功能
2019/11/19 Python
python实现小程序推送页面收录脚本
2020/04/20 Python
html5中valid、invalid、required的定义
2014/02/21 HTML / CSS
美国女孩洋娃娃店:American Girl
2017/10/24 全球购物
澳大利亚先进的皮肤和激光诊所购物网站:Soho Skincare
2018/10/15 全球购物
this关键字的含义
2015/04/08 面试题
男方父母婚礼答谢词
2014/01/25 职场文书
公司授权委托书范本
2014/04/03 职场文书
餐饮服务食品安全责任书
2014/07/25 职场文书
违章停车检讨书
2014/10/21 职场文书
导游词之桂林山水
2019/09/20 职场文书
导游词之无锡华莱坞
2019/12/02 职场文书