如何将插槽子项附加到 HTML body 标记以进行绝对定位? [英] How to append slot children to HTML body tag for absolute positioning?
问题描述
我正在创建一个下拉菜单 Web 组件,供消费者使用,例如:
<自定义菜单锚点><button>切换菜单</button></custom-menu-anchor><custom-menu-item>鱼</custom-menu-item><自定义菜单项><custom-icon name="鸡"/><span>鸡</span></custom-menu-item></自定义菜单>
这里,像
这样的槽项需要绝对定位.
要做到这一点,我们需要
- 创建叠加层
- 从 Web 组件中删除插槽元素
- 将它们附加到覆盖元素.
- 提供正确的定位
要设置完美的叠加层,我需要创建一个 overlay/surface
元素并删除 custom-menu-item
子项并将它们全部附加到叠加层元素.
为了实现这一点,我在 connectedCallback
生命周期方法中尝试了以下类似的方法:
const slot = this.shadowRoot.querySelector('slot');const surface = document.createElement('div');const 节点 = slot.assignedNodes();surface.append(...nodes);document.body.appendChild(surface);
这种方法的问题:
- 我注意到从插槽中删除
assignedNodes
会搞砸很多事情. - 当我尝试移动插槽的 lightDOM 时,许多事情都不起作用
slotchange
在元素移动后不起作用.- 此外,Web 组件可以在任何框架中使用,可以是
lit-html
、Vue
甚至普通的JavaScript
.我注意到在移动这些 DOM 元素后,这开始破坏消费者库的抽象.
这种需求适用于任何绝对定位/偏移的组件,如通知、对话框、下拉菜单、Snackbar 和上述方法显然很困难,当然也不是一种更简洁的做事方式.
我们如何才能更有效地避免所有提到的副作用?
四处移动轻量 dom 节点通常不是一个好主意 - 您的用户(和浏览器)希望它们停留在原地.
如果您想在其他地方呈现内容,您可以将内容作为属性提供.
lit-html 的简化示例(使用它通过 .
设置属性)
我的内容'}>
然后在您的自定义元素中,您需要对尚未在 dom 中的那个字符串做一些事情.
class MyEl 扩展 ... {onContentChanged() {document.body.querySelector('my-el-target').innerHTML = this.content;}}
PS:这过于简单化了——实际上你可能想在 connectedCallback 上创建/管理 my-el-target——或者让它由一个完整的控制器处理.此外,模板"可能应该是一个点亮的模板而不是一个普通的字符串......
I am creating a dropdown menu web component that will be used by consumers like:
<custom-menu>
<custom-menu-anchor>
<button>Toggle Menu</button>
</custom-menu-anchor>
<custom-menu-item>Fish</custom-menu-item>
<custom-menu-item>
<custom-icon name="chicken"/>
<span>Chicken</span>
</custom-menu-item>
</custom-menu>
Here, the slot items like <custom-menu-items>
need to be absolutely positioned.
To do this, we need to
- Create an overlay
- Remove slot elements from web component
- Attach them to the overlay element.
- Provide correct positioning
To setup a perfect overlay, I need to create an overlay/surface
element and remove the custom-menu-item
children and append them all to the overlay element.
To achieve this, I attempted something like below in connectedCallback
lifecycle method:
const slot = this.shadowRoot.querySelector('slot');
const surface = document.createElement('div');
const nodes = slot.assignedNodes();
surface.append(...nodes);
document.body.appendChild(surface);
Problems with this approach:
- I noticed that removing the
assignedNodes
from slot messes up many things. - Many things do not work when I attempt to move slot's lightDOM
slotchange
doesn't work once the elements are moved.- Further, web component could be used in any framework, It could be
lit-html
,Vue
or even plainJavaScript
. I noticed that this starts breaking abstractions of the consumer libraries after moving these DOM elements.
This need applies to any absolutely positioned/offset components like notification, dialog, dropdown, Snackbar and the above approach clearly struggles and is certainly not a cleaner way to do things.
How can we do this in a more effective manner avoiding all the mentioned side effects ?
Moving light dom nodes around is usually not a good idea - your users (and the browser) expects them to stay where they are.
If you want to render the content somewhere else you could provide the content as an attribute.
A simplified example for lit-html (using it to set a property via .
)
<my-el .content=${'<div>my content</div>'}></my-el>
Then in your custom element, you will need to do something with that string which is not yet in the dom.
class MyEl extends ... {
onContentChanged() {
document.body.querySelector('my-el-target').innerHTML = this.content;
}
}
PS: this is highly oversimplified - in real you probably want to create/manage that my-el-target on connectedCallback - or let it be handled by a full controller. Also, the "template" probably should be a lit-template instead of a plain string...
这篇关于如何将插槽子项附加到 HTML body 标记以进行绝对定位?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!