1

I'm trying to create an observer that triggers when the bounding rect of an element changes (a BoundingClientRectObserver), and this is what I have so far:

customElements.define(
  'bounding-client-rect-observer-helper',
  class extends HTMLDivElement {
    disconnectedCallback () { this.dispatchEvent(new Event('$disconnected')) }
  },
  {extends: 'div'}
)

class BoundingClientRectObserver {
  #intersectionObserver
  #helperElement
  #timeoutId
  constructor (element, callback, startNow=true) {
    if (typeof element == 'string') {
      element = document.querySelector(element)
    }

    this.element = element
    this.callback = callback
    
    this.#helperElement = document.createElement(
      'div', {is: 'bounding-client-rect-observer-helper'}
    )
    this.#helperElement.style.position = 'fixed'
    this.#helperElement.style.visibility = 'hidden'

    this.#intersectionObserver = new IntersectionObserver(
      entries => this.#debouncedCallback(entries),
      {root: this.element, threshold: [0,1.0]}
    )

    this.disconnected = this.disconnected.bind(this)
    this.running = false
    if (startNow) this.start()
  }

  start () {
    if (this.running) return
    this.element.append(this.#helperElement)
    this.#helperElement.addEventListener('$disconnected', this.disconnected)
    this.#intersectionObserver.observe(this.#helperElement)
    this.running = true
  }

  #innerCallback () {
    const rect = this.element.getBoundingClientRect()
    this.#helperElement.style.width = rect.width + 'px'
    this.#helperElement.style.height = rect.height + 'px'
    this.#helperElement.style.left = rect.left + 'px'
    this.#helperElement.style.top = rect.top + 'px'
    this.callback(rect)
  }

  #debouncedCallback (entries) {
    console.log(entries)
    clearTimeout(this.#timeoutId)
    this.#timeoutId = setTimeout(() => this.#innerCallback(), 200)
  }

  disconnected () {
    if (this.running) {this.element.append(this.#helperElement)}
  }

  stop () {
    if (!this.running) return
    this.#intersectionObserver.unobserve(this.#helperElement)
    this.#helperElement.removeEventListener('$disconnected', this.disconnected)
    this.#helperElement.remove()
    this.running = false
  }
}

I'm adding a helper element to the root element (the one I want to check if its bounding client rect changes) with the same bounding client rect, since in the IntersectionObserver docs it says that the element to observe should be a descendant of the root element. So in theory, everytime the size or position of the root element changes, the callback should be called, but this is not happening, and I suspect it's because bounding client rects in entries (I'm printing the entries in #debouncedCallback function) are empty. can you see where I got it wrong?

Felipe Sierra
  • 143
  • 2
  • 12
  • root: this.element - just guessing that that should be root:null ?. Are you trying to use the same element as the target element? I am not sure if you can change the root size after you have initially set it in the intersectionObserver. And why are you debouncing? Intersectionobserver is async. You should unobserve after first observation if you want control. – JoePythonKing Jan 03 '22 at 09:13
  • @JoePythonKing I want to check if `this.element` size or position changes, and that's why I'm setting it as the root element, and since the target must be a descendant of the root, I'm appending the `this.#helperElement` to it, and observing it with the intersection observer. – Felipe Sierra Jan 04 '22 at 02:19
  • Once we have working example(s) in https://stackoverflow.com/questions/40251082, then we'll know how to make `BoundingClientRectObserver`. – trusktr Dec 21 '22 at 21:03

0 Answers0