Skip to content

Elements

Webuum ships ready-made element mixins that package common patterns built on top of its helpers. Like the observers, they live in a separate entry point, so you only pay for them when you actually use them:

js
import { WebuumLazyElement } from 'webuum/elements'

WebuumLazyElement

WebuumLazyElement(Element) is a class mixin — it takes any custom element constructor and returns a subclass wired for lazy initialization, deferring expensive setup until the element scrolls into view.

Put your deferred initialization into a lazyCallback() method and control it with the $lazy prop:

  • when $lazy is truthy, lazyCallback() waits until the element intersects the viewport, then runs once — the mixin resets $lazy to false afterwards, so it won't fire again on later intersections
  • when $lazy is falsy, lazyCallback() runs right away on connectedCallback — lazy loading becomes opt-in (or opt-out) per instance via the data-lazy attribute

Under the hood the mixin runs defineElement() in the constructor, so Webuum features like props and the lifecycle-bound $signal work even on base classes that don't extend WebuumElement. On connect it starts a defineIntersectionObserver (with threshold: 0.1), and on disconnect it aborts $controller, which also disconnects the observer.

js
import { WebuumLazyElement } from 'webuum/elements'

customElements.define('x-comments', class extends WebuumLazyElement(HTMLElement) {
  static props = {
    $lazy: true,
  }

  lazyCallback() {
    // expensive work — fetch data, import a script, hydrate…
  }
})
html
<!-- initializes when scrolled into view -->
<x-comments></x-comments>

<!-- initializes immediately -->
<x-comments data-lazy="false"></x-comments>

It also works with Customized Built-in Elements — for example deferring a form's setup, including listeners bound to $signal:

js
import { WebuumLazyElement } from 'webuum/elements'

customElements.define('x-form', class extends WebuumLazyElement(HTMLFormElement) {
  static props = {
    $lazy: true,
  }

  lazyCallback() {
    this.noValidate = true
    this.addEventListener('submit', this.validateForm, { signal: this.$signal })
  }
}, { extends: 'form' })

TIP

If you need different IntersectionObserver options, or want to react to every intersection change instead of a one-shot initialization, use defineIntersectionObserver with your own intersectCallback(entry) directly — see the lazy load example.

Released under the MIT License.