跳到主要内容

Canvas 小地图(Minimap)——预览大型 Konva 舞台

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

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

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

从主舞台克隆节点

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

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

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();