部落格園導航目錄

油炸地獄發表於2020-08-02

部落格園裝飾——(三)部落格園導航目錄

一、功能描述

  1. 初始位置在左下角

  2. 滑鼠放上按鈕會顯示箭頭(目錄 展開方向收起方向 ),移開會顯示 “目錄” 兩個字

  3. 左鍵按住按鈕,可進行拖拽,且會根據目錄的整體大小自動調整在視窗內能拖動到的區域

  4. 右鍵點選按鈕,可展開或收起目錄

  5. 點選目錄內的級別高的條目,可將同級的其他條目內的子條目收起,並展開或收起自己內部的低階別的子條目。

  6. 點選目錄內的條目,可自動導航到其所在的頁面內的絕對位置(高度)

1.1 粗略演示

展開以及條目展開收起與導航.gif

有現成的目錄就在你左下角,可以自己親自體驗一下喲~

二、 核心演算法思想

其實我們最核心的問題就是如何自動根據頁面內所有的h2、h3、h4標籤,生成可分級展開或收起的目錄。

其餘的功能或外觀可以根據自己的需求與喜好進行設計與調整,所以此處重點講解如何自動生成目錄?

2.1 假設頁面情況

2.1.1 文字描述

先模擬部落格園的文章中的標題環境:

  1. 二、三、四級標題可能是 連續鄰近的
  2. 或者可能它們之間會 穿插很多其他標籤及內容

2.1.2 程式碼描述

<div id="cnblogs_post_body">
		<h2>h2-1</h2>
		<img src="../01-html文件結構和常用標籤/image/臭鼬.jpg" alt="">
		<h3>h3-1</h3>
		<h4>h4-1</h4>
		<a href="">花Q</a>
		<h3>h3-1</h3>
		<h4>h4-1</h4>
		<h3>h3-1</h3>

		<h2>h2-2</h2>
		<span>街頭霸王</span>
		<h3>h3-2</h3>
		<h3>h3-2</h3>
		<h3>h3-2</h3>
		<h4>h4-2</h4>
		<div>DD斬首</div>
		<h4>h4-2</h4>
		
		<h2>h2-3</h2>
		<h3>h3-3</h3>
		<span>桌布</span>
		<h4>h4-3</h4>
		<h3>h3-3</h3>
		<img src="../01-html文件結構和常用標籤/image/17.jpg" alt="">
		<h4>h4-3</h4>
		<h3>h3-3</h3>
		<h4>h4-3</h4>
	</div>

2.1.3 效果圖片展示

標題環境示意圖.png

2.1.4 問題描述

正如上圖所示,模擬出的文章環境中,夾著其他很多非二、三、四級標題標籤,而我們需要從中 過濾篩選 出標題標籤,才能進一步自動生成目錄的條目或結構。

2.1.5 解決辦法

var oH = $('#cnblogs_post_body').find('h2,h3,h4');

在js中通過上式篩選出h2,h3,h4標籤,並組成一個元素集oH

2.2 目錄結構

2.2.1 程式碼描述

  1. ul 是2,3,4級標題對應的目錄條目的容器(用於展開或收起)
  2. span 才是我們到時,目錄可點選的併產生相應事件的條目(用於導航或觸發條目的展開或收起)
<div class="diy_menu clearfix">
		<ul class="level2_con">
			<li>
				<!-- 第一個H2 -->
				<span id="0" class="level2">h2-1</span>
				<ul class="level3_con">
				
					<li>
						<!-- 第一個H3 -->
						<span id="1" class="level3">h3-1</span>
						<ul class="level4_con">
							<li>
								<!-- 第一個H3中的第一個H4 -->
								<span id="2" class="level4">h4-1</span>
							</li>
						</ul>
					</li>

					<li>
						<!-- 第二個H3 -->
						<span id="3" class="level3">h3-1</span>
						<ul class="level4_con">
							<li>
								<!-- 第二個H3中的第一個H4 -->
								<span id="4" class="level4">h4-1</span>
							</li>
						</ul>
					</li>
					
					<li>
						<!-- 第三個H3 -->
						<span id="5" class="level3">h3-1</span>
						<ul class="level4_con">
							<!-- 第三個H3中沒有H4 -->
						</ul>
					</li>

				</ul>				
			</li>

			<li>
				<!-- 第二個H2 -->
				<span id="6">h2-2</span>
				<ul>
					.
					.
					.
				</ul>
			</li>

				.
				.
				.
			
			
		</ul>
</div>

2 級標題的目錄條目,以 li>span+ul 的結構嵌入外層的 <ul class="level2_con"></ul> 內部

3 級標題的目錄條目,以 li>span+ul 的結構嵌入外層的 <ul class="level3_con"></ul> 內部

4 級標題的目錄條目,以 li>span 的結構嵌入外層的 <ul class="level4_con"></ul> 內部

2.2.2 圖片效果展示

目錄結構展示.jpg

可以自己對照上面的html的標籤結構,確認一下~

上面的目錄,是經過博主調整過樣式的,為了方便大家直觀清晰的看清目錄的結構

2.2.3 問題引入

為了適配多種情況(即不同的文章結構),以及實現自動根據頁面的文字結構自動生成這樣的目錄結構,當然不可能手動編寫html,來生成目錄,所以我們需要藉助js來識別文章裡的標題標籤,並通過演算法寫入,改變文件流,從而生成相應的目錄結構。

<!-- 即往這個初始結構中,自動寫入html程式碼 -->
<div class="diy_menu clearfix">
	<ul class="level2_con">
        
    	</ul>
</div>

寫入規則參照 2.2.1 下方的描述

2.3 ※ (前期準備)目錄生成演算法

2.3.1 圖文描述

由於我們獲得的標題標籤元素集 'oH'無法直接表現一個目錄結構,以及直觀地告訴我們這些標籤在頁面的絕對高度,所以我們需要兩個列表來直觀提供的展現出來,同時方便我們操作。

① 通過各個標籤在列表中的先後順序展現目錄結構

var tagName_list = [ ]

② 各個標籤對應的頁面的絕對高度

var tagHigh_list = [ ]

演算法剖析.png

2.3.2 程式碼描述

// 過濾篩選出h2,h3,h4的標籤元素
var oH = $('#cnblogs_post_body').find('h2,h3,h4');
var tagName_list = [];
var tagHigh_list = [];

// 相當於python中的for迴圈遍歷;元素集才可以使用each()函式,列表不能
oH.each(function(i){
    tagName_list.push($(this).prop('tagName'));
    tagHigh_list.push(Math.ceil($(this).offset().top));  // 得到的高度值進行向上取整
})
// console.log(tagName_list);
// console.log(tagHigh_list);

執行完上述程式碼後,將得到 標籤名列表對應標籤所在位置的高度值的列表

2.4 ※ (核心)目錄生成演算法

2.4.1 文字描述

  1. 我們最核心的思想就是使用 " if " 語句 判斷它是第幾級標題標籤從而在相應的位置插入相應的級別條目
  2. 正因為我們每一次迴圈需要判斷它是第幾級標籤,故我們需要一個列表 tagName_list ,將 "oH" 元素集對應的標籤名存起來
  3. 同時我們可以看出來,文章中的 標題標籤順序tagName_list 裡的標籤名的邏輯順序是一樣的,所以我們可以配合 " if " 語句,實現自動識別並寫入不同級別的條目,從而構成一個目錄,同時也從側面反映出我們設計一個 **tagName_list 列表 **的初衷
  4. eleh2_3eleh4 用於控制寫入位置(即節點)

2.4.2 程式碼描述

var itagName_list = tagName_list.length;
var eleh2_3 = undefined;  // 用於插入2級或3級標題標籤對應的條目的節點
var eleh4 = undefined;  // 用於插入4級標題標籤對應的條目的節點

for(var i=0;i<itagName_list;i++){

    var a = tagName_list[i];

    if (a == 'H2'){
        eleh2_3 = $('.level2_con');  // 獲取用於插入2級標題標籤對應的條目的節點
        var oh2 = oH.eq(i);
        var plus = '<li><span id="tagHigh_' + i + '" class="level2">'+ oh2.html() +'</span><ul class="level3_con"></ul></li>';
        eleh2_3.append(plus);
        // eleh2_3.html(eleh2_3.html() + plus);
        eleh2_3 = $('.level2_con>li:last ul'); //選擇最後一個即建立的最新的一個li,獲取用於插入3級標題標籤對應的條目的節點
        // console.log(eleh2_3.html());
    }

    else if( a == 'H3'){
        var oh3 = oH.eq(i);
        var plus = '<li><span id="tagHigh_' + i + '" class="level3">' + oh3.html() + '</span><ul class="level4_con"></ul></li>';
        // eleh2_3.html(eleh2_3.html() + plus);
        eleh2_3.append(plus);
        eleh4 = eleh2_3.children("li:last-child").children('ul');  // 獲取用於插入4級標題標籤對應的條目的節點
        // console.log(eleh4.html());
    }

    else if( a == 'H4'){
        var oh4 = oH.eq(i);
        var plus = '<li><span id="tagHigh_' + i + '" class="level4">' + oh4.html() + '</span></li>';
        // eleh4.html(eleh4.html() + plus);
        eleh4.append(plus);

    }

}

2.5 ※ 目錄生成演算法總覽

/*------------------------------------目錄:1.讀取頁面內的標籤並生成目錄----------------------------------*/

    // 過濾篩選出h2,h3,h4的標籤元素
    var oH = $('#cnblogs_post_body').find('h2,h3,h4');
    var tagName_list = [];
    var tagHigh_list = [];
    
    // 相當於python中的for迴圈遍歷;元素集才可以使用each()函式,列表不能
    oH.each(function(i){
        tagName_list.push($(this).prop('tagName'));
        tagHigh_list.push(Math.ceil($(this).offset().top));  // 得到的高度值進行向上取整
    })
    // console.log(tagName_list);
    // console.log(tagHigh_list);

    var itagName_list = tagName_list.length;
    var eleh2_3 = undefined;  // 用於插入2級或3級標題標籤對應的條目的節點
    var eleh4 = undefined;  // 用於插入4級標題標籤對應的條目的節點

    /*---------目錄自動生成演算法-------------*/
    for(var i=0;i<itagName_list;i++){

        var a = tagName_list[i];
        
        if (a == 'H2'){
            eleh2_3 = $('.level2_con');  // 獲取用於插入2級標題標籤對應的條目的節點
            var oh2 = oH.eq(i);
            var plus = '<li><span id="tagHigh_' + i + '" class="level2">'+ oh2.html() +'</span><ul class="level3_con"></ul></li>';
            eleh2_3.append(plus);
            // eleh2_3.html(eleh2_3.html() + plus);
            eleh2_3 = $('.level2_con>li:last ul'); // 選擇最後一個即建立的最新的一個li,獲取用於插入3級標題標籤對應的條目的節點
            // console.log(eleh2_3.html());
        }

        else if( a == 'H3'){
            var oh3 = oH.eq(i);
            var plus = '<li><span id="tagHigh_' + i + '" class="level3">' + oh3.html() + '</span><ul class="level4_con"></ul></li>';
            // eleh2_3.html(eleh2_3.html() + plus);
            eleh2_3.append(plus);
            eleh4 = eleh2_3.children("li:last-child").children('ul');  // 獲取用於插入4級標題標籤對應的條目的節點
            // console.log(eleh4.html());
        }

        else if( a == 'H4'){
            var oh4 = oH.eq(i);
            var plus = '<li><span id="tagHigh_' + i + '" class="level4">' + oh4.html() + '</span></li>';
            // eleh4.html(eleh4.html() + plus);
            eleh4.append(plus);
            
        }

    }

通過上述程式碼,解決了最核心的問題,實現了自動生成目錄的效果了

2.6 ※ 目錄內條目的展開收起與自動導航

 /*------------------------目錄:2.目錄內的條目展開和收起的動畫-------------------------------*/
      /*-------------利用事件委託,減少相同元素繫結相同事件的次數,提高效能---------------------*/
    
    var olevel2_con = $('.level2_con');

    olevel2_con.delegate('.level2, .level3, .level4', 'click', function(){
        // console.log($(this).prop('id').substring(8));
        // 滾動到相應標籤位置
        var tagHighIndex = $(this).prop('id').substring(8);

        var tagHigh = tagHigh_list[tagHighIndex];  // 取該條目對應的標籤的高度值

        $('html,body').stop().animate({'scrollTop':tagHigh-250},1000); 

        // 目錄自動合上與展開動畫
        if ( $(this).prop('className') == 'level2' ){
            $(this).parent().siblings().children('.level3_con').children('li').children('.level4_con').stop().slideUp();
            $(this).next().stop().slideToggle().parent().siblings().children('ul').stop().slideUp();   
        }
        else if ( $(this).prop('className') == 'level3' ){
            //當前點選的元素緊挨的同輩元素向下展開,再跳到此元素的父級(li),再跳到此父級的其他的同輩元素(li),
            //選擇其他同輩元素(li)的子元素ul,然後將它向上收起。
            // 通過stop() 可以修正反覆點選導致的持續動畫的問題
            $(this).next().stop().slideToggle().parent().siblings().children('ul').stop().slideUp();   
        }

        // console.log($(this).prop('className'));
    })

三、完整程式碼展示

前言

以下程式碼只是demo,用於開發和除錯的,HTML部分需要刪減才能貼上到部落格園應用

對於看不懂上面講解或下面程式碼的小白或者想要原始碼進行研究的朋友可以點選下面的連結~

連結:https://pan.baidu.com/s/1ockPW-6RpyonNWp9D0dSIA

提取碼:wz8q

※ 不懂如何食用的小白,可以看看我之前發的部落格園裝飾教程

3.1 HTML 部分

<body>
	<div id="cnblogs_post_body">
		<h2>h2-1</h2>
		<img src="../01-html文件結構和常用標籤/image/臭鼬.jpg" alt="">
		<h3>h3-1</h3>
		<h4>h4-1</h4>
		<a href="">花Q</a>
		<h3>h3-1</h3>
		<h4>h4-1</h4>
		<h3>h3-1</h3>

		<h2>h2-2</h2>
		<span>街頭霸王</span>
		<h3>h3-2</h3>
		<h3>h3-2</h3>
		<h3>h3-2</h3>
		<h4>h4-2</h4>
		<div>DD斬首</div>
		<h4>h4-2</h4>
		
		<h2>h2-3</h2>
		<h3>h3-3</h3>
		<span>桌布</span>
		<h4>h4-3</h4>
		<h3>h3-3</h3>
		<img src="../01-html文件結構和常用標籤/image/17.jpg" alt="">
		<h4>h4-3</h4>
		<h3>h3-3</h3>
		<h4>h4-3</h4>
	</div>
	
	<div class="totop"><span>↑</span></div>
	<div class="tobottom"><span>↓</span></div>
	
	<div class="diy_menu clearfix">
		<input id="diyMenu_btn" type="button" value="→" title="左鍵按住我,可以進行拖拽喲~右鍵點選可以展開喲~">
		<ul class="level2_con" title="左鍵按住左邊的按鈕可進行拖拽喲~右鍵點選可以隱藏喲~">
			

		</ul>
	</div>
	<!--
    <p>文件內容</p>
	<br />
	<br />
	<br />
	<br />
	<br />
	<br />
	<br />
	<br />
	<br />
	<br />
    
	(p{文件內容}+br*10)*20  上面這部分程式碼重複20遍,為了讓頁面足夠長
	-->
</body>

3.2 CSS 部分

body,ul{
    margin:0px;
    padding:0px;
}

.clearfix:after,.clearfix:before{ content: "";display: table;}
.clearfix:after{ clear:both;}
.clearfix{zoom:1;}

ul{list-style:none;}

.diy_menu{
    position:fixed;
    bottom:100px;
    left:75px;
    height: 75px;
    /*cursor: pointer;*/
    /*background-color: gold;*/
}

#diyMenu_btn{
    float:left;
    width:75px;
    font:bold 50px/75px '黑體';
    border:0px;
    color:white;
    cursor: pointer;
    border-radius: 20px;
    /*用於去除點選按鈕後的提示邊框*/
    outline: none;
    opacity: 0.7;
    background-color: #c2c0c0b5;
}

@keyframes color_turn{
    form{
        background-color: #c2c0c0b5;
    }
    to{
        background-color: #40c8f4;
    }
}

.btn_color_turn{
    animation:color_turn 750ms ease 2 alternate;
}

.level2_con{
    width:650px;
    border-radius: 10px;
    overflow: hidden;
    float:left;
    margin:0px;
    /* opacity: 0.7;*/
}

.level2_con .level2, .level3_con .level3, .level4_con .level4{
    display:block;
    width:650px;
    height:30px;
    line-height:30px;
    text-decoration:none;
    background-color:#fc6d86;
    color:#fff;
    font-size:20px;
    font-weight: bold;
    text-indent:10px;   
    border-bottom:4px dashed white;
    opacity: 0.7;     
    cursor:pointer;
}

#diyMenu_btn:hover, .level2_con .level2:hover, .level3_con .level3:hover, .level4_con .level4:hover{
    opacity: 1;
}

.level3_con .level3{
    background-color:#ff8ea2;
    color:#1eb21ee0;
    font-size:17px;
    text-indent:20px;   
    border-bottom:3px solid #1eb21ee0;  
}

.level4_con .level4{
    background-color:pink;
    color:#2893f0d1;
    font-size:14px;
    text-indent:30px;           
    border-bottom:2px dashed #2893f0d1;
}

.level3_con{
    display: none;
}

.level4_con{
    display: none;
}

3.3 JS 部分

$(function(){

    /*------------------------------------目錄:1.讀取頁面內的標籤並生成目錄----------------------------------*/

    // 過濾篩選出h2,h3,h4的標籤元素
    var oH = $('#cnblogs_post_body').find('h2,h3,h4');
    var tagName_list = [];
    var tagHigh_list = [];
    
    // 相當於python中的for迴圈遍歷;元素集才可以使用each()函式,列表不能
    oH.each(function(i){
        // i 為索引值
        // console.log(i);
        // console.log($(this).index());
        tagName_list.push($(this).prop('tagName'));
        tagHigh_list.push(Math.ceil($(this).offset().top));  // 得到的高度值進行向上取整
    })
    // console.log(tagName_list);
    // console.log(tagHigh_list);

    var itagName_list = tagName_list.length;
    var eleh2_3 = undefined;  // 用於插入2級或3級標題標籤對應的條目的節點
    var eleh4 = undefined;  // 用於插入4級標題標籤對應的條目的節點

    /*---------目錄自動生成演算法-------------*/
    for(var i=0;i<itagName_list;i++){

        var a = tagName_list[i];
        
        if (a == 'H2'){
            eleh2_3 = $('.level2_con');  // 獲取用於插入2級標題標籤對應的條目的節點
            var oh2 = oH.eq(i);
            var plus = '<li><span id="tagHigh_' + i + '" class="level2">'+ oh2.html() +'</span><ul class="level3_con"></ul></li>';
            eleh2_3.append(plus);
            // eleh2_3.html(eleh2_3.html() + plus);
            eleh2_3 = $('.level2_con>li:last ul'); // 選擇最後一個即建立的最新的一個li,獲取用於插入3級標題標籤對應的條目的節點
            // console.log(eleh2_3.html());
        }

        else if( a == 'H3'){
            var oh3 = oH.eq(i);
            var plus = '<li><span id="tagHigh_' + i + '" class="level3">' + oh3.html() + '</span><ul class="level4_con"></ul></li>';
            // eleh2_3.html(eleh2_3.html() + plus);
            eleh2_3.append(plus);
            eleh4 = eleh2_3.children("li:last-child").children('ul');  // 獲取用於插入4級標題標籤對應的條目的節點
            // console.log(eleh4.html());
        }

        else if( a == 'H4'){
            var oh4 = oH.eq(i);
            var plus = '<li><span id="tagHigh_' + i + '" class="level4">' + oh4.html() + '</span></li>';
            // eleh4.html(eleh4.html() + plus);
            eleh4.append(plus);
            
        }

    }

    /*------------------------目錄:2.目錄內的條目展開和收起的動畫-------------------------------*/
      /*-------------利用事件委託,減少相同元素繫結相同事件的次數,提高效能---------------------*/
    
    var olevel2_con = $('.level2_con');

    olevel2_con.delegate('.level2, .level3, .level4', 'click', function(){
        // console.log($(this).prop('id').substring(8));
        // 滾動到相應標籤位置
        var tagHighIndex = $(this).prop('id').substring(8);

        var tagHigh = tagHigh_list[tagHighIndex];

        $('html,body').stop().animate({'scrollTop':tagHigh-250},1000); 

        // 目錄自動合上與展開動畫
        if ( $(this).prop('className') == 'level2' ){
            $(this).parent().siblings().children('.level3_con').children('li').children('.level4_con').stop().slideUp();
            $(this).next().stop().slideToggle().parent().siblings().children('ul').stop().slideUp();   
        }
        else if ( $(this).prop('className') == 'level3' ){
            //當前點選的元素緊挨的同輩元素向下展開,再跳到此元素的父級(li),再跳到此父級的其他的同輩元素(li),
            //選擇其他同輩元素(li)的子元素ul,然後將它向上收起。
            // 通過stop() 可以修正反覆點選導致的持續動畫的問題
            $(this).next().stop().slideToggle().parent().siblings().children('ul').stop().slideUp();   
        }

        // console.log($(this).prop('className'));
    })

    /*------------------------目錄:3.目錄整體的展開和隱藏的動畫---------------------------------*/
        /*---------------左鍵按住按鍵可進行目錄拖拽,右鍵點選課展開和收起目錄------------------*/
    // 獲取按鈕元素
    var menu_btn = $('#diyMenu_btn');

    var olevel2 = $('.level2');
    // 一開始獲取原高度,是為了能夠使下面的animate動畫順利展開到原高度
    var OriHeight = olevel2_con.height();

    /*
        一開始是隱藏狀態,所以將height和width設為0,CSS部分按照正常顯示的寬度設定,
        頁面是等js載入完了,才會顯示頁面元素,所以無需擔心一開始目錄會出現突然消失的現象,
        更何況程式執行是一瞬間的事情,肉眼無法分辨,從這個角度思考,也是不用擔心。
    */
    olevel2_con.css({'height': 0, 'width': 0});

    // 為了使目錄能在瀏覽器視窗內拖拽的標誌,'0'表示目錄隱藏,預設目錄隱藏,所以初始值為0
    var unfoldFlag = 0;

    // 拖拽目錄
    var diyMenu = $('.diy_menu');
    var menu_btnHeight = menu_btn.outerHeight(true);
    var menuOriHigh = olevel2_con.outerHeight(true); 
    var oriColor = menu_btn.css('background-color');

    /*-----------------------滑鼠放在按鈕上會切換為箭頭顯示--------------------*/
    // 獲取原來按鈕的值
    var menuBtnVal = menu_btn.val(); 
    // 將初始顯示重新設定為目錄(即滑鼠未放上去時顯示目錄)
    menu_btn.val('目錄');
    // 由於目錄兩字如果使用原大小50px,會顯得特別大
    menu_btn.css({'font-size':25});

    menu_btn.mouseenter(function(){
        menu_btn.val(menuBtnVal);
        menu_btn.css({'font-size':50});
    })

    menu_btn.mouseleave(function(){
        // 離開按鈕前再一次獲取當前按鈕值,用於下一次顯示
        menuBtnVal = menu_btn.val();
        menu_btn.css({'font-size':25});
        menu_btn.val('目錄');
    })
    /*------------------------------------------------------------------------*/

    menu_btn.mousedown(function(e) {

        // 禁止了右鍵點選該按鈕,會彈出瀏覽器系統選單
        $(this).bind("contextmenu",function(e){
            return false;
        });

        // 獲取目錄在頁面的絕對位置,'e'代表滑鼠物件,'e.pageX' 表示滑鼠相對於頁面的橫向位置
        var positionDiv = diyMenu.offset();
        var distenceX = e.pageX - positionDiv.left;  // 獲得滑鼠點選位置相對於目錄左側的距離
        var distenceY = e.pageY - positionDiv.top;  // 獲得滑鼠點選位置相對於目錄頂部的距離
        // console.log(e.pageY, positionDiv.top);

        // alert(distenceX)
        // alert(positionDiv.left);
    
        // console.log(e.which);  // '1' 代表左鍵觸發事件,'2' 代表中鍵,'3' 代表右鍵

        // '3' 是右鍵,用於展開或隱藏目錄
        if( e.which == 3){

            // 移除變為藍色的動畫效果,使得animation動畫得以重複播放
            // 放在這裡,給計算機足夠的反應時間,不會出現bug
            menu_btn.removeClass('btn_color_turn');
            
            if ( menu_btn.val() == '→'){
                menu_btn.val('←');
                // 為了使目錄能在瀏覽器視窗內拖拽的標誌,'1'表示目錄展開
                unfoldFlag = 1;

                /*-------解決animate動畫無法直接讓高度恢復為auto值-----------------*/
                olevel2_con.stop().animate({
                    width:olevel2.width(),
                    height:30,
                },500,function(){
                    
                    olevel2_con.stop().animate({
                        height:OriHeight,
                    },1000,function(){
                        // 放在裡面就會在動畫結束後,才進行賦值
                        olevel2_con.css({'height':'auto'});
                    })

                })
                // 由於 animate動畫的持續時間,不會影響程式的正常執行(多工)
                // 所以下面這一句會在動畫還沒結束前,先執行,而由於動畫過程會動態改變height的值,所以瞬間'auto'被覆蓋掉了
                // olevel2_con.css({'height':'auto'});

            }

            else{
                menu_btn.val('→');   
                // 為了使目錄能在瀏覽器視窗內拖拽的標誌,'0'表示目錄收起,未展開
                unfoldFlag = 0;

                olevel2.parent().siblings().children('.level3_con').children('li').children('.level4_con').stop().slideUp();
                olevel2.next().stop().slideUp().parent().siblings().children('ul').slideUp();   
                
                olevel2_con.stop().animate({
                    height:30,
                },1000,function(){
                    olevel2_con.stop().animate({
                        width:0,
                        height:0,
                    },500)
                })
                    
            }
        }
        // '1'代表左鍵,所以左鍵負責拖拽
        else if( e.which == 1 ){

            // console.log(oriColor);

            menu_btn.css({'background-color': 'lightgreen'});

            $(document).mousemove(function(e) {
      
                // console.log(e.pageX, e.pageY);

                /* 
                    滑鼠移動後的橫向位置 - 滑鼠原位置相對於目錄左側的距離 = 目錄橫向移動後相對於頁面的橫向位置
                    (但不是相對於瀏覽器視窗的位置,由於沒有橫向滾動條,所以頁面寬度和瀏覽器寬度一樣,就無需減去頁面滾動距離)
                */
                var x = e.pageX - distenceX;
                /* 
                    重點:※ 如何獲取元素相對於瀏覽器視窗的距離?
                        滑鼠移動後的縱向位置 - 滑鼠原位置相對於目錄頂部的距離 = 目錄縱向移動後相對於頁面的縱向位置
                    (但不是相對於瀏覽器視窗的位置,由於有縱向滾動條,所以要獲得相對於瀏覽器視窗的縱向位置,就必須
                    減去頁面向上滾動的距離,即$(document).scrollTop())
                */  
                // var y = e.pageY - distenceY;  // 獲得目錄縱向移動後相對於頁面的縱向位置(但不是相對於瀏覽器視窗的位置)
                var y = e.pageY - distenceY - $(document).scrollTop();
                // console.log($(window).height(), diyMenu.height());

                if (x < 0) {
                    x = 0;
                }
                else if (x > $(window).width() - diyMenu.outerWidth(true)) {
                    x = $(window).width() - diyMenu.outerWidth(true);
                }

                if (y < 0) {
                    y = 0;
                } 
                else{
                    // 目錄沒有展開的情況下
                    if ( (unfoldFlag == 0) && (y > $(window).height() - menu_btnHeight)){
                        y = $(window).height() - menu_btnHeight;
                    }
                    // 目錄展開的情況下
                    else if( (unfoldFlag == 1) && (y > $(window).height() - olevel2_con.outerHeight(true))){
                        y = $(window).height() - olevel2_con.outerHeight(true);
                    }
                    
                }

                diyMenu.css({
                    'left': x + 'px',
                    'top': y + 'px'
                });
          
            });

        }

        $(document).mouseup(function() {
            $(document).off('mousemove');
            menu_btn.css({'background-color': oriColor});
            /*---------------------如何使用 jquery + CSS 實現背景色動畫效果?------------------*/
            // 不能放到此處,因為離下面那一句太近了,會出現bug,即animation動畫只能播放一次
            // menu_btn.removeClass('btn_color_turn');
            // 右鍵點選會播放animation換色動畫
            if (e.which == 3){
                menu_btn.addClass('btn_color_turn');
            }
        });
    });
    /*----------------------------------------------------------------------------------------------------------*/
})

3.4 效果展示

3.4.1 目錄展開收起與導航

展開以及條目展開收起與導航.gif

3.4.2 拖拽演示(左鍵按住按鈕拖拽)

拖拽區域.gif

3.4.3 按鈕內容與顏色變化

部落格園導航目錄

四、總結與後言

上面我著重講解了核心演算法,是因為實現了目錄最核心的兩個功能:自動生成條目導航

但並不是說其他功能或設計就沒什麼難度或不重要,就如視窗區域內拖動這個功能,網上尋找很多案例,都無法達到我的想法與預期,所以博主我絞盡腦汁,通過自己的思考終於解決了這一難題。個人認為關於這一功能的演算法講解與難點的剖析,還是很有探討價值的,故本人打算用另一篇隨筆進行講解。

最後再來一發~

連結:https://pan.baidu.com/s/1ockPW-6RpyonNWp9D0dSIA

提取碼:wz8q

※ 不懂如何食用的小白,可以看看我之前發的部落格園裝飾教程

博主還有其他幾篇關於部落格園裝飾的文章,可供觀看喲~

部落格園裝飾——(一)置頂選單欄

部落格園裝飾——(二)滾動到頁面頂部或底部

相關文章