在 IndexedDB 中存储图像和文件

前几天我们写了关于如何 在 localStorage 中保存图像和文件 的文章,它讲述的是在今天我们拥有的可用资源中务实的做法。然而,localStorage 存在一些性能问题 - 我们将在本博客的后续文章中进行介绍 - 而我们所期望的未来方法是利用 IndexedDB。在这里,我将带您逐步了解如何在 IndexedDB 中存储图像和文件,然后通过 ObjectURL 来展示它们。

总体方法

首先,让我们讨论一下我们将要创建 IndexedDB 数据库、将文件保存到其中,然后将其读出并在页面中展示所要经历的步骤。

  1. 创建或打开一个数据库。
  2. 创建一个 objectStore(如果它还不存在)。
  3. 将图像文件检索为 Blob。
  4. 启动一个数据库事务。
  5. 将该 Blob 保存到数据库中。
  6. 读出该保存的文件,并从中创建一个 ObjectURL,并将其设置为页面中图像元素的 src 属性。

创建代码

让我们分解一下完成此操作所需的所有代码部分。

创建或打开一个数据库。


// IndexedDB
window.indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.OIndexedDB || window.msIndexedDB,
    IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.OIDBTransaction || window.msIDBTransaction,
    dbVersion = 1;

/*
    Note: The recommended way to do this is assigning it to window.indexedDB,
    to avoid potential issues in the global scope when web browsers start
    removing prefixes in their implementations.

    You can assign it to a varible, like var indexedDB… but then you have
    to make sure that the code is contained within a function.
*/


// Create/open database
var request = indexedDB.open("elephantFiles", dbVersion);

request.onsuccess = function (event) {
console.log(“成功创建/访问 IndexedDB 数据库”);
db = request.result;

db.onerror = function (event) {
console.log(“创建/访问 IndexedDB 数据库出错”);
};

// Google Chrome 的临时解决方案,用于创建 objectStore。将在未来被弃用。
if (db.setVersion) {
if (db.version != dbVersion) {
var setVersion = db.setVersion(dbVersion);
setVersion.onsuccess = function () {
createObjectStore(db);
getImageFile();
};
}
else {
getImageFile();
}
}
else {
getImageFile();
}
}

// 供将来使用。目前仅在最新的 Firefox 版本中可用。
request.onupgradeneeded = function (event) {
createObjectStore(event.target.result);
};

使用此方法的目的是,当数据库创建或版本号升高时触发 onupgradeneeded 事件。此方法目前仅在 Firefox 中受支持,但很快就会在其他 Web 浏览器中可用。如果 Web 浏览器不支持此事件,可以使用已弃用的 setVersion 方法并连接到它的 onsuccess 事件。

创建一个 objectStore(如果它还不存在)。


// Create an objectStore
console.log("Creating objectStore")
dataBase.createObjectStore("elephants");

在这里,您将创建一个 ObjectStore,用于存储您的数据 - 在我们的例子中是文件 - 并且创建后您无需再次创建它,只需更新其内容即可。

将图像文件检索为 Blob。


// Create XHR and BlobBuilder
var xhr = new XMLHttpRequest(),
    blob;

xhr.open("GET", "elephant.png", true);
// Set the responseType to blob
xhr.responseType = "blob";

xhr.addEventListener("load", function () {
    if (xhr.status === 200) {
        console.log("Image retrieved");

        // File as response
        blob = xhr.response;

        // Put the received blob into IndexedDB
        putElephantInDb(blob);
    }
}, false);
// Send XHR
xhr.send();

此代码直接将文件的全部内容作为 Blob 获取。目前,此方法仅在 Firefox 中受支持。
收到整个文件后,将 Blob 发送到该函数,以便将其存储到数据库中。

启动一个数据库事务。


// Open a transaction to the database
var transaction = db.transaction(["elephants"], IDBTransaction.READ_WRITE);

要开始写入数据库,您需要使用 objectStore 名称和要执行的操作类型启动一个事务 - 在这种情况下是读和写。

将该 Blob 保存到数据库中。


// Put the blob into the dabase
transaction.objectStore("elephants").put(blob, "image");

事务启动后,您可以获取所需的 objectStore 的引用,然后将 Blob 放入其中并为其指定一个键。

读出该保存的文件,并从中创建一个 ObjectURL,并将其设置为页面中图像元素的 src 属性。


// Retrieve the file that was just stored
transaction.objectStore("elephants").get("image").onsuccess = function (event) {
    var imgFile = event.target.result;
    console.log("Got elephant!" + imgFile);

    // Get window.URL object
    var URL = window.URL || window.webkitURL;

    // Create and revoke ObjectURL
    var imgURL = URL.createObjectURL(imgFile);

    // Set img src to ObjectURL
    var imgElephant = document.getElementById("elephant");
    imgElephant.setAttribute("src", imgURL);

    // Revoking ObjectURL
    URL.revokeObjectURL(imgURL);
};

使用相同的交易获取您刚存储的图像文件,然后创建一个 ObjectURL 并将其设置为页面中图像的 src 属性。
同样,也可以将此操作应用于 JavaScript 文件,将其附加到 script 元素,然后对其进行解析。

完整代码

以下是完整的可运行代码。


(function () {
    // IndexedDB
    var indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.OIndexedDB || window.msIndexedDB,
        IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.OIDBTransaction || window.msIDBTransaction,
        dbVersion = 1.0;

    // Create/open database
    var request = indexedDB.open("elephantFiles", dbVersion),
        db,
        createObjectStore = function (dataBase) {
            // Create an objectStore
            console.log("Creating objectStore")
            dataBase.createObjectStore("elephants");
        },

        getImageFile = function () {
            // Create XHR and BlobBuilder
            var xhr = new XMLHttpRequest(),
                blob;

            xhr.open("GET", "elephant.png", true);
            // Set the responseType to blob
            xhr.responseType = "blob";

            xhr.addEventListener("load", function () {
                if (xhr.status === 200) {
                    console.log("Image retrieved");

                    // Blob as response
                    blob = xhr.response;

                    // Put the received blob into IndexedDB
                    putElephantInDb(blob);
                }
            }, false);
            // Send XHR
            xhr.send();
        },

        putElephantInDb = function (blob) {
            console.log("Putting elephants in IndexedDB");

            // Open a transaction to the database
            var transaction = db.transaction(["elephants"], IDBTransaction.READ_WRITE);

            // Put the blob into the dabase
            transaction.objectStore("elephants").put(blob, "image");

            // Retrieve the file that was just stored
            transaction.objectStore("elephants").get("image").onsuccess = function (event) {
                var imgFile = event.target.result;
                console.log("Got elephant!" + imgFile);

                // Get window.URL object
                var URL = window.URL || window.webkitURL;

                // Create and revoke ObjectURL
                var imgURL = URL.createObjectURL(imgFile);

                // Set img src to ObjectURL
                var imgElephant = document.getElementById("elephant");
                imgElephant.setAttribute("src", imgURL);

                // Revoking ObjectURL
                URL.revokeObjectURL(imgURL);
            };
        };

    request.onerror = function (event) {
        console.log("Error creating/accessing IndexedDB database");
    };

    request.onsuccess = function (event) {
        console.log("Success creating/accessing IndexedDB database");
        db = request.result;

        db.onerror = function (event) {
            console.log("Error creating/accessing IndexedDB database");
        };

        // Interim solution for Google Chrome to create an objectStore. Will be deprecated
        if (db.setVersion) {
            if (db.version != dbVersion) {
                var setVersion = db.setVersion(dbVersion);
                setVersion.onsuccess = function () {
                    createObjectStore(db);
                    getImageFile();
                };
            }
            else {
                getImageFile();
            }
        }
        else {
            getImageFile();
        }
    }

    // For future use. Currently only in latest Firefox versions
    request.onupgradeneeded = function (event) {
        createObjectStore(event.target.result);
    };
})();

Web 浏览器支持

IndexedDB
从很久以前开始就一直受 Firefox 和 Google Chrome 支持(多个版本之前)。计划在 IE10 和 Opera 的未来版本中支持。Safari 的情况尚不清楚。
onupgradeneeded
在最新的 Firefox 中受支持。计划很快在 Google Chrome 中支持,并且有望在 IE10 和 Opera 的未来版本中支持。Safari 的情况尚不清楚。
在 IndexedDB 中存储文件
在 Firefox 11 及更高版本中受支持。计划在 Google Chrome 中支持。希望 IE10 也能支持它。Safari 和 Opera 的情况尚不清楚。
XMLHttpRequest Level 2
Firefox 和 Google Chrome 从很久以前开始就一直支持,Safari 5+ 也支持,计划在 IE10 和 Opera 12 中支持。
responseType “blob”
目前仅在 Firefox 中受支持。很快就会在 Google Chrome 中支持,并计划在 IE10 和 Opera 12 中支持。Safari 的情况尚不清楚。

演示和代码

我整理了一个 使用 IndexedDB 在其中保存图像和文件的演示,您可以亲眼看到它的运行情况。请确保使用任何开发者工具来检查图像的元素,查看其 src 属性的值。还要确保检查 console.log 消息以跟踪操作。

在 IndexedDB 中存储文件的代码 也在 GitHub 上提供,所以赶快去玩吧!

关于 Robert Nyman [荣誉编辑]

Mozilla Hacks 的技术布道师和编辑。就 HTML5、JavaScript 和开放式 Web 发表演讲和撰写博客。Robert 是 HTML5 和开放式 Web 的坚定支持者,自 1999 年以来一直从事 Web 前端开发工作 - 在瑞典和纽约市。他定期在 http://robertnyman.com 上发布博客,并且喜欢旅行和结识新朋友。

更多由 Robert Nyman [荣誉编辑] 撰写的文章…


30 条评论

  1. Florian

    您好,

    您确定您的演示在 Firefox 上有效吗?因为在我这边,它抛出了一个异常:“无法克隆对象”。

    IE 10 中也会抛出此异常。

    当您尝试将“复杂”对象存储到数据库中时,会抛出此异常。

    我很好奇,因为我在开发的应用程序中遇到了同样的问题,而我找到的唯一解决办法是将图像以 base64 字符串的形式存储到 IndexedDB 中。

    Florian

    2012 年 2 月 24 日 下午 2:25

  2. Robert Nyman

    感谢您的提问!
    抱歉,我应该对此更加明确:在专门存储文件方面,它在 Firefox 11 及更高版本中有效 - 请尝试 Firefox Beta 或 Firefox Aurora 以及最新的 Google Chrome。

    我将在博客文章中更新此信息。

    我希望 IE10 在正式发布之前能支持存储文件。

    2012 年 2 月 24 日 下午 2:47

  3. Andrea Giammarchi

    现在我们谈谈 Robert,好东西!

    2012 年 2 月 26 日 下午 1:54

    1. Robert Nyman

      谢谢。:-)

      2012 年 2 月 26 日 下午 2:01

  4. Andrea Doimo

    我看到了 Chrome (19.0.1049.3 dev-m) 中的这个问题。
    这意味着演示正常运行吗?在“Web 浏览器支持”部分中,您指出 Chrome 不支持其中列出的某些功能。

    2012 年 2 月 28 日 上午 8:20

    1. Robert Nyman

      感谢您的提问!
      似乎有一个情况是 Google Chrome 会临时存储 URL,这会让人误以为它有效(但实际上没有存储实际文件)。

      我已更改了博客文章和演示代码以使用 responseType blob,这与之相辅相成,您可以在 更新后的演示 中看到它的运行情况。

      有关 Google Chrome 状态的更多信息,请参见 相关错误

      2012 年 2 月 28 日 上午 8:51

  5. abral

    我已经开发了两个演示,它们将文件保存到 12 月的开发者竞技比赛中的 IndexedDB 中,分别是 eLibri 和 FileSystemDB。

    2012 年 3 月 6 日 下午 3:46

    1. Robert Nyman

      感谢您的提示!

      2012 年 3 月 7 日 上午 1:28

      1. Ruben

        您好 Abral,我看到了您的应用程序 eLibri,太棒了!我试图使用 MS SQL 服务器和 ASP 做类似的事情,一直到从服务器获取 JSON 文件并将其放入 IndexedDB 数据库,一切都运行良好。您能解释一下您是如何做到的吗?也许能让我们看一下提供 JSON 文件的服务器文件?(我知道它是用 PHP 编写的,但这将很有帮助)提前感谢。

        2012 年 8 月 24 日 上午 8:46

  6. Odin Hørthe Omdal

    IDBTransaction.READ_WRITE 现在称为“readwrite”。

    我可以告诉您一些关于 Opera 的情况。
    IndexedDB:我认为它不会出现在 12 版本中,但我们正在努力开发它。
    XHR2 blob(和 arraybuffer)将在 Opera 12 中提供。
    onupgradeneeded:当然,我们正在实现最新的规范。

    2012 年 3 月 14 日 上午 7:24

    1. Robert Nyman

      谢谢!
      我已经更新了帖子,其中包含有关 Opera 的信息。

      2012 年 3 月 14 日 上午 7:31

  7. Víctor Quiroz

    很棒的帖子!这是我第一个真正有效的帖子。我只想知道如何使用循环插入多行?

    2012 年 4 月 24 日 上午 10:49

    1. Víctor Quiroz

      不用了,它像这样完美地运行。

      putElephantInDb = function (json) {
      console.log(“将大象放入 IndexedDB 中”);

      // 打开一个到数据库的事务

      $.each(json[0], function(i, w){
      var transaction = db.transaction([“elephant”], IDBTransaction.READ_WRITE);

      // 将 Blob 放入数据库中
      var put = transaction.objectStore(“elephant”).put(w, “json”);

      // 检索刚刚存储的文件
      transaction.objectStore(“elephant”).get(w.id, “json”).onsuccess = function (event) {
      var mijson = event.target.result;
      console.log(mijson);
      };
      });
      };

      2012 年 4 月 24 日 上午 10:59

      1. Robert Nyman

        很高兴您找到了解决方案!

        2012 年 4 月 24 日 下午 3:05

    2. Robert Nyman

      谢谢,很高兴您喜欢它!

      2012 年 4 月 24 日 下午 3:02

  8. 匿名

    有没有办法将大量内容(总计约 1 GB)下载到数据库中,而数据库需要始终处于一致状态(例如图表)?
    – 由于文件大小,解析整个数据块是不可能的。
    – 使用单个事务处理所有数据块也不可行,因为在下载过程中,我们需要返回到事件循环,因此事务会变为非活动状态。
    – 在 *相同* 数据库中使用临时对象存储是可行的,但会使下载期间所需的空间翻倍。

    2012 年 5 月 20 日 下午 2:40

    1. Robert Nyman

      好问题!目前,我还没有发现使用 IndexedDB 以最佳方式进行操作的方法。正如你所说,临时对象存储可能有效,但并非理想解决方案。

      如果你想出什么巧妙的方法,请告诉我们!

      2012 年 5 月 22 日 上午 2:13

  9. 米切尔

    你好,罗伯特:
    感谢你的演示。

    你知道为什么当我从我的 Web 服务器加载图像时会收到安全错误吗?

    安全错误:http://wheredidmybraingo.com/image.html 上的内容可能无法从 blob:ebac76f2-366c-c34d-af7c-fb1312f82999 加载数据。

    谢谢,米切尔

    2012 年 7 月 20 日 上午 0:51

  10. 米切尔

    我修复了!

    我删除了 revokeObjectURL。

    希望它能出现在文档中 :(

    http://www.w3.org/TR/IndexedDB/

    再次感谢你的演示,米切尔

    2012 年 7 月 20 日 上午 2:41

    1. Robert Nyman

      有意思。为什么 revokeObjectURL 会为你抛出这个错误?

      2012 年 7 月 31 日 上午 9:15

  11. 山姆

    使用 IndexedDB 在客户端可以存储多少数据?5 MB、50 MB 还是没有限制?

    2012 年 8 月 27 日 下午 11:27

    1. Robert Nyman

      这取决于网络浏览器,但通常至少为 50 MB。

      对于 Firefox,默认值为 50 MB,你可以申请更多存储空间。更多信息请参见 Firefox 中“indexDB”的存储限制是多少?

      2012 年 8 月 28 日 上午 1:49

  12. Ruben

    嗨,罗伯特:
    感谢你分享这个示例,我正在尝试使其正常工作,但我尝试从 Web 服务中检索 JSON 文件,并将其放入 IndexedDB objectStore 中,而不是图像。
    它看起来像这样:[{“field1″:”value1”},{“field2″:”value2”},{“field3″:”value3”},{“field4″:”value4”}]
    但由于我是 JavaScript 新手,这真是噩梦…
    你认为你可以发布一个关于如何执行此操作的示例吗?
    提前感谢。
    鲁本。

    2012 年 9 月 25 日 上午 3:37

    1. Robert Nyman

      我无法详细说明,但如果你已从服务器接收 JSON,则可以使用 网络浏览器中的原生 JSON 支持,然后使用上面代码中的 put 方法将其保存到 IndexedDB 中。

      2012 年 9 月 25 日 上午 7:39

      1. Ruben

        嗨!感谢罗伯特,我已经成功了,我只有在将记录放入 objectStore 时遇到问题,我正在执行以下操作

        var result = eval( ‘(‘ + xhr.responseText + ‘)’ );
        for(i=0; i<result.length; i++){
        var transaction = db.transaction(["elephants"], IDBTransaction.READ_WRITE);
        var put = transaction.objectStore("elephants").put(result[i], "image");

        但它实际上是在 objectStore 中放置了类似这样的内容:[object Object],[object Object],[object Object],[object Object]…
        我不确定是否需要使用 eval() 或者甚至循环,你能帮我一下吗?

        2012 年 9 月 25 日 下午 12:10

        1. Robert Nyman

          请勿使用 eval,请使用我之前评论中链接的原生 JSON。

          然后,在 put 中,第一个值应该是值,第二个值不应该是“image”,而应该是你的键的名称(除非你希望将所有内容存储在同一个键中)。

          2012 年 9 月 26 日 上午 2:00

          1. Ruben

            我刚弄清楚,我已经有一个对象,不需要对其进行任何操作,只需循环遍历并将其放入 objectStore 中即可。
            再次感谢罗伯特,以及你为这段代码付出的努力。
            干杯。

            2012 年 9 月 26 日 上午 11:45

          2. Robert Nyman

            太好了!祝你好运,谢谢!

            2012 年 9 月 26 日 下午 12:50

  13. 汤姆

    这在我的 FF(19.02) 中运行良好,但在 Chrome (25.0.1364.172 m) 或 IE (10.09.200…) 中不行。示例中的图像 src 预先设置为“elephant.png”,因此会给人一种它在这些浏览器中也能正常工作的错误印象,但我在这一行收到了错误:Uncaught Error: “DataCloneError: DOM IDBDatabase Exception 25”:var put = transaction.objectStore(“elephants”).put(blob, “image”);

    这是因为这些浏览器不支持 Blob 吗?

    谢谢
    汤姆

    2013 年 3 月 17 日 下午 2:19

    1. 罗伯特·尼曼 [编辑]

      是的,我认为这可能是由于 Blob 支持作为 responseType 所致。不过,img 元素的 src 应该更改为 Blob 引用,但我知道你的意思。

      2013 年 3 月 18 日 上午 2:53

这篇文章的评论已关闭。