FrankKai.github.io icon indicating copy to clipboard operation
FrankKai.github.io copied to clipboard

blob URL那些事儿

Open FrankKai opened this issue 5 years ago • 5 comments

先看一下blob URL长什么样:

blob:http://localhost:9090/39b60422-26f4-4c67-8456-7ac3f29115ec

http/https协议前加了一个blob:,是不是看起来很神奇,其实它不仅看起来神奇,在前端开发领域用处也十分广泛。

blob对象在前端开发中是非常常见的,下面我将列举几个应用场景:

  • canvas toDataURL后的base64格式属性,会超出标签属性值有最大长度的限制
  • <input type="file" />上传文件之后的File对象,最初只想在本地留存,时机合适再上传到服务器

blob仅仅是一种数据对象,在上述2个场景中,都需要比较重要的一个API,那就是window.URL.createObjectURL()。所以我们在介绍blob对象之余,也会对这个关键的API进行介绍。

所以文章主要分为以下几个部分:

  • Blob对象知识点
  • canvas toDataURL场景下的blob
  • <input type="file" />场景下的blob
  • 关键API window.URL.createObjectURL()
  • Blob,File对象如何转换为base64字符串
  • 如何创建一个可用来测试的Blob对象
  • 不使用canvas的情况下将image的url转换为blob url

FrankKai avatar Mar 21 '19 02:03 FrankKai

Blob对象知识点

  • 将一个ArrayBuffer转换为Blob对象时,可以指定一个MIME类型。例如const blob = new Blob([new Uint8Array(arrayBuffer)], { type: 'audio/AMR' });
  • Blob对象是一个类文件对象,熟知的FIle就是继承自Blob类的。
  • 使用Blob()构造器,可以将非blob对象转换为Blob。
  • 可以使用slice方法获取到Blob切片,而且可以修改MIME类型。
  • 还有3个Working Draft方法,stream,text,ArrayBuffer。
  • Blob对象会在磁盘中缓存,或者存储在内存中。 这取决于浏览器实现。

FrankKai avatar May 24 '19 08:05 FrankKai

关键API window.URL.createObjectURL()

  • URL.createObjectURL(object) object可以包括FIle,Blob和MediaSource对象
  • URL.createObjectURL()的生命周期仅仅局限于创建它的window对象的document
  • 需要通过URL.revokeObjectURL(objectURL)通知浏览器,不再持有文件的引用从而释放内存,若不释放,会在document销毁时自动释放

提醒:尤其第三点,涉及到了内存管理,一定要注意释放内存

疑问

  • URL的生命周期在vue组件中如何表现?

vue的单文件组件共有一个document,这也是它被称为单页应用的原因,因此可以在组件间直接通过blob URL进行通信。 在vue-router采用hash模式的情况下,页面间的路由跳转,不会重新加载整个页面,所以URL的生命周期非常强力,因此在跨页面(非新tab)的组件通信,也可以使用blob URL。 需要注意的是,在vue的hash mode模式下,需要更加注意通过URL.revokeObjectURL()进行的内存释放,在大量使用blob URL的情况下,可能会撑爆浏览器内存

组件发出blob URL
<label for="background">上传背景</label>
<input type="file" style="display: none"
           id="background" name="background"
           accept="image/png, image/jpeg" multiple="false"
           @change="backgroundUpload"
>
backgroundUpload(event) {
  const fileBlobURL = window.URL.createObjectURL(event.target.files[0]);
  this.$emit('background-change', fileBlobURL);
  // this.$bus.$emit('background-change', fileBlobURL);
},
组件接收blob URL
<BackgroundUploader @background-change="backgroundChangeHandler"></BackgroundUploader>
// this.$bus.$on("background-change", backgroundChangeHandler);
backgroundChangeHandler(url) {
    // some code handle blob url...
},
  • 如何查看当前document中的所有blob URL?

暂时没有找到方法获取所有的blob URL,但是可以通过sessionStorage和localStorage进行标记,通过key进行更新或者访问。 若是没有新tab的纯单页应用的跨组件通信,可以存储在sessionStorage或者localStorage;若是存在新的tab,blob URL跨tab已经失效,需要存储最原始的数据类型,使用时再进行转换。 需要注意的是,在一个blob URL使用结束后,需要从storage和内存中同时删除。

提醒:如果你的项目会涉及到多个tab间的通信,blob URL在另一个tab中是无效的。可以存储原始数据,使用时再转换为blob URL

FrankKai avatar May 25 '19 10:05 FrankKai

Blob,File对象如何转换为base64字符串

本地预览仅仅适用于浏览器预览,当需要持久化存储时,就需要将其上传到云存储上,现在用处比较广泛的是OSS、七牛云等公有云,私有云没接触过,应该是大同小异。 如果是上传到云存储,一个仅在当前document内生命周期的blob url就不生效了,那么就需要上传有效的数据上去,就我目前接触的项目来说,我们是将文件最终转换成base64格式,通过接入sdk接口的服务端接口实现上传。 那么问题来了,如果是canvas,可以通过toDataURL()转换为base64,第三方的canvas库也提供相同的api。可以将blob url转换为base64吗?那么如何将File,Blob对象转换为base64格式?

可以将blob url转换为base64吗?

不可以。

如何将File,Blob对象转换为base64格式?

用到一个神奇的对象:Reader对象 用到一个神奇的方法:readAsDataURL(blob) 当文件完成读取并成功转换后,会触发loadend事件,同时会在Reader实例的result属性中存储base64格式的数据。 若是比较抽象,可以看下面的示例:

transferBlobFileToBase64(file){
    const reader = new FileReader();
    reader.readAsDataURL(file);
    reader.onloadend = async function() {
        const fileBase64 = reader.result;
        // 下面的接口是一个接入了七牛sdk的接口,通过base64str接收base64格式数据,返回一个hash
        const { hash } = await uploadQiNiu({ base64Str: fileBase64 });
        // 根据环境对hash进行拼接,生成一个可访问的url并且存储到持久层(mysql, elastic search, tablestore, whatever)
    };
}

若需要上传以后传递到其他方法进行数据组装,可以采用下面的方法:

async transferBlobFileToBase64(file) {
  return new Promise((resolve) => {
    const reader = new FileReader();
    reader.readAsDataURL(file);
    reader.onloadend = async function() {
      const fileBase64 = reader.result;
      const { hash } = await uploadQiNiu({ base64Str: fileBase64 });
      resolve(`${pageConfig.QINIU_IMG_URL}/${hash}`);
    };
  });
},
async serverAPIRevoke(file) {
  const uploadedFile = await this.transferBlobFileToBase64(file);
  // 在这里进行数据的组装并且调用服务端接口持久化存储
}

FrankKai avatar Jul 10 '19 06:07 FrankKai

如何创建一个可用来测试的Blob对象

var newBlob = new Blob(array, options);
// array是个数组:数组元素可以是ArrayBuffer,ArrayBufferView, Blob, USVString 

普通的 ‘foo’也可以作为生成Blob对象。

var blob = new Blob(['foo'],{type:'text/plain'})
blob.text().then((data)=>{console.log(data)})// 'foo'

这样就生成一个最简易的blob对象了,可以用来测试一些api。

FrankKai avatar Sep 23 '19 03:09 FrankKai

不使用canvas的情况下将image的url转换为blob url

fetch api

fetch(url).then(response => response.blob()).then((blob)=>{
    const urlCreator = window.URL || window.webkitURL;
    const imageUrl = urlCreator.createObjectURL( blob );
    const img = new Image();
    img.crossOrigin = 'Anonymous'; // CORS
    img.src = imageUrl;
})

XHR

// Simulate a call to Dropbox or other service that can
// return an image as an ArrayBuffer.
const xhr = new XMLHttpRequest();

// Use JSFiddle logo as a sample image to avoid complicating
// this example with cross-domain issues.
xhr.open( "GET", url, true );

// Ask for the result as an ArrayBuffer.
xhr.responseType = "arraybuffer";

xhr.onload = function( e ) {
    // Obtain a blob: URL for the image data.
    const arrayBufferView = new Uint8Array( this.response );
    const blob = new Blob( [ arrayBufferView ], { type: "image/jpeg" } );
    const urlCreator = window.URL || window.webkitURL;
    const imageUrl = urlCreator.createObjectURL( blob );
    const img = new Image();
    img.crossOrigin = 'Anonymous'; // CORS
    img.src = imageUrl;
};

xhr.send();

问题源于此帖:https://www.v2ex.com/t/648393#reply3 XHR思路源于:https://jsfiddle.net/Jan_Miksovsky/yy7Zs 可测试图片:https://img.thedailybeast.com/image/upload/c_crop,d_placeholder_euli9k,h_1439,w_2560,x_0,y_0/dpr_1.5/c_limit,w_1044/fl_lossy,q_auto/v1492791705/articles/2013/01/17/why-the-lakers-kobe-bryant-and-wife-vanessa-are-staying-together/130114-Samuels-Kobe-divorce-01_pfkz0r

FrankKai avatar Feb 28 '20 09:02 FrankKai