如何解决等待 customElement 子项呈现以命令式地定位它们
我有一个组件 MyWrapper
,它由左侧的流动文本和右侧的边距组成。在边距中,有 MarginElement
个组件应该与文本中的 <mark>
标签垂直对齐。
import {html,css,LitElement} from 'lit';
import {customElement,property} from 'lit/decorators.js';
@customElement('my-wrapper')
export class MyWrapper extends LitElement {
static styles = css`:host{display:flex}`
render() {
return html`
<p>
<mark id="mark1">Lorem</mark> ipsum dolor sit amet,consectetur adipiscing elit,sed
do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam,quis nostrud exercitation ullamco
laboris nisi ut aliquip ex ea commodo consequat. Duis aute
irure dolor in reprehenderit in voluptate velit esse <mark id="mark2">cillium</mark>.
</p>
<div class="margin" style="width:50%">
<margin-element id="m1" related-element-id="mark1">Some note</margin-element>
<margin-element id="m2" related-element-id="mark2">Some other note</margin-element>
</div>`;
}
updated(){this.positionMargin()}
positionMargin() {
const elements = this.shadowRoot.querySelectorAll('margin-element')
elements.forEach(el => {
const relatedElement = this.shadowRoot.querySelector(`#${el.relatedElementId}`)
el.style.top = relatedElement.getBoundingClientRect().top + 'px'
})
}
}
@customElement('margin-element')
export class MarginElement extends LitElement {
static styles = css`:host {position: absolute; border: 1px solid red;}`
@property({type: String,attribute: 'related-element-id'}) relatedElementId: string
render() {return html`<slot></slot>`}
}
我想在 positionMargin()
上的渲染完成后运行函数 MyWrapper
,但我知道调用 relatedElement.getBoundingClientRect()
margin-element
本身必须被渲染。查看文档 updated()
似乎是调用 positionMargin()
的最佳位置。然而,到那时似乎还没有呈现子 <margin-element>
。因此,getBoundingClientRect()
不可用。
如何解决这个问题?
解决方法
多亏了 Lit 团队的帮助(谢谢!),我才弄明白了。解决方案是在解决 updateComplete
承诺之前等待子进程呈现。
这可以通过查询所有 <margin-element>
孩子并等待他们各自的 updateComplete
承诺解决来实现(另见 here)。
因此,一旦我添加了上面的代码
async getUpdateComplete() {
await super.getUpdateComplete();
const marginElements = Array.from(this.shadowRoot.querySelectorAll('margin-element'));
await Promise.all(marginElements.map(el => el.updateComplete));
return true;
}
这是一个带有 complete example 的灯光游乐场。
,当您返回一个 MarginElement
数组时,它会起作用,因为它会导致它们在当前渲染更新后触发自己的渲染生命周期,但它并不可靠。
您的主要问题是您希望垂直对齐偏离 DOM 中的相对 y 位置,但 x 位置堆叠在右侧。堆叠意味着 <margin-element>
要么需要使用某种流/网格布局,要么需要相互了解,这会很快变得令人讨厌。
相反,我会添加一个新的 <margin-wrapper>
元素,该元素负责绝对定位它的子元素 <margin-element>
并替换 <div class="margin">
。
然后在您的 positionMargin
函数中设置一个您传递给它的数组 <margin-wrapper .marks=${this.marksWithPositions}>
;
然后在 <margin-wrapper>
的 render
中,您有一系列可以绝对定位的标记,如果您在同一个 top
上获得两个标记,则可以将后续标记移开。
类似于:
render() {
return html`
<div class="wrapper">
<div class="content">
<p>
<mark id="mark1">Lorem</mark> ipsum dolor sit ...
<mark id="mark2">cillium</mark>.
</p>
</div>
<margin-wrapper .marks=${this.marksWithPositions}>
</div>
<button @click=${() => this.positionMargin()}>Position!</button>
`;
}
positionMargin() {
const elements = this.shadowRoot.querySelectorAll('margin-element')
const mwp = [];
elements.forEach(el => {
const markWithPos ={
top: relatedElement.getBoundingClientRect().top,text: e.innerText
});
this.marksWithPositions = mwp; // note that this needs to be an @property or @state
}
然后在 <margin-wrapper>
中:
render() {
const positioned = [];
let x = 0;
for(const m of this.marks) {
if(m.top > x)
positioned.push(m);
else
positioned.push({ top: x,m.text });
x += expectedElementHeight;
}
return html`
<div class="margin">
${positioned.map(p => html`
<margin-element style=${styleMap({top: p.top + 'px'})}>${p.text}</margin-element>`)}
</div>`;
}
或者,也可以使用 ref
执行某些操作,例如 <mark ${ref(e => ...)}>
,以便在每个 <mark>
呈现时触发一个事件。
请注意,<margin-wrapper>
的渲染不会导致移动 <mark>
元素的布局更改,因为您很容易获得循环竞争条件。您可能希望到处都有明确的宽度和高度,但如果没有,您还需要调整窗口大小和滚动观察者。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。