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


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 相关文章推荐
北京奥运官方网站幻灯切换效果flash版打包下载
Jan 30 Javascript
动态修改DOM 里面的 id 属性的弊端分析
Sep 03 Javascript
JavaScript根据数据生成百分比图和柱状图的实例代码
Jul 14 Javascript
jQuery中:first选择器用法实例
Dec 30 Javascript
JavaScript入门基础
Aug 12 Javascript
javascript简易画板开发
Apr 12 Javascript
JavaScript提高加载和执行效率的方法
Feb 03 Javascript
self.attachevent is not a function的解决方法
Apr 04 Javascript
微信小程序开发教程之增加mixin扩展
Aug 09 Javascript
canvas绘制爱心的几种方法总结(推荐)
Oct 31 Javascript
详解微信小程序审核不通过的解决方法
Jan 17 Javascript
详解js中Array的方法及技巧
Sep 12 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
一个显示某段时间内每个月的方法 返回由这些月份组成的数组
2012/05/16 PHP
php中filter函数验证、过滤用户输入的数据
2014/01/13 PHP
php获取当前时间的毫秒数的方法
2014/01/26 PHP
PHP写日志的实现方法
2014/11/05 PHP
php加密解密字符串示例
2016/10/13 PHP
PHP中检查isset()和!empty()函数的必要性
2019/02/13 PHP
经典海量jQuery插件 大家可以收藏一下
2010/02/07 Javascript
String.prototype实现的一些javascript函数介绍
2013/11/22 Javascript
JQuery 给元素绑定click事件多次执行的解决方法
2014/09/09 Javascript
JQuery 实现在同一页面锚点链接之间的平滑滚动
2014/10/29 Javascript
可以浮动某个物体的jquery控件用法实例
2015/07/24 Javascript
深入理解Webpack 中路径的配置
2017/06/17 Javascript
webpack多页面开发实践
2017/12/18 Javascript
javascript中数组的常用算法深入分析
2019/03/12 Javascript
JavaScript实现星级评价效果
2019/05/17 Javascript
JS字符串补全方法padStart()和padEnd()
2020/05/27 Javascript
[01:31]完美与DOTA2历程
2014/07/31 DOTA
python中常用的各种数据库操作模块和连接实例
2014/05/29 Python
详解Python 正则表达式模块
2018/11/05 Python
浅析Python 实现一个自动化翻译和替换的工具
2019/04/14 Python
Python函数参数匹配模型通用规则keyword-only参数详解
2019/06/10 Python
快速解决vue.js 模板和jinja 模板冲突的问题
2019/07/26 Python
使用python从三个角度解决josephus问题的方法
2020/03/27 Python
Python sublime安装及配置过程详解
2020/06/29 Python
pycharm使用技巧之自动调整代码格式总结
2020/11/04 Python
python 使用paramiko模块进行封装,远程操作linux主机的示例代码
2020/12/03 Python
英国第一的购买便宜玩具和游戏的在线购物网站:Bargain Max
2018/01/24 全球购物
舞蹈教育学专业推荐信
2013/11/27 职场文书
店长助理岗位职责
2013/12/13 职场文书
三好学生自我鉴定
2013/12/17 职场文书
大专学生求职信
2014/07/04 职场文书
2014年实验室工作总结
2014/12/03 职场文书
就业推荐表自我评价范文
2015/03/02 职场文书
圆明园观后感
2015/06/03 职场文书
Django中的JWT身份验证的实现
2021/05/07 Python
sql通过日期判断年龄函数的示例代码
2021/07/16 SQL Server