将形状从调色板拖到 Konvajs 舞台上

Dragging shapes from a palette onto a Konvajs stage

在 Konvajs 聊天流中,有人最近要求提供从调色板拖放到 Konvajs 库前面的 HTML5 canvas 的示例。没有现成的例子,我很好奇如何实现它。

我在 codepen 中回答了这个问题,但决定 post 在这里作为(我自己的)未来参考。请参阅下面我的回答。

这是我使用 jquery UI 可拖放的解决方案。 Konvajs 需要 jquery,因此使用 jquery UI 只是向前迈出了一小步。调色板是一组小 canvas 元素,每个可拖动项目绘制一个形状。调色板可以放在任何 html 元素上,不需要以任何方式附加到主舞台。

// Set up the canvas to catch the dragged shapes
var s1 = new Konva.Stage({container: 'container1', width: 500, height: 200});
// add a layer to host the 'dropped' shapes.
var layer1 = new Konva.Layer({draggable: false});
s1.add(layer1);

// set up the palette of draggable shapes - 5 sample shapes.
var palletteEle = $('#pallette');
var d, ps, l, c;
for (var i = 0; i<5; i = i + 1){
  // make a div to hold the shape
  d = $('<div id="shape' + i + '" class="draggable">Shape</div>')
  palletteEle.append(d)

  // make a mini stage to hold the shape
  ps = new Konva.Stage({container: 'shape' + i, width: 50, height: 50});

  // make a layer to hold the shape
  l = new Konva.Layer();

  // add layer to palette
  ps.add(l);

  // make a shape - red circles for example
  c = new Konva.Circle({x: 24, y: 24, radius: 22, fill: 'red', stroke: 'black'})    
  l.add(c);
  ps.draw();
}

// make a crosshair to give some idea of the drop location
var cross = new Konva.Line({points: [10, 0, 10, 20, 10, 10, 0, 10, 20, 10],
      stroke: 'gold',
      strokeWidth: 1,
      lineCap: 'round',
      lineJoin: 'round'})
layer1.add(cross);
//s1.draw();

// make the main stage a drop target
$('#container1').addClass('droppable');

// function to move the cross hairs
function moveCross(x, y){
  cross.x(x);
  y = y - $('#container1').offset().top;
  cross.y(y < 0 ? 0 : y);
  s1.draw();
}


// draggable setup. Movecross used to move the crosshairs. More work needed but shows the way. 
$( ".draggable" ).draggable({
    zIndex: 100, 
    helper: "clone", 
    opacity: 0.35,
    drag: function( event, ui ) {moveCross(ui.offset.left  , ui.offset.top + $(this).offset().top)}
});

// set up the droppable
$( ".droppable" ).droppable({
  drop: function( event, ui ) {
    dropShape(ui.position.left, ui.position.top)
  }
});

//  Function to create a new shape when we drop something dragged from the palette
function dropShape() {  
  var c1 = new Konva.Circle({x: cross.x(), y: cross.y(), radius: 22, fill: 'red', stroke: 'black'});
  layer1.add(c1);
  cross.x(0); cross.y(0);
  cross.moveToTop(); // move the cross to the top to stop going bahind previously dropped shapes.
  s1.draw();
}
p
{
  padding: 4px;
  
}
#container1
{
  display: inline-block;
  width: 500px; 
  height: 200px; 
  background-color: silver;
  overflow: hidden; 
}
#pallette
{
 height: 52px; width: 500px; 
 border: 1px solid #666;
  margin-bottom: 10px;
  z-index: 10;
}
.draggable
{
  width:50px;
  height: 50px;
  display: inline-block;
  border: 1px solid #666;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<script src="https://cdn.rawgit.com/konvajs/konva/1.6.5/konva.min.js"></script>
<p>Drag a red circle from the pallette and drop it on the grey canvas.
</p>
<div id='pallette'></div>
<div id='container1'></div>

我试过 Vanquished Wombat 的解决方案,这是一个很好的例子。但最终我希望我的调色板与 Konva 分开。所以我修改了原始片段以使用 Html5 拖放,没有任何 jQuery。请参阅下面的代码段。您可以将调色板中的星星和圆圈拖到 Konva canvas 中。目前您必须拖放到另一个形状上,但您可以轻松修改它以拖放到 canvas 上的任意位置。我为调色板项目使用文本,为拖动对象使用自定义图像只是为了好玩。但是您可以只使用 img 而不是使用 setDragImage 代码。

const CUSTOM_DATA_TYPE = 'text/x-node-type';

// Set up the canvas to catch the dragged shapes
var s1 = new Konva.Stage({
  container: 'container1',
  width: 500,
  height: 200
});
// add a layer to host the 'dropped' shapes.
var layer1 = new Konva.Layer({
  draggable: false
});
s1.add(layer1);

for (let t = 0; t < 10; t++) {
  let rect = document.getElementById('container1').getBoundingClientRect();
  let x = Math.floor(Math.random() * rect.width);
  let y = Math.floor(Math.random() * rect.height);
  let type = Math.floor(Math.random() * 100) % 2 == 0 ? 'circle' : 'star';
  dropShape(x, y, type);
}

//  Function to create a new shape when we drop something dragged from the palette
function dropShape(x, y, type) {
  var shape;

  if (type == 'circle') {
    shape = new Konva.Circle({
      x: x,
      y: y,
      radius: 22,
      fill: 'blue',
      stroke: 'black'
    });
  } else {
    shape = new Konva.Star({
      x: x,
      y: y,
      numPoints: 5,
      innerRadius: 10,
      outerRadius: 20,
      fill: 'purple',
      stroke: 'black'
    });
  }
  layer1.add(shape);
  s1.draw();
}

function cursorToCanvasPos(e) {
  let clientRect = document.getElementById('container1').getBoundingClientRect();
  let pointerPosition = {
    x: e.clientX - clientRect.x,
    y: e.clientY - clientRect.y,
  };
  return pointerPosition;
}

function getHoveredShape(e) {
  let pointerPosition = cursorToCanvasPos(e);

  return s1.getIntersection(pointerPosition);
}

function onDragStart(e, type) {
  // Do this or other things can mess with your drag
  e.stopPropagation();

  e.dataTransfer.setData(CUSTOM_DATA_TYPE, type);

  e.dataTransfer.effectAllowed = "all";

  var dragIcon = document.createElement('img');
  dragIcon.src = 'https://placehold.it/100x100';
  dragIcon.width = 100;
  e.dataTransfer.setDragImage(dragIcon, 150, 150);
}

function onDragOver(e) {
  // Might break if you don't have this
  e.stopPropagation();
  // Breaks for sure if you don't have this
  e.preventDefault();

  let thing = getHoveredShape(e);

  if (thing) {
    e.dataTransfer.dropEffect = "move";
    // Just fire off a custom even if you want to, this does nothing in this example.
    thing.fire('htmlDragOver');
  } else {
    e.dataTransfer.dropEffect = "none";
  }
}

function onDrop(e) {
  e.stopPropagation();

  let type = e.dataTransfer.getData(CUSTOM_DATA_TYPE);
  let pos = cursorToCanvasPos(e);

  dropShape(pos.x, pos.y, type);
}
p {
  padding: 4px;
}

#container1 {
  display: inline-block;
  width: 500px;
  height: 200px;
  background-color: silver;
  overflow: hidden;
}

#palette {
  height: 52px;
  width: 500px;
  border: 1px solid #666;
  margin-bottom: 10px;
  z-index: 10;
}

#palette span  {
  width: 50px;
  height: 25px;
  display: inline-block;
  border: 1px solid #666;
}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<script src="https://cdn.rawgit.com/konvajs/konva/1.6.5/konva.min.js"></script>


<p>Drag circle/star from the palette onto an existing shape on the canvas below.
</p>
<div id='palette'>
  <!-- Pre-load this image so it'll be used for our drag -->
  <img src="https://placehold.it/100x100" style="display: none">

  <span draggable="true" ondragstart="onDragStart(event, 'circle')">circle</span>
  <span draggable="true" ondragstart="onDragStart(event, 'star')">star</span>
</div>
<div id='container1' ondragover="onDragOver(event)" ondrop="onDrop(event)"></div>