FrankKai / FrankKai.github.io

FE blog
https://frankkai.github.io/
363 stars 39 forks source link

blob URL那些事儿 #138

Open FrankKai opened 5 years ago

FrankKai commented 5 years ago

先看一下blob URL长什么样:

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

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

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

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

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

FrankKai commented 5 years ago

Blob对象知识点

FrankKai commented 5 years ago

关键API window.URL.createObjectURL()

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

疑问

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...
},

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

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

FrankKai commented 5 years ago

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 commented 4 years ago

如何创建一个可用来测试的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 commented 4 years ago

不使用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