Web 前端实战:JQ 实现树形控件
阅读原文时间:2023年07月08日阅读:5

前言

这是一篇个人练习 Web 前端各种常见的控件、组件的实战系列文章。本篇文章将介绍个人通过 JQuery + 无序列表 + CSS 动画完成一个简易的树形控件。

最终实现的效果是:

这样结构比较复杂的嵌套再嵌套的 HTML 结构必须先写一个静态的观察,不可能一步到位,事情是逐渐发展的。遵循自顶向下、逐步求精、模块化三个原则。

静态实现

点击查看 HTML 代码

<ul class="category">
    <li class="category-item" data-displayed="false">
        <div class="category-tip">分类</div>
        <ul class="sub-category">
            <li class="sub-category-item">item 01</li>
            <li class="sub-category-item">item 02</li>
            <li class="sub-category-item">item 03</li>
        </ul>
    </li>
    <li class="category-item" data-displayed="false">
        <div class="category-tip">分类</div>
        <ul class="sub-category">
            <li class="sub-category-item">item 01</li>
            <li class="sub-category-item">item 02</li>
        </ul>
    </li>
    <li class="category-item" data-displayed="false">
        <div class="category-tip">分类</div>
        <ul class="sub-category">
            <li class="sub-category-item">item 01</li>
        </ul>
    </li>
</ul> 

基本的结构设计就是,在外层是无序列表 ul,每一项内容 li 下面嵌套 div 和 ul 标签。每一层数据展示是类名为category-tip的标签,如果标签下面还有可以展开的内容就有sub-cagetory的 ul。

点击查看 CSS 代码

.category-item {
    cursor: pointer;
    --li-height: 0;
}

.category-item-active {
    animation: category-display 0.18s cubic-bezier(0.42, 0, 0.18, 0.98) 0s;
}

.sub-category {
    display: none;
}

.sub-category-active {
    display: block;
    animation: category-show 0.6s ease-in-out 0s;
}

@keyframes category-show {
    from {
        opacity: 0;
    }

    to {
        opacity: 1;
    }
}

@keyframes category-display {
    from {
        height: 0;
    }

    to {
        height: var(--li-height);
    }
} 

观察最开始的效果展示可知,category-item被点击时需要展开到其子节点sub-category的高度。子节点需要有一种渐变的效果,动画设置的时间要比category-item节点展开的时间长一点。

点击查看 JQuery 代码

$(".category-item").on("click", function () {
    let displayed = $(this)[0].dataset.displayed;
    if ( displayed === "false" ) {
        $(this).css({
            "--li-height": `${ $(this).children(".sub-category").height() }px`
        });
        $(this).addClass("category-item-active");
        $(this).children(".sub-category").addClass("sub-category-active");
        $(this)[0].dataset.displayed = "true";
    } else {
        $(this).removeClass("category-item-active");
        $(this).children(".sub-category").removeClass("sub-category-active");
        $(this)[0].dataset.displayed = "false";
    }
}); 

要实现列表展开和收缩,必须要知道当前节点是否已经展开?因此,就必须给节点标记一个布尔值以便于判断是否展开的状态,正好 HTML 标签支持data-xxx的属性,我们可以把这个布尔值直接放在每一个需要展开的标签中:

<li class="category-item" data-displayed="false">......</li>

当点击节点时,JQuery 需要判断data-displayed是否已经展开,如果展开就移除category-item-active以及sub-category-active的动画;如果没有展开就添加这两个动画,实现展开效果。

改进

category-item-active有一个重大问题,就是点击时节点有类似于重影的重合效果。经过多次调试发现,动画开始时,高度从 0px 开始,所以发生了重影效果。正确是高度从category-tip的高度开始,到category-tipsub-category结束。

$(this).css({
    "--tip-height": `${ $(this).children(".category-tip").height() }px`,
    "--li-height": `${ $(this).children(".sub-category").height() }px`,
});


@keyframes category-display {
    from {
        height: 0;
    }

    to {
        height: calc(var(--li-height) + var(--tip-height));
    }
}

动态实现

上面都还是静态实现,树形控件肯定是多层的,接下来我们将写一个渲染树形控件的函数,并且用到递归函数完成 HTML 的渲染。

通过上面静态案例可知,抽离其数据为 JS 数组:

点击查看数据

let treeOcxData = [
    {
        tip: "分类",
        child: [
            { tip: "设计作品" },
            { tip: "技巧杂烩", child: [ { tip: "Web 前端" } ] }
        ]
    },
    {
        tip: "导航",
        child: [ { tip: "固钉" }, { tip: "回到顶部" }, { tip: "面包屑" } ]
    },
    {
        tip: "数据展示"
    }
]; 

抽离出来之后数据还是挺复杂的,不要慌,万事开头难。首先写一个递归函数能不能实现遍历所有的数据。

function rendTreeOcx(data) {
    for ( let i = 0; i < data.length; i++ ) {
        console.log(data[i].tip);
        if ( data[i].child ) {
            rendTreeOcx(data[i].child);
        }
    }
}

每一个数据都成功的遍历出来了,接下来就是渲染 HTML。

前面递归数组的递归函数能够全部依次打印出数组中每一条数据。在此基础之上,我们要添加 HTML 模板进去,完成树形控件的渲染工作。

点击查看 JS 代码

function rendTreeOcx(data, enableFold) {
    let template = `<ul class="tree-ocx-ul">`;
    if ( enableFold ) template = `<ul class="tree-ocx-ul tree-ocx-ul-enable-fold">`;
    for ( let i = 0; i < data.length; i++ ) {
        if ( data[i].child ) {
            template += `
                <li class="tree-ocx-li tree-ocx-li-enable-fold" data-is-folded="false">
                    <div class="tree-ocx-tip">
                        ${ data[i].tip }
                    </div>
                    ${ rendTreeOcx(data[i].child, true) }
            `;
        } else {
            template += `
                <li class="tree-ocx-li">
                    <div class="tree-ocx-tip tree-ocx-tip-normal">${ data[i].tip }</div>
            `;
        }
        template += `</li>`;
    }
    template += `</ul>`;
    return template;
} 

如果还有子节点就在if ( data[i].child )体内执行递归函数,否则就完成这一层的 HTML。

Gitee 仓库

https://gitee.com/shiramashiro/web-learning/tree/main/src/03.案例

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器

你可能感兴趣的文章