Skip to content

Conversation

@joshwhatk
Copy link
Contributor

@joshwhatk joshwhatk commented Feb 8, 2018

Context

While the load config method is valuable, I don’t want to have to rewrite the entire included load function to extend the functionality just to get the default benefits provided by Lozad.

As it currently sits, there is no way for me to test a custom implementation for retina support background-images (uses -webkit-image-set). without rewriting the entire default load function.

I also cannot currently override the markAsLoaded method. I used the .lazy-load class and would like to modify it to be .lazy-loaded to make my query for unloaded items simpler: .lazy-load vs .lazy-load:not([data-loaded]).

Solution

With a loaded callback (name WIP) option, I could add any additional functionality that I want without having to duplicate functionality that will just get out of date.

Example

const observer = lozad('.lazy-load', {
  loaded(element) {
    element.classList.add('lazy-loaded')
    element.classList.remove('lazy-load')
  }
})

observer.observe()

@ApoorvSaxena
Copy link
Owner

@joshwhatk this looks neat. While reviewing code, came across this line https://github.com/ApoorvSaxena/lozad.js/pull/86/files#diff-ada3201bd50efbeb7cff01652d1426cdR63 where markAsLoaded function is still called, shouldn't this be changed as well?

`markAsLoaded` is required to run for the `isLoaded` method to be useful.

Also added a negative test for when a custom `load` option is passed in.
@joshwhatk
Copy link
Contributor Author

@ApoorvSaxena good catch! Also, it appears that the markAsLoaded method is necessary for lozad to check whether elements have been loaded or not, so it shouldn't just be overridden.

Instead, I added loaded as an empty function and passed loaded as the second argument to the onIntersection method to make sure that it gets called everywhere.

test/index.js Outdated
assert.equal(image.getAttribute('src'), image.dataset.src)
})

// It('should run loaded function after loading an element', () => {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this seems unwanted, better remove the commented code

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@ApoorvSaxena ApoorvSaxena merged commit c2c9e67 into ApoorvSaxena:master Feb 15, 2018
@lsarni
Copy link
Contributor

lsarni commented Feb 15, 2018

@ApoorvSaxena Will you be pushing a new bower "release" with this change?

@joshwhatk joshwhatk deleted the add-loaded-callback branch February 15, 2018 16:36
@ApoorvSaxena
Copy link
Owner

@jimblue latest version v1.3.0 has this implementation

@perosb
Copy link

perosb commented Mar 3, 2018

how will this help with being able to start f.ex an animation when the image is downloaded?
this only adds a callback after the src has been set? then it might also be too late to add any "onload" hooks.

wasnt that the purpose of #79 which was closed in favor of this one.

@rgfx
Copy link

rgfx commented Mar 13, 2018

I would like to fade in the images when they load.

With is method with large images you will notice that the callback works only when the image is in view not when its fully loaded. So you still get a standard baseline load. Not very sexy.

loaded(element) {

should be

loading(element) {

Then there should be a truly loaded method.

Please look into pulling @aderaaij pull request, as I think it makes more sense.

Am I not understanding something? The above post agrees aswell, something is not right.

Please advise.

@joshwhatk
Copy link
Contributor Author

@rgfx @perosb What exactly are you working with? I would probably hook into the onload event for the images if I wanted to fade them in on their load. Something like this: https://jsfiddle.net/joshwhatk/kxq8jbn8/5/

@jimblue
Copy link

jimblue commented Mar 13, 2018

@joshwhatk as said here the data-loaded just happens when the strings get switched from data-src to src, that is not checking if the image is loaded.

@joshwhatk
Copy link
Contributor Author

@jimblue, absolutely, the data-loaded attribute is only used as a marker by lozad so that it doesn't try to load the image more than once. It has no understanding of how the image itself actually loads.

In the fiddle, I manually hook into the onload method from the image itself. You can also use the loaded callback to add this functionality for all images as can be seen here: https://jsfiddle.net/joshwhatk/kxq8jbn8/14/

But I think I'm hearing that the onload hooking should be the default behavior? If that is going to be the default behavior, we will need another data-something or lozad might try to load the image multiple times. That would be pretty cool.

@jimblue
Copy link

jimblue commented Mar 13, 2018

@joshwhatk I'm not sure @ApoorvSaxena want to go in this direction as this PR took priority over.
Still I do agree it would be better to always check that images are properly loaded by default.

I've worked on a local solution that could help.
It defines different types of scenario that use the same callback data-loaded.
They are only two scenarios for now, img and bg, other could be added easily.

For now it's working well, but you're probably right about adding another data-something to prevent lozad to load the image multiple times.

Here is the code:

// LAZYIMG
// ––––––––––––––––––––––

import 'intersection-observer'

export default () => {
  // Get all of the elements that are marked up to lazy load
  const elements = document.querySelectorAll('.lazyimg')

  // Create the observer
  let observer

  const observe = elements => {
    // Config
    const config = {
      rootMargin: '0px',
      threshold: 0
    }

    // Create the observer instance
    observer = new IntersectionObserver(onIntersection, config)

    for (let i = 0; i < elements.length; i++) {
      const element = elements[i]

      // If the element has been handled, skip to the next element
      if (isLoaded(element)) return

      // Observes the element
      observer.observe(element)
    }
  }

  const fetchImage = imgSrc => {
    // Create a promise to fetch an image
    return new Promise((resolve, reject) => {
      // Create a new image
      const image = new Image()

      // Handles errors
      image.addEventListener('load', resolve)
      image.addEventListener('error', reject)

      // Set this image to the paramenter imgSrc
      image.src = imgSrc
    })
  }

  const applyImage = (element, imgSrc, type) => {
    // Define element imgSrc
    if (type === 'img') element.src = imgSrc

    // Define element imgSrc
    if (type === 'bg') element.style.backgroundImage = `url(${imgSrc})`

    // Prevent this from being lazy loaded a second time
    markAsLoaded(element)
  }

  const preloadImage = element => {
    // Get imgSrc from the dataset
    let imgSrc
    let type

    // If element is an image
    if (element.dataset.src) {
      imgSrc = element.dataset.src
      type = 'img'
    }

    // If element is a background image
    if (element.dataset.bg) {
      imgSrc = element.dataset.bg
      type = 'bg'
    }

    // If imgSrc or type is not found break
    if (!imgSrc || !type) return

    // Fetches imgSrc and applies it to the viewport
    return fetchImage(imgSrc).then(() => applyImage(element, imgSrc, type))
  }

  // Mark element as loaded
  const markAsLoaded = element => element.setAttribute('data-loaded', true)

  // Check if element is loaded
  const isLoaded = element => element.getAttribute('data-loaded') === 'true'

  const onIntersection = entries => {
    // Loop through entries
    for (let i = 0; i < entries.length; i++) {
      const entry = entries[i]

      // If the element is in the viewport unobserve it
      if (entry.intersectionRatio > 0) {
        // Stop watching
        observer.unobserve(entry.target)

        // Load the element if it's not already
        preloadImage(entry.target)
      }
    }
  }

  return observe(elements)
}

@rgfx
Copy link

rgfx commented Mar 16, 2018

@joshwhatk your last fiddle doesn't show any images for me. I thought perhaps it was your placeholders but it wasn't. Will your solution work for the Picture tag? To fade in images when fully loaded.

@jimblue forgive my ignorance but what is import 'intersection-observer' are you using webpack or something? Will your script work with the picture tag as well, once I figure out how to use your script.

Thanks guys.

@jimblue
Copy link

jimblue commented Mar 16, 2018

@rgfx yes I use webpack to import the intersection observer polyfill before the script to improve compatibility on old browser but picture tag currently doesn't work.
I advice you to wait a little, the script I've write it's just for testing.
Even if it's working well, the best would be to add proper image loading into lozad core.
@ApoorvSaxena can we have your feedback, #49 is still not fixed, this outskirt discussion could help to do so... thanks

This was referenced May 3, 2018
@cfxd
Copy link

cfxd commented Jun 14, 2018

The callback doesn't quite work as expected, but @joshwhatk's fiddle solution works great. Thank you @joshwhatk!

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

Successfully merging this pull request may close these issues.

8 participants