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


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中ajax调用json数据的使用说明
Mar 17 Javascript
jQuery+.net实现浏览更多内容(改编php版本)
Mar 28 Javascript
手写的一个兼容各种浏览器的javascript getStyle函数(获取元素的样式)
Jun 06 Javascript
jQuery焦点图轮播效果实现方法
Dec 19 Javascript
JavaScript实现打印星型金字塔功能实例分析
Sep 27 Javascript
ES6学习教程之对象字面量详解
Oct 09 Javascript
JS实现瀑布流布局
Oct 21 Javascript
微信小程序实现折叠展开效果
Jul 19 Javascript
微信小程序渲染性能调优小结
Jul 30 Javascript
js实现的格式化数字和金额功能简单示例
Jul 30 Javascript
浅析vue-router实现原理及两种模式
Feb 11 Javascript
vue框架中props的typescript用法详解
Feb 17 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
使用sockets:从新闻组中获取文章(二)
2006/10/09 PHP
php strtotime 函数UNIX时间戳
2009/01/14 PHP
PHP var_dump遍历对象属性的函数与应用代码
2010/06/04 PHP
PHP中=赋值操作符对不同数据类型的不同行为
2011/01/02 PHP
php使用fullcalendar日历插件详解
2019/03/06 PHP
在laravel中实现ORM模型使用第二个数据库设置
2019/10/24 PHP
js用Date对象处理时间实现思路及代码
2013/01/31 Javascript
innerText和textContent对比及使用介绍
2013/02/27 Javascript
ExtJs默认的字体大小改变的几种方法(自己整理)
2013/04/18 Javascript
JQuery制作的放大效果的popup对话框(未添加任何jquery plugin)分享
2013/04/28 Javascript
JS实现的省份级联实例代码
2013/06/24 Javascript
jquery左右滚动焦点图banner图片鼠标经过显示上下页按钮
2013/10/11 Javascript
Javascript学习笔记之数组的构造函数
2014/11/23 Javascript
深入理解JavaScript系列(26):设计模式之构造函数模式详解
2015/03/03 Javascript
SWFObject基本用法实例分析
2015/07/20 Javascript
分享经典的JavaScript开发技巧
2015/11/21 Javascript
轻松学习jQuery插件EasyUI EasyUI创建树形菜单
2015/11/30 Javascript
逻辑表达式中与或非的用法详解
2016/06/06 Javascript
Javascript中引用类型传递的知识点小结
2017/03/06 Javascript
jQuery实现base64前台加密解密功能详解
2017/08/29 jQuery
js中split()方法得到的数组长度问题
2018/07/19 Javascript
在layui下对元素进行事件绑定的实例
2019/09/06 Javascript
[10:14]2018DOTA2国际邀请赛寻真——paiN Gaming不仅为自己而战
2018/08/14 DOTA
[57:53]Secret vs Pain 2018国际邀请赛小组赛BO2 第二场 8.17
2018/08/20 DOTA
Python操作SQLite简明教程
2014/07/10 Python
Python下载网络文本数据到本地内存的四种实现方法示例
2018/02/05 Python
完美解决安装完tensorflow后pip无法使用的问题
2018/06/11 Python
Python线程下使用锁的技巧分享
2018/09/13 Python
python实现横向拼接图片
2020/03/23 Python
如何在发生故障的节点上重新安装 SQL Server
2013/03/14 面试题
一些.net面试题
2014/10/06 面试题
学生保证书范文
2014/04/28 职场文书
2015年专项整治工作总结
2015/04/03 职场文书
公司规章制度范本
2015/08/03 职场文书
2016年主题党日活动总结
2016/04/05 职场文书
Django实现聊天机器人
2021/05/31 Python