Skip to main content

如何使用 Konva 在画布上预览大舞台?

需要生成画布的小预览吗?

生成小预览的方法有很多种。Konva 并没有提供任何自动执行此操作的方法。 但我们可以使用 Konva 的方法手动生成预览区域。

我们将展示两种选项 - 克隆和使用图像。在大型应用程序中,从应用的状态生成预览更为合适。

从主舞台克隆节点

我们可以简单地克隆舞台或图层,并从主画布区域的状态更新其内部节点。 同时,简化预览上的形状也是有意义的。比如隐藏文本,去掉轮廓和阴影等。

说明:尝试拖动圆形,双击以添加新的圆形。预览将在您完成拖动(dragend)或添加新形状后更新。

import Konva from 'konva';

// 创建预览容器
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);

// 生成随机形状
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);
}

// 创建较小的预览舞台
const previewStage = new Konva.Stage({
  container: 'preview',
  width: window.innerWidth / 4,
  height: window.innerHeight / 4,
  scaleX: 1 / 4,
  scaleY: 1 / 4,
});

// 克隆原始图层,并禁用其所有事件
let previewLayer = layer.clone({ listening: false });
previewStage.add(previewLayer);

function updatePreview() {
  // 我们只需要更新预览中的所有节点
  layer.children.forEach((shape) => {
    // 找到克隆节点
    const clone = previewLayer.findOne('.' + shape.name());
    // 更新其位置
    clone.position(shape.position());
  });
}

stage.on('dragmove', updatePreview);

// 双击或双击以添加新形状
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);

  // 移除所有图层
  previewLayer.destroy();
  // 生成新的
  previewLayer = layer.clone({ listening: false });
  previewStage.add(previewLayer);
});

使用图像预览

或者我们可以将舞台导出为图像并将其用作预览。 出于性能原因,我们并不会在每次 dragmove 事件时更新预览。

import Konva from 'konva';

// 创建预览容器
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);

// 生成随机形状
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;
  // 使用像素比率生成较小的预览
  const url = stage.toDataURL({ pixelRatio: scale });
  preview.src = url;
}

// 仅在拖动结束时更新预览以提高性能
stage.on('dragend', updatePreview);

// 双击或双击以添加新形状
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();
});

// 显示初始预览
updatePreview();