How to wait for all images to load from page.evalu

2020-07-17 06:39发布

问题:

I am trying to make the code execution wait for all images to load before puppeteer takes a screenshot. My DOM gets populated when initData() function is called, which is defined in the client side js file. Delay or timeout is an option but I am sure there must be a more efficient way of doing it.

    (async (dataObj) => {
             const url = dataObj.url;
             const payload = dataObj.payload;
             const browser = await puppeteer.launch({ headless: false,devtools:false});
             const page = await browser.newPage();
             await page.goto(url,{'waitUntil': 'networkidle0'});

             await page.evaluate((payload) => {
               initData(payload);
                //initData is a client side function that populates the DOM, need to wait 
                //here till the images are loaded. 
               },payload)

             await page.setViewport({ width: 1280, height: 720 })
             await page.screenshot({ path: 'test.png' });
             await browser.close();
    })(dataObj)

Thanks in advance.

回答1:

As mentioned in another answer, image elements have a complete property. You can write a function that returns true when all images in the document have been fetched:

function imagesHaveLoaded() { return Array.from(document.images).every((i) => i.complete); }

And you can wait for that function like so:

await page.waitForFunction(imagesHaveLoaded);

Putting the two together with your original code and adding a timeout so it doesn’t wait indefinitely, we get:

function imagesHaveLoaded() {
    return Array.from(document.images).every((i) => i.complete);
}

(async (dataObj) => {
         const url = dataObj.url;
         const payload = dataObj.payload;
         const browser = await puppeteer.launch({ headless: false, devtools: false});
         const page = await browser.newPage();
         await page.goto(url, { waitUntil: 'networkidle0' });

         await page.evaluate((payload) => {
           initData(payload);
         }, payload);

         await page.waitForFunction(imagesHaveLoaded, { timeout: YOUR_DESIRED_TIMEOUT });

         await page.setViewport({ width: 1280, height: 720 })
         await page.screenshot({ path: 'test.png' });
         await browser.close();
})(dataObj)


回答2:

You can do this using promises, by getting all <img> tags on your document and loop check until the browser fetch all of them (when img.complete == true for all imgs) then you resolve the promise.

HTMLImageElement.complete Read only

Returns a Boolean that is true if the browser has finished fetching the image, whether successful or not. It also shows true, if the image has no src value.

Ref.: MDN HTMLImageElement

I've implemented a function for that, returns a promise that resolves when all imgs are fetched and rejects in case it reaches the timeout (initially 30 seconds but it is changeable).

Usage:

// consuming the promise
imgReady().then(
  (imgs) => {
    // do stuff here
    console.log('imgs ready');
  },
  (err) => {
    console.log('imgs taking to long to load');
  }
);

// inside asyng functions
const imgs = await imgReady();

Note on window.onload: You can use window.onload too; however, window.onload waits for everything to load instead of only images.

/**
 * @param timeout: how long to wait until reject and cancel the execution.
 * @param tickrate: how long to recheck all imgs again.
 *
 * @returns
 *   A promise which resolve when all img on document gets fetched.
 *   The promise get rejected if it reach the @timeout time to execute.
 */
function imgReady(timeout = 30*1000, tickrate = 10) {
  const imgs = Array.from(document.getElementsByTagName('img'));
  const t0 = new Date().getTime();

  return new Promise((resolve, reject) => {

    const checkImg = () => {
      const t1 = new Date().getTime();

      if (t1 - t0 > timeout) {
        reject({
          message: `CheckImgReadyTimeoutException: imgs taking to loong to load.`
        });
      }

      if (imgs.every(x => x.complete)) {
        resolve(imgs);
      } else {
        setTimeout(checkImg, tickrate);
      }
    };

    checkImg();
  });
}

imgReady().then(console.log,console.error);
img{max-width: 100px;}
<img src="https://upload.wikimedia.org/wikipedia/commons/c/cc/ESC_large_ISS022_ISS022-E-11387-edit_01.JPG">
<br>
<img src="https://www.publicdomainpictures.net/pictures/90000/velka/planet-earth-1401465698wt7.jpg">