Repository: fabricjs/fabric.js
Pull request #10399 (2025-01-13 10:29:41)

feat: Add method toBlob
Before this method existed, I needed to do it this way.

```ts
const base64 = object.toDataURL();
const url = URL.createObjectURL(dataurl2blob(base64));
```

After this method was added, I only need to do it this way.
```ts
const blob = await object.toBlob();
const url = URL.createObjectURL(blob);
```

https://github.com/fabricjs/fabric.js/issues/3283

codesandbox[bot]: #### Review or Edit in CodeSandbox

Open the branch in <a href="https://codesandbox.io/p/github/zhe-he/fabric.js/patch-toBlob?mode=review&utm_source=gh_app">Web Editor</a> &bull; <a href="https://codesandbox.io/p/vscode?owner=zhe-he&repo=fabric.js&branch=patch-toBlob&utm_source=gh_app">VS Code</a> &bull; <a href="https://codesandbox.io/p/vscode?owner=zhe-he&repo=fabric.js&branch=patch-toBlob&insiders=true&utm_source=gh_app">Insiders</a><br>
Open <a href="https://codesandbox.io/p/devtool/preview/zhe-he/fabric.js/patch-toBlob?task=preview&port=51423&redirect=true&utm_source=gh_app">Preview</a>

<!-- open-in-codesandbox:complete -->

asturur: This is fine.
I need to think a second of the api options but is ok to add this
zhe-he: The export type might need to add support for WebP.

https://github.com/fabricjs/fabric.js/blob/master/src/typedefs.ts#L46
`export type ImageFormat = 'jpeg' | 'png';` ->
`export type ImageFormat = 'jpeg' | 'png' | 'webp';`



asturur: Yes webP support can be added, is well supported now

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 929cc8a23..7f9b6893a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,8 @@
 
 ## [next]
 
+- feat(): Add method toBlob. [#3283](https://github.com/fabricjs/fabric.js/issues/3283)
+
 ## [6.5.4]
 
 - docs() perf(): Reorder caching conditions for most common scenario and docs fixes. [#10366](https://github.com/fabricjs/fabric.js/pull/10366)
diff --git a/src/canvas/StaticCanvas.spec.ts b/src/canvas/StaticCanvas.spec.ts
new file mode 100644
index 000000000..aa2d8a514
--- /dev/null
+++ b/src/canvas/StaticCanvas.spec.ts
@@ -0,0 +1,12 @@
+import { StaticCanvas } from './StaticCanvas';
+
+describe('StaticCanvas', () => {
+  it('toBlob', async () => {
+    const canvas = new StaticCanvas(undefined, { width: 300, height: 300 });
+    const blob = await canvas.toBlob({
+      multiplier: 3,
+    });
+    expect(blob).toBeInstanceOf(Blob);
+    expect(blob?.type).toBe('image/png');
+  });
+});
diff --git a/src/canvas/StaticCanvas.ts b/src/canvas/StaticCanvas.ts
index 810004306..3bfb8d84f 100644
--- a/src/canvas/StaticCanvas.ts
+++ b/src/canvas/StaticCanvas.ts
@@ -26,7 +26,7 @@ import {
 } from '../util/animation/AnimationFrameProvider';
 import { runningAnimations } from '../util/animation/AnimationRegistry';
 import { uid } from '../util/internals/uid';
-import { createCanvasElementFor, toDataURL } from '../util/misc/dom';
+import { createCanvasElementFor, toBlob, toDataURL } from '../util/misc/dom';
 import { invertTransform, transformPoint } from '../util/misc/matrix';
 import type { EnlivenObjectOptions } from '../util/misc/objectEnlive';
 import {
@@ -1393,6 +1393,22 @@ export class StaticCanvas<
       quality,
     );
   }
+  toBlob(options = {} as TDataUrlOptions): Promise<Blob | null> {
+    const {
+      format = 'png',
+      quality = 1,
+      multiplier = 1,
+      enableRetinaScaling = false,
+    } = options;
+    const finalMultiplier =
+      multiplier * (enableRetinaScaling ? this.getRetinaScaling() : 1);
+
+    return toBlob(
+      this.toCanvasElement(finalMultiplier, options),
+      format,
+      quality,
+    );
+  }
 
   /**
    * Create a new HTMLCanvas element painted with the current canvas content.
diff --git a/src/shapes/Image.ts b/src/shapes/Image.ts
index 6918cb26f..45a040090 100644
--- a/src/shapes/Image.ts
+++ b/src/shapes/Image.ts
@@ -759,7 +759,9 @@ export class FabricImage<
 
   /**
    * Default CSS class name for canvas
+   * Will be removed from fabric 7
    * @static
+   * @deprecated
    * @type String
    * @default
    */
diff --git a/src/shapes/Object/Object.ts b/src/shapes/Object/Object.ts
index 775e0303f..4e427ba18 100644
--- a/src/shapes/Object/Object.ts
+++ b/src/shapes/Object/Object.ts
@@ -31,6 +31,7 @@ import {
   createCanvasElement,
   createCanvasElementFor,
   toDataURL,
+  toBlob,
 } from '../../util/misc/dom';
 import { invertTransform, qrDecompose } from '../../util/misc/matrix';
 import { enlivenObjectEnlivables } from '../../util/misc/objectEnlive';
@@ -1403,6 +1404,13 @@ export class FabricObject<
       options.quality || 1,
     );
   }
+  toBlob(options: toDataURLOptions = {}) {
+    return toBlob(
+      this.toCanvasElement(options),
+      options.format || 'png',
+      options.quality || 1,
+    );
+  }
 
   /**
    * Returns true if any of the specified types is identical to the type of an instance
diff --git a/src/util/index.ts b/src/util/index.ts
index 2309eec07..e17408d31 100644
--- a/src/util/index.ts
+++ b/src/util/index.ts
@@ -51,6 +51,7 @@ export {
   createImage,
   copyCanvasElement,
   toDataURL,
+  toBlob,
 } from './misc/dom';
 export { toFixed } from './misc/toFixed';
 export {
diff --git a/src/util/misc/dom.spec.ts b/src/util/misc/dom.spec.ts
new file mode 100644
index 000000000..52efccc3e
--- /dev/null
+++ b/src/util/misc/dom.spec.ts
@@ -0,0 +1,22 @@
+import { toBlob, createCanvasElement } from './dom';
+
+describe('DOM utils', () => {
+  it('toBlob without format', async () => {
+    const canvas = createCanvasElement();
+    const blob = await toBlob(canvas);
+    expect(blob).toBeInstanceOf(Blob);
+    expect(blob?.type).toEqual('image/png');
+  });
+  it('toBlob png', async () => {
+    const canvas = createCanvasElement();
+    const blob = await toBlob(canvas, 'png');
+    expect(blob).toBeInstanceOf(Blob);
+    expect(blob?.type).toEqual('image/png');
+  });
+  it('toBlob jpeg', async () => {
+    const canvas = createCanvasElement();
+    const blob = await toBlob(canvas, 'jpeg');
+    expect(blob).toBeInstanceOf(Blob);
+    expect(blob?.type).toEqual('image/jpeg');
+  });
+});
diff --git a/src/util/misc/dom.ts b/src/util/misc/dom.ts
index f8a62e12c..762fcabaf 100644
--- a/src/util/misc/dom.ts
+++ b/src/util/misc/dom.ts
@@ -61,3 +61,12 @@ export const isHTMLCanvas = (
 ): canvas is HTMLCanvasElement => {
   return !!canvas && (canvas as HTMLCanvasElement).getContext !== undefined;
 };
+
+export const toBlob = (
+  canvasEl: HTMLCanvasElement,
+  format?: ImageFormat,
+  quality?: number,
+) =>
+  new Promise((resolve, _) => {
+    canvasEl.toBlob(resolve, `image/${format}`, quality);
+  }) as Promise<Blob | null>;


Test patch:
diff --git a/src/color/color.test.ts b/src/color/color.test.ts
index b13c38d66..8fa6b6777 100644
--- a/src/color/color.test.ts
+++ b/src/color/color.test.ts
@@ -223,7 +223,7 @@ describe('test Color.fromHsla for color', () => {
       stringToParse: 'hsl( -450,  50%,   50%, .5)',
       expectedSource: [127, 64, 191, 0.5],
     },
-      {
+    {
       name: 'fromHsla with Saturation 0',
       stringToParse: 'hsla(0, 0%, 50%, 1)',
       expectedSource: [128, 128, 128, 1],
@@ -324,4 +324,4 @@ describe('parsing colors for color', () => {
     expect(colorUppercase).toBeInstanceOf(Color);
     expect(colorUppercase.getSource()).toEqual(expectedSource);
   });
-});
\ No newline at end of file
+});