JavaScript 进阶营

javascript:自定义选项卡插件

2017-12-15  本文已影响0人  Alisallon

对于经常开发前端的人,有很多喜欢直接用jQuery及jQuery插件,也有很多人喜欢自己写插件。相对于jQuery及jQuery插件,自己写的插件一般都很简单,易懂,也容易维护,也不臃肿。因最近夜里时间比较宽裕,所以准备分享一些自己写插件的过程和最终成果。

自定义选项卡插件

一、新建测试用的demo.html

1、添加3个<li>标签做选项卡用
<ul>
    <!--选项卡1-->
    <li>button 1</li>
    <!--选项卡2-->
    <li>button 2</li>
    <!--选项卡3-->
    <li>button 3</li>
</ul>
2、添加3个<div>做选项卡的分页

选项卡在切换的时候,将会显示对应的分页。
为了区分和看的明显,给不同的div设置不同的背景颜色和设置一个高度。

<!--选项卡分页1-->
<div id="tab1" style="background-color: antiquewhite; height: 100px;">
    this is tab1.
</div>
<!--选项卡分页2-->
<div id="tab2" style="background-color: aqua; height: 100px;">
    this is tab1.
</div>
<!--选项卡分页3-->
<div id="tab3" style="background-color: aquamarine; height: 100px;">
    this is tab1.
</div>

效果如下:


image.png

二、设置<li>标签横排显示

1、给3个<li>标签设置style:
<ul>
    <li style="float: left;">button 1</li>
    <li style="float: left;">button 2</li>
    <li style="float: left;">button 3</li>
</ul>

效果如下:


image.png
2、消除<li>的float对<div>的影响:

发现tab1分页显示有问题,此时需要给<div>设置一个style:clear:left,消除
<li>的float对它的影响:

......
<style>
    .tabNode{
        clear: both;
    }
</style>
.....
<div id="tab1" class="tabNode" style="background-color: antiquewhite; height: 100px;">
    this is tab 1.
</div>
<div id="tab2" class="tabNode" style="background-color: aqua; height: 100px;">
    this is tab 2.
</div>
<div id="tab3" class="tabNode" style="background-color: aquamarine; height: 100px;">
    this is tab 3.
</div>

效果如下:


image.png

二、美化<li>标签

我们发现<li>标签上带着小黑点,而且三个标签是连在一起的,为了好看,我们需要美化一下<li>标签。为了使三个标签通用一个style,我做了以下定义和设置:

......
<style>
    .rootTab > li {
        /*清理左侧的小黑点*/
        list-style: none;
        /*使后面的标签和当前标签横排显示*/
        float: left;
        margin: 1px;
        padding: 15px;
        /*添加边框*/
        border: black solid 1px;
        /*添加圆角*/
        border-radius: 2px;
        background-color: burlywood;
    }
    .tabNode{
        clear: both;
    }
</style>
......
<ul class="rootTab">
    <li>button 1</li>
    <li>button 2</li>
    <li>button 3</li>
</ul>
......

效果如下(好看多了吧 ^_^ ):


image.png

三、写js,控制<li>标签的切换

在</body>和</html>标签中间添加<script></script>,准备在这里直接写js代码:

......
</body>
<script>
    // TODO 准备在这里写
</script>
</html>
1、找到<li>标签

为了控制<li>标签,我们就要先找到<li>标签。因为<li>标签的父标签<ul>已经有一个class,所以我们可以通过该class找到<ul>标签,再通过<ul>标签的子节点找到其拥有的这些<li>标签:

<script>
    // TODO 准备在这里写
    var rootTabNodeList = document.querySelectorAll("ul.rootTab");
    // 此时rootTabNodeList是个NodeList,这个里面存放的是拥有rootTab class的所有ul节点
    // 下面准备遍历该rootTabNodeList,一个一个处理拥有rootTab class的ul节点
    rootTabNodeList.forEach(function (rootTabNode) {
        // 继续寻找该ul节点拥有的所有li节点
        var childLiNodeList = rootTabNode.querySelectorAll("li");
        // 此时childLiNodeList是个NodeList,这个里面存放的是rootTabNode拥有的所有li节点
    });
</script>
2、给<li>标签添加点击事件

继续遍历childLiNodeList,给每一个<li>节点添加onclick事件:

<script>
    // TODO 准备在这里写
    ......
        // 此时childLiNodeList是个NodeList,这个里面存放的是rootTabNode拥有的所有li节点
        // 继续遍历childLiNodeList,给每一个<li>节点添加onclick事件
        childLiNodeList.forEach(function (childLiNode) {
            childLiNode.onclick = function () {
                alert("点击了我");
            };
        });
    ......
</script>

测试查看效果,包您满意 ^_^

3、把<div>分页和<li>标签关联起来

到目前为止,<div>分页和<li>标签还没产生任何联系,为了使<li>标签能够控制<div>分页,我给<li>标签添加了一个自定义的data属性,该属性的值,即对应<div>分页的id:

<ul class="rootTab">
    <!--对应tab1 div分页-->
    <li data-tab-node="tab1">button 1</li>
    <!--对应tab2 div分页-->
    <li data-tab-node="tab2">button 2</li>
    <!--对应tab3 div分页-->
    <li data-tab-node="tab3">button 3</li>
</ul>

这样,我们可以在js中通过获取<li>标签"data-tab"属性值,来获得对应的分页,并控制<div>分页的显示和隐藏。

4、获得<li>标签对应的<div>分页
<script>
    // TODO 准备在这里写
    ......
        // 继续遍历childLiNodeList,给每一个<li>节点添加onclick事件
        childLiNodeList.forEach(function (childLiNode) {
             // 获得<li>节点的data-tab-node属性
            var tabNodeId = childLiNode.getAttribute("data-tab-node");
            // 获得该tabNodeId对应的<div>分页节点
            var tabNode = document.getElementById(tabNodeId);
            childLiNode.onclick = function () {
            };
        });
    ......
</script>
5、控制<li>标签对应的<div>分页

现在打开页面,可以看到,3个<div>分页都是显示的,所以我们需要先把这三个<div>分页隐藏,并且在点击
<li>节点时,显示对应的<div>分页。

<script>
    // TODO 准备在这里写
    ......
    rootTabNodeList.forEach(function (rootTabNode) {
        // 保存当前rootTab拥有的所有<li>节点
        var tabButtonList = [];
        // 保存当前rootTab拥有的所有<div>分页节点
        var tabNodeList = [];
        ......
        childLiNodeList.forEach(function (childLiNode) {
            // 保存当前<li>节点到tabButtonList
            tabButtonList.push(childLiNode);
            ......
            // 保存当前<div>分页到tabNodeList
            tabNodeList.push(tabNode);
            childLiNode.onclick = function () {
                // 隐藏所有<div>分页
                hidenAllTabNode();
                // 显示当前对应的<div>分页
                tabNode.style.display = "block";
            };
        });

        // 隐藏所有<div>分页
        function hidenAllTabNode() {
            tabNodeList.forEach(function (t) {
                t.style.display = "none";
            })
        }
    });
</script>

效果如下(点击tab button查看):


image.png
image.png
image.png
6、美化<li>标签效果

上面效果中,<li>标签点击时没什么变化,我们需要做下美化和脚本控制,增加鼠标滑过,鼠标点击等样式:

<style>
    ......
    .rootTab > li:hover {
        background-color: #de6090;
    }

    .rootTab > li.active,
    .rootTab > li:active {
        background-color: #de282e;
    }
    ......
</style>
......
<script>
    // TODO 准备在这里写
    ......
            childLiNode.onclick = function () {
                // 隐藏所有<div>分页
                hidenAllTabNode();
                // 显示当前对应的<div>分页
                tabNode.style.display = "block";
                // 清除所有<li>节点的激活状态
                clearActiveForAllTabButton();
                // 给当前<li>节点的激活状态
                if (childLiNode.className.indexOf(" active") < 0) {
                    childLiNode.className += " active";
                }
            };
    ......

        // 隐藏所有<div>分页
        function hidenAllTabNode() {
            tabNodeList.forEach(function (t) {
                t.style.display = "none";
            })
        }

        // 清除所有<li>节点的激活状态
        function clearActiveForAllTabButton() {
            tabButtonList.forEach(function (t) {
                t.className = t.className.replace(" active", "");
            })
        }
    });
</script>

效果如下:
鼠标点击后


image.png

鼠标悬浮时


image.png
7、初始化选项卡显示

在刚打开网页的时候,我们发现3个<div>分页还都一起显示,跟我们预期不一样,所以需要在网页打开时,在js里做一些初始化的操作,使页面刚打开时,默认选中选项卡1,显示<div>分页1。


<script>
    // TODO 准备在这里写
    ......
    rootTabNodeList.forEach(function (rootTabNode) {
        ......

        // 显示指定index选项卡
        function show(index) {
            if(index < 0 || index >= tabNodeList.length){
                index = 0;
            }
            // 隐藏所有<div>分页
            hidenAllTabNode();
            // 显示指定index的<div>分页
            tabNodeList[index].style.display = "block";
            // 清除所有<li>节点的激活状态
            clearActiveForAllTabButton();
            // 激活指定index的<li>节点
            if (tabButtonList[index].className.indexOf(" active") < 0) {
                tabButtonList[index].className += " active";
            }
        }

        // 默认显示第2个选项卡
        show(1);
    });
</script>

效果如下:


image.png

四、封装插件

上面几步,我们把想要的基本功能实现了,这一步就是要把上面的实现逻辑封装一下,封装好的js,可以被更好的移植和使用。

1、创建一个TabBar对象

封装时需要用到闭包,我们先创建一个Tab对象:

var TabBar = (function () {
        function TabBar() {
        }
        return TabBar;
    }());
2、复制基本功能逻辑到TabBar的构造方法中
<script>
    // TODO 准备在这里写
    var TabBar = (function () {
        function TabBar() {
            var rootTabNodeList = document.querySelectorAll("ul.rootTab");
            // 此时rootTabNodeList是个NodeList,这个里面存放的是拥有rootTab class的所有ul节点
            // 下面准备循环遍历该rootTabNodeList,一个一个处理拥有rootTab class的ul节点
            rootTabNodeList.forEach(function (rootTabNode) {
                // 保存当前rootTab拥有的所有<li>节点
                var tabButtonList = [];
                // 保存当前rootTab拥有的所有<div>分页节点
                var tabNodeList = [];
                // 继续寻找该ul节点拥有的所有li节点
                var childLiNodeList = rootTabNode.querySelectorAll("li");
                // 此时childLiNodeList是个NodeList,这个里面存放的是rootTabNode拥有的所有li节点
                // 继续遍历childLiNodeList,给每一个<li>节点添加onclick事件
                childLiNodeList.forEach(function (childLiNode) {
                    // 保存当前<li>节点到tabButtonList
                    tabButtonList.push(childLiNode);
                    // 获得<li>节点的data-tab-node属性
                    var tabNodeId = childLiNode.getAttribute("data-tab-node");
                    // 获得该tabNodeId对应的<div>分页节点
                    var tabNode = document.getElementById(tabNodeId);
                    // 保存当前<div>分页到tabNodeList
                    tabNodeList.push(tabNode);
                    childLiNode.onclick = function () {
                        // 隐藏所有<div>分页
                        hidenAllTabNode();
                        // 显示当前对应的<div>分页
                        tabNode.style.display = "block";
                        // 清除所有<li>节点的激活状态
                        clearActiveForAllTabButton();
                        // 给当前<li>节点的激活状态
                        if (childLiNode.className.indexOf(" active") < 0) {
                            childLiNode.className += " active";
                        }
                    };
                });

                // 隐藏所有<div>分页
                function hidenAllTabNode() {
                    tabNodeList.forEach(function (t) {
                        t.style.display = "none";
                    })
                }

                // 清除所有<li>节点的激活状态
                function clearActiveForAllTabButton() {
                    tabButtonList.forEach(function (t) {
                        t.className = t.className.replace(" active", "");
                    })
                }

                // 显示指定index选项卡
                function show(index) {
                    if(index < 0 || index >= tabNodeList.length){
                        index = 0;
                    }
                    // 隐藏所有<div>分页
                    hidenAllTabNode();
                    // 显示指定index的<div>分页
                    tabNodeList[index].style.display = "block";
                    // 清除所有<li>节点的激活状态
                    clearActiveForAllTabButton();
                    // 激活指定index的<li>节点
                    if (tabButtonList[index].className.indexOf(" active") < 0) {
                        tabButtonList[index].className += " active";
                    }
                }

                // 默认显示第2个
                show(1);
            });
        }
        return TabBar;
    }());

    // 初始化TabBar
    new TabBar();
</script>

记得初始化TabBar

new TabBar();

运行查看效果,应该和第三步的效果一样。

3、给TabBar添加show方法,方便在外面控制选项卡的显示

添加show方法:

<script>
    // TODO 准备在这里写
    var TabBar = (function () {
        function TabBar() {}
        .......
        TabBar.prototype = {
            // 控制选项卡显示
            // rootTabId:选项卡的id,即<ul>标签的id
            // index:显示指定id的选项卡中的哪一个分页
            show:function (rootTabId, index) {

            }
        };
        return TabBar;
    }());
    .......
</script>

实现show方法,优化脚本,全部脚本如下:

<script>
    // TODO 准备在这里写
    var TabBar = (function () {
        // 保存选项卡id和选项卡拥有的选项卡按钮的映射关系
        tabButtonMap = [];
        // 保存选项卡id和选项卡拥有的分页的映射关系
        tabNodeMap = [];
        function TabBar() {
            var rootTabNodeList = document.querySelectorAll("ul.rootTab");
            // 此时rootTabNodeList是个NodeList,这个里面存放的是拥有rootTab class的所有ul节点
            // 下面准备循环遍历该rootTabNodeList,一个一个处理拥有rootTab class的ul节点
            rootTabNodeList.forEach(function (rootTabNode) {
                // 保存当前rootTab拥有的所有<li>节点
                var tabButtonList = [];
                // 保存当前rootTab拥有的所有<div>分页节点
                var tabNodeList = [];
                // 继续寻找该ul节点拥有的所有li节点
                var childLiNodeList = rootTabNode.querySelectorAll("li");
                // 此时childLiNodeList是个NodeList,这个里面存放的是rootTabNode拥有的所有li节点
                // 继续遍历childLiNodeList,给每一个<li>节点添加onclick事件
                childLiNodeList.forEach(function (childLiNode, index) {
                    // 保存当前<li>节点到tabButtonList
                    tabButtonList.push(childLiNode);
                    // 获得<li>节点的data-tab-node属性
                    var tabNodeId = childLiNode.getAttribute("data-tab-node");
                    // 获得该tabNodeId对应的<div>分页节点
                    var tabNode = document.getElementById(tabNodeId);
                    // 保存当前<div>分页到tabNodeList
                    tabNodeList.push(tabNode);
                    childLiNode.onclick = function () {
                        // 显示对应index的<div>分页
                        show(tabButtonList, tabNodeList, index);
                    };
                });
                // 添加该选项卡id和该选项卡拥有的选项卡按钮列表到tabButtonMap
                tabButtonMap[rootTabNode.id] = tabButtonList;
                // 添加该选项卡id和选项卡拥有的分页列表到tabNodeMap
                tabNodeMap[rootTabNode.id] = tabNodeList;

                // 默认显示第1个
                show(tabButtonList, tabNodeList, 0);
            });
        }

        // 显示指定index选项卡
        function show(tabButtonList, tabNodeList, index) {
            if(tabButtonList === null || tabButtonList === undefined){
                return;
            }
            if(tabButtonList.length < 0){
                return;
            }
            if(tabNodeList === null || tabNodeList === undefined){
                return;
            }
            if(tabNodeList.length < 0){
                return;
            }
            if (index < 0 || index >= tabNodeList.length) {
                index = 0;
            }
            // 隐藏所有<div>分页
            tabNodeList.forEach(function (t) {
                t.style.display = "none";
            });
            // 显示指定index的<div>分页
            tabNodeList[index].style.display = "block";
            // 清除所有<li>节点的激活状态
            tabButtonList.forEach(function (t) {
                t.className = t.className.replace(" active", "");
            });
            // 激活指定index的<li>节点
            if (tabButtonList[index].className.indexOf(" active") < 0) {
                tabButtonList[index].className += " active";
            }
        }

        TabBar.prototype = {
            // 控制选项卡显示
            // rootTabId:选项卡的id,即<ul>标签的id
            // index:显示指定id的选项卡中的哪一个分页
            show:function (rootTabId, index) {
                show(tabButtonMap[rootTabId], tabNodeMap[rootTabId], index);
            }
        };
        return TabBar;
    }());
</script>

给<ul>标签设置id:tabBar1

<ul id="tabBar1" class="rootTab">

在脚本中调用:

new TabBar();

new TabBar().show("tabBar1", 1);

运行查看效果是否是预期的效果。

4、设置TabBar对象为单例

因为在脚本中,我们处理的是所有class包含rootTab的元素,所以当页面中有其他的class包含rootTab选项卡时,也会被该脚本处理。为了减少部分开销,不每次都new TabBar(),就需要把TabBar对象设置为单例。
在html中添加另一个选项卡,最终成果(html+css+js)如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>选项框</title>
</head>
<style>
    .rootTab > li {
        /*清理左侧的小黑点*/
        list-style: none;
        /*使后面的标签和当前标签横排显示*/
        float: left;
        margin: 1px;
        padding: 15px;
        /*添加边框*/
        border: black solid 1px;
        /*添加圆角*/
        border-radius: 2px;
        background-color: burlywood;
        /*禁止鼠标双击选择文字*/
        user-select: none;
        /*添加手指鼠标显示*/
        cursor: pointer;
    }

    .rootTab > li:hover {
        background-color: #de6090;
    }

    .rootTab > li.active,
    .rootTab > li:active {
        background-color: #de282e;
    }

    .tabNode {
        clear: both;
    }
</style>
<body>
<ul id="tabBar1" class="rootTab">
    <!--对应tab1 div分页-->
    <li data-tab-node="tab1">button 1</li>
    <!--对应tab2 div分页-->
    <li data-tab-node="tab2">button 2</li>
    <!--对应tab3 div分页-->
    <li data-tab-node="tab3">button 3</li>
</ul>
<div id="tab1" class="tabNode" style="clear:left; background-color: antiquewhite; height: 100px;">
    this is tab 1.
</div>
<div id="tab2" class="tabNode" style="background-color: aqua; height: 100px;">
    this is tab 2.
</div>
<div id="tab3" class="tabNode" style="background-color: aquamarine; height: 100px;">
    this is tab 3.
</div>
<ul id="tabBar2" class="rootTab">
    <!--对应tab1 div分页-->
    <li data-tab-node="tab4">button 4</li>
    <!--对应tab2 div分页-->
    <li data-tab-node="tab5">button 5</li>
    <!--对应tab3 div分页-->
    <li data-tab-node="tab6">button 6</li>
</ul>
<div id="tab4" class="tabNode" style="background-color: antiquewhite; height: 100px;">
    this is tab 4.
</div>
<div id="tab5" class="tabNode" style="background-color: aqua; height: 100px;">
    this is tab 5.
</div>
<div id="tab6" class="tabNode" style="background-color: aquamarine; height: 100px;">
    this is tab 6.
</div>
</body>
<script>
    // TODO 准备在这里写
    var TabBar = (function () {
        // 保存单例对象
        var instance = null;

        function TabBar() {
            if (instance === null) {
                instance = new TabBarObj();
            }
            return instance;
        }

        // 创建内部TabBar类
        var TabBarObj = (function () {
            // 保存选项卡id和选项卡拥有的选项卡按钮的映射关系
            tabButtonMap = [];
            // 保存选项卡id和选项卡拥有的分页的映射关系
            tabNodeMap = [];

            function TabBarObj() {
                var rootTabNodeList = document.querySelectorAll("ul.rootTab");
                // 此时rootTabNodeList是个NodeList,这个里面存放的是拥有rootTab class的所有ul节点
                // 下面准备循环遍历该rootTabNodeList,一个一个处理拥有rootTab class的ul节点
                rootTabNodeList.forEach(function (rootTabNode) {
                    // 保存当前rootTab拥有的所有<li>节点
                    var tabButtonList = [];
                    // 保存当前rootTab拥有的所有<div>分页节点
                    var tabNodeList = [];
                    // 继续寻找该ul节点拥有的所有li节点
                    var childLiNodeList = rootTabNode.querySelectorAll("li");
                    // 此时childLiNodeList是个NodeList,这个里面存放的是rootTabNode拥有的所有li节点
                    // 继续遍历childLiNodeList,给每一个<li>节点添加onclick事件
                    childLiNodeList.forEach(function (childLiNode, index) {
                        // 保存当前<li>节点到tabButtonList
                        tabButtonList.push(childLiNode);
                        // 获得<li>节点的data-tab-node属性
                        var tabNodeId = childLiNode.getAttribute("data-tab-node");
                        // 获得该tabNodeId对应的<div>分页节点
                        var tabNode = document.getElementById(tabNodeId);
                        // 保存当前<div>分页到tabNodeList
                        tabNodeList.push(tabNode);
                        childLiNode.onclick = function () {
                            // 显示对应index的<div>分页
                            show(tabButtonList, tabNodeList, index);
                        };
                    });
                    // 添加该选项卡id和该选项卡拥有的选项卡按钮列表到tabButtonMap
                    tabButtonMap[rootTabNode.id] = tabButtonList;
                    // 添加该选项卡id和选项卡拥有的分页列表到tabNodeMap
                    tabNodeMap[rootTabNode.id] = tabNodeList;

                    // 默认显示第1个
                    show(tabButtonList, tabNodeList, 0);
                });
            }

            // 显示指定index选项卡
            function show(tabButtonList, tabNodeList, index) {
                if (tabButtonList === null || tabButtonList === undefined) {
                    return;
                }
                if (tabButtonList.length < 0) {
                    return;
                }
                if (tabNodeList === null || tabNodeList === undefined) {
                    return;
                }
                if (tabNodeList.length < 0) {
                    return;
                }
                if (index < 0 || index >= tabNodeList.length) {
                    index = 0;
                }
                // 隐藏所有<div>分页
                tabNodeList.forEach(function (t) {
                    t.style.display = "none";
                });
                // 显示指定index的<div>分页
                tabNodeList[index].style.display = "block";
                // 清除所有<li>节点的激活状态
                tabButtonList.forEach(function (t) {
                    t.className = t.className.replace(" active", "");
                });
                // 激活指定index的<li>节点
                if (tabButtonList[index].className.indexOf(" active") < 0) {
                    tabButtonList[index].className += " active";
                }
            }

            TabBarObj.prototype = {
                // 控制选项卡显示
                // rootTabId:选项卡的id,即<ul>标签的id
                // index:显示指定id的选项卡中的哪一个分页
                show: function (rootTabId, index) {
                    show(tabButtonMap[rootTabId], tabNodeMap[rootTabId], index);
                    return this;
                }
            };
            return TabBarObj;
        }());
        return TabBar;
    }());
    new TabBar().show("tabBar1", 1).show("tabBar2", 0);
</script>
</html>

注意在脚本中调用:

new TabBar().show("tabBar1", 1).show("tabBar2", 0);

运行查看效果,如果与预期的一致,恭喜你,大功告成 ^_^ 。


image.png
上一篇下一篇

猜你喜欢

热点阅读