您好,登录后才能下订单哦!
密码登录
登录注册
点击 登录注册 即表示同意《亿速云用户服务条款》
# 怎么用JS+CSS+HTML制作原生时序图
时序图(Sequence Diagram)是UML中展示对象间交互关系的图表,在软件开发中常用于描述系统模块间的调用流程。本文将详细介绍如何不依赖第三方库,仅用原生HTML+CSS+JavaScript实现一个可交互的时序图生成器。
## 一、基础结构设计
### 1.1 HTML骨架搭建
```html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>原生时序图生成器</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="container">
<div class="toolbar">
<button id="add-actor">添加参与者</button>
<button id="add-message">添加消息</button>
<button id="export-png">导出PNG</button>
</div>
<div class="diagram-container">
<svg id="diagram-svg" width="100%" height="600"></svg>
<div id="actor-inputs"></div>
</div>
</div>
<script src="script.js"></script>
</body>
</html>
body {
font-family: 'Arial', sans-serif;
margin: 0;
padding: 20px;
background-color: #f5f5f5;
}
.container {
background: white;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
padding: 20px;
max-width: 1200px;
margin: 0 auto;
}
.toolbar {
margin-bottom: 20px;
}
button {
padding: 8px 15px;
margin-right: 10px;
background: #4CAF50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background: #45a049;
}
#diagram-svg {
background-color: #fff;
border: 1px solid #ddd;
}
.actor {
cursor: move;
user-select: none;
}
.message {
stroke: #333;
stroke-width: 2;
marker-end: url(#arrowhead);
}
.lifeline {
stroke: #999;
stroke-width: 1;
stroke-dasharray: 5,5;
}
class SequenceDiagram {
constructor() {
this.actors = [];
this.messages = [];
this.nextActorId = 1;
this.nextMessageId = 1;
this.svg = document.getElementById('diagram-svg');
this.initSVG();
this.setupEventListeners();
}
initSVG() {
// 创建箭头标记定义
const defs = document.createElementNS("http://www.w3.org/2000/svg", "defs");
const marker = document.createElementNS("http://www.w3.org/2000/svg", "marker");
marker.setAttribute("id", "arrowhead");
marker.setAttribute("markerWidth", "10");
marker.setAttribute("markerHeight", "7");
marker.setAttribute("refX", "9");
marker.setAttribute("refY", "3.5");
marker.setAttribute("orient", "auto");
const polygon = document.createElementNS("http://www.w3.org/2000/svg", "polygon");
polygon.setAttribute("points", "0 0, 10 3.5, 0 7");
marker.appendChild(polygon);
defs.appendChild(marker);
this.svg.appendChild(defs);
}
addActor(name = `参与者${this.nextActorId}`) {
const actor = {
id: `actor-${this.nextActorId++}`,
name,
x: 100 + (this.actors.length * 150),
y: 50
};
this.actors.push(actor);
this.render();
return actor;
}
renderActors() {
// 清除现有参与者
document.querySelectorAll('.actor-group').forEach(el => el.remove());
this.actors.forEach(actor => {
const group = document.createElementNS("http://www.w3.org/2000/svg", "g");
group.setAttribute("class", "actor-group");
group.setAttribute("id", actor.id);
// 参与者矩形
const rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
rect.setAttribute("x", actor.x - 40);
rect.setAttribute("y", actor.y);
rect.setAttribute("width", 80);
rect.setAttribute("height", 40);
rect.setAttribute("rx", 5);
rect.setAttribute("ry", 5);
rect.setAttribute("fill", "#e3f2fd");
rect.setAttribute("stroke", "#2196F3");
// 参与者文本
const text = document.createElementNS("http://www.w3.org/2000/svg", "text");
text.setAttribute("x", actor.x);
text.setAttribute("y", actor.y + 25);
text.setAttribute("text-anchor", "middle");
text.setAttribute("fill", "#1565C0");
text.textContent = actor.name;
// 生命线
const lifeline = document.createElementNS("http://www.w3.org/2000/svg", "line");
lifeline.setAttribute("x1", actor.x);
lifeline.setAttribute("y1", actor.y + 40);
lifeline.setAttribute("x2", actor.x);
lifeline.setAttribute("y2", "550");
lifeline.setAttribute("class", "lifeline");
group.appendChild(rect);
group.appendChild(text);
group.appendChild(lifeline);
this.svg.appendChild(group);
});
}
}
class SequenceDiagram {
// ... 接上文代码
addMessage(fromId, toId, message = "消息") {
const fromActor = this.actors.find(a => a.id === fromId);
const toActor = this.actors.find(a => a.id === toId);
if (!fromActor || !toActor) return;
const msg = {
id: `msg-${this.nextMessageId++}`,
from: fromId,
to: toId,
text: message,
y: 100 + (this.messages.length * 40)
};
this.messages.push(msg);
this.render();
}
renderMessages() {
document.querySelectorAll('.message-group').forEach(el => el.remove());
this.messages.forEach(msg => {
const fromActor = this.actors.find(a => a.id === msg.from);
const toActor = this.actors.find(a => a.id === msg.to);
if (!fromActor || !toActor) return;
const group = document.createElementNS("http://www.w3.org/2000/svg", "g");
group.setAttribute("class", "message-group");
// 消息线
const line = document.createElementNS("http://www.w3.org/2000/svg", "line");
line.setAttribute("x1", fromActor.x);
line.setAttribute("y1", msg.y);
line.setAttribute("x2", toActor.x);
line.setAttribute("y2", msg.y);
line.setAttribute("class", "message");
// 消息文本
const text = document.createElementNS("http://www.w3.org/2000/svg", "text");
const midX = (fromActor.x + toActor.x) / 2;
text.setAttribute("x", midX);
text.setAttribute("y", msg.y - 5);
text.setAttribute("text-anchor", "middle");
text.textContent = msg.text;
group.appendChild(line);
group.appendChild(text);
this.svg.appendChild(group);
});
}
render() {
this.renderActors();
this.renderMessages();
}
}
class SequenceDiagram {
// ... 接上文代码
setupEventListeners() {
// 添加参与者按钮
document.getElementById('add-actor').addEventListener('click', () => {
const name = prompt("输入参与者名称:", `参与者${this.nextActorId}`);
if (name) this.addActor(name);
});
// 添加消息按钮
document.getElementById('add-message').addEventListener('click', () => {
if (this.actors.length < 2) {
alert("至少需要两个参与者才能添加消息");
return;
}
const fromId = prompt("输入发送者ID:", this.actors[0].id);
const toId = prompt("输入接收者ID:", this.actors[1].id);
const message = prompt("输入消息内容:", "示例消息");
if (fromId && toId && message) {
this.addMessage(fromId, toId, message);
}
});
// 实现拖动功能
this.svg.addEventListener('mousedown', (e) => {
const actorElement = e.target.closest('.actor-group');
if (!actorElement) return;
const actorId = actorElement.id;
const actor = this.actors.find(a => a.id === actorId);
if (!actor) return;
let isDragging = true;
const startX = e.clientX;
const startY = e.clientY;
const startActorX = actor.x;
const moveHandler = (e) => {
if (!isDragging) return;
const dx = e.clientX - startX;
actor.x = startActorX + dx;
this.render();
};
const upHandler = () => {
isDragging = false;
document.removeEventListener('mousemove', moveHandler);
document.removeEventListener('mouseup', upHandler);
};
document.addEventListener('mousemove', moveHandler);
document.addEventListener('mouseup', upHandler);
});
}
}
autoLayout() {
// 等距排列参与者
const spacing = this.svg.clientWidth / (this.actors.length + 1);
this.actors.forEach((actor, i) => {
actor.x = spacing * (i + 1);
});
// 等距排列消息
const startY = 120;
const messageSpacing = (500 - startY) / (this.messages.length + 1);
this.messages.forEach((msg, i) => {
msg.y = startY + (messageSpacing * (i + 1));
});
this.render();
}
setupExport() {
document.getElementById('export-png').addEventListener('click', () => {
const svgData = new XMLSerializer().serializeToString(this.svg);
const canvas = document.createElement("canvas");
canvas.width = this.svg.clientWidth;
canvas.height = this.svg.clientHeight;
const ctx = canvas.getContext("2d");
const img = new Image();
img.onload = () => {
ctx.drawImage(img, 0, 0);
const png = canvas.toDataURL("image/png");
const link = document.createElement("a");
link.download = "时序图.png";
link.href = png;
link.click();
};
img.src = "data:image/svg+xml;base64," + btoa(unescape(encodeURIComponent(svgData)));
});
}
// 初始化时序图
document.addEventListener('DOMContentLoaded', () => {
const diagram = new SequenceDiagram();
// 添加示例数据
diagram.addActor("用户");
diagram.addActor("系统");
diagram.addActor("数据库");
diagram.addMessage("actor-1", "actor-2", "登录请求");
diagram.addMessage("actor-2", "actor-3", "查询用户");
diagram.addMessage("actor-3", "actor-2", "返回数据");
diagram.addMessage("actor-2", "actor-1", "登录成功");
diagram.autoLayout();
diagram.setupExport();
});
通过上述实现,我们完成了一个具备以下功能的原生时序图编辑器: 1. 动态添加参与者 2. 在参与者之间添加消息 3. 拖拽调整参与者位置 4. 自动布局功能 5. 导出为PNG图片
这个实现避免了第三方库的依赖,展示了如何使用原生Web技术构建专业图表工具。如需进一步增强,可以考虑: - 添加消息类型区分(同步/异步/返回) - 实现撤销/重做功能 - 增加激活条(Activation Bar) - 支持JSON导入导出
完整代码已包含所有核心功能,读者可以直接复制使用或根据需求进行扩展。 “`
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。