如何创建Konva-React上下文菜单 [英] How to create a Konva-React context menu

查看:166
本文介绍了如何创建Konva-React上下文菜单的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

据我所知,Konva并没有一种简单/内置的方式来创建右键单击对象的上下文菜单。我正在忙于一个需要使用上下文菜单的项目,所以我想我自己创建一个。

To the best of my knowledge there isn't an easy/built in way with Konva to create a context menu for right clicking on objects. I am busy working on a project which requires the use of context menus, so I thought I'd just create my own.

不用说我对Konva还很陌生,所以我希望SO上的某人可能有更多的经验来帮助我克服最后的障碍。

Needless to say I am fairly new to Konva, so I was hoping someone on SO might have more experience to help me get over the last hurdles.

我创建了一个沙箱,位于这里

I have create a sandbox, located HERE

要求为:


  1. 对象应该是可拖动的。 (我从Konva沙箱复制了一个工作示例。)

  2. 在右键单击时,对象应显示一个上下文菜单。

  3. 上下文菜单应是动态的,因此允许多个项目,每个项目都在单击时执行自己的回调。

  4. 选择后,应该关闭上下文菜单。

  1. An object should be draggable. (I copied a working example off the Konva sandbox.)
  2. An object should show a context menu when right clicked upon.
  3. The context menu should be dynamic, thus allow for multiple items, each executing its own callback when clicked upon.
  4. Once a selection has been made, the context menu should be closed.

到目前为止,我已经把大部分事情弄对了,但是我一直在努力的事情是:

Thus far I have gotten most of it right, but the things I am struggling with are:


  1. 我无法弄清楚如何将鼠标悬停在一个上下文菜单项上,使其突出显示,然后移至下一个应突出显示的菜单项,并将旧的菜单项还原为原始设置。

  2. 从上下文菜单中移出将重新绘制整个对象。我不明白为什么。

  3. 单击一项会触发两项的回调。为什么?我的目标是单击的特定菜单项,但同时获得了两个菜单项?

  4. 这点不是一个错误,而是我不确定如何进行操作:我将如何避免多次用户右键单击对象时要创建的上下文菜单?从概念上讲,我知道我可以使用上下文菜单的名称在图层(?)中搜索任何项目并将其关闭,但是我不知道如何执行此操作。

  1. I cannot figure out how to hover over one context menu item, have it highlighted, then move to the next which should be highlighted and the old one restored to original settings.
  2. Moving out of the context menu repaints the whole object. I don't understand why.
  3. Clicking on one items fires both item's callbacks. Why? I a targeting the specific menu item which was clicked on, but getting both?
  4. This point is less of a bug and more that I am unsure as how to proceed: How would I prevent multiple context menus to be create if a user right clicks multiple times on the object? Conceptually I understand that I could search for any items in a layer(?) with the name of the context menu and close it, however I have no idea how to do this.

我将不胜感激。预先感谢。

I would appreciate any help. Thanks in advance.

推荐答案

不是在反应,而是纯JS,但是,它可以照亮您的某些需求

Not in react but plain JS I am afraid, but it shines a light on some of what you will have to do.

单击粉色圆圈,然后选择选项2,然后单击子选项2。

Click the pink circle, then take option 2 and click sub-option 2.

需要更多工作的区域:


  • 通过JSON传递菜单配置数据

  • 使添加回调成为一种方法类中

  • 在皮革上添加超时以允许鼠标晃动

  • 如何在用户单击鼠标或单击时处理隐藏子菜单另一个选择

  • 添加显示&隐藏动画

  • deliver the menu config data via JSON
  • make adding callbacks a method within the class
  • add a timeout on the hide to allow shaky mouse hands
  • how to handle hiding sub-menus when user mouse-outs or clicks another option
  • add reveal & hide animations

// Set up the canvas / stage
var stage = new Konva.Stage({container: 'container1', width: 600, height: 300});

// Add a layer some sample shapes
var layer = new Konva.Layer({draggable: false});
stage.add(layer);

// draw some shapes.
var circle = new Konva.Circle({ x: 80, y: 80, radius: 30, fill: 'Magenta'});
layer.add(circle);

var rect = new Konva.Rect({ x: 80, y: 80, width: 60, height: 40, fill: 'Cyan'});
layer.add(rect);

stage.draw();

// that is the boring bit over - now menu fun

// I decided to set up a plain JS object to define my menu structure - could easily receive from async in JSON format. [Homework #1]
var menuData = { options: [
  {key: 'opt1', text: 'Option 1', callBack: null},
  {key: 'opt2', text: 'Option 2', callBack: null, 
    options: [ 
      {key: 'opt2-1', text: 'Sub 1', callBack: null}, 
      {key: 'opt2-2', text: 'Sub 2', callBack: null} 
   ]
  },
  {key: 'opt3', text: 'Option 3', callBack: null},
  {key: 'opt4', text: 'Option 4', callBack: null}  
]};

// Define a menu 'class' object.
var menu = function(menuData) {

  var optHeight = 20;  // couple of dimension constants. 
  var optWidth = 100;
  var colors = ['white','gold'];
  
  this.options = {}; // prepare an associative list accessible by key - will put key into the shape as the name so we can can get from click event to this entry

  this.menuGroup = new Konva.Group({}); // prepare a canvas group to hold the option rects for this level. Make it accessible externally by this-prefix

  var _this = this;  // put a ref for this-this to overcome this-confusion later. 

  // recursive func to add a menu level and assign its option components.
  var addHost = function(menuData, hostGroup, level, pos){  // params are the data for the level, the parent group, the level counter, and an offset position counter
    var menuHost = new Konva.Group({ visible: false});  // make a canvas group to contain new options
    hostGroup.add(menuHost); // add to the parent group

    // for every option at this level
    for (var i = 0; i < menuData.options.length; i = i + 1 ){
      var option = menuData.options[i]; // get the option into a var for readability

      // Add a rect as the background for the visible option in the menu.
      option.optionRect = new Konva.Rect({x: (level * optWidth), y: (pos + i) * optHeight, width: optWidth, height: optHeight, fill: colors[0], stroke: 'silver', name: option.key });
      option.optionText = new Konva.Text({x: (level * optWidth), y: (pos + i) * optHeight, width: optWidth, height: optHeight, text: ' ' + option.text, listening: false, verticalAlign: 'middle'})
  console.log(option.optionText.height())
      option.optionRect
        .on('mouseover', function(){
          this.fill(colors[1])
          layer.draw();
          })
        .on('mouseleave', function(){
          this.fill(colors[0])
          layer.draw();
          })
      
      // click event listener for the menu option 
      option.optionRect.on('click', function(e){

        var key = this.name(); // get back the key we stashed in the rect so we can get the options object from the lookup list 

        if (_this.options[key] && (typeof _this.options[key].callback == 'function')){ // is we found an option and it has a real function as a callback then call it.
          _this.options[key].callback();
        } 
        else {
          console.log('No callback for ' + key)
        }
        
      })
      menuHost.add(option.optionRect); // better add the rect and text to the canvas or we will not see it
      menuHost.add(option.optionText);       
      
      _this.options[option.key] = option; // stash the option in the lookup list for later retrieval in click handlers.

      // pay attention Bond - if this menu level has a sub-level then we call into this function again.  
      if (option.options){
        
        var optionGroup = addHost(option, menuHost, level + 1, i)  // params 3 & 4 are menu depth and popout depth for positioning the rects. 

        // make an onclick listener to show the sub-options
        option.callback = function(e){
          optionGroup.visible(true);
          layer.draw();
        }        
      }
    }
    return menuHost; // return the konva group 
  } 

  // so - now we can call out addHost function for the top level of the menu and it will recurse as needed down the sub-options.
  var mainGroup = addHost(menuData, this.menuGroup, 0, 0);

  // lets be nice and make a show() method that takes a position x,y too.
  this.show = function(location){
    location.x = location.x - 10;  // little offset to get the group under the mouse
    location.y = location.y - 10;
    
    mainGroup.position(location);
    mainGroup.show(); // notice we do not draw the layer here - leave that to the caller to avoid too much redraw.
  }

  // and if we have a show we better have a hide.
  this.hide = function(){
    mainGroup.hide();
  }
  
  // and a top-level group listener for mouse-out to hide the menu. You might want to put a timer on this [Homework #3]
  mainGroup.on('mouseleave', function(){
    this.hide();
    layer.draw();
  })
  
   
  // end of the menu class object.
  return this;
}


// ok - now we can get our menu data turned into a menu
var theMenu = new menu(menuData);
layer.add(theMenu.menuGroup); // add the returned canvas group to the layer
layer.draw();  // and never forget to draw the layer when it is time!

//
// now we can add some arbitrary callbacks to some of the options.
//
// make a trivial function to pop a message when we click option 1
var helloFunc = function(){
  alert('hello')
}
// make this the callback for opt1 - you can move this inside the menu class object as a setCallback(name, function()) method if you prefer [homework #2] 
theMenu.options['opt1'].callback = helloFunc;

// put a function on sub2 just to show it works.
theMenu.options['opt2-2'].callback = function(){ alert('click on sub-2') };

// and the original reason for this - make it a context menu on a shape.
circle.on('click', function(e){
  theMenu.show({x: e.evt.offsetX, y: e.evt.offsetY});
    layer.draw(); 
})

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/konva/2.5.1/konva.min.js"></script>
<div id='container1' style="width: 300px, height: 200px; background-color: silver;"></div>

这篇关于如何创建Konva-React上下文菜单的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆