原生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 相关文章推荐
jQuery .tmpl(), .template()学习资料小结
Jul 18 Javascript
js计算两个时间之间天数差的实例代码
Nov 19 Javascript
javascript中的原型链深入理解
Feb 24 Javascript
javascript随机显示背景图片的方法
Jun 18 Javascript
JS 实现随机验证码功能
Feb 15 Javascript
Vue2.0 axios前后端登陆拦截器(实例讲解)
Oct 27 Javascript
父组件中vuex方法更新state子组件不能及时更新并渲染的完美解决方法
Apr 25 Javascript
JavaScript实现读取与输出XML文件数据的方法示例
Jun 05 Javascript
Redux实现组合计数器的示例代码
Jul 04 Javascript
前后端如何实现登录token拦截校验详解
Sep 03 Javascript
微信小程序国际化探索实现(附源码地址)
May 20 Javascript
JavaScript 实现拖拽效果组件功能(兼容移动端)
Nov 11 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
PHP中的错误处理、异常处理机制分析
2012/05/07 PHP
php中动态变量用法实例
2015/06/10 PHP
php通过淘宝API查询IP地址归属等信息
2015/12/25 PHP
PHP微信公众号自动发送红包API
2016/06/01 PHP
WordPress JQuery处理沙发头像
2009/06/22 Javascript
Jquery 组合form元素为json格式,asp.net反序列化
2009/07/09 Javascript
js跨域问题之跨域iframe自适应大小实现代码
2010/07/17 Javascript
jquery异步调用页面后台方法&amp;#8207;(asp.net)
2011/03/01 Javascript
lyhucSelect基于Jquery的Select数据联动插件
2011/03/29 Javascript
jquery实现弹出div,始终显示在屏幕正中间的简单实例
2014/03/08 Javascript
jQuery中animate用法实例分析
2015/03/09 Javascript
javascript中递归的两种写法
2017/01/17 Javascript
javascript实现多张图片左右无缝滚动效果
2017/03/22 Javascript
详解webpack解惑:require的五种用法
2017/06/09 Javascript
基于jquery日历价格、库存等设置插件
2020/07/05 jQuery
vue展示dicom文件医疗系统的实现代码
2018/08/27 Javascript
vue v-for循环重复数据无法添加问题解决方法【加track-by='索引'】
2019/03/15 Javascript
微信小程序swiper使用网络图片不显示问题解决
2019/12/13 Javascript
JavaScript常用工具函数库汇总
2020/09/17 Javascript
Python 连连看连接算法
2008/11/22 Python
Python如何实现MySQL实例初始化详解
2017/11/06 Python
Python实现的寻找前5个默尼森数算法示例
2018/03/25 Python
Python读写/追加excel文件Demo分享
2018/05/03 Python
Python 实现域名解析为ip的方法
2019/02/14 Python
vscode 配置 python3开发环境的方法
2019/09/19 Python
Python识别html主要文本框过程解析
2020/02/18 Python
用Python生成HTML表格的方法示例
2020/03/06 Python
css3+jq创作含苞待放的荷花
2014/02/20 HTML / CSS
html5使用Canvas绘图的使用方法
2017/11/21 HTML / CSS
巧用HTML5给按钮背景设计不同的动画简单实例
2016/08/09 HTML / CSS
Shopee印度尼西亚:东南亚与台湾市场最大电商平台
2018/06/17 全球购物
南非最大的在线时尚商店:Zando
2019/07/21 全球购物
ManoMano英国:欧洲第一家专注于DIY和园艺市场的电商平台
2020/03/12 全球购物
高校自主招生校长推荐信
2015/03/23 职场文书
电影红河谷观后感
2015/06/11 职场文书
SQL实现LeetCode(176.第二高薪水)
2021/08/04 MySQL