van
van copied to clipboard
Intersection observer
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'.
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(
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
}
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.
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
}
I think you should use selectedCanvasID.val
instead of selectedCanvasID
for all its appearances.
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())
I'm confused. Do you want Thumb
reactive to selectedCanvasID
, or not reactive to selectedCanvasID
?
I want class galleryThumb, deleteButton and playButton to be reactive to selectedCanvasID I want class image to not be reactive to selectedCanvasID
My understanding is class image shouldn't be reactive to the selectedCanvasID
. Do you have a link to show the entire code?
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)
}
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.