You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Fabric JS v6升级后自定义Qrcode类无法被loadFromJSON识别

问题描述

将Fabric JS版本升级至v6+后,因该版本移除了createClass方法,改写代码实现了继承fabric.Group的自定义QrcodeObject类。组件可正常渲染,使用canvas.toJSON()保存数据也正常,但调用loadFromJSON()恢复画布数据时,出现错误提示Fabric: No class registered for Qrcode。

自定义类代码

import * as fabric from "fabric";
import { QRCodeSVG } from "qrcode.react";

import { FabricObjectMetadataOptions } from "../common/interfaces";
import { convertJSXToXMLString } from "../utils";

export class QrCodeObject extends fabric.Group {
  static type = "Qrcode";
  private element: fabric.FabricObject;

  constructor(options: QrCodeObjectProps) {
    super([], {
      ...options,
      lockRotation: true,
      lockSkewingX: true,
      lockSkewingY: true,
    });

    this.element = {} as fabric.FabricObject;
    this.initialize(options);
    return this;
  }

  private async initialize(options: QrCodeObjectProps) {
    const svgString = QrCodeObject.toSVGString(options);
    this.element = await this.createSVGFromString(svgString, options);
    this.add(this.element);
    this.lockMiddleControls();
  }

  private createSVGFromString(
    svgString: string,
    options: QrCodeObjectProps
  ): Promise<fabric.FabricObject> {
    return new Promise((resolve) => {
      fabric.loadSVGFromString(svgString).then((resultSVG) => {
        const obj = fabric.util.groupSVGElements(
          resultSVG.objects as fabric.FabricObject[],
          resultSVG.options
        );
        obj.set({
          metadata: options.metadata,
        });
        resolve(obj);
      });
    });
  }

  static toSVGString({
    content,
    widthContainer,
    level = "L",
    bgColor = "#ffffff",
    fgColor = "#000000",
  }: QrCodeReactProps) {
    const svg = QRCodeSVG({
      value: content,
      size: widthContainer,
      level,
      bgColor,
      fgColor,
    });

    const xmlString = convertJSXToXMLString(svg);
    return xmlString;
  }

  private lockMiddleControls() {
    this.setControlsVisibility({
      mt: false,
      mb: false,
      ml: false,
      mr: false,
      bl: true,
      br: true,
      tl: true,
      tr: true,
      mtr: false,
    });
  }

  toJSON() {
    return super.toObject();
  }
}

export interface QrCodeReactProps {
  content: string;
  widthContainer: number;
  bgColor?: string;
  fgColor?: string;
  level?: "L" | "M" | "Q" | "H";
  includeMargin?: boolean;
  imageSettings?: {
    src: string;
    height: number;
    width: number;
    x?: number;
    y?: number;
    excavate?: boolean;
  };
}

export interface QrCodeObjectProps
  extends QrCodeReactProps,
    Partial<fabric.FabricObjectProps> {
  id: string;
  name: string;
  metadata: FabricObjectMetadataOptions;
  content: string;
  widthContainer: number;
}

declare module "fabric" {
  namespace fabric {
    class Qrcode extends QrCodeObject {
      constructor(element: any, options: any);
    }
  }
}

保存与恢复逻辑代码

const handlers = {
    save: () => {
      saveData(canvas?.toJSON());
    },
    restore: (jsonData: string) => canvas?.loadFromJSON(jsonData, () => {
          canvas?.renderAll();
        });
  };

解决方案

Fabric JS v6+要求自定义类必须手动注册到全局类注册表,且需实现fromObject静态方法,才能让loadFromJSON正确识别并重建对象。以下是具体修复步骤:

1. 实现fromObject静态方法

在QrCodeObject类中添加该方法,负责从JSON数据重建对象,处理异步初始化逻辑。

2. 注册自定义类

将QrCodeObject注册到Fabric全局注册表,让Fabric能识别"Qrcode"类型。

3. 完善toJSON方法

确保JSON序列化时包含二维码的核心配置属性,否则恢复时无法重建正确的二维码。

修改后的自定义类代码

import * as fabric from "fabric";
import { QRCodeSVG } from "qrcode.react";

import { FabricObjectMetadataOptions } from "../common/interfaces";
import { convertJSXToXMLString } from "../utils";

export class QrCodeObject extends fabric.Group {
  static type = "Qrcode";
  private element: fabric.FabricObject;
  // 存储二维码核心配置,用于JSON序列化
  private qrConfig: QrCodeReactProps;

  constructor(options: QrCodeObjectProps) {
    super([], {
      ...options,
      lockRotation: true,
      lockSkewingX: true,
      lockSkewingY: true,
    });

    this.element = {} as fabric.FabricObject;
    // 保存二维码配置
    this.qrConfig = {
      content: options.content,
      widthContainer: options.widthContainer,
      bgColor: options.bgColor,
      fgColor: options.fgColor,
      level: options.level,
      includeMargin: options.includeMargin,
      imageSettings: options.imageSettings
    };
    this.initialize(options);
    return this;
  }

  private async initialize(options: QrCodeObjectProps) {
    const svgString = QrCodeObject.toSVGString(this.qrConfig);
    this.element = await this.createSVGFromString(svgString, options);
    this.add(this.element);
    this.lockMiddleControls();
  }

  private createSVGFromString(
    svgString: string,
    options: QrCodeObjectProps
  ): Promise<fabric.FabricObject> {
    return new Promise((resolve) => {
      fabric.loadSVGFromString(svgString).then((resultSVG) => {
        const obj = fabric.util.groupSVGElements(
          resultSVG.objects as fabric.FabricObject[],
          resultSVG.options
        );
        obj.set({
          metadata: options.metadata,
        });
        resolve(obj);
      });
    });
  }

  static toSVGString({
    content,
    widthContainer,
    level = "L",
    bgColor = "#ffffff",
    fgColor = "#000000",
  }: QrCodeReactProps) {
    const svg = QRCodeSVG({
      value: content,
      size: widthContainer,
      level,
      bgColor,
      fgColor,
    });

    const xmlString = convertJSXToXMLString(svg);
    return xmlString;
  }

  private lockMiddleControls() {
    this.setControlsVisibility({
      mt: false,
      mb: false,
      ml: false,
      mr: false,
      bl: true,
      br: true,
      tl: true,
      tr: true,
      mtr: false,
    });
  }

  // 重写toJSON,包含自定义属性
  toJSON(propertiesToInclude: string[] = []) {
    return super.toObject([
      ...propertiesToInclude,
      "qrConfig",
      "metadata",
      "id",
      "name"
    ]);
  }

  // 实现fromObject静态方法,支持异步重建
  static async fromObject(object: any, callback?: (obj: QrCodeObject) => void) {
    const qrObj = new QrCodeObject({
      ...object,
      ...object.qrConfig
    });
    // 等待初始化完成
    await qrObj.initialize(qrObj);
    callback?.(qrObj);
    return qrObj;
  }
}

// 注册自定义类到Fabric全局注册表
fabric.Qrcode = QrCodeObject;
fabric.registerClass(QrCodeObject);

export interface QrCodeReactProps {
  content: string;
  widthContainer: number;
  bgColor?: string;
  fgColor?: string;
  level?: "L" | "M" | "Q" | "H";
  includeMargin?: boolean;
  imageSettings?: {
    src: string;
    height: number;
    width: number;
    x?: number;
    y?: number;
    excavate?: boolean;
  };
}

export interface QrCodeObjectProps
  extends QrCodeReactProps,
    Partial<fabric.FabricObjectProps> {
  id: string;
  name: string;
  metadata: FabricObjectMetadataOptions;
  content: string;
  widthContainer: number;
}

declare module "fabric" {
  namespace fabric {
    class Qrcode extends QrCodeObject {
      constructor(element: any, options: any);
    }
  }
}

修改恢复逻辑

由于fromObject是异步方法,调整恢复逻辑确保等待所有对象重建完成后再渲染:

const handlers = {
    save: () => {
      saveData(canvas?.toJSON());
    },
    restore: async (jsonData: string) => {
      if (!canvas) return;
      await canvas.loadFromJSON(jsonData);
      canvas.renderAll();
    }
  };

关键说明

  • 类注册:通过fabric.registerClass(QrCodeObject)和fabric.Qrcode = QrCodeObject完成注册,Fabric解析JSON时会根据type: "Qrcode"匹配到该类。
  • fromObject方法:Fabric核心方法,负责将JSON数据转换为实例对象,必须处理异步初始化逻辑。
  • toJSON方法:自定义属性需手动加入序列化列表,否则恢复时会丢失关键配置。

内容的提问来源于stack exchange,提问作者M_thew

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 08:59:51