إنشاء كائن Blob من سلسلة base64 في جافاسكريبت
لدي بيانات ثنائية مشفرة بصيغة base64 في سلسلة نصية.
const contentType = 'image/png';
const b64Data = 'iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==';
أود إنشاء عنوان URL من نوع blob: يحتوي على هذه البيانات وعرضه للمستخدم.
const blob = new Blob(????, {type: contentType});
const blobUrl = URL.createObjectURL(blob);
window.location = blobUrl;
لم أتمكن من معرفة كيفية إنشاء Blob.
في بعض الحالات، يمكنني تجنب هذا باستخدام عنوان URL من نوع data: بدلاً من ذلك.
const dataUrl = `data:${contentType};base64,${b64Data}`;
window.location = dataUrl;
ومع ذلك، في معظم الحالات تكون عناوين الـ data: كبيرة بشكل يمنع استخدامها.
كيف يمكنني فك ترميز سلسلة base64 إلى كائن Blob في JavaScript؟
الإجابة رقم 1
ستقوم دالة atob بفك تشفير سلسلة مشفرة بـ base64 إلى سلسلة جديدة تحتوي على حرف لكل بايت من البيانات الثنائية.
const byteCharacters = atob(b64Data);
سيكون نقطة رمز كل حرف (charCode) هي قيمة البايت. يمكننا إنشاء مصفوفة من قيم البايت عن طريق تطبيق هذا باستخدام طريقة .charCodeAt لكل حرف في السلسلة.
const byteNumbers = new Array(byteCharacters.length);
for (let i = 0; i < byteCharacters.length; i++) {
byteNumbers[i] = byteCharacters.charCodeAt(i);
}
يمكنك تحويل هذا المصفوفة من قيم البايت إلى مصفوفة بايت من نوع حقيقي عن طريق تمريرها إلى منشئ Uint8Array.
const byteArray = new Uint8Array(byteNumbers);
يمكن تحويل هذا بدوره إلى Blob عن طريق وضعه داخل مصفوفة وتمريرها إلى منشئ Blob.
const blob = new Blob([byteArray], {type: contentType});
الكود أعلاه يعمل. ومع ذلك يمكن تحسين الأداء قليلاً عن طريق معالجة byteCharacters في شرائح أصغر، بدلاً من كل شيء مرة واحدة. في تجاربي المبدئية، يبدو أن حجم الشريحة 512 بايت هو حجم جيد. وهذا يعطينا الدالة التالية.
const b64toBlob = (b64Data, contentType='', sliceSize=512) => {
const byteCharacters = atob(b64Data);
const byteArrays = [];
for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
const slice = byteCharacters.slice(offset, offset + sliceSize);
const byteNumbers = new Array(slice.length);
for (let i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i);
}
const byteArray = new Uint8Array(byteNumbers);
byteArrays.push(byteArray);
}
const blob = new Blob(byteArrays, {type: contentType});
return blob;
}
const blob = b64toBlob(b64Data, contentType);
const blobUrl = URL.createObjectURL(blob);
window.location = blobUrl;
مثال كامل:
const b64toBlob = (b64Data, contentType='', sliceSize=512) => {
const byteCharacters = atob(b64Data);
const byteArrays = [];
for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
const slice = byteCharacters.slice(offset, offset + sliceSize);
const byteNumbers = new Array(slice.length);
for (let i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i);
}
const byteArray = new Uint8Array(byteNumbers);
byteArrays.push(byteArray);
}
const blob = new Blob(byteArrays, {type: contentType});
return blob;
}
const contentType = 'image/png';
const b64Data = 'iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==';
const blob = b64toBlob(b64Data, contentType);
const blobUrl = URL.createObjectURL(blob);
const img = document.createElement('img');
img.src = blobUrl;
document.body.appendChild(img);الإجابة رقم 2
إليك طريقة أكثر اختصارًا بدون أي تبعيات أو مكتبات.
تتطلب واجهة برمجة التطبيقات الجديدة fetch. (هل يمكنني استخدامها؟)
var url = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="
fetch(url)
.then(res => res.blob())
.then(console.log)بهذه الطريقة يمكنك أيضًا الحصول بسهولة على ReadableStream و ArrayBuffer والنص و JSON.
(للعلم، هذا يعمل أيضًا مع node-fetch في Node)
كدالة:
const b64toBlob = (base64, type = 'application/octet-stream') =>
fetch(`data:${type};base64,${base64}`).then(res => res.blob())
لكنني أشجعك على عدم استخدام base64 في المقام الأول. هناك طرق أفضل لإرسال واستقبال البيانات الثنائية. JSON ليست دائمًا الخيار الأفضل، فهي تستهلك المزيد من عرض النطاق الترددي وتضيع وقت المعالجة في (فك) الترميز. استخدم canvas.toBlob بدلاً من canvas.toDataURL واستخدم FormData لإرسال الملفات الثنائية. يمكنك أيضًا إعادة حمولة متعددة الأجزاء وفك ترميزها باستخدام await response.formData() القادمة من استجابة الخادم. يمكن استخدام FormData في كلا الاتجاهين.
لقد أجريت اختبار أداء بسيط على نسخة جيري المتزامنة من ES6.
النسخة المتزامنة ستقوم بحظر واجهة المستخدم لفترة من الوقت. إبقاء أدوات المطور مفتوحة يمكن أن يبطئ أداء الجلب
document.body.innerHTML += '<input autofocus placeholder="try writing">'
// get some dummy gradient image
var img=function(){var a=document.createElement("canvas"),b=a.getContext("2d"),c=b.createLinearGradient(0,0,1500,1500);a.width=a.height=3000;c.addColorStop(0,"red");c.addColorStop(1,"blue");b.fillStyle=c;b.fillRect(0,0,a.width,a.height);return a.toDataURL()}();
async function perf() {
const blob = await fetch(img).then(res => res.blob())
// turn it to a dataURI
const url = img
const b64Data = url.split(',')[1]
// Jeremy Banks solution
const b64toBlob = (b64Data, contentType = '', sliceSize=512) => {
const byteCharacters = atob(b64Data);
const byteArrays = [];
for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
const slice = byteCharacters.slice(offset, offset + sliceSize);
const byteNumbers = new Array(slice.length);
for (let i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i);
}
const byteArray = new Uint8Array(byteNumbers);
byteArrays.push(byteArray);
}
const blob = new Blob(byteArrays, {type: contentType});
return blob;
}
// bench blocking method
let i = 500
console.time('blocking b64')
while (i--) {
await b64toBlob(b64Data)
}
console.timeEnd('blocking b64')
// bench non blocking
i = 500
// so that the function is not reconstructed each time
const toBlob = res => res.blob()
console.time('fetch')
while (i--) {
await fetch(url).then(toBlob)
}
console.timeEnd('fetch')
console.log('done')
}
perf()الإجابة رقم 3
تنفيذ محسّن (ولكن أقل قابلية للقراءة):
function base64toBlob(base64Data, contentType) {
contentType = contentType || '';
var sliceSize = 1024;
var byteCharacters = atob(base64Data);
var bytesLength = byteCharacters.length;
var slicesCount = Math.ceil(bytesLength / sliceSize);
var byteArrays = new Array(slicesCount);
for (var sliceIndex = 0; sliceIndex < slicesCount; ++sliceIndex) {
var begin = sliceIndex * sliceSize;
var end = Math.min(begin + sliceSize, bytesLength);
var bytes = new Array(end - begin);
for (var offset = begin, i = 0; offset < end; ++i, ++offset) {
bytes[i] = byteCharacters[offset].charCodeAt(0);
}
byteArrays[sliceIndex] = new Uint8Array(bytes);
}
return new Blob(byteArrays, { type: contentType });
}
الإجابة رقم 4
فيما يلي كود TypeScript الخاص بي والذي يمكن تحويله بسهولة إلى JavaScript ويمكنك استخدامه
/**
* Convert BASE64 to BLOB
* @param base64Image Pass Base64 image data to convert into the BLOB
*/
private convertBase64ToBlob(base64Image: string) {
// Split into two parts
const parts = base64Image.split(';base64,');
// Hold the content type
const imageType = parts[0].split(':')[1];
// Decode Base64 string
const decodedData = window.atob(parts[1]);
// Create UNIT8ARRAY of size same as row data length
const uInt8Array = new Uint8Array(decodedData.length);
// Insert all character code into uInt8Array
for (let i = 0; i < decodedData.length; ++i) {
uInt8Array[i] = decodedData.charCodeAt(i);
}
// Return BLOB image after conversion
return new Blob([uInt8Array], { type: imageType });
}
الإجابة رقم 5
لكل عشاق النسخ واللصق مثلّي، إليكم دالة تنزيل جاهزة تعمل على كروم وفايرفوكس وإيدج:
window.saveFile = function (bytesBase64, mimeType, fileName) {
var fileUrl = "data:" + mimeType + ";base64," + bytesBase64;
fetch(fileUrl)
.then(response => response.blob())
.then(blob => {
var link = window.document.createElement("a");
link.href = window.URL.createObjectURL(blob, { type: mimeType });
link.download = fileName;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
});
}
الإجابة رقم 6
انظر هذا المثال: https://jsfiddle.net/pqhdce2L/
function b64toBlob(b64Data, contentType, sliceSize) {
contentType = contentType || '';
sliceSize = sliceSize || 512;
var byteCharacters = atob(b64Data);
var byteArrays = [];
for (var offset = 0; offset < byteCharacters.length; offset += sliceSize) {
var slice = byteCharacters.slice(offset, offset + sliceSize);
var byteNumbers = new Array(slice.length);
for (var i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i);
}
var byteArray = new Uint8Array(byteNumbers);
byteArrays.push(byteArray);
}
var blob = new Blob(byteArrays, {type: contentType});
return blob;
}
var contentType = 'image/png';
var b64Data = Your Base64 encode;
var blob = b64toBlob(b64Data, contentType);
var blobUrl = URL.createObjectURL(blob);
var img = document.createElement('img');
img.src = blobUrl;
document.body.appendChild(img);الإجابة رقم 7
لدعم جميع المتصفحات، وخاصة على أندرويد، ربما يمكنك إضافة هذا:
try{
blob = new Blob(byteArrays, {type : contentType});
}
catch(e){
// TypeError old Google Chrome and Firefox
window.BlobBuilder = window.BlobBuilder ||
window.WebKitBlobBuilder ||
window.MozBlobBuilder ||
window.MSBlobBuilder;
if(e.name == 'TypeError' && window.BlobBuilder){
var bb = new BlobBuilder();
bb.append(byteArrays);
blob = bb.getBlob(contentType);
}
else if(e.name == "InvalidStateError"){
// InvalidStateError (tested on FF13 WinXP)
blob = new Blob(byteArrays, {type : contentType});
}
else{
// We're screwed, blob constructor unsupported entirely
}
}
الإجابة رقم 8
إذا كنت تستطيع تحمّل إضافة تبعية واحدة إلى مشروعك، فهناك الحزمة الرائعة blob-util في npm التي توفر دالة مفيدة base64StringToBlob. بمجرد إضافتها إلى package.json يمكنك استخدامها بهذا الشكل:
import { base64StringToBlob } from 'blob-util';
const contentType = 'image/png';
const b64Data = 'iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==';
const blob = base64StringToBlob(b64Data, contentType);
// Do whatever you need with your blob...
الإجابة رقم 9
بالنسبة لبيانات الصورة، أجد أنه من الأبسط استخدام canvas.toBlob (غير متزامن)
function b64toBlob(b64, onsuccess, onerror) {
var img = new Image();
img.onerror = onerror;
img.onload = function onload() {
var canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
var ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
canvas.toBlob(onsuccess);
};
img.src = b64;
}
var base64Data = 'data:image/jpg;base64,/9j/4AAQSkZJRgABAQA...';
b64toBlob(base64Data,
function(blob) {
var url = window.URL.createObjectURL(blob);
// do something with url
}, function(error) {
// handle error
});
الإجابة رقم 10
أنا أنشر طريقة أكثر إعلانية لمزامنة تحويل Base64. بينما asynchronous fetch().blob() طريقة أنيقة جدًا وأنا أحب هذا الحل كثيرًا، إلا أنه لا يعمل على Internet Explorer 11 (وربما Edge - لم أختبر الأخير)، حتى مع استخدام polyfill - انظر إلى تعليقي على منشور Endless لمزيد من التفاصيل.
const blobPdfFromBase64String = base64String => {
const byteArray = Uint8Array.from(
atob(base64String)
.split('')
.map(char => char.charCodeAt(0))
);
return new Blob([byteArray], { type: 'application/pdf' });
};
مكافأة
إذا كنت تريد طباعته، يمكنك أن تفعل شيئًا مثل:
const isIE11 = !!(window.navigator && window.navigator.msSaveOrOpenBlob); // Or however you want to check it
const printPDF = blob => {
try {
isIE11
? window.navigator.msSaveOrOpenBlob(blob, 'documents.pdf')
: printJS(URL.createObjectURL(blob)); // http://printjs.crabbly.com/
} catch (e) {
throw PDFError;
}
};
مكافأة × 2 - فتح ملف BLOB في علامة تبويب جديدة لمتصفح إنترنت إكسبلورر 11
إذا كنت قادرًا على إجراء بعض المعالجة الأولية لسلسلة Base64 على الخادم، يمكنك عرضها تحت عنوان URL معين واستخدام الرابط في printJS :)
الإجابة رقم 11
لقد لاحظت أن Internet Explorer 11 يصبح بطيئًا للغاية عند تقسيم البيانات كما اقترح جيريمي. هذا صحيح بالنسبة إلى Chrome، لكن Internet Explorer يبدو أنه يواجه مشكلة عند تمرير البيانات المقسمة إلى منشئ Blob. على جهازي، تمرير 5 ميغابايت من البيانات يجعل Internet Explorer ينهار واستهلاك الذاكرة يتصاعد بشكل كبير. يقوم Chrome بإنشاء الـ blob في وقت قصير جدًا.
قم بتشغيل هذا الكود للمقارنة:
var byteArrays = [],
megaBytes = 2,
byteArray = new Uint8Array(megaBytes*1024*1024),
block,
blobSlowOnIE, blobFastOnIE,
i;
for (i = 0; i < (megaBytes*1024); i++) {
block = new Uint8Array(1024);
byteArrays.push(block);
}
//debugger;
console.profile("No Slices");
blobSlowOnIE = new Blob(byteArrays, { type: 'text/plain'});
console.profileEnd();
console.profile("Slices");
blobFastOnIE = new Blob([byteArray], { type: 'text/plain'});
console.profileEnd();
لذلك قررت تضمين الطريقتين اللتين وصفهما جيريمي في دالة واحدة. الفضل له في ذلك.
function base64toBlob(base64Data, contentType, sliceSize) {
var byteCharacters,
byteArray,
byteNumbers,
blobData,
blob;
contentType = contentType || '';
byteCharacters = atob(base64Data);
// Get BLOB data sliced or not
blobData = sliceSize ? getBlobDataSliced() : getBlobDataAtOnce();
blob = new Blob(blobData, { type: contentType });
return blob;
/*
* Get BLOB data in one slice.
* => Fast in Internet Explorer on new Blob(...)
*/
function getBlobDataAtOnce() {
byteNumbers = new Array(byteCharacters.length);
for (var i = 0; i < byteCharacters.length; i++) {
byteNumbers[i] = byteCharacters.charCodeAt(i);
}
byteArray = new Uint8Array(byteNumbers);
return [byteArray];
}
/*
* Get BLOB data in multiple slices.
* => Slow in Internet Explorer on new Blob(...)
*/
function getBlobDataSliced() {
var slice,
byteArrays = [];
for (var offset = 0; offset < byteCharacters.length; offset += sliceSize) {
slice = byteCharacters.slice(offset, offset + sliceSize);
byteNumbers = new Array(slice.length);
for (var i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i);
}
byteArray = new Uint8Array(byteNumbers);
// Add slice
byteArrays.push(byteArray);
}
return byteArrays;
}
}
الإجابة رقم 12
الطريقة باستخدام fetch هي الحل الأفضل، ولكن إذا كان أي شخص بحاجة إلى استخدام طريقة بدون fetch فإليها، كما أن الطرق المذكورة سابقاً لم تعمل لدي:
function makeblob(dataURL) {
const BASE64_MARKER = ';base64,';
const parts = dataURL.split(BASE64_MARKER);
const contentType = parts[0].split(':')[1];
const raw = window.atob(parts[1]);
const rawLength = raw.length;
const uInt8Array = new Uint8Array(rawLength);
for (let i = 0; i < rawLength; ++i) {
uInt8Array[i] = raw.charCodeAt(i);
}
return new Blob([uInt8Array], { type: contentType });
}
الإجابة رقم 13
فقط في المتصفح
Uint8Array.from(atob(YOUR_BASE64_DATA), (c) => c.charCodeAt(0))
قارن مع fetch
!(async () => {
const start = performance.now();
let i = 0;
while (i++ < 1e3) {
const dataUrl =
"data:application/octet-stream;base64,H4sIAAAAAAAAA0vOzyvOz0nVy8lP10jISM3JyVdIr8osUFCpdkksSdXLyy/X0KxN0ORKHlU3qm5U3ai6UXWj6kauOgBVt1KRLwcAAA==";
body = await (await fetch(dataUrl)).blob();
}
console.log(performance.now() - start); // 508.19999999925494ms
})();
!(async () => {
const start = performance.now();
let i = 0;
while (i++ < 1e3) {
const base64Data =
"H4sIAAAAAAAAA0vOzyvOz0nVy8lP10jISM3JyVdIr8osUFCpdkksSdXLyy/X0KxN0ORKHlU3qm5U3ai6UXWj6kauOgBVt1KRLwcAAA==";
body = Uint8Array.from(atob(base64Data), (c) => c.charCodeAt(0));
}
console.log(performance.now() - start); // 7.899999998509884ms
})();
يعتمد على حجم بياناتك، اختر الأداء المناسب.
