admin管理员组

文章数量:1335447

How can I fix the size of a TextBox and dynamically decrease the fontSize if the text gets to large for the TextBox? Yes a similar question exists here but it only works for one line of text.

I want to achieve exactly that: (example from the imgflip meme editor)

I have tried following approach:

let text = new fabric.Textbox(box.text, {
    top: box.top,
    left: box.left,
    width: box.width,
});
if (text.width > box.width) {
    text.fontSize *= box.width / (text.width + 1);
    text.width = box.width;
}
if (text.height > box.height) {
    text.fontSize *= box.height / (text.height + 1);
    text.height = box.height;
}
canvas.add(text);

This way the fontSize decreases by the ratio of which the width or height of the textbox changed. But this causes the text to get extremely small sometimes because the text won't get wrapped as nicely as it could. The fontSize and the wrapping need to find an optimum somehow. Any ideas? Thanks!

How can I fix the size of a TextBox and dynamically decrease the fontSize if the text gets to large for the TextBox? Yes a similar question exists here but it only works for one line of text.

I want to achieve exactly that: (example from the imgflip meme editor)

I have tried following approach:

let text = new fabric.Textbox(box.text, {
    top: box.top,
    left: box.left,
    width: box.width,
});
if (text.width > box.width) {
    text.fontSize *= box.width / (text.width + 1);
    text.width = box.width;
}
if (text.height > box.height) {
    text.fontSize *= box.height / (text.height + 1);
    text.height = box.height;
}
canvas.add(text);

This way the fontSize decreases by the ratio of which the width or height of the textbox changed. But this causes the text to get extremely small sometimes because the text won't get wrapped as nicely as it could. The fontSize and the wrapping need to find an optimum somehow. Any ideas? Thanks!

Share Improve this question asked May 15, 2020 at 22:29 SandrogoSandrogo 5958 silver badges16 bronze badges
Add a ment  | 

2 Answers 2

Reset to default 7

I actually found a solution. Just in case someone has the same problem.

Adjusting the font size for the width works well with my original code:

if (text.width > box.width) {
    text.fontSize *= box.width / (text.width + 1);
    text.width = box.width;
}

This will only adjust the font size for really long words because the Textbox automatically wraps the text. But this wrapping causes the height to shrink too much with my original code. In order to take the wrapping into consideration I ended up gradually decreasing the font size and recalculating the text wrapping by calling canvas.renderAll() every time:

while (text.height > box.height && text.fontSize > 12) {
    text.fontSize--;
    canvas.renderAll();
}

This might be inefficient but it served my use case.

Text wrap working and font size changes w.r.t sticky note width and height. Editing mode activates on double click.

export const createStickyNotes = (canvas, options) => {
  fabric.StickyNote = fabric.util.createClass(fabric.Group, {
    type: "StickyNote",
    initialize: function (options) {
      this.set(options);
      var height = this.height;
      var width = this.width;

      this.rectObj = new fabric.Rect({
        width: width,
        height: height,
        fill: this.rectObj?.fill ?? "rgba(251,201,112,1)",
        originX: "center",
        originY: "center",
        objectCaching: false,
        stateProperties: ["fill"],
      });
      this.textObj = new fabric.Textbox(this.textObj?.text ?? "Notes", {
        originX: "center",
        originY: "center",
        textAlign: "center",
        width: 100,
        hasControls: false,
        fontSize: this.textObj?.fontSize ?? 30,
        lineHeight: 1,
        stateProperties: ["text", "fontSize"],
        scaleX: this.textObj?.scaleX ?? 1,
        scaleY: this.textObj?.scaleY ?? 1,
        objectCaching: false,
        breakWords: true,
        fontFamily: "Open Sans",
      });

      this._objects = [this.rectObj, this.textObj];
      //   this custom _set function will set custom properties value to object when it will load from json.
      // at that time loadFromJson function will call this initialize function.
      // this._setCustomProperties(this.options);
      canvas.renderAll();

      //evenet will fire if the object is double clicked by mouse
      this.on("mousedblclick", (e) => {
        var pasteFlag = false;
        var scaling = e.target.getScaledWidth() / 100;
        var textForEditing;
        canvas.bringToFront(e.target);
        e.target.selectable = false;
        const [rectObj, textObj] = this.getObjects();
        textObj.clone(function (clonedObj) {
          clonedObj.set({
            left: e.target.left,
            top: e.target.top,
            lockMovementY: true,
            lockMovementX: true,
            hasBorders: false,
            scaleX: scaling,
            scaleY: scaling,
            breakWords: true,
            width: textObj.width,
            stateProperties: [],
          });
          textForEditing = clonedObj;
        });

        this.remove(textObj);
        canvas.add(textForEditing);
        canvas.setActiveObject(textForEditing);

        textForEditing.enterEditing();
        textForEditing.selectAll();

        textForEditing.paste = (function (paste) {
          return function (e) {
            disableScrolling();
            pasteFlag = true;
          };
        })(textForEditing.paste);

        textForEditing.on("changed", function (e) {
          var fontSize = textForEditing.fontSize;
          var charCount = Math.max(textForEditing._text.length, 1);
          var charWR =
            (textForEditing.textLines.length * width) / (charCount * fontSize);

          if (textForEditing.height < height - 15) {
            fontSize = Math.min(
              Math.sqrt(
                ((height - 10 - fontSize) / 1.16) *
                  (width / (charCount * charWR))
              ),
              30
            );
          }
          if (textForEditing.height > height - 15) {
            fontSize = Math.sqrt(
              ((height - 10) / 1.16) * (width / (charCount * charWR))
            );
          }
          if (pasteFlag) {
            pasteFlag = false;
            while (
              textForEditing.height > height - 15 &&
              textForEditing.fontSize > 0
            ) {
              fontSize = textForEditing.fontSize -= 0.2;
              canvas.renderAll();
            }
          }
          textForEditing.fontSize = fontSize;
        });

        textForEditing.on("editing:exited", () => {
          enableScrolling();
          canvas.setActiveObject(textObj);
          textObj.set({
            text: textForEditing.text,
            fontSize: textForEditing.fontSize,
            visible: true,
          });
          this.add(textObj);
          this.selectable = true;
          canvas.remove(textForEditing);
          canvas.discardActiveObject();
        });
      });

      function disableScrolling() {
        var x = window.scrollX;
        var y = window.scrollY;
        window.onscroll = function () {
          window.scrollTo(x, y);
        };
      }

      var _wrapLine = function (_line, lineIndex, desiredWidth, reservedSpace) {
        var lineWidth = 0,
          splitByGrapheme = this.splitByGrapheme,
          graphemeLines = [],
          line = [],
          // spaces in different languges?
          words = splitByGrapheme
            ? fabric.util.string.graphemeSplit(_line)
            : _line.split(this._wordJoiners),
          word = "",
          offset = 0,
          infix = splitByGrapheme ? "" : " ",
          wordWidth = 0,
          infixWidth = 0,
          largestWordWidth = 0,
          lineJustStarted = true,
          additionalSpace = splitByGrapheme ? 0 : this._getWidthOfCharSpacing();

        reservedSpace = reservedSpace || 0;
        desiredWidth -= reservedSpace;
        for (var i = 0; i < words.length; i++) {
          // i would avoid resplitting the graphemes
          word = fabric.util.string.graphemeSplit(words[i]);
          wordWidth = this._measureWord(word, lineIndex, offset);
          offset += word.length;

          // Break the line if a word is wider than the set width
          if (this.breakWords && wordWidth >= desiredWidth) {
            if (!lineJustStarted) {
              graphemeLines.push(line);
              line = [];
              lineWidth = 0;
              lineJustStarted = true;
            }
            this.fontSize *= desiredWidth / (wordWidth + 1);
            // Loop through each character in word
            for (var w = 0; w < word.length; w++) {
              var letter = word[w];
              var letterWidth =
                (this.getMeasuringContext().measureText(letter).width *
                  this.fontSize) /
                this.CACHE_FONT_SIZE;
              line.push(letter);
              lineWidth += letterWidth;
            }
            word = [];
          } else {
            lineWidth += infixWidth + wordWidth - additionalSpace;
          }

          if (lineWidth >= desiredWidth && !lineJustStarted) {
            graphemeLines.push(line);
            line = [];
            lineWidth = wordWidth;
            lineJustStarted = true;
          } else {
            lineWidth += additionalSpace;
          }

          if (!lineJustStarted) {
            line.push(infix);
          }
          line = line.concat(word);

          infixWidth = this._measureWord([infix], lineIndex, offset);
          offset++;
          lineJustStarted = false;
          // keep track of largest word
          if (wordWidth > largestWordWidth && !this.breakWords) {
            largestWordWidth = wordWidth;
          }
        }

        i && graphemeLines.push(line);

        if (largestWordWidth + reservedSpace > this.dynamicMinWidth) {
          this.dynamicMinWidth =
            largestWordWidth - additionalSpace + reservedSpace;
        }

        return graphemeLines;
      };

      fabric.util.object.extend(fabric.Textbox.prototype, {
        _wrapLine: _wrapLine,
      });

      function enableScrolling() {
        window.onscroll = function () {};
      }
    },

    toObject: function (propertiesToInclude) {
      // This function is used for serialize this object. (used for create json)
      // not inlclude this.textObj and this.rectObj into json because when object will load from json, init fucntion of this class is called and it will assign this two object textObj and rectObj again.
      var obj = this.callSuper(
        "toObject",
        [
          "objectCaching",
          "textObj",
          "rectObj",
          // ... property list that you want to add into json when this object is convert into json using toJSON() function. (serialize)
        ].concat(propertiesToInclude)
      );
      // delete objects array from json because then object load from json, Init function will call. which will automatically re-assign object and assign _object array.
      delete obj.objects;
      return obj;
    },
  });

  fabric.StickyNote.async = true;
  fabric.StickyNote.fromObject = function (object, callback) {
    // This function is used for deserialize json and convert object json into button object again. (called when we call loadFromJson() fucntion on canvas)
    return fabric.Object._fromObject("StickyNote", object, callback);
  };

  return new fabric.StickyNote(options);
};

//How to use 

 var options = {
      width: 100,
      height: 100,
      originX: "center",
      originY: "center",
    };
    var notes = StickyNotes(canvas, options);
    canvas.add(notes);

本文标签: javascriptFabricJS fixed size TextBox with dynamic fontsIze (shrink text to fit size)Stack Overflow