【问题标题】:Fabric.js - Grouped iText not editableFabric.js - 分组 iText 不可编辑
【发布时间】:2023-04-02 10:25:01
【问题描述】:

标题说明了一切。当 fabric.ITextfabric.Group 的一部分时,它不再像可编辑文本那样做出反应。

这是一个用例:http://jsfiddle.net/vAjYd/

有没有办法解决这个问题?否则我必须写我的小组。

顺便说一句:fabric.Group 有很多优点,但是禁用的事件使得它无法用于 UI 元素(即这个用例或按钮组作为文本和矩形组)。

【问题讨论】:

标签: fabricjs


【解决方案1】:

我回答这个问题可能为时已晚,但肯定有许多像我一样正在寻找这个答案的人会找到解决方案。 您可以找到一个完美的 iText 解决方案,用户可以在其中编辑 iText 的文本,它是 fabric.group 的一部分 Working solution

这里遵循的方法是:

  1. 当用户双击所需的组时,我们取消所有元素的组合
  2. 将焦点放在 iText 元素上
  3. 进入iText的编辑模式并选择所有文本,以便用户可以直接开始编辑..
  4. 一旦用户退出编辑模式,元素就会再次组合在一起。

    // ungroup objects in group
    var items
    var ungroup = function (group) {
        items = group._objects;
        group._restoreObjectsState();
        canvas.remove(group);
        for (var i = 0; i < items.length; i++) {
            canvas.add(items[i]);
        }
        // if you have disabled render on addition
        canvas.renderAll();
    };
    
    // Re-group when text editing finishes
    var dimensionText = new fabric.IText("Dimension Text", {
        fontFamily: 'Comic Sans',
        fontSize: 14,
        stroke: '#000',
        strokeWidth: 1,
        fill: "#000",
        left: 170,
        top: 60
    });
    dimensionText.on('editing:exited', function () {
        var items = [];
        canvas.forEachObject(function (obj) {
            items.push(obj);
            canvas.remove(obj);
        });
        var grp = new fabric.Group(items.reverse(), {});
        canvas.add(grp);
        grp.on('mousedown', fabricDblClick(grp, function (obj) {
            ungroup(grp);
            canvas.setActiveObject(dimensionText);
            dimensionText.enterEditing();
            dimensionText.selectAll();
        }));
    });
    

【讨论】:

  • 不幸的是,这不再适用于 1.7.1+ 版本。取消选择 IText 后标尺将无法恢复,并且控制台中显示以下错误:TypeError: this._objects[i] is undefined.
【解决方案2】:

我知道为时已晚,但这是解决您问题的有效代码: http://jsfiddle.net/fd4yrxq6/2/

我只是使用上面的 anwares 做了一些小改动:

...
// ungroup objects in group
var items;
var ungroup = function (group) {
    items = group._objects;
    group._restoreObjectsState();
    canvas.remove(group);
    canvas.renderAll();
    for (var i = 0; i < items.length; i++) {
        canvas.add(items[i]);
    }
    // if you have disabled render on addition
    canvas.renderAll();
};
...
dimensionText.on('editing:exited', function () {
        for (var i = 0; i < items.length; i++) {
        canvas.remove(items[i]);
    }
    var grp = new fabric.Group(items, {});
    canvas.add(grp);
    grp.on('mousedown', fabricDblClick(grp, function (obj) {
        ungroup(grp);
        canvas.setActiveObject(dimensionText);
        dimensionText.enterEditing();
        dimensionText.selectAll();
    }));
});
...

var canvas = new fabric.Canvas('canvas');
canvas.setWidth(300);
canvas.setHeight(300);

// Double-click event handler
var fabricDblClick = function (obj, handler) {
    return function () {
        if (obj.clicked) handler(obj);
        else {
            obj.clicked = true;
            setTimeout(function () {
                obj.clicked = false;
            }, 500);
        }
    };
};

// ungroup objects in group
var items;
var ungroup = function (group) {
    items = group._objects;
    group._restoreObjectsState();
    canvas.remove(group);
    canvas.renderAll();
    for (var i = 0; i < items.length; i++) {
        canvas.add(items[i]);
    }
    // if you have disabled render on addition
    canvas.renderAll();
};

// Re-group when text editing finishes
var dimensionText = new fabric.IText("Dimension Text", {
    fontFamily: 'Comic Sans',
    fontSize: 14,
    stroke: '#000',
    strokeWidth: 1,
    fill: "#000",
    left: 170,
    top: 60
});
dimensionText.on('editing:exited', function () {
        for (var i = 0; i < items.length; i++) {
        canvas.remove(items[i]);
    }
    var grp = new fabric.Group(items, {});
    canvas.add(grp);
    grp.on('mousedown', fabricDblClick(grp, function (obj) {
        ungroup(grp);
        canvas.setActiveObject(dimensionText);
        dimensionText.enterEditing();
        dimensionText.selectAll();
    }));
});

function addRuler() {
    var dimension_mark = new fabric.Path('M0,0L0,-5L0,5L0,0L150,0L150,-5L150,5L150,0z');
    dimension_mark.set({
        left: 150,
        top: 70,
        stroke: '#333333',
        strokeWidth: 2,
        scaleY: 1
    });
    var dimension_group = new fabric.Group([dimension_mark, dimensionText], {
        left: 50,
        top: 50
    });
    canvas.add(dimension_group);
    dimension_group.on('mousedown', fabricDblClick(dimension_group, function (obj) {
        ungroup(dimension_group);
        canvas.setActiveObject(dimensionText);
        dimensionText.enterEditing();
        dimensionText.selectAll();
        canvas.renderAll();
    }));
}
addRuler();

style = {
        left: 100,
        top: 100,
        width: 20,
        height: 20,
        fill: "green",
        lockRotation: true
    };
    var rectangle = new fabric.Polygon([
        { x: 20, y: 0 },
        { x: 0, y: 20 },
        { x: 20, y: 40 },
        { x: 40, y: 20 },
    ], style);
    canvas.add(rectangle);
.canvas {
    border: 1px solid Black;
    width: 300px;
    height: 300px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/4.2.0/fabric.min.js"></script>
<p id="addObj">Fabric.js - iText editing from Group</p>
<canvas id="canvas" class="canvas" ></canvas>

【讨论】:

    【解决方案3】:

    感谢您的回答,但是它不能确保只有组中的项目被重新组合,我们必须将 'canvas.forEachObject' 更改为 'groupItems'因为它将尝试仅将属于该组的所有项目放回画布中。见以下代码

    // ungroup objects in group
    var groupItems = []
    var ungroup = function (group) {
        groupItems = group._objects;
        group._restoreObjectsState();
        canvas.remove(group);
        for (var i = 0; i < groupItems.length; i++) {
            canvas.add(groupItems[i]);
        }
        // if you have disabled render on addition
        canvas.renderAll();
    };
    
    // Re-group when text editing finishes
    var dimensionText = new fabric.IText("Dimension Text", {
        fontFamily: 'Comic Sans',
        fontSize: 14,
        stroke: '#000',
        strokeWidth: 1,
        fill: "#000",
        left: 170,
        top: 60
    });
    dimensionText.on('editing:exited', function () {
        var items = [];
        groupItems.forEach(function (obj) {
            items.push(obj);
            canvas.remove(obj);
        });
        var grp = new fabric.Group(items.reverse(), {});
        canvas.add(grp);
        grp.on('mousedown', fabricDblClick(grp, function (obj) {
            ungroup(grp);
            canvas.setActiveObject(dimensionText);
            dimensionText.enterEditing();
            dimensionText.selectAll();
        }));
    });
    

    【讨论】:

      【解决方案4】:

      我是最后一个答案,但更好的答案, 一个gif演示值一千字

      如您所见,效果很好,链接地址https://jsfiddle.net/hejie/rhf069ob/latest

      解决方案

      1. 创建一个组
      2. 双击群组,隐藏textbox inside group
      3. 创建一个临时文本框进行编辑,与textbox inside group的位置相同
      4. 在临时文本框上编辑后,将新文本提交到textbox inside group,并删除临时文本框
      5. 完成。

      为什么这个解决方案比group/ungroup 更好?

      1. ungroup => editing => group,会创建新的 group 实例,原来的 group 实例被销毁,这对你的对象状态管理是非常有害的
      2. 旋转组后很难编辑,因为文本框 insdie 组也会旋转。

      简而言之,不要使用分组/取消分组解决方案! 试试下面的代码:

      var canvas = new fabric.Canvas('canvas');
      canvas.setWidth(300);
      canvas.setHeight(300);
      
      function createGroup() {
          let circle = new fabric.Circle({
          radius:40,
          fill: 'rgba(200, 0, 0, 0.3)',
          originX: 'center',
          originY: 'center',
        });
        
        let text = new fabric.Textbox("AJLoveChina", {
          originX: 'center',
          originY: 'center',
          textAlign: 'center',
          fontSize: 12,
        })
        
        let group = new fabric.Group([circle, text], {
          left: 100,
          top: 100,
          originX: 'center',
          originY: 'center',
        });
        
        group.on('mousedblclick', () => {
          // textForEditing is temporary obj, 
          // and will be removed after editing
          let textForEditing = new fabric.Textbox(text.text, {
            originX: 'center',
            originY: 'center',
            textAlign: text.textAlign,
            fontSize: text.fontSize,
            
            left: group.left,
            top: group.top,
          })
          
          // hide group inside text
          text.visible = false;
          // note important, text cannot be hidden without this
          group.addWithUpdate();
          
          textForEditing.visible = true;
          // do not give controls, do not allow move/resize/rotation on this 
          textForEditing.hasConstrols = false;
      
          
          // now add this temporary obj to canvas
          canvas.add(textForEditing);
          canvas.setActiveObject(textForEditing);
          // make the cursor showing
          textForEditing.enterEditing();
          textForEditing.selectAll();
          
          
          // editing:exited means you click outside of the textForEditing
          textForEditing.on('editing:exited', () =>{
              let newVal = textForEditing.text;
            let oldVal = text.text;
            
            // then we check if text is changed
            if (newVal !== oldVal) {
              text.set({
                  text: newVal,
                visible: true,
              })
              
              // comment before, you must call this
              group.addWithUpdate();
              
              // we do not need textForEditing anymore
              textForEditing.visible = false;
              canvas.remove(textForEditing);
              
              // optional, buf for better user experience
              canvas.setActiveObject(group);
            }
          })
        })
        
        canvas.add(group);
      }
      
      createGroup();
      #canvas {
        width:300px;
        height:300px;
        border: 1px solid #333;
      }
      <script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/4.4.0/fabric.min.js"></script>
      <h4>fabricjs editing textbox inside fabric Group</h4>
      <canvas id="canvas"></canvas>

      【讨论】:

        【解决方案5】:

        目前 IText 无法处理这些事件,因为如果它包含在一个组中,它们就不会传递给它。我认为这也是处理该问题的首选方式,因为它会使用户感到困惑——如果他开始编辑多个文本怎么办。这最终可能会一团糟。也许你可以稍微修改你的脚本来解决这个问题。

        【讨论】:

        • 很抱歉,这不是一个答案。 “尝试解决方法”?举个例子或有帮助的东西怎么样?请看下面的答案 - 它有一个解决方法,甚至是一个工作示例!
        • 我同意多个文本,但这不仅仅是一个案例 - 仍然需要功能,例如,当我们要将文本与圆圈相关联时
        【解决方案6】:

        如果您想要 itext 的真实视觉填充(如 css),您可以在 itext 后面添加一个不可选择的 Rect。并根据 itext 的所有事件调整 Rect 位置,例如移动、缩放等。

        self.createITextWithPadding = function (event) {
        
                       var itext = new fabric.IText('Done', {
                           fontSize: 18,
                           padding : tnbConstants.ITEXT.PADDING,
                           fill: '#FFF',
        
                       });
        
                       itext.left = (event.pageX - $("#tnb-panel").position().left)-itext.width/2;
                       itext.top = (event.pageY-$("#tnb-panel").position().top)-itext.height/2;
        
                       var bg = new fabric.Rect({
                            fill: '#32b775',
                           left : itext.left - tnbConstants.ITEXT.PADDING,
                           top :itext.top - tnbConstants.ITEXT.PADDING,
                           rx: 5,
                           ry: 5,
                           width: itext.width + tnbConstants.ITEXT.PADDING*2,
                           height:itext.height + tnbConstants.ITEXT.PADDING*2,
                           selectable : false
                       });
        
                       itext.bgRect = bg;
                       itext.on("moving",self.adjustBackRect);
                       itext.on("scaling",self.adjustBackRect);
        
                       return itext;
                   }
        
        self.adjustBackRect = function (e) {//e is event
                       var text = e.target;
                       var bgRect = text.bgRect;
        
                       bgRect.set({
                           left : text.left - text.padding,
                           top : text.top - text.padding,
                           width : text.width * text.scaleX + text.padding * 2,
                           height : text.height * text.scaleY + text.padding * 2 ,
                       });
        
        
                       console.log("text width :" + (text.width * text.scaleX + text.padding*2));
                       console.log("text height :" + (text.height * text.scaleY + text.padding*2));
                       console.log("bg width :" + (bgRect.width ));
                       console.log("bg height :" + (bgRect.height ));
                   };
        

        【讨论】:

        • 你能举一个你的解决方案的例子吗?
        • @soundslikeodd 我已经附上了例子,你可以看看
        猜你喜欢
        • 2015-04-21
        • 1970-01-01
        • 2015-06-09
        • 2021-06-27
        • 2023-03-03
        • 2018-06-15
        • 1970-01-01
        • 2015-06-05
        • 1970-01-01
        相关资源
        最近更新 更多