遇到的问题:
今天在练习写一个自定义右键多级菜单的时候,遇到一个问题,就是第三级的子菜单定位不对,这个定位是依据父菜单的offsetWidth来确定的。打印了一下offsetWidth的值,一直都是0;在这里卡了很久。后来去搜索,原来元素不显示的时候offsetWidth的值是获取不到的,为0;但是我的三级子菜单是在二级子菜单显示的情况下才出现的。后来分析才发现,在显示三级子菜单之前会将所有菜单隐藏,然后通过事件冒泡显示三级菜单,再显示二级菜单,因此我就获取不到二级菜单的offsetWidth。
所以我解决的方法就是将事件改成捕获的方式,先显示二级菜单,再显示三级菜单。
又或者这样解决,在显示三级子菜单之前会将所有菜单隐藏这里加个定时器,延长二级菜单的时间,等到我的三级菜单显示了再隐藏二级,然后事件冒泡又把二级显示出来。这个定时器300毫秒应该就足够。
解决方法:
检查要获取的元素是不是已经显示了才获取offsetWidth,一定要确保获取offsetWidth的时候元素已经显示。
检查是不是有发生先隐藏一下,再重新显示这个过程,可能你获取offsetWidth的时候就刚好是隐藏了的时候。
原因:元素不显示(display:none
)的时候offsetWidth的值是获取不到的,为0;
下面记录 自定义右键多级菜单 的这个练习
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>自定义多级右键菜单</title>
<style>
html,
body {
height: 100%;
overflow: hidden;
}
body,
div,
ul,
li {
margin: 0;
padding: 0;
}
ul {
list-style: none;
}
.container {
padding-top: 100px;
}
.container>h2 {
text-align: center;
}
.menu {
display: none;
position: absolute;
top: 0;
left: 0;
}
.menu ul {
padding: 2px;
border: 1px solid #979797;
background: #f1f1f1 url(img/line.png) repeat-y 24px 0;
box-shadow: 2px 2px 2px rgba(0, 0, 0, .6);
}
.menu ul li {
height: 24px;
padding: 0 30px;
line-height: 24px;
white-space: nowrap;
cursor: pointer;
}
.menu ul li.sub {
background: url(img/arrow.png) no-repeat right 9px;
}
.menu ul li.active {
height: 22px;
padding: 0 29px;
line-height: 22px;
background-color: #f1f3f6;
/* 加了border会影响小三角形的位置 */
background-position: right -8px;
border: 1px solid #aecff7;
border-radius: 3px;
}
.menu ul ul{
display: none;
position: absolute;
top: 0;
left: 0;
}
</style>
</head>
<body>
<div class="container">
<h2>自定义多级右键菜单</h2>
</div>
<ul class="menu" id="menu">
<ul>
<li><strong>JavaScript 学习</strong></li>
<li class="sub">
第一课
<ul>
<li>网页特效原理分析</li>
<li>响应用户操作</li>
<li>提示框效果</li>
<li>事件驱动</li>
<li>元素属性操作</li>
</ul>
</li>
<li class="sub">
第二课
<ul>
<li>改变网页背景颜色</li>
<li>函数传参</li>
<li>高重用性函数的编写</li>
<li>126邮箱全选效果</li>
<li>循环及遍历操作</li>
</ul>
</li>
<li class="sub">
第三课
<ul>
<li class="sub">
JavaScript组成
<ul>
<li>ECMAScript</li>
<li>DOM</li>
<li>BOM</li>
<li>JavaScript兼容性来源</li>
</ul>
</li>
<li>JavaScript出现的位置、优缺点</li>
<li>变量、类型、typeof、数据类型转换、变量作用域</li>
<li class="sub">
闭包
<ul>
<li>什么是闭包</li>
<li>简单应用</li>
<li>闭包缺点</li>
</ul>
</li>
<li>运算符</li>
<li>程序流程控制</li>
<li class="sub">
定时器的使用
<ul>
<li>setInterval</li>
<li>setTimeout</li>
</ul>
</li>
</ul>
</li>
</ul>
</ul>
<script>
var menu = document.getElementById('menu');
var uls = menu.getElementsByTagName('ul');
var lis = menu.getElementsByTagName('li');
var htmlWidth = document.documentElement.offsetWidth;
var htmlHeight = document.documentElement.offsetHeight;
// 获取ul的位置
var getOffset = {
top: function (ele) {
return ele.offsetTop + (ele.offsetParent ? arguments.callee(ele.offsetParent) : 0);
},
left: function (ele) {
return ele.offsetLeft + (ele.offsetParent ? arguments.callee(ele.offsetParent) : 0);
}
}
for (var i = 0; i < lis.length; i++) {
//鼠标移入,要在捕获阶段,不然的话会先渲染最后一级子菜单再渲染父菜单,这样子父菜单没有显示,子菜单中设置定位的时候offsetWidth就是0;不用捕获阶段可以使用定时器在移出的时候延长父菜单的显示时间,让第二次渲染可以拿到父菜单的offsetWidth。
lis[i].addEventListener('mouseover',function () {
var oul = this.getElementsByTagName('ul');
this.className += ' active';
//显示子菜单,显示之前先隐藏除自己以外的子菜单
if (oul.length > 0) {
for (var i = 0; i < this.parentNode.children.length; i++) {
if (this.parentNode.children[i].getElementsByTagName('ul')[0]) {
this.parentNode.children[i].getElementsByTagName('ul')[0].style.display = 'none';
}
}
oul[0].style.display = 'block';
//子菜单展示位置的top 和父菜单li一样
oul[0].style.top = this.offsetTop + 'px';
//子菜单展示位置的left 是父菜单的宽度
oul[0].style.left = this.offsetWidth + 'px';
setWidth(oul[0]);
//最大显示范围
var maxWidth = htmlWidth - oul[0].offsetWidth;
var maxHeight = htmlHeight - oul[0].offsetHeight;
//防止溢出
if (maxWidth < getOffset.left(oul[0])) {
//超出范围,将位置设置为相对父菜单的左边
oul[0].style.left = -oul[0].offsetWidth + 'px';
}
if (maxHeight < getOffset.top(oul[0])) {
//超出范围,将位置设置为相对父菜单的上边
oul[0].style.top = -oul[0].offsetHeight + this.offsetTop + this.offsetHeight + 'px';
}
}
},true);
//鼠标移出
lis[i].onmouseout = function () {
var oul = this.getElementsByTagName('ul');
this.className = this.className.replace(/\s?active/, '');
for (var i = 0; i < this.parentNode.children.length; i++) {
if (this.parentNode.children[i].getElementsByTagName('ul')[0]) {
this.parentNode.children[i].getElementsByTagName('ul')[0].style.display = 'none';
}
}
}
}
//自定义右键菜单
document.oncontextmenu = function (e) {
e = e || window.event;
menu.style.top = e.clientY + 'px';
menu.style.left = e.clientX + 'px';
menu.style.display = 'block';
setWidth(uls[0]);
//最大显示范围,页面大小 - 当前menu的大小
var maxWidth = htmlWidth - menu.offsetWidth;
var maxHeight = htmlHeight - menu.offsetHeight;
//防止溢出
if (maxWidth < menu.offsetLeft) {
//超出范围,将menu位置设置为最大值
menu.style.left = maxWidth + 'px';
}
if (maxHeight < menu.offsetTop) {
//超出范围,将menu位置设置最大值
menu.style.top = maxHeight + 'px';
}
return false;
}
//单击后隐藏菜单
document.onclick = function () {
menu.style.display = 'none';
}
//取li中最大的宽度, 并赋给同级所有li
function setWidth(oul){
var maxWidth=0;
for(var i=0;i<oul.children.length;i++){
var li = oul.children[i];
var liWidth = li.clientWidth-parseInt(getCSS(li,'paddingLeft'))*2;
if(liWidth>maxWidth){
maxWidth=liWidth;
}
}
for(var i=0;i<oul.children.length;i++){
oul.children[i].style.width=maxWidth+'px';
}
}
function getCSS(ele,attr){
if(ele.currentStyle){
return ele.currentStyle[attr];
}else{
return getComputedStyle(ele,null)[attr];
}
}
</script>
</body>
</html>