自動生成博客目錄

前面的話

  有朋友在博客下面留言,詢問博客目錄是如何生成的。接下來就詳細介紹實現過程css

 

操做說明

  關於博客目錄自動生成,已經封裝成catalog.js文件,只要引用該文件便可html

    //默認地,爲頁面上全部的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便可json

  【功能簡要說明】數組

  一、點擊目錄項,對應章節標題將顯示在可視區上方瀏覽器

  二、滾動滾輪,目錄項會對應章節標題的變化而相應地變化app

  三、點擊目錄右上角的關閉按鈕,能夠將目錄縮小爲"顯示目錄"四個字,再次單擊縮小後的目錄,可恢復默認狀態編輯器

  四、目錄能夠拖拽至任意地方ide

 

目錄參照

  首先,要肯定的是,基於什麼生成目錄。是文章中的<h3>標籤,仍是文章中的class="list"的標籤。因此,更人性化的作法是,將其做爲參數,默認參數爲<h3>標籤函數

  因爲博客園的博文除了本身生成的博客內容外,博客園還會添加諸如評論、公告、廣告等元素。因此,第一步要先定位博文post

  博文最終都處於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-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;
  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-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 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級事件處理程序的兼容寫法

  但願這兩個插件可以使博客查看更方便

  歡迎交流

相關文章
相關標籤/搜索