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 上面的文档。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 周边上新:园子的第一款马克杯温暖上架
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?
· 使用C#创建一个MCP客户端