Skip to main content

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.

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

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.

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

Instructions: Try to drag circles and double-click to add new ones. The preview will update after you finish dragging (dragend) or add a new shape.