Skip to main content

How to Build an Interactive Floor Plan with JavaScript Canvas

Interactive floor plans and building maps are used for real estate, facility management, and wayfinding applications. Konva.js makes it easy to draw complex polygonal shapes with hover detection, tooltips, and custom styling per section.

Instructions: hover over sections of the building to see its description.

import Konva from 'konva';

function getData() {
  return {
    '1st Floor': {
      color: 'blue',
      points: [366, 298, 500, 284, 499, 204, 352, 183, 72, 228, 74, 274],
    },
    '2nd Floor': {
      color: 'red',
      points: [72, 228, 73, 193, 340, 96, 498, 154, 498, 191, 341, 171],
    },
    '3rd Floor': {
      color: 'yellow',
      points: [73, 192, 73, 160, 340, 23, 500, 109, 499, 139, 342, 93],
    },
    Gym: {
      color: 'green',
      points: [498, 283, 503, 146, 560, 136, 576, 144, 576, 278, 500, 283],
    },
  };
}

function updateTooltip(tooltip, x, y, text) {
  tooltip.getText().text(text);
  tooltip.position({
    x: x,
    y: y,
  });
  tooltip.show();
}

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

// add background image first
const imageLayer = new Konva.Layer();
stage.add(imageLayer);

Konva.Image.fromURL('https://konvajs.org/assets/line-building.png', function (bgImage) {
  bgImage.setAttrs({
    x: 1,
    y: 0,
  });
  imageLayer.add(bgImage);
});

const shapesLayer = new Konva.Layer();
const tooltipLayer = new Konva.Layer();

const tooltip = new Konva.Label({
  opacity: 0.75,
  visible: false,
  listening: false,
});

tooltip.add(
  new Konva.Tag({
    fill: 'black',
    pointerDirection: 'down',
    pointerWidth: 10,
    pointerHeight: 10,
    lineJoin: 'round',
    shadowColor: 'black',
    shadowBlur: 10,
    shadowOffsetX: 10,
    shadowOffsetY: 10,
    shadowOpacity: 0.5,
  })
);

tooltip.add(
  new Konva.Text({
    text: '',
    fontFamily: 'Calibri',
    fontSize: 18,
    padding: 5,
    fill: 'white',
  })
);

tooltipLayer.add(tooltip);

// get areas data
const areas = getData();

// draw areas
for (const key in areas) {
  const area = areas[key];
  const points = area.points;

  const shape = new Konva.Line({
    points: points,
    fill: area.color,
    opacity: 0,
    closed: true,
    name: 'area',
    // custom attr
    key: key,
  });

  shapesLayer.add(shape);
}

// add layers in correct order
stage.add(shapesLayer);
stage.add(tooltipLayer);

stage.on('mouseover', (evt) => {
  const shape = evt.target;
  if (shape && shape.name() === 'area') {  // only change opacity if it's an area shape
    shape.opacity(0.5);
  }
});

stage.on('mouseout', (evt) => {
  const shape = evt.target;
  if (shape && shape.name() === 'area') {  // only change opacity if it's an area shape
    shape.opacity(0);
    tooltip.hide();
  }
});

stage.on('mousemove', (evt) => {
  const shape = evt.target;
  if (shape && shape.name() === 'area') {  // only change opacity if it's an area shape
    const mousePos = stage.getPointerPosition();
    const x = mousePos.x;
    const y = mousePos.y - 5;
    updateTooltip(tooltip, x, y, shape.getAttr('key'));
  }
});