diff --git a/docs/recipes/canvas.md b/docs/recipes/canvas.md index 145adabb..b8833b50 100644 --- a/docs/recipes/canvas.md +++ b/docs/recipes/canvas.md @@ -21,6 +21,11 @@ rrweb.record({ sampling: { canvas: 15, }, + // optional image format settings + dataURLOptions: { + type: 'image/webp', + quality: 0.6, + }, }); ``` diff --git a/docs/recipes/canvas.zh_CN.md b/docs/recipes/canvas.zh_CN.md index e02e899e..77086460 100644 --- a/docs/recipes/canvas.zh_CN.md +++ b/docs/recipes/canvas.zh_CN.md @@ -21,6 +21,11 @@ rrweb.record({ sampling: { canvas: 15, }, + // 图像的格式 + dataURLOptions: { + type: 'image/webp', + quality: 0.6, + }, }); ``` diff --git a/guide.md b/guide.md index fa990655..c3f1e5ec 100644 --- a/guide.md +++ b/guide.md @@ -151,6 +151,7 @@ The parameter of `rrweb.record` accepts the following options. | maskInputFn | - | customize mask input content recording logic | | maskTextFn | - | customize mask text content recording logic | | slimDOMOptions | {} | remove unnecessary parts of the DOM
refer to the [list](https://github.com/rrweb-io/rrweb/blob/588164aa12f1d94576f89ae0210b98f6e971c895/packages/rrweb-snapshot/src/types.ts#L97-L108) | +| dataURLOptions | {} | Canvas image format and quality ,This parameter will be passed to the OffscreenCanvas.convertToBlob(),Using this parameter effectively reduces the size of the recorded data | | inlineStylesheet | true | whether to inline the stylesheet in the events | | hooks | {} | hooks for events
refer to the [list](https://github.com/rrweb-io/rrweb/blob/9488deb6d54a5f04350c063d942da5e96ab74075/src/types.ts#L207) | | packFn | - | refer to the [storage optimization recipe](./docs/recipes/optimize-storage.md) | diff --git a/guide.zh_CN.md b/guide.zh_CN.md index 92635712..e867c93d 100644 --- a/guide.zh_CN.md +++ b/guide.zh_CN.md @@ -151,6 +151,7 @@ setInterval(save, 10 * 1000); | hooks | {} | 各类事件的回调
类型详见[列表](https://github.com/rrweb-io/rrweb/blob/9488deb6d54a5f04350c063d942da5e96ab74075/src/types.ts#L207) | | packFn | - | 数据压缩函数,详见[优化存储策略](./docs/recipes/optimize-storage.zh_CN.md) | | sampling | - | 数据抽样策略,详见[优化存储策略](./docs/recipes/optimize-storage.zh_CN.md) | +| dataURLOptions | {} | Canvas 图像快照的格式和质量,这个参数将传递给 OffscreenCanvas.convertToBlob(),使用这个参数能有效减小录制数据的大小 | | recordCanvas | false | 是否记录 canvas 内容, 可用选项:false, true | | inlineImages | false | 是否将图片内容记内联录制 | | collectFonts | false | 是否记录页面中的字体文件 | diff --git a/packages/rrweb/src/record/index.ts b/packages/rrweb/src/record/index.ts index dc4f80bd..cd996c65 100644 --- a/packages/rrweb/src/record/index.ts +++ b/packages/rrweb/src/record/index.ts @@ -64,6 +64,7 @@ function record( hooks, packFn, sampling = {}, + dataURLOptions = {}, mousemoveWait, recordCanvas = false, userTriggeredOnInput = false, @@ -240,6 +241,7 @@ function record( blockSelector, mirror, sampling: sampling.canvas, + dataURLOptions, }); const shadowDomManager = new ShadowDomManager({ @@ -252,6 +254,7 @@ function record( maskTextSelector, inlineStylesheet, maskInputOptions, + dataURLOptions, maskTextFn, maskInputFn, recordCanvas, @@ -290,6 +293,7 @@ function record( maskAllInputs: maskInputOptions, maskTextFn, slimDOM: slimDOMOptions, + dataURLOptions, recordCanvas, inlineImages, onSerialize: (n) => { @@ -471,6 +475,7 @@ function record( keepIframeSrcFn, blockSelector, slimDOMOptions, + dataURLOptions, mirror, iframeManager, stylesheetManager, diff --git a/packages/rrweb/src/record/mutation.ts b/packages/rrweb/src/record/mutation.ts index 590e5d50..3a0b3afb 100644 --- a/packages/rrweb/src/record/mutation.ts +++ b/packages/rrweb/src/record/mutation.ts @@ -169,6 +169,7 @@ export default class MutationBuffer { private recordCanvas: observerParam['recordCanvas']; private inlineImages: observerParam['inlineImages']; private slimDOMOptions: observerParam['slimDOMOptions']; + private dataURLOptions: observerParam['dataURLOptions']; private doc: observerParam['doc']; private mirror: observerParam['mirror']; private iframeManager: observerParam['iframeManager']; @@ -191,6 +192,7 @@ export default class MutationBuffer { 'recordCanvas', 'inlineImages', 'slimDOMOptions', + 'dataURLOptions', 'doc', 'mirror', 'iframeManager', @@ -301,6 +303,7 @@ export default class MutationBuffer { maskTextFn: this.maskTextFn, maskInputFn: this.maskInputFn, slimDOMOptions: this.slimDOMOptions, + dataURLOptions: this.dataURLOptions, recordCanvas: this.recordCanvas, inlineImages: this.inlineImages, onSerialize: (currentN) => { diff --git a/packages/rrweb/src/record/observers/canvas/canvas-manager.ts b/packages/rrweb/src/record/observers/canvas/canvas-manager.ts index 557826f0..5a2cedb1 100644 --- a/packages/rrweb/src/record/observers/canvas/canvas-manager.ts +++ b/packages/rrweb/src/record/observers/canvas/canvas-manager.ts @@ -1,4 +1,4 @@ -import type { ICanvas, Mirror } from 'rrweb-snapshot'; +import type { ICanvas, Mirror, DataURLOptions } from 'rrweb-snapshot'; import type { blockClass, canvasManagerMutationCallback, @@ -63,6 +63,7 @@ export class CanvasManager { blockSelector: string | null; mirror: Mirror; sampling?: 'all' | number; + dataURLOptions: DataURLOptions; }) { const { sampling = 'all', @@ -70,6 +71,7 @@ export class CanvasManager { blockClass, blockSelector, recordCanvas, + dataURLOptions, } = options; this.mutationCb = options.mutationCb; this.mirror = options.mirror; @@ -77,7 +79,9 @@ export class CanvasManager { if (recordCanvas && sampling === 'all') this.initCanvasMutationObserver(win, blockClass, blockSelector); if (recordCanvas && typeof sampling === 'number') - this.initCanvasFPSObserver(sampling, win, blockClass, blockSelector); + this.initCanvasFPSObserver(sampling, win, blockClass, blockSelector, { + dataURLOptions, + }); } private processMutation: canvasManagerMutationCallback = ( @@ -102,6 +106,9 @@ export class CanvasManager { win: IWindow, blockClass: blockClass, blockSelector: string | null, + options: { + dataURLOptions: DataURLOptions; + }, ) { const canvasContextReset = initCanvasContextObserver( win, @@ -202,6 +209,7 @@ export class CanvasManager { bitmap, width: canvas.width, height: canvas.height, + dataURLOptions: options.dataURLOptions, }, [bitmap], ); diff --git a/packages/rrweb/src/record/workers/image-bitmap-data-url-worker.ts b/packages/rrweb/src/record/workers/image-bitmap-data-url-worker.ts index e5addc0b..9b11641b 100644 --- a/packages/rrweb/src/record/workers/image-bitmap-data-url-worker.ts +++ b/packages/rrweb/src/record/workers/image-bitmap-data-url-worker.ts @@ -1,4 +1,5 @@ import { encode } from 'base64-arraybuffer'; +import type { DataURLOptions } from 'rrweb-snapshot'; import type { ImageBitmapDataURLWorkerParams, ImageBitmapDataURLWorkerResponse, @@ -25,12 +26,13 @@ interface ImageBitmapDataURLResponseWorker { async function getTransparentBlobFor( width: number, height: number, + dataURLOptions: DataURLOptions, ): Promise { const id = `${width}-${height}`; if (transparentBlobMap.has(id)) return transparentBlobMap.get(id)!; const offscreen = new OffscreenCanvas(width, height); offscreen.getContext('2d'); // creates rendering context for `converToBlob` - const blob = await offscreen.convertToBlob(); // takes a while + const blob = await offscreen.convertToBlob(dataURLOptions); // takes a while const arrayBuffer = await blob.arrayBuffer(); const base64 = encode(arrayBuffer); // cpu intensive transparentBlobMap.set(id, base64); @@ -45,16 +47,20 @@ worker.onmessage = async function (e) { if (!('OffscreenCanvas' in globalThis)) return worker.postMessage({ id: e.data.id }); - const { id, bitmap, width, height } = e.data; + const { id, bitmap, width, height, dataURLOptions } = e.data; - const transparentBase64 = getTransparentBlobFor(width, height); + const transparentBase64 = getTransparentBlobFor( + width, + height, + dataURLOptions, + ); const offscreen = new OffscreenCanvas(width, height); const ctx = offscreen.getContext('2d')!; ctx.drawImage(bitmap, 0, 0); bitmap.close(); - const blob = await offscreen.convertToBlob(); // takes a while + const blob = await offscreen.convertToBlob(dataURLOptions); // takes a while const type = blob.type; const arrayBuffer = await blob.arrayBuffer(); const base64 = encode(arrayBuffer); // cpu intensive diff --git a/packages/rrweb/src/types.ts b/packages/rrweb/src/types.ts index ab9ea833..05702dd5 100644 --- a/packages/rrweb/src/types.ts +++ b/packages/rrweb/src/types.ts @@ -6,6 +6,7 @@ import type { SlimDOMOptions, MaskInputFn, MaskTextFn, + DataURLOptions, } from 'rrweb-snapshot'; import type { PackFn, UnpackFn } from './packer/base'; import type { IframeManager } from './record/iframe-manager'; @@ -251,6 +252,7 @@ export type recordOptions = { hooks?: hooksParam; packFn?: PackFn; sampling?: SamplingStrategy; + dataURLOptions?: DataURLOptions; recordCanvas?: boolean; userTriggeredOnInput?: boolean; collectFonts?: boolean; @@ -290,6 +292,7 @@ export type observerParam = { userTriggeredOnInput: boolean; collectFonts: boolean; slimDOMOptions: SlimDOMOptions; + dataURLOptions: DataURLOptions; doc: Document; mirror: Mirror; iframeManager: IframeManager; @@ -323,6 +326,7 @@ export type MutationBufferParam = Pick< | 'recordCanvas' | 'inlineImages' | 'slimDOMOptions' + | 'dataURLOptions' | 'doc' | 'mirror' | 'iframeManager' @@ -563,6 +567,7 @@ export type ImageBitmapDataURLWorkerParams = { bitmap: ImageBitmap; width: number; height: number; + dataURLOptions: DataURLOptions; }; export type ImageBitmapDataURLWorkerResponse =