原生JS实现导航下拉菜单效果


Posted in Javascript onNovember 25, 2020

这个导航下拉菜单需要实现的功能是:下拉菜单的宽度与浏览器视口的宽度一样宽;一级导航只有两项,当鼠标移到一级导航上的导航项时,相应的二级导航出现。在本案例中通过改变二级导航的高度来实现二级导航的显示和消失。为了便于理解我画了一个图,如下:

原生JS实现导航下拉菜单效果

在这个案例主要用到的知识有:设置定时器,清除定时器,mouseout和mouseover事件,另外还有css中position相关知识。本案例分为两部分讲解。第一部分html和css,第二部分js。

一. html和css

将导航这个导航条包裹在一个div中,这个div的position值为relative,高度为50px(导航条的高度为50px),宽度为100%,将最外层的div的position属性设置为relative是因为二级导航要根据这个div来定位。这个导航条的结构是二级嵌套无序列表。每一个一级导航项li都嵌套了它对应的无序列表。需要将嵌套的无序列表移除文档流。所以嵌套的无序列表的position值为absolute,top:50px(导航条的高度)。left:0;right:0;通过设置这些值可以使嵌套的无序列表宽度为浏览器视口的宽度。通过将li的display值设置inline-block并且将外层div的text-align设置为center使得导航项居中显示

注:在这个案例中一定要将嵌套的无序列表的position的值设置为absolute,使它移除文档流。

html和css代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <title>下拉菜单</title>
 <link rel="stylesheet" type="text/css" href="index.css" rel="external nofollow" >
</head>
<body>
 <div class='header'>
  <ul class='outer' id='outer'>
   <li class='outerList' id='outerList1'><a href='#' id='link1' class='link'>产品<span></span></a>
    <ul class='inter' id='inter1'>
     <li>
      <a href='#'>
       <img src='img/01fea55541ed73000001714a430253.jpg'>
       <strong>纳斯</strong>
      </a>
     </li>
     <li>
      <a href='#'>
       <img src='img/thumb_image3.jpg'>
       <strong>纯色</strong>
      </a>
     </li>
     <li>
      <a href='#'>
       <img src='img/白胡子.jpg'>
       <strong>保温杯</strong>
      </a>
     </li>
     <li>
      <a href='#'>
       <img src='img/宠物.jpg'>
       <strong>设计周边</strong>
      </a>
     </li>
    </ul>
   </li>
   <li class='outerList' id='outerList2'><a href='#' id='link2' class='link'>服务<span></span></a>
    <ul class='inter' id = 'inter2'>
     <li>
      <a href='#'>
       <img src='img/狮子座.jpg'>
       <strong>售后服务</strong>
      </a>
     </li>
     <li>
      <a href='#'>
       <img src='img/莲花禅.jpg'>
       <strong>设计师</strong>
      </a>
     </li>
    </ul>
   </li>
  </ul>
 </div>
 <script type="text/javascript" src='index.js'></script>
</body>
</html>

css代码如下: 

*{
 padding: 0;
 margin: 0;
}
.header{
 position: relative;
 width: 100%;
 height: 50px;
 background-color: #000000;
 text-align: center;
 z-index: 2;
}
.header .outer li{
 display: inline-block;
 list-style: none;
}
.outerList{
 height: 50px;
 line-height: 50px;

}
.outerList a{
 display: block;
 padding: 0 15px;
 color: #fff;
 text-decoration: none;
}
.outerList:hover a{
 color: #EDECEC;

}
.outerList .link span{
 display: block;
 height: 0;
 width: 100%;
 position: relative;
 top: -10px;
 left: 0;
 background-color: #fff;

}
.outerList:hover .link span{
 height: 1px;
}
.outerList .inter{
 position: absolute;
 left: 0;
 height: 0;
 overflow: hidden;
 top: 50px;
 right: 0;
 background-color:rgba(0,0,0,0.5);
}
.outerList .inter li{
 margin-top: 30px;
}
.outerList .inter strong{
 display:block;
 height: 25px;
 line-height: 25px;
 text-align: center;
}

二. js部分

在js部分涉及到的知识主要有:设置定时器,清除定时器,mouseout和mouseover事件。

mouseout事件当鼠标从一个元素上移入另一个元素的上时,会在失去鼠标的那个元素上触发mouseout事件。获得鼠标的那个元素可能是失去鼠标的元素的父元素或子元素,获得鼠标的那个元素也可能位于失去鼠标元素的外部。当在一级导航项上触发mouseout事件时,我们需要判断获得鼠标的元素是不是一级导航项的子孙元素。当一个元素触发了mouseout事件时,去鼠标的元素为目标元素(target),获得鼠标的元素为相关元素(relatedTarget)。所以需要判断相关元素是否为一级导航项的子孙元素,如果是子孙元素,则相应的导航项的二级导航项高度不变。如果不是子孙元素,则相应的二级导航项消失。判断是否为子孙元素的代码如下:

var flag1 = false,flag2 = false;

if(relatedTarget !== null){
  var parented = relatedTarget.parentNode;
  do{
   if(parented === outerList1 || relatedTarget === outerList1){
    flag1 = true;
    break;
   }else if(parented === outerList2 || relatedTarget === outerList2){
    flag2 = true;
    break;
   }else{
    parented = parented.parentNode;
   }
  }while(parented !== null);
 }

注:通过判断flag1和flag2的值来确定是否该把二级菜单的高度变为0,如果flag1的值为false则让outerList1对应的二级菜单消失,如果flag2为false则将outerList2对应的二级菜单消失。

mouseover事件当鼠标移入一个元素内部时,获得鼠标的元素上触发这个事件,获得鼠标的元素可能位于失去鼠标的外部,也可能位于失去鼠标元素的内部。获得鼠标的元素是目标元素,失去鼠标的元素为相关元素。在这个案例中我们只需要判断mouseover的目标元素,但是对于mouseout事件我们需要判断相关元素。

注:在支持DOM的浏览器中,mouseout和mouseover的相关元素都保存在事件对象(event)的relatedTagrget属性中,但是在IE浏览器中,对于mouseout事件而言,相关事件保持在事件对象(event)的toElement属性中,对于mouseover事件而言,相关事件保存在事件对象(event)的fromElement属性中。

设置定时器和清除定时器在这个案例中嵌套无序列表的消失和出现是通过改变它的高度实现的,它的高度是逐渐变化,所以我使用的setTimeout这个定时器,为了能够清除定时器还要将定时器标识保存在一个变量中。清除定时器的目的是为了防止当快速移动鼠标时嵌套无序列表的高度抖动(即:一个定时器里的回调函数让高度增加,另一个定时器的回调函数让高度减小)。

js代码如下: 

var untilEvent = {
 addEvent:function(element,type,hander){
  if(element.addEventListener){
   element.addEventListener(type,hander,false);
  }else if(element.attachEvent){
   element.attachEvent('on'+type,hander);
  }else{
   element['on'+type] = hander;
  }
 },
 getEvent:function(event){
  return event?event:window.event;
 },
 getTarget:function(event){
  return event.target||event.srcElement;
 },
 getRelated:function(event){
  if(event.relatedTarget){
   //兼容DOM的浏览器将相关元素保持在relatedTarget属性中
   return event.relatedTarget;
  }else if(event.toElement){
   //在IE浏览器中mouseout事件的相关元素保存在toElement属性中
   return event.toElement;
  }else if(event.fromElement){
   //在IE浏览器中mouseover事件的相关元素保持在fromElement属性中
   return event.fromElement;
  }else{
   return null;
  }
 }

};
//下面这四个元素用于表示四个定时器的标识,最开始我只使用两个定时器,当快速移动时
//动画会乱。
var timeDec1,timeAdd1,timeAdd2,timeDec2;//定时器标识
function getOuter(){
 var outer = document.getElementById('outer');
 untilEvent.addEvent(outer,'mouseover',callBackOver);
 untilEvent.addEvent(outer,'mouseout',callBackOut);
}
//mouseout事件:当鼠标从一个元素移入另一个元素时在鼠标离开的那个元素
//上触发,获得鼠标的元素可能在失去鼠标元素的外部也可能在失去鼠标元素的
//内部.所以需要判断mouseout事件的相关元素是否为外部li(即id为outerList或id为outerList2)元素
//的子孙元素,如果是子孙元素,则内部无序列表无须收起。
function callBackOut(event){
 var event = untilEvent.getEvent(event);
 var relatedTarget = untilEvent.getRelated(event);
 var outerList1 = document.getElementById('outerList1');
 var inter1 = document.getElementById('inter1');
 var outerList2 = document.getElementById('outerList2');
 var inter2 = document.getElementById('inter2');
 var flag1 = false,flag2 = false;
 if(relatedTarget !== null){
  var parented = relatedTarget.parentNode;
  do{
   if(parented === outerList1 || relatedTarget === outerList1){
    flag1 = true;
    break;
   }else if(parented === outerList2 || relatedTarget === outerList2){
    flag2 = true;
    break;
   }else{
    parented = parented.parentNode;
   }
  }while(parented !== null);
 }
 if(!flag1){
  var str1 = 'flag1';
  changeHeightDec(inter1,timeAdd1,str1);
 }
 if(!flag2){
  var str2 = 'flag2';
  changeHeightDec(inter2,timeAdd2,str2);
 }
}
function changeHeightDec(element,timer,flag){
 var offHeight = 70;
 var inverTimer = 10;
 clearTimeout(timer);
 change();
 function change(){
  var height = parseInt(element.style.height);
  if(!height)height = 0;
  if(height > 0){
   if(height - offHeight > 0){
   element.style.height = height - offHeight +'px';
   }else{
    element.style.height = 0+'px';
   }
   if(flag === 'flag1'){
    timeDec1= setTimeout(change,inverTimer);
   }else{
    timeDec2 = setTimeout(change,inverTimer);
   }
  }
 }
}
function callBackOver(event){
 var event = untilEvent.getEvent(event);
 var target = untilEvent.getTarget(event);
 var inter1 = document.getElementById('inter1');
 var inter2 = document.getElementById('inter2');
 if(target.id == 'outerList1' || target.id == "link1"){
  var str1 = "flag1";
  changeHeight(inter1,timeDec1,str1);
 }
 if(target.id == 'outerList2' || target.id == 'link2'){
  var str2 = "flag2";
  changeHeight(inter2,timeDec2,str2);
 }
}
function changeHeight(element,timer,flag){
 var totalHeight = 160;
 var inverHeight = 10;
 var inverTimer = 10;
 clearTimeout(timer);
 //当鼠标移入时清除让内部ul长度减小的定时器,保证鼠标移入后
 //内部ul长度立即增加
 change();
 function change(){
  var height = parseInt(element.style.height);
  if(!height) height = 0;
  if(height < totalHeight){
   if(height + inverHeight > totalHeight){
    element.style.height = totalHeight + "px";
   }else{
    element.style.height = height + inverHeight +'px';
   }
   if(flag === 'flag1'){
    timeAdd1 = setTimeout(change,inverTimer);
    }else{
     timeAdd2 = setTimeout(change,inverTimer);
    }
  }
 }
}
untilEvent.addEvent(window,'load',getOuter);

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
javascript 语法基础 想学习js的朋友可以看看
Dec 16 Javascript
对xmlHttp对象方法和属性的理解
Jan 17 Javascript
使用js实现按钮控制文本框加1减1应用于小时+分钟
Dec 09 Javascript
jquery.form.js实现将form提交转为ajax方式提交的方法
Apr 07 Javascript
Javascript闭包(Closure)详解
May 05 Javascript
javascript字符串循环匹配实例分析
Jul 17 Javascript
jquery实现的伪分页效果代码
Oct 29 Javascript
DOM 事件的深入浅出(二)
Dec 05 Javascript
获取当前按钮或者html的ID名称实例(推荐)
Jun 23 Javascript
Vue.js与 ASP.NET Core 服务端渲染功能整合
Nov 16 Javascript
jQuery实现带右侧索引功能的通讯录示例【附源码下载】
Apr 17 jQuery
小程序实现上下移动切换位置
Sep 23 Javascript
jQuery插件FusionCharts实现的MSBar2D图效果示例【附demo源码】
Mar 24 #jQuery
基于HTML5+JS实现本地图片裁剪并上传功能
Mar 24 #Javascript
详解Vue-基本标签和自定义控件
Mar 24 #Javascript
JS验证input输入框(字母,数字,符号,中文)
Mar 23 #Javascript
jQuery编写textarea输入字数限制代码
Mar 23 #jQuery
移动端刮刮乐的实现方式(js+HTML5)
Mar 23 #Javascript
BootStrap+Mybatis框架下实现表单提交数据重复验证
Mar 23 #Javascript
You might like
论坛头像随机变换代码
2006/10/09 PHP
php中处理模拟rewrite 效果
2006/12/09 PHP
Laravel框架数据库CURD操作、连贯操作总结
2014/09/03 PHP
php自定文件保存session的方法
2014/12/10 PHP
php实现将字符串按照指定距离进行分割的方法
2015/03/14 PHP
PHP 输出缓冲控制(Output Control)详解
2016/08/25 PHP
PHP 记录访客的浏览信息方法
2018/01/29 PHP
PDO::prepare讲解
2019/01/29 PHP
laravel 解决Validator使用中出现的问题
2019/10/25 PHP
常见的5个PHP编码小陋习以及优化实例讲解
2021/02/27 PHP
JS 事件绑定函数代码
2010/04/28 Javascript
精通Javascript系列之数据类型 字符串
2011/06/08 Javascript
ie下动态加态js文件的方法
2011/09/13 Javascript
js实现字符串的16进制编码不加密
2014/04/25 Javascript
用js读、写、删除Cookie代码分享及详细注释说明
2014/06/05 Javascript
jquery实现的点击翻书效果代码
2015/11/04 Javascript
jquery表单验证插件formValidator使用方法
2016/04/01 Javascript
jquery插件bootstrapValidator数据验证详解
2016/11/09 Javascript
Vue实现路由跳转和嵌套
2017/06/20 Javascript
JavaScript方法_动力节点Java学院整理
2017/06/28 Javascript
[36:33]Ti4 循环赛第四日 附加赛NEWBEE vs Mouz
2014/07/13 DOTA
[58:42]DOTA2上海特级锦标赛C组败者赛 Newbee VS Archon第一局
2016/02/27 DOTA
python获取当前计算机cpu数量的方法
2015/04/18 Python
Python使用MyQR制作专属动态彩色二维码功能
2019/06/04 Python
解决pytorch-yolov3 train 报错的问题
2020/02/18 Python
python爬虫---requests库的用法详解
2020/09/28 Python
python 读取串口数据的示例
2020/11/09 Python
Spartoo比利时:欧洲时尚购物网站
2017/12/06 全球购物
英国婚礼商城:Wedding Mall
2019/11/02 全球购物
党的群众路线教育实践活动总结报告
2014/07/03 职场文书
医院义诊活动总结
2014/07/04 职场文书
2015年度个人业务工作总结
2015/04/27 职场文书
《叶问2》观后感
2015/06/15 职场文书
用Python制作灯光秀短视频的思路详解
2021/04/13 Python
python和C/C++混合编程之使用ctypes调用 C/C++的dll
2022/04/29 Python