【问题标题】:Waiting for image to load in JavaScript等待图像在 JavaScript 中加载
【发布时间】:2011-01-21 11:28:01
【问题描述】:

我正在进行 Ajax 调用,它会返回一些信息,包括图像路径。

我在我的 HTML 中准备了所有这些信息,这些信息将显示为一种弹出窗口。我只是将弹出 div 的可见性从隐藏切换到可见。

要设置弹出 div 的位置,我必须根据图像的高度进行计算。因此,在设置位置并将可见性切换为可见之前,我必须等待图像加载以了解其尺寸。

我尝试了递归、setTimeout、完整的 img 属性、while 循环的技巧......但没有成功。

那么,我该怎么做呢?也许我应该在我的 Ajax 调用中返回尺寸。

【问题讨论】:

  • 为什么它不适用于Image.complete 属性?

标签: javascript image load wait


【解决方案1】:

Promise 等待图片加载示例:

function waitForImage(imgElem) {
    return new Promise(res => {
        if (imgElem.complete) {
            return res();
        }
        imgElem.onload = () => res();
        imgElem.onerror = () => res();
    });
}



// test
(async () => {
  const img = document.querySelector('img');
  // to not to cache, set src dynamically
  img.src = 'https://www.gravatar.com/avatar/71094909be9f40bd576b1974a74966aa?s=48&d=identicon&r=PG&f=1';

  console.log('height before', img.naturalHeight); // 0
  await waitForImage(img);
  console.log('height after', img.naturalHeight); // 48
})();
<img src=""/>

【讨论】:

    【解决方案2】:

    窗口加载事件怎么样?

    window.addEventListener('load', (event) => {
      //do stuff with images
    });
    

    window.onload = (event) => {
      //do stuff with images
    };
    

    https://developer.mozilla.org/en-US/docs/Web/API/Window/load_event

    这对我有用,因为我需要浏览器在运行布局脚本之前计算图像的大小。

    【讨论】:

    • 这似乎并不能完全回答这个问题,因为在页面本身加载之后可以加载图像很多次,包括这个例子。窗口上的 onload 事件确实等待加载图像,但仅等待初始页面加载。在这个问题的上下文中,onload 事件应该直接与正在加载的图像元素相关联。
    【解决方案3】:

    accepted answer 已过时,但确实显示了基本的Image#onload 回调方法。如今,您可能希望承诺图像加载以避免回调地狱。

    This answer 是一个很好的承诺图像加载处理程序,但缺少一些关键点,正如my comment 所指出的那样。

    这是Image 的另一个更通用的说法。在 onerror 处理程序中拒绝并将实际的图像对象传递给解析器对于使函数具有最小的可重用性很重要。

    改进可能包括附加参数(例如crossOrigin)。 settings 对象或提供 Image 作为参数是另一种泛化函数的方法(设置 src 会触发请求,因此应该在添加处理程序后最后执行)。

    const loadImage = src =>
      new Promise((resolve, reject) => {
        const img = new Image();
        img.onload = () => resolve(img);
        img.onerror = reject;
        img.src = src;
      })  
    ;
    
    loadImage("http://placekitten.com/90/100").then(image => 
      console.log(image, `\nloaded? ${image.complete}`)
    );

    通过上述功能,Promise.all 可用于并行加载一批图像(Promise.allSettled 很有用,如果您想在某些图像未加载的情况下继续加载)。

    const loadImage = src =>
      new Promise((resolve, reject) => {
        const img = new Image();
        img.onload = () => resolve(img);
        img.onerror = reject;
        img.src = src;
      })  
    ;
    
    const imageUrls = [
      "http://placekitten.com/85/150",
      "http://placekitten.com/85/130",
      "http://placekitten.com/85/110",
    ];
    Promise.all(imageUrls.map(loadImage)).then(images => {
      const canvas = document.createElement("canvas");
      document.body.appendChild(canvas);
      const ctx = canvas.getContext("2d");
      images.forEach((image, i) =>
        ctx.drawImage(image, i * 90, 0, image.width, image.height)
      );
    });

    【讨论】:

      【解决方案4】:

      只需将您的图像 onload 包装在一个带有 promise 的函数中,然后使用 await 调用它。

      async drawImg(ctx, image){
      
          return new Promise(resolve => {
      
                image.onload = function () {
                      ctx.drawImage(image, 10, 10, 200, 180);
                      resolve('resolved');
                   }
      
          });
      
        }
      

      应该没问题

      【讨论】:

      • 这里的async 关键字是不必要的——返回一个promise 就足以让调用者await 它。使用reject() 承诺 onerror 处理程序也是一个好主意,这样调用者可以在加载失败时捕获错误。最后一个建议:您可以将图像对象本身传递给resolve(this),以便调用者可以绘制图像或用它做他们想做的事情。
      【解决方案5】:

      我的页面上加载了缓慢的 CAPTCHA (1st_image) 图像,我想仅在加载 CAPTCHA 图像 (1st_image) 后显示另一个图像 (2nd_image)。所以我不得不等待 CAPTCHA (1st_image) 图像首先加载。

      这是一个完美的解决方案,可以先等待图像加载,然后再加载另一个图像(不要忘记在他们等待其他图像加载时显示“请稍候!”图像):

      <script>
          function checkImageLoad() {
              if (document.getElementById("1st_image").complete == true) {
                  console.log("1st_image Loaded!");
              }
              document.getElementById("2nd_image").src = "http://example.org/2nd_image.png";
          }
      </script>
      <body onload="checkImageLoad();">
      <img id="1st_image" src="http://example.org/1st_image.png">
      <img id="2nd_image" src="http://example.org/loading_please_wait.gif">
      

      无论是网速慢还是快,浏览器都会等待整个页面加载完所有图片(即使它们是外部链接的)然后执行该功能,所以第二张图片只有在第一张图片加载正常。

      高级说明:您可以使用图片“src”中的网页网址先加载网页,然后再显示图片。目的是从外部网页加载 cookie,这可能会影响显示的第二张图片(如 CAPTCHA)

      【讨论】:

        【解决方案6】:
        var img = new Image();
        img.onload = function() { alert("Height: " + this.height); }
        img.src = "http://path/to/image.jpg";
        

        请注意,按照上述顺序执行此操作很重要:首先附加处理程序,然后设置src。如果您以相反的方式执行此操作,并且图像在缓存中,您可能会错过该事件。 JavaScript 在浏览器中的单线程上运行(除非您使用网络工作者),但 浏览器 不是单线程的。浏览器看到src,识别资源可用,加载它,触发事件,查看元素以查看它是否有任何需要排队等待回调的处理程序,没有看到任何处理程序是完全有效的,并且在src 行和附加处理程序的行之间完成事件处理。 (如果注册了回调,则不会在行之间发生回调,它们会在队列中等待,但如果没有,则事件不需要等待。)

        【讨论】:

        • 恭喜!顺便说一句:我对 AJAX 主题一无所知,但万一使用 AJAX 获得的图像以与直接写入 HTML 代码的图像相同的方式缓存,您最终可以添加它以避免浏览器的图像缓存(显然仅当您需要时):img.src="path/to/image.jpg"+"?refresh="+newDate().getTime();
        • @Marco 或者在 XHR 上设置 cache: false :) 虽然我认为这与这里无关,因为 AJAX 调用返回的是图像的 URL,而不是图像本身。但是是的,我想他仍然可以使用随机查询字符串值来确保不会发生用户代理缓存。
        • 你可以添加一个事件监听器,还是替换onload
        • @IQAndreas:你可以使用img.addEventListener("load", ...),是的。 (或在旧 IE 上 img.attachEvent("onload", ...))。
        • 我改变第二行和第三行的顺序有关系吗?
        【解决方案7】:

        如果你使用 jQuery,你可以使用它的load 事件。

        看一下例子:

        $('img.userIcon').load(function(){
            if($(this).height() > 100) {
                $(this).addClass('bigImg');
            }
        });
        

        【讨论】:

        • -1:问题中没有提到 jQuery,所以这样的答案可能会很混乱。
        • 除非请求 jquery,否则请不要使用 jquery
        • 请注意以下与图像一起使用时加载方法的注意事项(取自api.jquery.com/load-event): > 它不能始终如一地工作,也不能可靠地跨浏览器 > 它不能正确触发WebKit 如果图像 src 设置为与以前相同的 src > 它没有正确冒泡 DOM 树 > 可以停止为已经存在于浏览器缓存中的图像触发
        猜你喜欢
        • 2016-10-17
        • 1970-01-01
        • 1970-01-01
        • 2012-11-05
        • 2010-11-30
        • 2018-03-06
        • 2017-07-19
        • 2015-01-24
        • 1970-01-01
        相关资源
        最近更新 更多