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


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 相关文章推荐
JS高级笔记
Jul 13 Javascript
javasctipt如何显示几分钟前、几天前等
Apr 30 Javascript
JQuery日历插件My97DatePicker日期范围限制
Jan 20 Javascript
js跨域资源共享 基础篇
Jul 02 Javascript
jQuery实现图片滑动效果
Mar 08 Javascript
socket.io学习教程之基本应用(二)
Apr 29 Javascript
一次记住JavaScript的6个正则表达式方法
Feb 22 Javascript
微信小程序实现指定显示行数多余文字去掉用省略号代替
Jul 25 Javascript
学习使用ExpressJS 4.0中的新Router的用法
Nov 06 Javascript
express启用https使用小记
May 21 Javascript
微信小程序自定义单项选择器样式
Jul 25 Javascript
解决antd datepicker 获取时间默认少8个小时的问题
Oct 29 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检测移动设备类mobile detection使用实例
2014/04/14 PHP
destoon在各个服务器下设置URL Rewrite(伪静态)的方法
2014/06/21 Servers
YII路径的用法总结
2014/07/09 PHP
php表单敏感字符过滤类
2014/12/08 PHP
PHP设计模式之命令模式示例详解
2020/12/20 PHP
jQuery中filter(),not(),split()使用方法
2010/07/06 Javascript
jquery-easyui关闭tab自动切换到前一个tab
2010/07/29 Javascript
纯CSS打造的导航菜单(附jquery版)
2010/08/07 Javascript
jQuery Dialog 弹出层对话框插件
2010/08/09 Javascript
JS模拟面向对象全解(一、类型及传递)
2011/07/13 Javascript
JS target与currentTarget区别说明
2011/08/28 Javascript
jquery div拖动效果示例代码
2013/12/08 Javascript
用jQuery与JSONP轻松解决跨域访问的问题
2014/02/04 Javascript
让jQuery Mobile不显示讨厌loading界面的方法
2014/02/19 Javascript
使用AngularJS对路由进行安全性处理的方法
2015/06/18 Javascript
javascript基本算法汇总
2016/03/09 Javascript
jQuery模拟实现的select点击选择效果【附demo源码下载】
2016/11/09 Javascript
详解wow.js中各种特效对应的类名
2017/09/13 Javascript
vue 详情跳转至列表页实现列表页缓存
2019/03/27 Javascript
小程序扫描普通链接二维码跳转小程序指定界面方法
2019/05/07 Javascript
Element-UI+Vue模式使用总结
2020/01/02 Javascript
js中位数不足自动补位扩展padLeft、padRight实现代码
2020/04/06 Javascript
[05:53]完美世界携手游戏风云打造 卡尔工作室观战系统篇
2013/04/22 DOTA
[06:14]《辉夜杯》外卡赛附加赛 4支战队巡礼
2015/10/23 DOTA
Python深度优先算法生成迷宫
2018/01/22 Python
python修改list中所有元素类型的三种方法
2018/04/09 Python
解决python3爬虫无法显示中文的问题
2018/04/12 Python
Python中asyncio模块的深入讲解
2019/06/10 Python
Python爬虫实现的根据分类爬取豆瓣电影信息功能示例
2019/09/15 Python
python访问hdfs的操作
2020/06/06 Python
python else语句在循环中的运用详解
2020/07/06 Python
编码实现字符串转整型的函数
2012/06/02 面试题
农业开发项目建议书
2014/05/16 职场文书
国家领导干部党的群众路线教育实践活动批评与自我批评材料
2014/09/23 职场文书
优秀党员推荐材料
2014/12/18 职场文书
退休教师追悼词
2015/06/23 职场文书