zoukankan      html  css  js  c++  java
  • 自动生成博客目录

    前面的话

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

    操作说明

      关于博客目录自动生成,已经封装成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();
            }
        }            
        })(); 
    };
    复制代码

    最后

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

      如果点击右键,会出现自定义菜单,按住ctrl键,再点击右键,会出现原生右键菜单。这是我曾经开发的一个有意思的小功能。为了让两个功能能够兼容,于是window.onload改成了DOM2级事件处理程序的兼容写法

  • 相关阅读:
    Assert.isTrue 用法
    P2967 [USACO09DEC]视频游戏的麻烦Video Game Troubles
    最近目标2333
    LibreOJ β Round #2」贪心只能过样例
    CF1062F Upgrading Cities 拓扑排序
    CF1108F MST Unification
    CF915D Almost Acyclic Graph 拓扑排序
    Swift日历控件Calendar
    README.md的markdown语法
    MAC打开App显示已损坏
  • 原文地址:https://www.cnblogs.com/xieqing/p/6520240.html
Copyright © 2011-2022 走看看