JSFiddle - React, Tailwind, and code Playground
by velo_ninja
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Diagram Editor</title>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/konva.min.js"></script>
<style>
/* Reset & Base */
* { margin: 0; padding: 0; box-sizing: border-box; }
body { display: flex; height: 100vh; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: #eef1f5; }
/* Toolbar */
#toolbar {
width: 260px;
background: #2c3e50;
padding: 20px;
display: flex;
flex-direction: column;
gap: 15px;
box-shadow: 2px 0 10px rgba(0,0,0,0.1);
}
#toolbar button {
background: #3498db;
color: #fff;
border: none;
border-radius: 5px;
padding: 12px;
font-size: 14px;
cursor: pointer;
transition: background 0.3s ease, transform 0.2s ease;
}
#toolbar button:hover {
background: #5dade2;
transform: translateY(-2px);
}
/* Canvas Container */
#container {
flex: 1;
margin: 20px;
background: #fff;
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0,0,0,0.1);
position: relative;
overflow: hidden;
}
/* Context Menu */
#context-menu {
position: absolute;
display: none;
background: #fff;
border-radius: 5px;
border: 1px solid #ccc;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
z-index: 1000;
min-width: 180px;
}
#context-menu ul { list-style: none; }
#context-menu li {
padding: 10px 14px;
font-size: 13px;
color: #333;
cursor: pointer;
transition: background 0.2s ease;
}
#context-menu li:hover { background: #f4f6fa; }
/* Konva Canvas Styling */
#container canvas {
border-radius: 8px;
background: #fcfcfc;
}
</style>
</head>
<body>
<div id="toolbar">
<button id="addHopper">Add...
CSS
/* Global styles */
body {
margin: 0;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #f4f7fc;
display: flex;
height: 100vh;
color: #333;
}
/* Toolbar styles */
#toolbar {
width: 240px;
background: #2e3b4e;
color: white;
padding: 20px;
box-sizing: border-box;
border-right: 2px solid #2c3e50;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
}
/* Toolbar buttons */
#toolbar button {
background-color: #4caf50;
color: white;
font-size: 16px;
padding: 12px 20px;
margin: 10px 0;
border: none;
border-radius: 5px;
width: 100%;
cursor: pointer;
transition: background-color 0.3s ease;
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.1);
}
#toolbar button:hover {
background-color: #45a049;
}
#toolbar button:active {
background-color: #388e3c;
}
/* Container for the canvas area */
#container {
flex-grow: 1;
position: relative;
padding: 25px;
margin-left: 25px;
margin-right: 25px;
margin-top: 25px;
margin-bottom: 25px;
background-color: #fff;
border-radius: 8px;
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.1);
overflow: hidden;
}
/* Context menu */
#context-menu {
position: absolute;
display: none;
background-color: #fff;
border-radius: 8px;
border: 1px solid #ccc;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
z-index: 10;
width: 200px;
}
#context-menu ul {
list-style: none;
margin: 0;
padding: 0;
}
#context-menu li {
padding: 10px;
cursor: pointer;
font-size: 14px;
color: #333;
transition: background-color 0.3s ease, padding 0.2s ease;
}
#context-menu li:hover {
background-color: #f4f4f4;
padding-left: 15px;
}
/* Animation for the context menu */
@keyframes menuSlideIn {
from {
transform: translateX(20px);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
#context-menu {
animation: menuSlideIn 0.3s ease-out;
}
/* A more modern and clean scrollbar style...
JavaScript
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth - 200,
height: window.innerHeight
});
const wireLayer = new Konva.Layer();
const elementLayer = new Konva.Layer();
stage.add(wireLayer);
stage.add(elementLayer);
let elements = [];
let wires = [];
let wireStart = null;
let contextMenuTarget = null;
// Add element function to create shapes with labels
function addElement(type) {
const group = new Konva.Group({
x: 100 + Math.random() * 300,
y: 100 + Math.random() * 200,
draggable: true
});
let shape;
switch(type) {
case 'hopper':
shape = new Konva.Rect({ width: 100, height: 50, fill: '#ffa', stroke: 'black', strokeWidth: 2 });
break;
case 'control':
shape = new Konva.Circle({ radius: 30, fill: '#aff', stroke: 'black', strokeWidth: 2 });
break;
case 'vacuum':
shape = new Konva.RegularPolygon({ sides: 3, radius: 40, fill: '#faf', stroke: 'black', strokeWidth: 2 });
break;
default:
shape = new Konva.Rect({ width: 100, height: 50, fill: '#ddd', stroke: 'black', strokeWidth: 2 });
}
const label = new Konva.Text({ text: type, fontSize: 14, x: 0, y: shape.height() + 10, align: 'center' });
label.x((shape.width() - label.width()) / 2); // Center label horizontally
group.add(shape);
group.add(label);
// Create the comment but keep it invisible until hover
let comment = null;
// Show comment on hover
group.on('mouseenter', () => {
// Create the comment only on hover (if it doesn't exist yet)
if (!comment) {
comment = new Konva.Text({
text: "Comment", // Placeholder comment text
fontSize: 14,
fill: 'blue',
opacity: 0, // Hidden by default
x: 0,
y: shape.height() + 10
});
group.add(comment);
}
// Show the comment
comment.opacity(1); // Show comment on hover
group.getLayer().batchDraw(); // Redraw the layer
// Change cursor to hand when...