跳到主要内容

如何使用 Konva 预览 Canvas 上的大型舞台?

需要生成 Canvas 的小型预览吗?

生成小型预览的方法很多。Konva 不提供自动执行此操作的方法。 但可以使用 Konva 方法手动生成预览区域。

下面介绍两种方式:克隆和使用图像。在大型应用中,最好根据应用 state 生成预览。

从主舞台克隆节点

可以克隆舞台或图层,并根据主 Canvas 区域的 state 更新其内部节点。 在预览中简化图形也很有意义,例如隐藏文本、移除描边和阴影等。

操作说明:尝试拖动圆形,并双击以添加新圆形。完成拖动(dragend)或添加新图形后,预览会更新。

import Konva from 'konva';

// Create preview container
const preview = document.createElement('div');
preview.id = 'preview';
preview.style.position = 'absolute';
preview.style.top = '2px';
preview.style.right = '2px';
preview.style.border = '1px solid grey';
preview.style.backgroundColor = 'lightgrey';
document.body.appendChild(preview);

const stage = new Konva.Stage({
  container: 'container',
  width: window.innerWidth,
  height: window.innerHeight,
});

const layer = new Konva.Layer();
stage.add(layer);

// generate random shapes
for (let i = 0; i < 10; i++) {
  const shape = new Konva.Circle({
    x: Math.random() * stage.width(),
    y: Math.random() * stage.height(),
    radius: Math.random() * 30 + 5,
    fill: Konva.Util.getRandomColor(),
    draggable: true,
    // each shape MUST have unique name
    // so we can easily update the preview clone by name
    name: 'shape-' + i,
  });
  layer.add(shape);
}

// create smaller preview stage
const previewStage = new Konva.Stage({
  container: 'preview',
  width: window.innerWidth / 4,
  height: window.innerHeight / 4,
  scaleX: 1 / 4,
  scaleY: 1 / 4,
});

// clone original layer, and disable all events on it
let previewLayer = layer.clone({ listening: false });
previewStage.add(previewLayer);

function updatePreview() {
  // we just need to update ALL nodes in the preview
  layer.children.forEach((shape) => {
    // find cloned node
    const clone = previewLayer.findOne('.' + shape.name());
    // update its position from the original
    clone.position(shape.position());
  });
}

stage.on('dragmove', updatePreview);

// add new shapes on double click or double tap
stage.on('dblclick dbltap', () => {
  const shape = new Konva.Circle({
    x: stage.getPointerPosition().x,
    y: stage.getPointerPosition().y,
    radius: Math.random() * 30 + 5,
    fill: Konva.Util.getRandomColor(),
    draggable: true,
    name: 'shape-' + layer.children.length,
  });
  layer.add(shape);

  // remove all layer
  previewLayer.destroy();
  // generate new one
  previewLayer = layer.clone({ listening: false });
  previewStage.add(previewLayer);
});

使用图像预览

也可以将舞台导出为图像,并将其用作预览。 出于性能考虑,不会在每个 dragmove 事件发生时更新预览。

import Konva from 'konva';

// Create preview container
const preview = document.createElement('img');
preview.id = 'preview';
preview.style.position = 'absolute';
preview.style.top = '2px';
preview.style.right = '2px';
preview.style.border = '1px solid grey';
preview.style.backgroundColor = 'lightgrey';
document.body.appendChild(preview);

const stage = new Konva.Stage({
  container: 'container',
  width: window.innerWidth,
  height: window.innerHeight,
});

const layer = new Konva.Layer();
stage.add(layer);

// generate random shapes
for (let i = 0; i < 10; i++) {
  const shape = new Konva.Circle({
    x: Math.random() * stage.width(),
    y: Math.random() * stage.height(),
    radius: Math.random() * 30 + 5,
    fill: Konva.Util.getRandomColor(),
    draggable: true,
    name: 'shape-' + i,
  });
  layer.add(shape);
}

function updatePreview() {
  const scale = 1 / 4;
  // use pixelRatio to generate smaller preview
  const url = stage.toDataURL({ pixelRatio: scale });
  preview.src = url;
}

// update preview only on dragend for performance
stage.on('dragend', updatePreview);

// add new shapes on double click or double tap
stage.on('dblclick dbltap', () => {
  const shape = new Konva.Circle({
    x: stage.getPointerPosition().x,
    y: stage.getPointerPosition().y,
    radius: Math.random() * 30 + 5,
    fill: Konva.Util.getRandomColor(),
    draggable: true,
    name: 'shape-' + layer.children.length,
  });
  layer.add(shape);
  updatePreview();
});

// show initial preview
updatePreview();