Weboldal keresés

javascript - FabricJS rögzített méretű TextBox dinamikus betűmérettel (szöveg zsugorítása a mérethez illesztéshez)


Hogyan tudom rögzíteni egy Szövegdoboz méretét és dinamikusan csökkenteni a betűméretet, ha a szöveg túl nagy lesz a Szövegdobozhoz? Igen, létezik hasonló kérdés itt, de az csak egy sor szövegre működik.

Pontosan ezt akarom elérni: (példa az imgflip mém szerkesztőből)

Megpróbáltam a következő megközelítést:

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);

Így a betűméret a szövegdoboz szélességének vagy magasságának változási arányával csökken. De ez néha azt okozza, hogy a szöveg rendkívül kicsi lesz, mert a szöveg nem törik meg olyan szépen, ahogy lehetne. A betűméretnek és a tördelésnek valahogyan meg kell találniuk az optimális arányt. Van ötleted? Köszönöm!

Válasz #1

Valójában találtam egy megoldást. Csak arra az esetre, ha valakinek ugyanilyen problémája lenne.

A betűméret beállítása a szélességhez jól működik az eredeti kódommal:

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

Ez csak a nagyon hosszú szavak betűméretét fogja beállítani, mert a Szövegdoboz automatikusan tördelni fogja a szöveget. De ez a tördelés az eredeti kódommal túl sokat csökkenti a magasságot. A tördelés figyelembevételéhez végül fokozatosan csökkentettem a betűméretet, és minden alkalommal újraszámoltam a szövegtördelést a canvas.renderAll() meghívásával:

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

Ez lehet, hogy nem hatékony, de megfelelt az én esetemnek.

Válasz #2

A szövegtörés működik, és a betűméret változik a ragadós jegyzet szélessége és magassága szerint. A szerkesztő mód dupla kattintással aktiválódik.

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);

Kapcsolódó cikkek