offsetWidth或者clientWidth获取不到,值为0的坑

遇到的问题:

今天在练习写一个自定义右键多级菜单的时候,遇到一个问题,就是第三级的子菜单定位不对,这个定位是依据父菜单的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>

猜你喜欢

转载自blog.csdn.net/weixin_44679078/article/details/106262849