Element 中的 DropDown 定位解析


一、前言

進來事情較少。

今天早上在群里面有看到,對於 iview select 的下拉框定位問題的討論。

因為主要用的是 Element-UI,就對這一塊做了深入的了解。

二、發現問題

如上面說的,在得到這個問題后,想到應該不只是 select 下拉所有的 dropdowm 的定位應該是一樣的。

就直接在 Element-UI 官網看下,選擇了級聯看了下:

在這里看到 dropdown 是直接掛載在 body 下的。

簡單分析后得出:

1、dropdown 是通過 absolute 進行絕對定位的;

2、第一次點擊前,沒有掛載到 dom,點擊后才掛載,后面是通過 display: none 控制顯示

比較好奇的就是這個絕對定位的 left、top 是怎么計算出來的?

查了一圈,並且看了 Element-UI 源碼也沒有發現什么(這里是自己了解不夠到位)。

在遇到這篇文章后,才有了下面的進一步深入:Element 源碼解析系列7-select

三、底層原理

在看了這一篇文章后,才對 popper.js 有深入了解。

在 Element-ui 中對其做了封裝在:src\utils\vue-popper.js 這里。這個的作用主要就是計算絕對定位的位置、實時改變

下面的代碼都是在源碼中的 src\utils\popper.js 文件中

1、初始化

在 Popper 進行初始化的時候,會先對要彈出的 element 進行樣式初始化,設置 postion 是 absolute 還是 fixed。

會進行第一次 update、_setupEventListeners

2、計算位置

這個主要是從 update 里面找到的:

    Popper.prototype.update = function() {
        var data = { instance: this, styles: {} };

        // store placement inside the data object, modifiers will be able to edit `placement` if needed
        // and refer to _originalPlacement to know the original value
        data.placement = this._options.placement;
        data._originalPlacement = this._options.placement;

        // compute the popper and reference offsets and put them inside data.offsets
        data.offsets = this._getOffsets(this._popper, this._reference, data.placement);

        // get boundaries
        data.boundaries = this._getBoundaries(data, this._options.boundariesPadding, this._options.boundariesElement);

        data = this.runModifiers(data, this._options.modifiers);

        if (typeof this.state.updateCallback === 'function') {
            this.state.updateCallback(data);
        }
    };

其中 _getOffsets 就是計算對應的偏移的

3、實時更新位置

_setupEventListeners 的作用是設置監聽,主要有 resize 和 scroll 兩個監聽事件:

    Popper.prototype._setupEventListeners = function() {
        // NOTE: 1 DOM access here
        this.state.updateBound = this.update.bind(this);
        root.addEventListener('resize', this.state.updateBound);
        // if the boundariesElement is window we don't need to listen for the scroll event
        if (this._options.boundariesElement !== 'window') {
            var target = getScrollParent(this._reference);
            // here it could be both `body` or `documentElement` thanks to Firefox, we then check both
            if (target === root.document.body || target === root.document.documentElement) {
                target = root;
            }
            target.addEventListener('scroll', this.state.updateBound);
            this.state.scrollTarget = target;
        }
    };

就是當屏幕變化或者滾動時,會再次計算 Popper 的定位位置信息,並更新。

4、底層解析

開始感覺這個定位計算會不會很難什么的,在從 update 的 _getOffsets 步步找下去發現計算的代碼:

    function getBoundingClientRect(element) {
        var rect = element.getBoundingClientRect();

        // whether the IE version is lower than 11
        var isIE = navigator.userAgent.indexOf("MSIE") != -1;

        // fix ie document bounding top always 0 bug
        var rectTop = isIE && element.tagName === 'HTML'
            ? -element.scrollTop
            : rect.top;

        return {
            left: rect.left,
            top: rectTop,
            right: rect.right,
            bottom: rect.bottom,
            width: rect.right - rect.left,
            height: rect.bottom - rectTop
        };
    }

其中 element.getBoundingClientRect() 是獲取一個元素的大小以及相對視口的位置。

看了這個后真的感覺,所有看似很復雜、牛掰的操作都是源自於最基礎最底層的。

在這里用到了 DOM API ,Element.getBoundingClientRect(),這里點擊看到 MDN 上面的文檔。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM