自動生成博客目錄

来源:http://www.cnblogs.com/xiaohuochai/archive/2016/12/09/6146939.html
-Advertisement-
Play Games

[1]操作說明 [2]目錄參照 [3]目錄連接 [4]目錄顯示 [5]目錄樣式 [6]點擊事件 [7]隱藏功能 [8]滾輪功能 [9]拖拽功能 [10]代碼展示 ...


前面的話

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

 

操作說明

  關於博客目錄自動生成,已經封裝成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-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級事件處理程式的相容寫法

  希望這兩個插件能夠使博客查看更方便

  歡迎交流


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 公司 部門 姓名 職位 分享內容 痛客夢工廠科技有限公司 技術部 張應欽 Web前端開發工程師 HTML5與CSS3 註:此帖子詳見本人博客文件HTML5與CSS3.docx文件 一、 HTML5 二、 CSS3 三、 jQuery與CSS3選擇器(詳見PDF文檔) 註:部分實例見分享會文件demo ...
  • 用 Vue.js 2.x 與相配套的 Vue Router、Vuex 搭建了一個最基本的後臺管理系統的骨架。 當然先要安裝 node.js(包括了 npm)、vue-cli 項目結構如圖所示: assets 中是靜態資源,components 中是組件(以 .vue 為尾碼名的文件),store 中 ...
  • 在學習產品的道路上,第一篇產品分析報告。 鏈接:http://www.woshipm.com/evaluating/486208.html ...
  • 移動端不同尺寸設備dpi不同,會造成1px線條不同程度的縮放,可利用媒體查詢device-pixel-ratio,進行不同情況匹配: @media(-webkit-min-device-pixel-ratio:1.5),(min-device-pixel-ratio:1.5){//dpi:1.5 . ...
  • 測試手機為IPHONE6,開發者工具版本0.10.102800 微信小程式里的canvas 非 h5 canvas有很多不一樣的地方,以下把微信小程式的canvas叫做wxcanvas 下麵全是我一點點測試出的乾貨,耐心看: 1.wxcanvas,不像h5canvas那樣有width和height屬 ...
  • 以後用起來就方便了把 css文件引入 <link rel="stylesheet" type="text/css" href="css/jquery.mCustomScrollbar.css"> js引入 <script type="text/javascript" src="js/jquery.m ...
  • 開始記錄js學習; udacity,edx上不去; ...
  • 移動端響應式頁面開發說簡單也簡單,根據屏幕尺寸調節根字體大小。 大寬度用%,高度和小寬度全部使用rem,簡單粗暴。 之前閱讀過大漠老師的 "使用Flexible實現手淘H5頁面的終端適配" ,介紹了手淘項目的H5製作規範。 在手淘的設計師和前端開發協作過程中:手淘設計師常選擇iPhone6作為基準設 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...