原生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 倒计时源代码.(时.分.秒) 详细注释版
May 09 Javascript
面向对象的Javascript之三(封装和信息隐藏)
Jan 27 Javascript
以Python代码实例展示kNN算法的实际运用
Oct 26 Javascript
JS封装的三级联动菜单(使用时只需要一行js代码)
Oct 24 Javascript
Bootstrap 网格系统布局详解
Mar 19 Javascript
详解vue slot插槽的使用方法
Jun 13 Javascript
jquery手机触屏滑动拼音字母城市选择器的实例代码
Dec 11 jQuery
JS中获取 DOM 元素的绝对位置实例详解
Apr 23 Javascript
jQuery表单元素过滤选择器用法实例分析
Feb 20 jQuery
vue elementui 实现搜索栏公共组件封装的实例代码
Jan 20 Javascript
详解JavaScript中的链式调用
Nov 27 Javascript
vite+vue3.0+ts+element-plus快速搭建项目的实现
Jun 24 Vue.js
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用户登录之cookie信息安全分析
2016/05/13 PHP
JavaScript语法着色引擎(demo及打包文件下载)
2007/06/13 Javascript
jQuery EasyUI API 中文文档 - ComboBox组合框
2011/10/07 Javascript
javascript实现tabs选项卡切换效果(自写原生js)
2013/03/19 Javascript
利用JavaScript实现新闻滚动效果(实例代码)
2013/11/27 Javascript
上传图片js判断图片尺寸和格式兼容IE
2014/09/01 Javascript
Angularjs中如何使用filterFilter函数过滤
2016/02/06 Javascript
用JS动态改变表单form里的action值属性的两种方法
2016/05/25 Javascript
jQuery时间日期三级联动(推荐)
2016/11/27 Javascript
基于canvas的二维码邀请函生成插件
2017/02/14 Javascript
jquery中关于bind()方法的使用技巧分享
2017/03/30 jQuery
Vuejs 2.0 子组件访问/调用父组件的方法(示例代码)
2018/02/08 Javascript
vue2.0 循环遍历加载不同图片的方法
2018/03/06 Javascript
Vue项目全局配置微信分享思路详解
2018/05/04 Javascript
详解实现一个通用的“划词高亮”在线笔记功能
2019/04/23 Javascript
jqGrid表格底部汇总、合计行footerrow处理
2019/08/21 Javascript
vue组件创建的三种方式小结
2020/02/03 Javascript
详解JavaScript类型判断的四种方法
2020/10/21 Javascript
[01:54]TI珍贵瞬间系列(三):翻盘
2020/08/28 DOTA
简述Python中的面向对象编程的概念
2015/04/27 Python
python实现的多线程端口扫描功能示例
2017/01/21 Python
python WindowsError的错误代码详解
2017/07/23 Python
Python面向对象之类的封装操作示例
2019/06/08 Python
利用Python实现Shp格式向GeoJSON的转换方法
2019/07/09 Python
Pytorch to(device)用法
2020/01/08 Python
利用html5 canvas动态画饼状图的示例代码
2018/04/02 HTML / CSS
营销与策划应届生求职信
2013/11/04 职场文书
优秀毕业生求职信范文
2014/01/02 职场文书
工商局局长个人对照检查材料思想汇报
2014/09/23 职场文书
镇副书记专题民主生活会对照检查材料思想汇报
2014/10/02 职场文书
初中教师德育工作总结2015
2015/05/12 职场文书
会计手工模拟做账心得体会
2016/01/22 职场文书
pytorch中[..., 0]的用法说明
2021/05/20 Python
OpenCV-Python实现图像平滑处理操作
2021/06/08 Python
Java中的Kotlin 内部类原理
2022/06/16 Java/Android