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 元素的状态字样赋给当前状态的文本中。但有一点不能忘记,在点击目标状态之后,整个状态列表会隐藏的。如此就是实现了点击修改状态字样的效果。

扫描二维码关注公众号,回复: 4249543 查看本文章

第二部分修改状态图标。观察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;
    }
}

终于,我们历经坎坷还是完成了需求,其中需要注意到就是明确何时该插入阻止事件冒泡。

猜你喜欢

转载自blog.csdn.net/bertZuo/article/details/84064007