[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:'×', 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 = '×'; 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 = '×'; 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級事件處理程式的相容寫法
希望這兩個插件能夠使博客查看更方便
歡迎交流