如何在初始插槽分配后对第一个 paint/layout 做出反应

How to react on first paint/layout after initial slot distribution

我有一个自定义元素 my-el,它在其 DOM 模板(影子根)中包含一个 slot 元素。在网页中,我通过在标签 <my-el> </my-el> 之间放置其他元素来使用 my-el,如下所示。

<my-el>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
</my-el>

我打算测量 div 中放置在 my-el 中的尺寸,因为它们在分布式树中,尽快 (即第一个时间)它们呈现在页面上。根据 spec:

To assign slotables, for a slot slot with an optional suppress signaling flag (unset unless stated otherwise), run these steps:

  1. Let slotables be the result of finding slotables for slot.

  2. If suppress signaling flag is unset, and slotables and slot’s assigned nodes are not identical, then run signal a slot change for slot.

...

如果我没理解错的话,插槽表的第一次分发不会触发 slotchange 事件,因为 抑制信号标志 在插槽未执行时设置初始分布,在 中进一步说明 Shadow DOM v1:独立的 Web 组件 。这意味着我无法在 slotchange 的回调中对子元素进行初始测量。此外,以下方法不能保证在那时将呈现子项:

connectedCallback() {
  super.connectedCallback();

  requestAnimationFrame(() => {
    // Measure chilren
  });
}

问题

如何在 my-el 中的 slot 中分发后第一次呈现时触发我的测量?

您可以在您的自定义元素中定义一个变量,该变量将在您的元素首次呈现时设置(在 connectedCallbackslotchange 事件中)。

然后查看变量值判断是否是第一次渲染

customElements.define( 'c-e', class extends HTMLElement 
{
    connectedCallback () 
    {
        var sh = this.attachShadow( { mode: 'open' } )
        sh.appendChild( T1.content.cloneNode( true ) )
        
        sh.querySelector( 'button').onclick = ev =>   
            this.appendChild( document.createElement( 'div') ).innerHTML = 'new div'

        sh.querySelector( 'slot' ).addEventListener( 'slotchange', ev => 
            !len && ( len = this.render() ) )

        var len = this.render()
    }

    render () 
    {
        var count = this.querySelectorAll( 'div' ).length
        if ( count )
            console.info( '%s elem. first rendered in %s', count, this.id )
        return count
    }
} )
<c-e id=CE1>
  <div>div 1</div>
  <div>div 2</div>
</c-e>
<c-e id=CE2>
</c-e>

<template id=T1>
  <style>
    :host {
      display: inline-flex;
      flex-direction: column;
      width: 150px;
      border: 1px solid lightblue;
      min-height: 4em;
      margin: 5px;
      background: #f7f7f7;
    }
  </style>
  <div>
    <button>add div</button>
  </div>
  <slot>
  </slot>
</template>