Skip to main content

HTML5 Canvas Simple Window Designer

That is a very simple demo that draws a window frame.

Instructions: You can change its width and height

import Konva from 'konva';

var width = window.innerWidth;
var height = window.innerHeight;

var stage = new Konva.Stage({
  container: 'container',
  width: width,
  height: height,
});

var layer = new Konva.Layer();
stage.add(layer);

var widthInput = document.createElement('input');
widthInput.type = 'number';
widthInput.value = '1000';
var widthLabel = document.createElement('span');
widthLabel.innerText = 'Width: ';
var widthContainer = document.createElement('div');
widthContainer.style.float = 'left';
widthContainer.style.padding = '10px';
widthContainer.appendChild(widthLabel);
widthContainer.appendChild(widthInput);

var heightInput = document.createElement('input');
heightInput.type = 'number';
heightInput.value = '2000';
var heightLabel = document.createElement('span');
heightLabel.innerText = 'Height: ';
var heightContainer = document.createElement('div');
heightContainer.style.float = 'left';
heightContainer.style.padding = '10px';
heightContainer.appendChild(heightLabel);
heightContainer.appendChild(heightInput);

var controls = document.createElement('div');
controls.style.position = 'absolute';
controls.style.top = '4px';
controls.style.left = '4px';
controls.appendChild(widthContainer);
controls.appendChild(heightContainer);
document.body.appendChild(controls);

function createFrame(frameWidth, frameHeight) {
  var padding = 70;
  var group = new Konva.Group();
  var top = new Konva.Line({
    points: [
      0,
      0,
      frameWidth,
      0,
      frameWidth - padding,
      padding,
      padding,
      padding,
    ],
    fill: 'white',
  });

  var left = new Konva.Line({
    points: [
      0,
      0,
      padding,
      padding,
      padding,
      frameHeight - padding,
      0,
      frameHeight,
    ],
    fill: 'white',
  });

  var bottom = new Konva.Line({
    points: [
      0,
      frameHeight,
      padding,
      frameHeight - padding,
      frameWidth - padding,
      frameHeight - padding,
      frameWidth,
      frameHeight,
    ],
    fill: 'white',
  });

  var right = new Konva.Line({
    points: [
      frameWidth,
      0,
      frameWidth,
      frameHeight,
      frameWidth - padding,
      frameHeight - padding,
      frameWidth - padding,
      padding,
    ],
    fill: 'white',
  });

  var glass = new Konva.Rect({
    x: padding,
    y: padding,
    width: frameWidth - padding * 2,
    height: frameHeight - padding * 2,
    fill: 'lightblue',
  });

  group.add(glass, top, left, bottom, right);

  group.find('Line').forEach((line) => {
    line.closed(true);
    line.stroke('black');
    line.strokeWidth(1);
  });

  return group;
}

function createInfo(frameWidth, frameHeight) {
  var offset = 20;
  var arrowOffset = offset / 2;
  var arrowSize = 5;

  var group = new Konva.Group();
  var lines = new Konva.Shape({
    sceneFunc: function (ctx) {
      ctx.fillStyle = 'grey';
      ctx.lineWidth = 0.5;

      ctx.moveTo(0, 0);
      ctx.lineTo(-offset, 0);

      ctx.moveTo(0, frameHeight);
      ctx.lineTo(-offset, frameHeight);

      ctx.moveTo(0, frameHeight);
      ctx.lineTo(0, frameHeight + offset);

      ctx.moveTo(frameWidth, frameHeight);
      ctx.lineTo(frameWidth, frameHeight + offset);

      ctx.stroke();
    },
  });

  var leftArrow = new Konva.Shape({
    sceneFunc: function (ctx) {
      // top pointer
      ctx.moveTo(-arrowOffset - arrowSize, arrowSize);
      ctx.lineTo(-arrowOffset, 0);
      ctx.lineTo(-arrowOffset + arrowSize, arrowSize);

      // line
      ctx.moveTo(-arrowOffset, 0);
      ctx.lineTo(-arrowOffset, frameHeight);

      // bottom pointer
      ctx.moveTo(-arrowOffset - arrowSize, frameHeight - arrowSize);
      ctx.lineTo(-arrowOffset, frameHeight);
      ctx.lineTo(-arrowOffset + arrowSize, frameHeight - arrowSize);

      ctx.strokeShape(this);
    },
    stroke: 'grey',
    strokeWidth: 0.5,
  });

  var bottomArrow = new Konva.Shape({
    sceneFunc: function (ctx) {
      // top pointer
      ctx.translate(0, frameHeight + arrowOffset);
      ctx.moveTo(arrowSize, -arrowSize);
      ctx.lineTo(0, 0);
      ctx.lineTo(arrowSize, arrowSize);

      // line
      ctx.moveTo(0, 0);
      ctx.lineTo(frameWidth, 0);

      // bottom pointer
      ctx.moveTo(frameWidth - arrowSize, -arrowSize);
      ctx.lineTo(frameWidth, 0);
      ctx.lineTo(frameWidth - arrowSize, arrowSize);

      ctx.strokeShape(this);
    },
    stroke: 'grey',
    strokeWidth: 0.5,
  });

  // left text
  var leftLabel = new Konva.Label();

  leftLabel.add(
    new Konva.Tag({
      fill: 'white',
      stroke: 'grey',
    })
  );
  var leftText = new Konva.Text({
    text: heightInput.value + 'mm',
    padding: 2,
    fill: 'black',
  });

  leftLabel.add(leftText);
  leftLabel.position({
    x: -arrowOffset - leftText.width(),
    y: frameHeight / 2 - leftText.height() / 2,
  });

  leftLabel.on('click tap', function () {
    createInput('height', this.getAbsolutePosition(), leftText.size());
  });

  // bottom text
  var bottomLabel = new Konva.Label();

  bottomLabel.add(
    new Konva.Tag({
      fill: 'white',
      stroke: 'grey',
    })
  );
  var bottomText = new Konva.Text({
    text: widthInput.value + 'mm',
    padding: 2,
    fill: 'black',
  });

  bottomLabel.add(bottomText);
  bottomLabel.position({
    x: frameWidth / 2 - bottomText.width() / 2,
    y: frameHeight + arrowOffset,
  });

  bottomLabel.on('click tap', function () {
    createInput('width', this.getAbsolutePosition(), bottomText.size());
  });

  group.add(lines, leftArrow, bottomArrow, leftLabel, bottomLabel);

  return group;
}

function createInput(metric, pos, size) {
  var wrap = document.createElement('div');
  wrap.style.position = 'absolute';
  wrap.style.backgroundColor = 'rgba(0,0,0,0.1)';
  wrap.style.top = 0;
  wrap.style.left = 0;
  wrap.style.width = '100%';
  wrap.style.height = '100%';

  document.body.appendChild(wrap);

  var input = document.createElement('input');
  input.type = 'number';

  var similarInput = metric === 'width' ? widthInput : heightInput;
  input.value = similarInput.value;

  input.style.position = 'absolute';
  input.style.top = pos.y + 3 + 'px';
  input.style.left = pos.x + 'px';

  input.style.height = size.height + 3 + 'px';
  input.style.width = size.width + 3 + 'px';

  wrap.appendChild(input);

  input.addEventListener('change', function () {
    similarInput.value = input.value;
    updateCanvas();
  });

  input.addEventListener('input', function () {
    similarInput.value = input.value;
    updateCanvas();
  });

  wrap.addEventListener('click', function (e) {
    if (e.target === wrap) {
      document.body.removeChild(wrap);
    }
  });

  input.addEventListener('keyup', function (e) {
    if (e.keyCode === 13) {
      document.body.removeChild(wrap);
    }
  });
}

function updateCanvas() {
  layer.children.forEach((child) => child.destroy());

  var frameWidth = parseInt(widthInput.value, 10);
  var frameHeight = parseInt(heightInput.value, 10);

  var wr = stage.width() / frameWidth;
  var hr = stage.height() / frameHeight;

  var ratio = Math.min(wr, hr) * 0.8;

  var frameOnScreenWidth = frameWidth * ratio;
  var frameOnScreenHeight = frameHeight * ratio;

  var group = new Konva.Group({});

  group.x(Math.round(stage.width() / 2 - frameOnScreenWidth / 2) + 0.5);
  group.y(Math.round(stage.height() / 2 - frameOnScreenHeight / 2) + 0.5);

  layer.add(group);

  var frameGroup = createFrame(frameWidth, frameHeight);
  frameGroup.scale({ x: ratio, y: ratio });
  group.add(frameGroup);

  var infoGroup = createInfo(frameOnScreenWidth, frameOnScreenHeight);
  group.add(infoGroup);
}

widthInput.addEventListener('change', updateCanvas);
widthInput.addEventListener('input', updateCanvas);

heightInput.addEventListener('change', updateCanvas);
heightInput.addEventListener('input', updateCanvas);

updateCanvas();