在 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 元素,然后它会解析 JavaScript 代码。

完整的代码

这是完整的可运行代码。


(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 和开放网络发表演讲和博客文章。Robert 坚信 HTML5 和开放网络,自 1999 年以来一直在从事网页前端开发工作,曾在瑞典和纽约市工作。他也会定期在 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

    我已经开发了两个演示,它们将文件保存到 IndexedDB 中,用于 12 月的开发者比赛,分别是 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)下载到数据库中,而数据库需要始终保持一致状态(例如图形)?
    – 由于大小问题,解析整个 Blob 是不可能的。
    – 对所有块使用单个事务也不可能,因为在下载过程中,我们需要返回到事件循环,因此事务会变为非活动状态。
    – 在*相同*数据库中使用临时对象存储是可能的,但这会在下载期间使所需空间增加一倍。

    2012 年 5 月 20 日 下午 2:40

    1. Robert Nyman

      好问题!目前,我不知道使用 IndexedDB 以任何最佳方式做到这一点。就像你说的,临时对象存储可能有效,但这不是理想的解决方案。

      如果你想出了什么聪明的方法,请告诉我们!

      2012 年 5 月 22 日 上午 2:13

  9. Mitchell

    你好,Robert,
    感谢你的演示。

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

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

    谢谢,Mitchell

    2012 年 7 月 20 日 上午 0:51

  10. Mitchell

    我修复了它 :)

    我删除了 revokeObjectURL。

    希望它能在文档中 :(

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

    再次感谢你的演示,Mitchell

    2012 年 7 月 20 日 上午 2:41

    1. Robert Nyman

      有趣。为什么 revokeObjectURL 会对你抛出那个错误?

      2012 年 7 月 31 日 上午 9:15

  11. Sam

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

    2012 年 8 月 27 日 下午 11:27

    1. Robert Nyman

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

      对于 Firefox,默认情况下为 50 MB,你可以申请更多。有关更多信息,请访问 Firefox 中“indexDB”的存储限制是什么?

      2012 年 8 月 28 日 上午 1:49

  12. Ruben

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

    2012 年 9 月 25 日 上午 3:37

    1. Robert Nyman

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

      2012 年 9 月 25 日 上午 7:39

      1. Ruben

        嗨!感谢 Robert,我成功了,我只在将记录放入 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");

        但实际上它正在将类似 [object Object],[object Object],[object Object],[object Object]" 之类的东西放入 objectStore 中……
        我不确定是否需要使用 eval() 或者甚至循环,你能帮帮我吗?

        2012 年 9 月 25 日 下午 12:10

        1. Robert Nyman

          请不要使用 eval,请使用我上面评论中链接的原生 JSON。

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

          2012 年 9 月 26 日 上午 2:00

          1. Ruben

            我刚刚弄明白了,我已经有一个对象,不需要对其进行任何操作,只需遍历并将其放入 objectStore 中即可。
            再次感谢 Robert,并且感谢你编写了这段代码。
            干杯。

            2012 年 9 月 26 日 上午 11:45

          2. Robert Nyman

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

            2012 年 9 月 26 日 下午 12:50

  13. Tom

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

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

    谢谢
    Tom

    2013 年 3 月 17 日 下午 2:19

    1. Robert Nyman [编辑]

      是的,我认为这可能是由于 responseType 的 Blob 支持。虽然 img 元素的 src 应该更改为 Blob 引用,但我明白你的意思。

      2013 年 3 月 18 日 上午 2:53

本文的评论已关闭。