图片的 JavaScript 加载进度

IT技术 javascript image html progress
2021-03-13 01:09:15

JS中有没有办法在加载图像时获取加载图像的进度?我想使用 HTML5 的新 Progress 标签来显示加载图像的进度。

我希望有类似的东西:

var someImage = new Image()
someImage.onloadprogress = function(e) { progressBar.value = e.loaded / e.total };
someImage.src = "image.jpg";
6个回答

有了这个,您在 Image() 对象上添加了 2 个新函数:

 Image.prototype.load = function(url){
        var thisImg = this;
        var xmlHTTP = new XMLHttpRequest();
        xmlHTTP.open('GET', url,true);
        xmlHTTP.responseType = 'arraybuffer';
        xmlHTTP.onload = function(e) {
            var blob = new Blob([this.response]);
            thisImg.src = window.URL.createObjectURL(blob);
        };
        xmlHTTP.onprogress = function(e) {
            thisImg.completedPercentage = parseInt((e.loaded / e.total) * 100);
        };
        xmlHTTP.onloadstart = function() {
            thisImg.completedPercentage = 0;
        };
        xmlHTTP.send();
    };

    Image.prototype.completedPercentage = 0;

在这里,您使用加载函数并将图像附加到 div 上。

var img = new Image();
img.load("url");
document.getElementById("myDiv").appendChild(img);

在加载状态期间,您可以使用 来检查进度百分比img.completedPercentage

这在Safari v10 中似乎不起作用它似乎并没有实际将新load函数添加到对象原型中,img.load()并被列为undefined.
2021-04-19 01:09:15
你可以使用 img 对象作为 img 标签的 src。
2021-04-21 01:09:15
这条线有什么作用?parseInt(thisImg.completedPercentage = (e.loaded / e.total) * 100); 是不是写错了?
2021-05-07 01:09:15
哦,是的,应该可以,但它错了,让我编辑一下
2021-05-07 01:09:15
@duhaime 我做到了。有时我对 SO 的近视性质感到非常沮丧,因为我被要求从上面的链接中删除我的答案。据说它“不太可能对其他人有用”。我对此提出异议,但是..我离题了。无论如何,Safari 的标准 uBlock 插件过滤器阻止Image.prototype.load完成,而它们不在 Chrome 和 Firefox 中。fork 开发者没有承认我的错误报告,所以我不知道它是否会得到解决。如果您不使用 uBlock,请禁用所有插件并查看是否有助于隔离原因。
2021-05-08 01:09:15

塞巴斯蒂安的回答非常好,是我对这个问题见过的最好的回答。但是,有一些可能的改进。我使用他的代码修改如下:

Image.prototype.load = function( url, callback ) {
    var thisImg = this,
        xmlHTTP = new XMLHttpRequest();

    thisImg.completedPercentage = 0;

    xmlHTTP.open( 'GET', url , true );
    xmlHTTP.responseType = 'arraybuffer';

    xmlHTTP.onload = function( e ) {
        var h = xmlHTTP.getAllResponseHeaders(),
            m = h.match( /^Content-Type\:\s*(.*?)$/mi ),
            mimeType = m[ 1 ] || 'image/png';
            // Remove your progress bar or whatever here. Load is done.

        var blob = new Blob( [ this.response ], { type: mimeType } );
        thisImg.src = window.URL.createObjectURL( blob );
        if ( callback ) callback( this );
    };

    xmlHTTP.onprogress = function( e ) {
        if ( e.lengthComputable )
            thisImg.completedPercentage = parseInt( ( e.loaded / e.total ) * 100 );
        // Update your progress bar here. Make sure to check if the progress value
        // has changed to avoid spamming the DOM.
        // Something like: 
        // if ( prevValue != thisImage completedPercentage ) display_progress();
    };

    xmlHTTP.onloadstart = function() {
        // Display your progress bar here, starting at 0
        thisImg.completedPercentage = 0;
    };

    xmlHTTP.onloadend = function() {
        // You can also remove your progress bar here, if you like.
        thisImg.completedPercentage = 100;
    }

    xmlHTTP.send();
};

主要是我添加了一个哑剧类型和一些小细节。按照塞巴斯蒂安的描述使用。效果很好。

你不应该做你的回调xmlHTTP.onload,因为像图像性能naturalWidthnaturalHeight尚未推向市场。你应该做一个单独的thisImg.onload = function() {if ( callback ) callback();};
2021-04-21 01:09:15
使用的一个缺点XMLHttpRequest是未在请求中设置 Origin 标头 - 但您只需直接在对象src设置属性Image(至少在 Chrome 中)
2021-04-22 01:09:15
你知道哪些浏览器会支持这个吗?是否移动?下面的答案是 XHR 解决方案并非所有浏览器都支持......
2021-04-25 01:09:15
同意@manakor 很高兴看到此答案中提到的浏览器支持
2021-05-08 01:09:15
@manakor:我发现caniuse.com对于像您这样的问题非常宝贵。
2021-05-12 01:09:15

只是为了增加改进,我修改了朱利安的答案(反过来修改了塞巴斯蒂安的)。我已将逻辑移至函数而不是修改Image对象。该函数返回一个Promise用URL对象解析的a ,只需要作为标签src属性插入即可image

/**
 * Loads an image with progress callback.
 *
 * The `onprogress` callback will be called by XMLHttpRequest's onprogress
 * event, and will receive the loading progress ratio as an whole number.
 * However, if it's not possible to compute the progress ratio, `onprogress`
 * will be called only once passing -1 as progress value. This is useful to,
 * for example, change the progress animation to an undefined animation.
 *
 * @param  {string}   imageUrl   The image to load
 * @param  {Function} onprogress
 * @return {Promise}
 */
function loadImage(imageUrl, onprogress) {
  return new Promise((resolve, reject) => {
    var xhr = new XMLHttpRequest();
    var notifiedNotComputable = false;

    xhr.open('GET', imageUrl, true);
    xhr.responseType = 'arraybuffer';

    xhr.onprogress = function(ev) {
      if (ev.lengthComputable) {
        onprogress(parseInt((ev.loaded / ev.total) * 100));
      } else {
        if (!notifiedNotComputable) {
          notifiedNotComputable = true;
          onprogress(-1);
        }
      }
    }

    xhr.onloadend = function() {
      if (!xhr.status.toString().match(/^2/)) {
        reject(xhr);
      } else {
        if (!notifiedNotComputable) {
          onprogress(100);
        }

        var options = {}
        var headers = xhr.getAllResponseHeaders();
        var m = headers.match(/^Content-Type\:\s*(.*?)$/mi);

        if (m && m[1]) {
          options.type = m[1];
        }

        var blob = new Blob([this.response], options);

        resolve(window.URL.createObjectURL(blob));
      }
    }

    xhr.send();
  });
}

/*****************
 * Example usage
 */

var imgContainer = document.getElementById('imgcont');
var progressBar = document.getElementById('progress');
var imageUrl = 'https://placekitten.com/g/2000/2000';

loadImage(imageUrl, (ratio) => {
  if (ratio == -1) {
    // Ratio not computable. Let's make this bar an undefined one.
    // Remember that since ratio isn't computable, calling this function
    // makes no further sense, so it won't be called again.
    progressBar.removeAttribute('value');
  } else {
    // We have progress ratio; update the bar.
    progressBar.value = ratio;
  }
})
.then(imgSrc => {
  // Loading successfuly complete; set the image and probably do other stuff.
  imgContainer.src = imgSrc;
}, xhr => {
  // An error occured. We have the XHR object to see what happened.
});
<progress id="progress" value="0" max="100" style="width: 100%;"></progress>

<img id="imgcont" />

美丽的答案!
2021-04-26 01:09:15
嗯,它的工作。如果您的浏览器缓存了图像,请尝试在您的开发工具上选中“禁用缓存”(如果加载速度过快,请使用节流)。
2021-05-08 01:09:15
这可以转换成一个片段吗?
2021-05-13 01:09:15
谢谢~我意识到我可以通过-1而不是false如果进度不可计算。这将使参数始终保持一个数字。我会进行编辑。
2021-05-14 01:09:15
(老实说,我不知道您是否可以在 SO 中使用 XMLHttpRequest - 但这与其他答案相比将是一个很好的答案)
2021-05-17 01:09:15

实际上,在最新的 chrome 中你可以使用它。

$progress = document.querySelector('#progress');

var url = 'https://placekitten.com/g/2000/2000';

var request = new XMLHttpRequest();
request.onprogress = onProgress;
request.onload = onComplete;
request.onerror = onError;

function onProgress(event) {
  if (!event.lengthComputable) {
    return;
  }
  var loaded = event.loaded;
  var total = event.total;
  var progress = (loaded / total).toFixed(2);

  $progress.textContent = 'Loading... ' + parseInt(progress * 100) + ' %';

  console.log(progress);
}

function onComplete(event) {
  var $img = document.createElement('img');
  $img.setAttribute('src', url);
  $progress.appendChild($img);
  console.log('complete', url);
}

function onError(event) {
  console.log('error');
}


$progress.addEventListener('click', function() {
  request.open('GET', url, true);
  request.overrideMimeType('text/plain; charset=x-user-defined');
  request.send(null);
});
<div id="progress">Click me to load</div>

Христо:我建议您定义“最新”和“它”是什么。你也可能会以这种方式获得更多的赞成票。
2021-04-18 01:09:15
这是 IMO 的最佳答案。工作原理:发送资源的 XHR 请求,并监控进度。请求完成后,您可以使用浏览器中的缓存资源来创建图像,它应该立即准备就绪。
2021-04-22 01:09:15
这兼容所有浏览器吗?
2021-05-05 01:09:15

这是 Julian Jensen 代码的一个小更新,以便能够在加载后在 Canvas 中绘制图像:

xmlHTTP.onload = function( e ) {
        var h = xmlHTTP.getAllResponseHeaders(),
            m = h.match( /^Content-Type\:\s*(.*?)$/mi ),
            mimeType = m[ 1 ] || 'image/png';
            // Remove your progress bar or whatever here. Load is done.

        var blob = new Blob( [ this.response ], { type: mimeType } );
        thisImg.src = window.URL.createObjectURL( blob );

         thisImg.onload = function()
            {
                if ( callback ) callback( this );
            };
    };