Fabric.js - 分组 iText 不可编辑

Posted

技术标签:

【中文标题】Fabric.js - 分组 iText 不可编辑【英文标题】:Fabric.js - Grouped iText not editable 【发布时间】:2014-08-18 10:12:41 【问题描述】:

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

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

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

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

【问题讨论】:

你能分享你对这个问题的解决方案吗?我目前面临同样的问题,但似乎无法找到解决方案。我已经走到这一步了:http://pastebin.com/dQ742AvG 但无法弄清楚如何为 IText 启用编辑 fabricjs.com/docs/fabric.IText.html 不起作用.. 【参考方案1】:

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

这里遵循的方法是:

    当用户双击所需的组时,我们取消所有元素的组合 将焦点放在 iText 元素上 进入iText的编辑模式并选择所有文本,以便用户可以直接开始编辑..

    一旦用户退出编辑模式,元素就会再次组合在一起。

    // 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

解决方案

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

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

    ungroup => editing => group,会创建新的 group 实例,原来的 group 实例被销毁,这对你的对象状态管理是非常有害的 旋转组后很难编辑,因为文本框 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 我已经附上了例子,你可以看看

以上是关于Fabric.js - 分组 iText 不可编辑的主要内容,如果未能解决你的问题,请参考以下文章

Fabric.js 与 IText

当包含的对象改变大小时,Fabric.js 自动调整组大小

Fabric.js 实现文本自动换行

Fabric.js 文本字段 - Android 问题

fabric.js - 基于父级旋转“子”对象,不带分组

大写 Fabric JS 上的文本输入