Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Intersection observer #285

Open
HEAVYPOLY opened this issue Mar 9, 2024 · 11 comments
Open

Intersection observer #285

HEAVYPOLY opened this issue Mar 9, 2024 · 11 comments

Comments

@HEAVYPOLY
Copy link

HEAVYPOLY commented Mar 9, 2024

How would you set up an intersection observer with vanJS? I have gallery of image thumbnails and would like to dynamically load/unload them when they come into view with intersection observer

const GalleryThumb = (canvas) => {
  // let imageArrayBuffer = getLatestAsset(canvas.id, 'image')
  // const image = URL.createObjectURL(imageBlob)
  const Thumb = () => div(
    {
      id: `galleryThumb${canvas.id}`,
      class: 'galleryThumb',
      'data-clicked': 'false',
      onclick: (e) => galleryThumbInput(e, this, canvas.id),
    },
    canvas.id?.slice(0, 4),
    div({
      class: 'image',
      style: `background-image:url(${canvas.thumbnail})`,
      loading: 'lazy',
    }),
    div({ class: 'hoverable deleteButton hidden' }),
    div({ class: 'hoverable playButton hidden' }),
    div({ class: 'hoverable restoreButton hidden' })
  )
  galleryObserver.observe(Thumb)

  return Thumb
}

let galleryObserver = false

function setupGalleryObserver () {
  const options = {
    root: galleryUI, // observing intersections relative to the viewport
    rootMargin: '900px', // margin around the root
    threshold: 0.1 // callback is executed when at least 10% of the target is visible
  }

  const callback = (entries, observer) => {
    entries.forEach(entry => {
      if (entry.isIntersecting) {
        prnt('INTERSECTING')
      } else {
        if (isVisible(galleryUI)) {
          entry.target.classList.add('loadingStatic')
          freeImage(entry.target.thumbnail)
        }
        prnt('NOT INTERSECTING')
      }
    })
  }
  galleryObserver = new IntersectionObserver(callback, options)
}

galleryObserver.observe(Thumb) throws TypeError: Failed to execute 'observe' on 'IntersectionObserver': parameter 1 is not of type 'Element'.

@Tao-VanJS
Copy link
Member

I think you wanted Thumb to be an element, not a function. In other words, the line

const Thumb = () => div(

should be

const Thumb = div(

@HEAVYPOLY
Copy link
Author

Thanks, that worked for intersection Observer. In this case, I want the GalleryThumbs to react when their canvas.id === selectedCanvasID, but not sure how to go about it. Are these still reactive?

let selectedCanvasID = vanX.reactive('123')

const GalleryThumbs = () => {
  prnt('selected id', selectedCanvasID)
  return vanX.list(() => div({ id: 'galleryGrid' }), canvases, ({ val: canvas }) =>
    GalleryThumb(canvas)
  )
}

const GalleryThumb = (canvas) => {
  const selected = selectedCanvasID === canvas.id
  prnt('GALLERY', selected)
  const Thumb = div(
    {
      id: `galleryThumb${canvas.id}`,
      class: `galleryThumb ${selectedCanvasID === canvas.id ? 'selected' : ''}`,
      onclick: (e) => {
        if (selectedCanvasID === canvas.id) { goPaint(); loadFromID(canvas.id); return }
        selectedCanvasID = canvas.id
        prnt('selected canvas id', canvas.id, e.target)
      },
    },
    div({
      class: 'image',
      loading: 'lazy',
    }),
    div({ class: `hoverable deleteButton ${selectedCanvasID === canvas.id ? '' : 'hidden'}` }),
    div({ class: 'hoverable playButton hidden' }),
    div({ class: 'hoverable restoreButton hidden' })
  )

  const galleryObserver = createGalleryObserver(canvas)
  galleryObserver.observe(Thumb)

  return Thumb
}

@Tao-VanJS
Copy link
Member

GalleryThumbs will be reactive because of vanX.list. But selectedCanvasID as a reactive primitive doesn't make much sense. You can simply use van.state for it. vanX.reactive is for objects or arrays as a collection of reactive fields.

@HEAVYPOLY
Copy link
Author

HEAVYPOLY commented Mar 10, 2024

Thanks,

I've tried switching to van.state, but the gallery thumbs are not rerendering when selectedCanvasID is changed.

This fires when they are first generated, but never again.
prnt('gallery thumb rendered', canvas.id, selectedCanvasID)

This fires when gallery thumb is clicked, but does not cause rerender (which I want)
prnt('selected canvas id', canvas.id, e.target)

is there a way to force rerender thumbs when selectedCanvasID is changed? (only 1 should be selected at a time)

let selectedCanvasID = van.state('123')

const GalleryThumbs = () => {
  prnt('gallery thumbs', selectedCanvasID)
  return vanX.list(() => div({ id: 'galleryGrid' }), canvases, ({ val: canvas }) =>
    GalleryThumb(canvas)
  )
}

const GalleryThumb = (canvas) => {
  prnt('gallery thumb rendered', canvas.id, selectedCanvasID)
  const Thumb = div(
    {
      id: `galleryThumb${canvas.id}`,
      class: `galleryThumb ${selectedCanvasID === canvas.id ? 'selected' : ''}`,
      onclick: (e) => {
        if (selectedCanvasID === canvas.id) { goPaint(); loadFromID(canvas.id); return }
        selectedCanvasID = canvas.id
        prnt('selected canvas id', canvas.id, e.target)
      },
    },
    div({
      class: 'image',
      loading: 'lazy',
    }),
    div({ class: `hoverable deleteButton ${selectedCanvasID === canvas.id ? '' : 'hidden'}` }),
    div({ class: `hoverable playButton ${selectedCanvasID === canvas.id ? '' : 'hidden'}` }),
    div({ class: `hoverable restoreButton ${selectedCanvasID === canvas.id ? '' : 'hidden'}` }),
  )

  const galleryObserver = createGalleryObserver(canvas)
  galleryObserver.observe(Thumb)

  return Thumb
}

@Tao-VanJS
Copy link
Member

I think you should use selectedCanvasID.val instead of selectedCanvasID for all its appearances.

@HEAVYPOLY
Copy link
Author

Thanks, .val works!

Now, when the selectedCanvasID changes, the thumbnail image flashes, is there a way to not rerender the image when selectedCanvasID is changed? (it should only be handled by galleryObserver and not affected by state)

let selectedCanvasID = van.state('123')

const GalleryThumbs = () => {
  prnt('gallery thumbs', selectedCanvasID)
  return vanX.list(() => div({ id: 'galleryGrid' }), canvases, ({ val: canvas }) =>
    GalleryThumb(canvas)
  )
}

const GalleryThumb = (canvas) => {
  const Thumb = div(
    {
      id: `galleryThumb${canvas.id}`,
      class: `galleryThumb ${selectedCanvasID.val === canvas.id ? 'selected' : ''}`,
      onclick: (e) => {
        if (selectedCanvasID.val === canvas.id) { goPaint(); loadFromID(canvas.id); return }
        selectedCanvasID.val = canvas.id
        prnt('selected canvas id', canvas.id, e.target)
      },
    },
    div({
      class: 'image',
      loading: 'lazy',
    }),
    div({ class: `hoverable deleteButton ${selectedCanvasID.val === canvas.id ? '' : 'hidden'}` }),
    div({ class: `hoverable playButton ${selectedCanvasID.val === canvas.id ? '' : 'hidden'}` }),
  )

  const galleryObserver = createGalleryObserver(canvas)
  galleryObserver.observe(Thumb)

  return Thumb
}
van.add(galleryGridContainer, GalleryThumbs())

@Tao-VanJS
Copy link
Member

I'm confused. Do you want Thumb reactive to selectedCanvasID, or not reactive to selectedCanvasID?

@HEAVYPOLY
Copy link
Author

HEAVYPOLY commented Mar 10, 2024

I want class galleryThumb, deleteButton and playButton to be reactive to selectedCanvasID
I want class image to not be reactive to selectedCanvasID

@Tao-VanJS
Copy link
Member

My understanding is class image shouldn't be reactive to the selectedCanvasID. Do you have a link to show the entire code?

@HEAVYPOLY
Copy link
Author

Here's the intersection observer, I think this is everything related to the gallery thumb. is the observer being recreated on each change of selectedCanvasID?

const createGalleryObserver = (canvas) => {
  const options = {
    root: null,
    rootMargin: '0px',
    threshold: 0.1,
  }

  const callback = (entries, observer) => {
    entries.forEach((entry) => {
      if (entry.isIntersecting) {
        const imageElement = entry.target.querySelector('.image')
        if (imageElement) {
          getLatestAsset(canvas.id, 'image').then((imageArrayBuffer) => {
            const imageBlob = arrayToBlob(imageArrayBuffer, 'image/webp').then(
              (blob) => {
                const imageUrl = URL.createObjectURL(blob)
                imageElement.style.backgroundImage = `url(${imageUrl})`
              }
            )
          })
        }
      }
    })
  }

  return new IntersectionObserver(callback, options)
}

@Tao-VanJS
Copy link
Member

Calling GalleryThumb will create the intersection observer. So that's a possibility. Without the access to the full code, I am not able to tell what exactly can trigger the calling to GalleryThumb. I'm not able to do any debugging, either.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants