1. 程式人生 > >JS實現QQ面板-修改狀態功能

JS實現QQ面板-修改狀態功能

實現的功能是在點選狀態時顯示狀態列表,選中不同狀態會切換當前狀態,點選螢幕其他地方可以隱藏狀態列表。由此可見,實現該效果需要三個功能組成。

首先確定HTML結構,狀態有三部分表示,當前狀態的圖示;一個向下的箭頭;當前狀態字樣。狀態列表中內容由狀態圖示和狀態字樣組成。

<div id="loginState" class="login-state-trigger login-state" title="選擇線上狀態">
        <div id="loginStateShow" class="login-state-show online">狀態</div>
        <div class="login-state-down">下</div>
        <div class="login-state-txt" id="login2qq_state_txt">線上</div>
        <ul id="loginStatePanel" class="statePanel login-state" style="display: none">
            <li id="online" class="statePanel_li">
                <div class="stateSelect_icon online"></div>
                <div class="stateSelect_text">我在線上</div>
            </li>
            <li id="callme" class="statePanel_li">
                <div class="stateSelect_icon callme"></div>
                <div class="stateSelect_text">Q我吧</div>
            </li>
            <li id="away" class="statePanel_li">
                <div class="stateSelect_icon away"></div>
                <div class="stateSelect_text">離開</div>
            </li>
            <li id="busy" class="statePanel_li">
                <div class="stateSelect_icon busy"></div>
                <div class="stateSelect_text">忙碌</div>
            </li>
            <li id="silent" class="statePanel_li">
                <div class="stateSelect_icon silent"></div>
                <div class="stateSelect_text">請勿打擾</div>
            </li>
            <li id="hidden" class="statePanel_li">
                <div class="stateSelect_icon hidden"></div>
                <div class="stateSelect_text">隱身</div>
            </li>
        </ul>
    </div>

然後,寫一下CSS樣式,這個應該還行吧,主要就是初始時狀態列表隱藏,在顯示時整個列表會覆蓋當前狀態。

.login-state-trigger{cursor: pointer;display: block;float: left;height: 16px;overflow: hidden;width: 120px;margin: 10px 0 0 20px;}
.login-state-down {background: url("images/ptlogin.png") no-repeat scroll 0 -22px transparent;float: left;height: 6px;margin-top: 5px;overflow: hidden;text-indent: -999em;width: 7px;}
.login-state-show {float: left;height: 14px;overflow: hidden;text-indent: -999em;width: 14px;margin: 1px 4px 0 0;}
.login-state-txt {float: left;margin-left: 5px;font-size: 12px;line-height:18px!important;}
.login-state .callme {background: url("images/ptlogin.png") -72px 0 no-repeat;}
.login-state .online {background: url("images/ptlogin.png") 0 0 no-repeat;}
.login-state .away {background: url("images/ptlogin.png") -18px 0 no-repeat;}
.login-state .busy {background: url("images/ptlogin.png") -36px 0 no-repeat;}
.login-state .silent {background: url("images/ptlogin.png") -108px 0 no-repeat;}
.login-state .hidden {background: url("images/ptlogin.png") -54px 0 no-repeat;}
.statePanel {display: none;position: absolute;left: 21px;top: 15px;z-index: 10;margin: 0;border-width: 1px;border-style: solid;border-color: #ccc #6a6a6a #666 #cdcdcd;padding: 0;width: 100px;height: 133px;overflow: hidden;background: white;font-size: 12px;line-height: 1.5;}
.statePanel .statePanel_li {display: block;float: left;margin: 0;padding: 3px 0;width: 100px;height: 16px;line-height: 16px;overflow: hidden;zoom: 1;cursor: pointer;}
.stateSelect_icon {float: left;margin: 2px 0 0 5px;width: 14px;height: 14px;overflow: hidden;}
.stateSelect_text {margin: 0 0 0 22px;}

最最關鍵的還是JS,上述三個功能都由JS實現,三個功能我們來依次完成。

點選狀態時顯示狀態列表

首先,定義一個函式,在頁面載入完呼叫它,這個函式就是封裝所有的這些功能。第一步是選擇到整個的父元素,畢竟所有的操作要基於它,因為要顯示狀態列表,所以也得獲取到這個狀態列表的元素。

function drag(){
  var loginState=document.getElementById('loginState'),
      stateList=document.getElementById('loginStatePanel');

  loginState.onclick=function(e){
    stateList.style.display='block';
    }
}

如此就很輕鬆的實現了第一個需求的功能,我們暫不談會不會有bug,反正驗證最終效果是可以點選顯示狀態列表了。

選中不同狀態會切換當前狀態

首先還是要分析一下,磨刀不誤砍柴工嘛。要實現這個功能,其實還是要細分成兩個步驟的,顯示了狀態列表之後,在選中其中每個狀態項時需要改變其背景顏色,然後才是點選選中的狀態替換掉當前的狀態。當然了,這部分是在drag函式中實現。還是先完成簡單的吧,改變背景顏色。那這就需要獲取到每一個 li 元素,再用到onmouseover和onmouseout事件,在滑鼠滑上時它們的背景顏色改變,滑鼠滑過又會恢復原樣。

var lis=stateList.getElementsByTagName('li');
for(var i=0,l=lis.length;i<l;i++){
    lis[i].onmouseover=function(){
        this.style.background='#567';
    }
    lis[i].onmouseout=function(){
        this.style.background='#FFF';
    }
}

背景顏色改變好之後,就要著手實現狀態的切換效果了。既然要切換狀態,那麼我們來對比一下當前狀態和狀態列表中狀態,當前狀態包含了三部分,之前有提到,狀態列表中狀態只有兩部分,切換時改變的是狀態圖示和狀態字樣。明白這一點之後就應該把重心放在這兩部分上。注意,下面程式碼的drag函式和 li 元素遍歷不是另寫一個,只是為了更直觀將該部分程式碼嵌入原有中而已。

function drag(){
    var stateTxt=document.getElementById('login2qq_state_txt'),
        loginStateShow=document.getElementById('loginStateShow');
    for(var i=0,l=lis.length;i<l;i++){
        var id=this.id;
        stateList.style.display='none';
        stateTxt.innerHTML=getByClass('stateSelect_text',id)[0].innerHTML;
        loginStateShow.className='';
        loginStateShow.className='login-state-show '+id;
      }
    }
}

我們來分修改狀態圖示和狀態字樣兩部分來分析一下上述程式碼。第一部分修改狀態字樣,為了修改狀態字樣先獲取到狀態字樣的元素,我們要修改的是這個狀態文字,所以就要用到innerHTML。我們來觀察一下HTML結構,其中狀態字樣所在的div元素的class名都是stateSelect_text,既然是class名,要去獲取這個class可以封裝成一個函式getByClass。

function getByClass(clsName,parent){
    var oParent=parent?document.getElementById(parent):document,
        eles=[],
        elements=oParent.getElementsByTagName('*');

    for(var i=0,l=elements.length;i<l;i++){
        if(elements[i].className==clsName){
            eles.push(elements[i]);
        }
    }
    return eles;
}

封裝getByClass這樣一個函式,用處還是挺大的,以後只要是想獲取指定class的元素都可以呼叫這個函式,第二個引數parent不是必須的,沒有時就從document中尋找這個class名的元素。這個函式的最終是返回一個數組,畢竟class可以重名嘛。

還是回到第一部分修改狀態字樣效果上,呼叫了getByClass函式,傳入引數,指定的class為stateSelect_text,它所在的父元素id,這裡的id是變數,存入的是遍歷的每個 li 元素的id。接著,就是將獲取到的 li 元素的狀態字樣賦給當前狀態的文字中。但有一點不能忘記,在點選目標狀態之後,整個狀態列表會隱藏的。如此就是實現了點選修改狀態字樣的效果。

第二部分修改狀態圖示。觀察HTML結構,當前狀態中的圖示所在div元素,它的class名由兩部分組成login-state-show和online,因為預設的當前狀態和狀態列表中的第一個狀態項是一樣的,只是狀態字樣不一樣,之前我們改好啦。那麼圖示的修改,關鍵點在於這個online,一個是當前列表中的class為online,另一個是狀態列表中第一個項的id為online,所以可想而知,修改圖示,只需要把每一個 li 元素的id值傳給當前狀態中class的online這個位置,在此之前需要將當前元素的class清空,是為了清空原有的login-state-show,畢竟之後還會傳入嘛,這樣不就完成了狀態圖示的修改嘛。但需要注意一點的是login-state-show後面需要空格,這要和HTML結構中完全一致,不然class會誤認為你傳入的 'login-state-show'+id 是組成的一個值。

存在的bug

完成了我們細分的兩部分,應該是實現了狀態的切換效果,在瀏覽器中試驗一下,卻發現在點選選中狀態列表任一項之後整個列表不會隱藏了。存在問題就要去解決問題,回顧我們寫到現在的JS程式碼,我們似乎是遺漏的一個問題。在點選 li 元素時,我們讓stateList隱藏,也就是 ul 隱藏,因為事件冒泡,在點選 li 元素之後會向上冒泡,傳遞事件到它的父元素loginState那,而恰巧該父元素存在事件是讓整個 ul 顯示,因此無論怎麼點選整個狀態列表都不會隱藏了。

解決辦法就是阻止冒泡,而且是在點選 li 元素事件上阻止冒泡。

  lis[i].onclick=function(e){
    e = e || window.event;
    if(e.stopPropagation){
      e.stopPropagation();
    }else{
      e.cancelBubble=true;
    }
}

點選其他地方隱藏狀態列表 

以上其實完成了狀態切換的功能,但這不完善,如果我點選之後出現了狀態列表,但是我又突然不想換狀態了,此刻只能點選到列表中的一項才能隱藏整個列表,這樣使用者體驗就很不好。我們想要的就是在狀態列表顯示時不想切換狀態,可以點選任何地方隱藏狀態列表。

function drag(){
    document.onclick=function(){
        stateList.style.display='none';
    }
}

又出現的bug

所有功能完成了,在試驗時卻發現無論如何點選都不再出現狀態列表。聯想之前出現的bug,猜測可能還是冒泡的原因。檢查一下,果然如此,因為在實現第一個功能時,點選當前狀態顯示狀態列表,由於事件冒泡,事件向上傳遞,最外層的document接收到事件,而它正好被我們設定了隱藏狀態列表的事件,所有狀態列表一直是隱藏狀態。

解決辦法還是阻止冒泡,這次是在點選當前狀態事件上阻止冒泡,避免事件傳遞到document。

loginState.onclick=function(e){
    e = e || window.event;
    if(e.stopPropagation){
        e.stopPropagation();
    }else{
        e.cancelBubble=true;
    }
}

終於,我們歷經坎坷還是完成了需求,其中需要注意到就是明確何時該插入阻止事件冒泡。