自动生成博客目录

前面的话

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

操作说明

  关于博客目录自动生成,已经封装成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:'&times;',
            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-80px;max-118px;overflow:hidden;cursor:default;}
        .boxHide{border:none;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;
  if(oBox.isMove) return; var target = e.target || e.srcElement; //设置关闭按钮的点击事件 if(target.id == 'boxQuit'){ if(target.isHide){ target.innerHTML = '显示目录'; target.className = 'box-quit box-quitAnother' this.className = 'box boxHide'; target.isHide = false; }else{ target.innerHTML = '&times;'; target.className = 'box-quit'; this.className = 'box'; target.isHide = true; } } }
复制代码

滚轮功能

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

复制代码
//设置滚轮事件
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);
window.onscroll = wheel;
复制代码

拖拽功能

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

复制代码
//拖拽实现
oBox.onmousedown = function(e){
    //设置oBox的正在移动状态为假
    oBox.isMove = false;
    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){
        //设置oBox的正在移动状态为真
        oBox.isMove = true;
        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();
    }
}    
复制代码

代码展示

复制代码
//事件处理程序兼容写法
function addEvent(target,type,handler){
    if(target.addEventListener){
        target.addEventListener(type,handler,false);
    }else{
        target.attachEvent('on'+type,function(event){
            return handler.call(target,event);
        });
    }
}
//DOM结构稳定后,再操作
addEvent(window,'load', fnCata);

function fnCata(){
    /*动态样式*/
    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-80px;max-118px;overflow:hidden;cursor:default;background:rgba(0,0,0,0.1);}
            .boxHide{border:none;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 boxHide'
        });
        //创建关闭按钮
        buildPart({
            selector:'span',
            id:'boxQuit',
            className:'box-quit box-quitAnother',
            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;
            if(oBox.isMove) return;
            var target = e.target || e.srcElement;
            //设置关闭按钮的点击事件
            if(target.id == 'boxQuit'){
                if(target.isHide){
                    target.innerHTML = '显示目录';
                    target.className = 'box-quit box-quitAnother'
                    this.className = 'box boxHide';        
                    target.isHide = false;
                }else{
                    target.innerHTML = '&times;';
                    target.className = 'box-quit';
                    this.className = 'box';    
                    target.isHide = true;            
                }
            }
            //获取target的href值
            var sHref = target.getAttribute('href');
            //设置目录项的点击事件
            if(/anchor/.test(sHref)){
                anchorActive(target);
            }
        }    

        //由于点击事件和滚轮事件都需要将目录项发生样式变化,所以声明锚点激活函数
        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);
        window.onscroll = wheel;

    //拖拽实现
    oBox.onmousedown = function(e){
        //设置oBox的正在移动状态为假
        oBox.isMove = false;
        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){
            //设置oBox的正在移动状态为真
            oBox.isMove = true;
            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();
        }
    }            
    })(); 
};
原文地址:https://www.cnblogs.com/tangshiguang/p/6759321.html