2

With Vue3, I'm trying to create a Picture component with lazyload integrated to it.

Everything works fine when I load the page and none of the Picture are in viewport (when I scroll down, the images are loaded successively).

However when the page loads and one of the Picture is already on viewport, it loads every other images that are not yet visible.

Here is Picture component

<template>
  <div
    class="picture"
    v-lazy-load="!!placeholder"
  >
    <img
      data-placeholder
      class="picture__placeholder"
      :src="/^http/.test(placeholder) ? placeholder : require(`@/assets/images/${placeholder}`)"
      v-if="placeholder"
    >
    <img
      data-image
      class="picture__image"
      :alt="alt"
      :data-url="/^http/.test(src) ? src : require(`@/assets/images/${src}`)"
    >
  </div>
</template>

<script lang="ts">
import { defineComponent } from 'vue'
import { DirectiveElement } from '@/scripts/contracts/interfaces'

export default defineComponent({
  name: 'Picture',
  directives: {
    'lazy-load': {
      mounted(el: DirectiveElement, binding) {

        const image = el.querySelector('.picture__image') as HTMLImageElement

        if (!image) {
          console.error('[v-lazy-load] provided component doesn\'t contain element with class \'image\'')
          return false
        }

        if (typeof binding.value !== 'boolean') {
          console.error('[v-lazy-load] provided value is not a boolean')
          return false
        }

        if (Object.keys(binding.modifiers).length) console.warn('[v-lazy-load] no modifiers allowed')

        function loadImage() {
          if (image) {
            el.eventFn = () => el.classList.add('picture--loaded')
            image.addEventListener('load', el.eventFn)

            el.eventError = () => console.error('[v-lazy-load] error eventlistener')
            image.addEventListener('error', el.eventError)

            image.src = image.dataset.url as string
          }
        }

        function handleIntersect(
          entries: IntersectionObserverEntry[],
          observer: IntersectionObserver,
        ) {
          entries.forEach((entry) => {
            if (entry.isIntersecting) {
              loadImage()
              observer.unobserve(el)
            }
          })
        }

        function createObserver() {
          const options = { root: null, threshold: 0 }
          const observer = new IntersectionObserver(handleIntersect, options)
          observer.observe(el)
        }

        if (window.IntersectionObserver && binding.value) return createObserver()
        return loadImage()
      },
      unmounted(el: DirectiveElement) {
        const image = el.querySelector('.picture__image') as HTMLImageElement

        if (image) {
          image.removeEventListener('load', el.eventFn as EventListener)
          image.removeEventListener('error', el.eventError as EventListener)
        }
      },
    },
  },
  props: {
    alt: { type: String, default: '' },
    src: { type: String, required: true },
    placeholder: { type: String, default: '' },
  },
})
</script>

Here the parent component

<template>
  <div class="parent">

    <Picture
      src="desert.jpg"
      style="width: 80%;"
      placeholder="placeholder.png"
    />
    
    ...
    
    <Picture
      src="mountain.jpg"
      style="width: 80%;"
      placeholder="placeholder.png"
    />
  </div>
</template>

Can someone help me with this, please?

Mewenlr
  • 51
  • 1
  • 4

0 Answers0