使用 Blob 和 msSaveBlob 以本地方式保存文件
本主题从使用 Web 存储以本地方式保存文件停止的位置开始,演示 如何使用 Blob 构造函数以及 window.navigator.msSaveBlob
和window.navigator.msSaveOrOpenBlob
方法以本地方式保存文件(任意大小)。
本主题包含下列部分:
使用 BlobBuilder API 创建 Blob
使用 Blob 构造函数可以直接在客户端上创建和操作 Blob(通常等效于一个文件)。Internet Explorer 10 的 msSaveBlob 和 msSaveOrOpenBlob 方法允许用户在客户端上保存文件,方法如同从 Internet 下载文件,这是此类文件保存到“下载”文件夹的原因。
请注意,msSaveBlob
和 msSaveOrOpenBlob
之间的区别就在于前者仅为用户提供“保存”按钮,而后者不但提供“保存”按钮,还提供“打开”按钮。
为了帮助了解 Blob 构造函数和 msSaveBlob
/msSaveOrOpenBlob
如何用于在客户端上保存更改的文件,请考虑以下示例:
<!DOCTYPE html> <html> <head> <meta content="text/html; charset=utf-8" http-equiv="Content-Type"> <title>Example 1</title> </head> <body> <h1>Example 1</h1> <script> var blobObject = new Blob(["I scream. You scream. We all scream for ice cream."]); window.navigator.msSaveBlob(blobObject, 'msSaveBlob_testFile.txt'); // The user only has the option of clicking the Save button. alert('File save request made using msSaveBlob() - note the single "Save" button below.'); var fileData = ["Before you insult a person, walk a mile in their shoes. That way, when you insult them, you'll be a mile away - and have their shoes."]; blobObject = new Blob(fileData); window.navigator.msSaveOrOpenBlob(blobObject, 'msSaveBlobOrOpenBlob_testFile.txt'); // Now the user will have the option of clicking the Save button and the Open button. alert('File save request made using msSaveOrOpenBlob() - note the two "Open" and "Save" buttons below.'); </script> </body> </html>
使用 Blob()
构造函数,我们首先创建一个 Blob 对象,其参数是包含所需文件内容的数组:
var blobObject = new Blob(["I scream. You scream. We all scream for ice cream."]);
我们接下来从 blobObject
复制该内容,并将其保存到一个文本文件(该文件保存在下载文件夹中):
window.navigator.msSaveBlob(blobObject, 'msSaveBlob_testFile.txt');
msSaveBlob_testFile.txt
保存到 Downloads 文件夹。
然后,使用 msSaveOrOpenBlob
方法重复该过程,该方法为用户提供了“保存”和“打开”选项。
下例添加 Blob 功能检测:
<!DOCTYPE html> <html> <head> <meta content="text/html; charset=utf-8" http-equiv="Content-Type"> <title>Example 2</title> </head> <body> <h1>Example 2</h1> <script> function requiredFeaturesSupported() { return ( BlobConstructor() && msSaveOrOpenBlobSupported() ); } function BlobConstructor() { if (!window.Blob) { document.getElementsByTagName('body')[0].innerHTML = "<h1>The Blob constructor is not supported - upgrade your browser and try again.</h1>"; return false; } // if return true; } // BlobConstructor function msSaveOrOpenBlobSupported() { if (!window.navigator.msSaveOrOpenBlob) { // If msSaveOrOpenBlob() is supported, then so is msSaveBlob(). document.getElementsByTagName('body')[0].innerHTML = "<h1>The msSaveOrOpenBlob API is not supported - try upgrading your version of IE to the latest version.</h1>"; return false; } // if return true; } // msSaveOrOpenBlobSupported if (requiredFeaturesSupported()) { var blobObject = new Blob(["I scream. You scream. We all scream for ice cream."]); window.navigator.msSaveBlob(blobObject, 'msSaveBlob_testFile.txt'); alert('File save request made using msSaveBlob() - note the single "Save" button below.'); var fileData = ["Before you insult a person, walk a mile in their shoes. That way, when you insult them, you'll be a mile away - and have their shoes."]; blobObject = new Blob(fileData); window.navigator.msSaveOrOpenBlob(blobObject, 'msSaveBlobOrOpenBlob_testFile.txt'); alert('File save request made using msSaveOrOpenBlob() - note the two "Open" and "Save" buttons below.'); } </script> </body> </html>
将文件保存到客户端后,下一步是从保存的文件中检索数据。以下示例基于读取本地文件和使用 Web 存储以本地方式保存文件,允许创建一个基于 canvas 的绘图,将其保存到本地文件并显示此类保存的绘图。此示例通常按照如下方式使用:
- 使用鼠标或手指(仅触摸设备)在框内创建一个绘图。
- 单击“保存”按钮,然后在“信息栏”中单击得到的“保存”按钮。
- 通过单击 x 关闭第二个“信息栏”。
- 单击“擦除”按钮。
- 单击“加载”按钮,然后单击得到的“浏览”按钮并选择以前保存的绘图文件。随即将显示保存的绘图。
请注意,绘图保存后,跳过第 4 步可允许用户合成多个绘图。
<!DOCTYPE html> <html> <head> <meta content="text/html; charset=utf-8" http-equiv="Content-Type"> <title>Example 3</title> <style> html { -ms-touch-action: none; /* Capture all touch events for our own purposes. */ text-align: center; } #hideWrapper { display: none; /* Do not show the file picker dialog until we're ready to do so. */ } </style> </head> <body> <h1>Example 3</h1> <canvas id="drawingSurface" width="500" height="500" style="border:1px solid black;"> </canvas> <!-- The canvas element can only be manipulated via JavaScript --> <div> <button id="erase">Erase</button> <button id="save">Save</button> <button id="load">Load</button> </div> <div id="hideWrapper"> <p>Select one of your saved canvas drawings to display:</p> <input type="file" id="fileSelector" /> <!-- By design, if you select the exact same files two or more times, the 'change' event will not fire. --> </div> <script> function requiredFeaturesSupported() { return ( BlobConstructorSupported() && msSaveOrOpenBlobSupported() && canvasSupported() && fileApiSupported() ); } // requiredFeaturesSupported function BlobConstructorSupported() { if (!window.Blob) { document.getElementsByTagName('body')[0].innerHTML = "<h1>The Blob constructor is not supported - upgrade your browser and try again.</h1>"; return false; } // if return true; } // BlobConstructorSupported function msSaveOrOpenBlobSupported() { if (!window.navigator.msSaveOrOpenBlob) { // If msSaveOrOpenBlob() is supported, then so is msSaveBlob(). document.getElementsByTagName('body')[0].innerHTML = "<h1>The msSaveOrOpenBlob API is not supported - upgrade Internet Explorer and try again.</h1>"; return false; } return true; } // msSaveOrOpenBlobSupported function canvasSupported() { if (!document.createElement('canvas').getContext) { document.getElementsByTagName('body')[0].innerHTML = "<h1>Canvas is not supported - upgrade your browser and try again.</h1>"; return false; } return true; } // canvasSupported function fileApiSupported() { if (document.getElementById('fileSelector').files && window.FileReader) { return true; } else { document.getElementsByTagName('body')[0].innerHTML = "<h1>The File API is not sufficiently supported - upgrade your browser and try again.</h1>"; return false; } } // fileSelectorSupported if ( requiredFeaturesSupported() ) { var canvas = document.getElementById('drawingSurface'); // A static variable, due to the fact that one or more local functions access it. var context = canvas.getContext('2d'); // A static variable, due to the fact that one or more local functions access it. context.fillStyle = "purple"; // Because purple is cool. if (window.navigator.msPointerEnabled) { canvas.addEventListener('MSPointerMove', paintCanvas, false); } else { canvas.addEventListener('mousemove', paintCanvas, false); } document.getElementById('erase').addEventListener('click', eraseCanvas, false); document.getElementById('save').addEventListener('click', saveCanvas, false); document.getElementById('load').addEventListener('click', loadCanvas, false); document.getElementById('fileSelector').addEventListener('change', handleFileSelection, false); // Add an onchange event listener for the <input id="fileSelector"> element. } // if ( requiredFeaturesSupported() ) function paintCanvas(event) { // The "event" object contains the position of the pointer/mouse. context.fillRect(event.offsetX, event.offsetY, 4, 4); // Draw a 4x4 rectangle at the given coordinates (relative to the canvas box). As of this writing, not all browsers support offsetX and offsetY. } function saveCanvas() { var drawingFileName = "canvas" + Math.round( (new Date()).getTime() / 1000 ) + ".txt"; // Produces a unique file name every second. var blobObject = new Blob( [canvas.toDataURL()] ); // Create a blob object containing the user's drawing. window.navigator.msSaveBlob(blobObject, drawingFileName); // Copy the blob object content and save it to a file. document.getElementById('hideWrapper').style.display = 'none'; // Remove the file picker dialog from the screen if the Save button gets clicked. } // saveCanvas function eraseCanvas() { context.clearRect(0, 0, context.canvas.width, context.canvas.height); document.getElementById('hideWrapper').style.display = 'none'; // Remove the file picker dialog from the screen if the Erase button gets clicked. } // eraseCanvas function loadCanvas() { document.getElementById('hideWrapper').style.display = 'block'; // Unhide the file picker dialog so the user can select a saved canvas drawing to load into the canvas element. } // loadCanvas function handleFileSelection(evt) { var files = evt.target.files; // The file selected by the user (as a FileList object). if (!files) { alert("The selected file is invalid - do not select a folder. Please reselect and try again."); return; } // "files" is a FileList of file objects. Try to display the contents of the selected file: var file = files[0]; // The way the <input> element is set up, the user cannot select multiple files. if (!file) { alert("Unable to access " + file.name.toUpperCase() + "Please reselect and try again."); return; } if (file.size == 0) { alert("Unable to access " + file.name.toUpperCase() + " because it is empty. Please reselect and try again."); return; } if (!file.type.match('text/.*')) { alert("Unable to access " + file.name.toUpperCase() + " because it is not a known text file type. Please reselect and try again."); return; } // Assert: we have a valid file. startFileRead(file); // Asychronously fire off a file read request. document.getElementById('hideWrapper').style.display = 'none'; // Remove the file picker dialog from the screen since we have a valid file. } // handleFileSelection function startFileRead(fileObject) { var reader = new FileReader(); // // Set up asynchronous handlers for file-read-success, file-read-abort, and file-read-errors: reader.onloadend = displayDrawing; // "onloadend" fires when the file contents have been successfully loaded into memory. reader.abort = handleFileReadAbort; // "abort" files on abort. reader.onerror = handleFileReadError; // "onerror" fires if something goes awry. if (fileObject) { // Safety first. reader.readAsText(fileObject); // Asynchronously start a file read thread. Other supported read methods include readAsArrayBuffer() and readAsDataURL(). } else { alert("fileObject is null in startFileRead()."); } } // startFileRead function displayDrawing(evt) { var img = new Image(); // The canvas drawImage() method expects an image object. img.src = evt.target.result; // Obtain the file contents, which was read into memory (whose format is a text data URL string). // eraseCanvas(); To allow composite drawings, remove this comment. img.onload = function() { // Only render the saved drawing when the image object has fully loaded the drawing into memory. context.drawImage(img, 0, 0); // Draw the image starting at canvas coordinate (0, 0) - the upper left-hand corner of the canvas. } // img.onload */ } // displayFileText function handleFileReadAbort(evt) { alert("File read aborted."); } // handleFileReadAbort function handleFileReadError(evt) { switch (evt.target.error.name) { case "NotFoundError": alert("The file could not be found at the time the read was processed."); break; case "SecurityError": alert("A file security error occured."); break; case "NotReadableError": alert("The file cannot be read. This can occur if the file is open in another application."); break; case "EncodingError": alert("The length of the data URL for the file is too long."); break; default: alert("File error code " + evt.target.error.name); } // switch } // handleFileReadError </script> </body> </html>
你可能尚未注意到,示例 3 中的一个特征是无法连续两次重新加载同一绘图文件。即,无法执行以下过程:
- 绘图显示时,单击“擦除”。
- 使用鼠标或手指(仅触摸设备)在框内创建一个绘图。
- 单击“保存”按钮,然后在“信息栏”中单击得到的“保存”按钮。
- 通过单击 x 关闭第二个“信息栏”。
- 单击“擦除”按钮。
- 单击“加载”按钮,然后单击出现的“浏览”按钮并选择在第 3 步中创建的绘图文件。随即将显示保存的绘图。
- 单击“擦除”按钮。
- 单击“加载”按钮,然后单击得到的“浏览”按钮并选择在第 6 步中选择的同一绘图文件。此时不会显示该绘图。
此行为是故意如此设计的,你可以轻松解决此问题。第一步是将 <input type="file" id="fileSelector" />
元素放到 <form>
元素中,如下所示:
<div id="hideWrapper"> <p>Select one of your saved canvas drawings to display:</p> <form> <input type="file" id="fileSelector" /> </form> </div> <script>
下一步是通过单击“加载”按钮时调用表单的 reset()
方法来清除表单子元素中之前的任何用户输入。
function loadCanvas() { document.querySelector('#hideWrapper > form').reset(); // Allow the input element to pick the same file consecutively more than once. document.getElementById('hideWrapper').style.display = 'block'; // Unhide the file picker dialog so the user can select a saved canvas drawing to load into the canvas element. } // loadCanvas
此处提供此更新的示例:示例 4 (右键单击网页并选择“查看源”以查看它的源代码)。
同样,示例 5 通过改进简单的绘图“应用”以及将 canvas.toDataURL()
替换为 canvas.msToBlob()
来扩展示例 4。使用 canvas.toDataURL()
,可以预防使用其他 应用程序轻松查看已保存绘图的可能性。将绘图保存为 PNG 文件的好处是允许很多标准应用程序(包括浏览器)显示绘图。通过切换到 canvas.msToBlob()
,我们可以直接将文件 保存为 PNG 格式,如下所示:
function saveCanvas() { var drawingFileName = "canvas" + Math.round( (new Date()).getTime() / 1000 ) + ".png"; // Produces a unique file name every second. window.navigator.msSaveBlob(globals.canvas.msToBlob(), drawingFileName); // Save the user's drawing to a file. document.getElementById('filePickerWrapper').style.display = 'none'; // Remove the file picker dialog from the screen since we just saved the user's file. } // saveCanvas
此外,将文件保存为 PNG 格式可允许我们将 startFileRead(file)
(及其三个关联的 回调函数)替换为以下四行代码:
img.src = window.URL.createObjectURL(file); img.onload = function() { globals.context.drawImage(img, 0, 0); window.URL.revokeObjectURL(this.src); }
此处提供完整的示例:示例 5(右键单击网页并 选择“查看源”以查看它的源代码)。
我们得出结论,使用 Windows Internet Explorer 的 msSaveBlob
和 msSaveOrOpenBlob
方法以及 Blob 构造函数,可以在客户端上保存修改的文件 - 这在 Internet Explorer 10 之前相对较难。