How to preview large stage on canvas with Konva?

Need to generate a small preview of the canvas?

There are many ways to generate small preview. Konva doesn’t provide any methods to do this automatically.
But we can use Konva methods to generate preview area manually.

We will show two options - cloning and using images. In large applications it is better to generate preview from the state of the app.

Clone nodes from the main stage.

So we can just clone the stage or the layer and update its internal nodes from the state of the main canvas area.

Also it will make sense to simplify shapes on the preview. Like hide texts, remove strokes and shadows, etc.

Instructions: try to drag a circle. See how the preview is updating. Double click to add a new shape.

Stage_Preview_Clone.htmlview raw
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/[email protected]/konva.min.js"></script>
<meta charset="utf-8" />
<title>Konva Canvas Stage Preview Demo</title>
<style>
body {
margin: 0;
padding: 0;
overflow: hidden;
background-color: #f0f0f0;
}

#preview {
position: absolute;
top: 2px;
right: 2px;
border: 1px solid grey;
background-color: lightgrey;
}
</style>
</head>

<body>
<div id="container"></div>
<div id="preview"></div>
<script>
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 (var 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 MUSH have uniq 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
// we will use "let" here, because we can redefine layer later
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,
// each shape MUSH have uniq name
// so we can easily update the preview clone by name
name: 'shape-' + layer.children.length,
});
layer.add(shape);

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

Use image preview

Or we can export the stage to an image and use it as a preview.

For performance reasons we are not updating the preview on every dragmove events.

Stage_Preview_Image.htmlview raw
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/[email protected]/konva.min.js"></script>
<meta charset="utf-8" />
<title>Konva Canvas Stage Preview Demo</title>
<style>
body {
margin: 0;
padding: 0;
overflow: hidden;
background-color: #f0f0f0;
}

#preview {
position: absolute;
top: 2px;
right: 2px;
border: 1px solid grey;
background-color: lightgrey;
}
</style>
</head>

<body>
<div id="container"></div>
<img id="preview" />
<script>
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 (var 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 MUSH have uniq name
// so we can easily update the preview clone by name
name: 'shape-' + i,
});
layer.add(shape);
}

function updatePreview() {
const scale = 1 / 4;
// use pixelRatio to generate smaller preview
const url = stage.toDataURL({ pixelRatio: scale });
document.getElementById('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,
// each shape MUSH have uniq name
// so we can easily update the preview clone by name
name: 'shape-' + layer.children.length,
});
layer.add(shape);

updatePreview();
});

// show initial preview
updatePreview();
</script>
</body>
</html>
Enjoying Konva? Please consider to support the project.