国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

徒手擼UI之Tree

2i18ns / 2755人閱讀

摘要:是一個組件庫目前擁有的組件語法編寫,無依賴原生模塊化,以上支持,請開啟靜態服務器預覽效果,靜態服務器傳送門采用變量配置樣式辛苦造輪子,歡迎來倉庫四月份找工作,求內推,坐標深圳寫在前面去年年底項目中嘗試著寫過一個分頁的組件,然后就有了寫的想法

QingUI是一個UI組件庫

目前擁有的組件:DatePicker, TimePicker, Paginator, Tree, Cascader, Checkbox, Radio, Switch, InputNumber, Input

ES6語法編寫,無依賴

原生模塊化,Chrome63以上支持,請開啟靜態服務器預覽效果,靜態服務器傳送門

采用CSS變量配置樣式

辛苦造輪子,歡迎來github倉庫star:QingUI

四月份找工作,求內推,坐標深圳
寫在前面

去年年底項目中嘗試著寫過一個分頁的Angular組件,然后就有了寫QingUI的想法

過程還是非常有意思的

接下來我會用幾篇文章分別介紹每個組件的大概思路,請大家耐心等待

這一篇介紹Tree樹結構

最重要的,求star,求內推

repo: QingUI
少廢話,先上圖

渲染

作為樹組件,想都不用想,肯定用遞歸

但是QingUI的組件統一用一個div.qing qing-component包裹,所以用div把遞歸函數包起來

const tpl = `
    
${this.renderTrunk(this.data)}
`;

然后我們再來看renderTrunk函數

首先簡要介紹一下標簽結構

trunk可以理解為樹的一項,是樹干

fruit是包裹信息用的,里面有三角箭頭,checkbox和label

如果它有子項,則fruit后面再加一個sub,sub里面當然又是一個或多個trunk

indent

Tree配置項里有一個indent,指的是所有的子項相對于父項縮進的距離

頂層項沒有父項,所以不需要縮進

于是我們就需要在遞歸的時候判斷,現在是頂層項還是子項

這個簡單

// data是遞歸函數傳進來的
const inner = data !== this.data;

于是我們的縮進也解決了

const marginLeft = `${inner ? `style="margin-left: ${this.indent}px;"` : ""}`;
expand

Tree配置項里還有一個expand,它有三個選項:none、all和first

它決定的是初始加載的時候子項是全部閉合、全部展開還是只有第一個頂層項展開

子項是否展開是如何控制的呢?

當然是通過高度,height: 0; overflow: hidden;的時候閉合,height: auto; overflow: hidden;的時候展開

于是就成了下面這樣

expand等于first時,意思是只有頂層項的第一項才會展開

這里有一個小技巧

expand我們給了三個可選項,但是萬一用戶偏偏傳個hello進來呢?

反正none是默認項,條件判斷的時候,我只認all和first,除此之外都是默認配置

這屬于對錯誤參數的靜默處理,我也不告訴你傳錯了,但是你也別想要任何效果

這樣就不需要對參數多做一個校驗了

for (let i = 0; i < data.length; i++) {
    let arrowTpl, subTpl;
    if (this.expand === "all") {
        arrowTpl = "";
        subTpl = "
"; } else if (this.expand === "first") { const boo = !inner && i === 0; arrowTpl = boo ? "" : ""; subTpl = `
`; } else { arrowTpl = ""; subTpl = "
"; } }
fruit

fruit這里,如果沒有子項,是不需要三角箭頭的

另外不需要checkbox的話就不顯示checkbox,只作為樹結構展示用

tpl += `
    
${item.sub ? arrowTpl : ""} ${this.checkable ? "" : ""} ${item.label}
`;

最后,如果有子項,別忘了遞歸

if (item.sub) {
    tpl += subTpl;
    tpl += this.renderTrunk(item.sub);
}

整個模板部分大概就是這樣子的

我們看一下全貌:

render() {
    const tpl = `
        
${this.renderTrunk(this.data)}
`; this.$mount.innerHTML = tpl; } renderTrunk(data) { const inner = data !== this.data; const marginLeft = `${inner ? `style="margin-left: ${this.indent}px;"` : ""}`; let tpl = ""; for (let i = 0; i < data.length; i++) { const item = data[i]; tpl += `
`; let arrowTpl, subTpl; if (this.expand === "all") { arrowTpl = ""; subTpl = "
"; } else if (this.expand === "first") { const boo = !inner && i === 0; arrowTpl = boo ? "" : ""; subTpl = `
`; } else { arrowTpl = ""; subTpl = "
"; } tpl += `
${item.sub ? arrowTpl : ""} ${this.checkable ? "" : ""} ${item.label}
`; if (item.sub) { tpl += subTpl; tpl += this.renderTrunk(item.sub); tpl += "
"; } tpl += "
"; } return tpl; }
映射

當我需要點擊checkbox導致data的某個對象的checked屬性變更時,我的做法是維護一套DOM結構的映射

映射跟DOM結構是一一對應的,跟data也是一一對應的

它可以很好的作為橋梁同步數據

為什么不直接操作data呢?

data是用戶傳進來的結構化數據,后面還需要通過回調傳回去的,我們不應該修改用戶的數據結構

映射需要保存些什么?

保存對應的DOM節點

保存checked屬性的值

保存當前節點在樹結構中的位置

queue

一個小問題,如何保存當前節點在樹結構中的位置呢?

當我們說在樹結構中的位置的時候,我們想知道的是當前節點在第幾級,以及當前節點在該級的第幾個

我用queue關鍵字來保存位置信息,可能不是非常語義化,你來打我呀!

舉個栗子

假如我要知道宋佳的位置,queue的值為"111",表示它是一個三級子項,它的爺爺第一級是第二項,它的爹爹第二級也是第二項,它自己也是第二項

再舉個栗子,倪妮的queue就是"10"

有兩個例子,應該不會有理解上的偏差了吧

data = [
    {
        label: "霍思燕",
    },
    {
        label: "江疏影",
        sub: [
            {
                label: "倪妮",
            },
            {
                label: "高圓圓",
                sub: [
                    {
                        label: "張雨綺",
                    },
                    {
                        label: "宋佳",
                    },
                ],
            },
        ],
    },
];

ES6有一個新特性,如果對象的鍵和值變量是一樣的,那么不需要寫冒號,又給你省了不少時間可以用來浪費,開不開心!

buildCbTree(fatherCb, item) {
    const childCbs = fatherCb.children;
    for (let i = 0; i < childCbs.length; i++) {
        const fruit = childCbs[i].firstElementChild;
        const sub = fruit.nextElementSibling;
        const cb = fruit.firstElementChild.nextElementSibling;
        const checked = cb.classList.contains("checked");
        const queue = item.queue ? item.queue + String(i) : String(i);
        let obj = {cb, checked, queue};
        if (sub) {
            obj.sub = [];
            this.buildCbTree(sub, obj);
        }
        item.sub.push(obj);
    }
}

它的初始參數是什么呢?

this.$mount.firstElementChild就是開始加的包裹元素,上面有標識QingUI的class,所以它就是根元素

this.buildCbTree(this.$mount.firstElementChild, {sub: this.cbTree});
checkbox事件

我們先來捋一捋,當我們點擊某一項的checkbox時

它自己只有兩種狀態,要么checked,要么去除checked

如果它有子項,則所有子項以及孫項以及所有的后代項跟隨它的腳步,要么全部checked,要么全部去除checked

但是如果它有父項(它是有可能沒有父項的,如果自己是頂層項的話),需要根據自己的兄弟來決定父項的checked屬性,依照這個邏輯往上遞歸

如果自己和兄弟都去除了checked,則父項去除checked

如果自己和兄弟都checked,則父項checked

如果自己或者兄弟至少有一個checked,則父項somechecked

所以每一次點擊都要做三條線的處理

$cb.addEventListener("click", function(event) {
    event.stopPropagation();
    const checked = !this.classList.contains("checked");
    // cb事件
    checked ? self.$cbEvent(item, "all") : self.$cbEvent(item, "none");
    // cb子代事件
    checked ? self.childCbsEvent($sub, "all") : self.childCbsEvent($sub, "none");
    // cb父代事件
    self.fatherCbsEvent(queue);
});
checkbox自代事件

自己雖然只有兩種狀態,但我們可以把它作為抽象函數,因為無論是父項、子項還是自己,都是checkbox而已,只不過是一個還是多個

所以我們給它三種狀態,作為action參數傳進來

$cbEvent(item, action) {
    const $cb = item.cb;
    const CL = $cb.classList;
    switch (action) {
        case "all":
            item.checked = true;
            CL.contains("somechecked") ? CL.remove("somechecked") : "";
            CL.add("checked");
            break;
        case "some":
            item.checked = false;
            CL.contains("checked") ? CL.remove("checked") : "";
            CL.add("somechecked");
            break;
        case "none":
            item.checked = false;
            CL.contains("somechecked") ? CL.remove("somechecked") : "";
            CL.contains("checked") ? CL.remove("checked") : "";
            break;
    }
}
checkbox子代事件

子代事件也只有兩種狀態,跟自身同步

只需要遞歸就好了

需要提醒的是,這里所有的操作都是針對映射cbTree進行的

childCbsEvent($sub, action) {
    if (!$sub) {
        return;
    }
    const self = this;
    function recursive($sub) {
        for (const $item of $sub) {
            self.$cbEvent($item, action);
            if ($item.sub) {
                recursive($item.sub);
            }
        }
    }
    recursive($sub);
}
checkbox父代事件

要決定父項的狀態,先要找到自己的兄弟,家族財產怎么分割,還得兄弟一起商量

兄弟怎么找呢?

先要找到包裹自己和兄弟的那個實體,你才能遍歷呀,這時候queue就派上用場了

現在我知道實體的位置,只需要this.cbTree[a][b][c]這么找下去不就完了!

findFatherItem(queue) {
    const n = queue.length - 1;
    // 頂級item沒有父item
    if (n === 0) {
        return;
    }
    let fatherItem = this.cbTree;
    for (let i = 0; i < n; i++) {
        const char = queue.charAt(i);
        if (i < n - 1) {
            fatherItem = fatherItem[char].sub;
        } else {
            fatherItem = fatherItem[char];
        }
    }
    return fatherItem;
}

找到了實體,接下來遍歷就行了

當然,這里面的兄弟實際上是包括自己的

因為DOM操作比較慢,我原以為這里做計算的時候,有可能自身的class變更還沒生效呢

因為在我的印象里,DOM操作好像是異步的,其實不是的,所以把自己納入進來是沒問題的

findSiblingCbs($sub) {
    let $siblingCbs = [];
    for (const $item of $sub) {
        $siblingCbs.push($item.cb);
    }
    return $siblingCbs;
}

最后就是根據最初的邏輯把結果丟進那個抽象函數里

當然,記住樹結構里一切都是遞歸的

fatherCbsEvent(queue) {
    const fatherItem = this.findFatherItem(queue);
    if (!fatherItem) {
        return;
    }
    const $siblingCbs = this.findSiblingCbs(fatherItem.sub);
    let allFlag = true;
    let noneFlag = true;
    for (const $item of $siblingCbs) {
        const cl = $item.classList;
        if (!cl.contains("checked")) {
            allFlag = false;
        }
        if (cl.contains("checked") || cl.contains("somechecked")) {
            noneFlag = false;
        }
        // flag全都已經變化,退出循環
        if (!allFlag && !noneFlag) {
            break;
        }
    }
    if (allFlag) {
        this.$cbEvent(fatherItem, "all");
    } else if (noneFlag) {
        this.$cbEvent(fatherItem, "none");
    } else {
        this.$cbEvent(fatherItem, "some");
    }
    this.fatherCbsEvent(fatherItem.queue);
}
更新data

不知道你們意識到沒有,上面做的所有事,僅僅是改變了DOM樣式和映射cbTree

用戶想在回調里拿到的是一開始傳進來的data呀

不過這好辦,因為cbTree和data的結構是一毛一樣的

只需要一個遞歸就解決問題

最終我們得到的data就是checked屬性已經產生變化的data

updateData(tree, data) {
    for (let i = 0; i < tree.length; i++) {
        const ti = tree[i];
        const di = data[i];
        if (ti.checked) {
            di.checked = true;
        } else {
            di.checked = false;
        }
        if (ti.sub) {
            this.updateData(ti.sub, di.sub);
        }
    }
}
高度動畫

前面講expand配置項的時候,我們提到過高度為0或是auto的問題

初始是什么沒關系

我們想讓展開或收起的過程更加平滑一點

然而我們知道height: auto;是無法產生CSS動畫的,CSS動畫必須得有一個確定的數值

那么怎么辦呢?

有人說可以用max-height屬性,給max-height一個很大的固定的值,就可以產生動畫了

我覺得這樣,動畫效果不會好,而且很不優雅

試想一下,比如說現在高度是0,我能不能把高度改成auto,再通過JS計算出真實的高度,再把高度改成0

現在我們手里有它本來的確定的高度值,就可實現動畫了

最后又把它的高度改成auto,因為它有可能有子項,固定高度會出問題的

有點繞,看代碼

subHeightToggle($sub) {
    let h = $sub.getBoundingClientRect().height;
    if (h > 0) {
        // 從auto變成具體的值
        $sub.style.height = `${h}px`;
        setTimeout(() => {
            $sub.style.height = "0px";
        }, 0);
    } else {
        $sub.style.height = "auto";
        h = $sub.getBoundingClientRect().height;
        $sub.style.height = "0px";
        setTimeout(() => {
            $sub.style.height = `${h}px`;
        }, 0);
        // 動畫完成變成auto
        setTimeout(() => {
            $sub.style.height = "auto";
        }, 200);
    }
}

我們老是說盡量不要操作DOM,其實這是要看付出回報比的,如果能夠實現平滑效果,實現方式又足夠優雅

咱們的計算機沒你說的那么脆弱

寫在后面

Tree比較核心的邏輯就在這里了

樹結構算是QingUI里比較難的組件了,其中的實現方式我也試過好幾版

越是難,其實越有成就感

下一篇文章介紹Cascader,敬請期待

最后,求star,求內推

repo: QingUI

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/93736.html

相關文章

  • 徒手UIDatePicker

    摘要:是一個組件庫目前擁有的組件語法編寫,無依賴原生模塊化,以上支持,請開啟靜態服務器預覽效果,靜態服務器傳送門采用變量配置樣式辛苦造輪子,歡迎來倉庫四月份找工作,求內推,坐標深圳寫在前面去年年底項目中嘗試著寫過一個分頁的組件,然后就有了寫的想法 QingUI是一個UI組件庫目前擁有的組件:DatePicker, TimePicker, Paginator, Tree, Cascader, ...

    zilu 評論0 收藏0
  • 徒手UIPaginator

    摘要:是一個組件庫目前擁有的組件語法編寫,無依賴原生模塊化,以上支持,請開啟靜態服務器預覽效果,靜態服務器傳送門采用變量配置樣式辛苦造輪子,歡迎來倉庫四月份找工作,求內推,坐標深圳寫在前面去年年底項目中嘗試著寫過一個分頁的組件,然后就有了寫的想法 QingUI是一個UI組件庫目前擁有的組件:DatePicker, TimePicker, Paginator, Tree, Cascader, ...

    liuhh 評論0 收藏0
  • 徒手UITimePicker

    摘要:是一個組件庫目前擁有的組件語法編寫,無依賴原生模塊化,以上支持,請開啟靜態服務器預覽效果,靜態服務器傳送門采用變量配置樣式辛苦造輪子,歡迎來倉庫四月份找工作,求內推,坐標深圳寫在前面去年年底項目中嘗試著寫過一個分頁的組件,然后就有了寫的想法 QingUI是一個UI組件庫目前擁有的組件:DatePicker, TimePicker, Paginator, Tree, Cascader, ...

    Codeing_ls 評論0 收藏0
  • 徒手UICascader

    摘要:但是如果一剎那我不想選江疏影了,我想選張雨綺因為胸大,首先我要從霍思燕換到高圓圓,然后轉到張雨綺,選中展示出來,這時候就要先刪除霍思燕,然后把高圓圓和張雨綺進來。 QingUI是一個UI組件庫目前擁有的組件:DatePicker, TimePicker, Paginator, Tree, Cascader, Checkbox, Radio, Switch, InputNumber, I...

    junnplus 評論0 收藏0
  • 徒手框架--高并發環境下的請求合并

    摘要:我們就可以將這些請求合并,達到一定數量我們統一提交。總結一個比較生動的例子給大家講解了一些多線程的具體運用。學習多線程應該多思考多動手,才會有比較好的效果。地址徒手擼框架系列文章地址徒手擼框架實現徒手擼框架實現 原文地址:https://www.xilidou.com/2018/01/22/merge-request/ 在高并發系統中,我們經常遇到這樣的需求:系統產生大量的請求,但是這...

    劉東 評論0 收藏0

發表評論

0條評論

2i18ns

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<