怎么用JS+CSS+HTML制作原生时序图

发布时间:2022-02-22 11:15:15 作者:iii
来源:亿速云 阅读:140
# 怎么用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>

1.2 CSS基础样式

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

二、核心功能实现

2.1 参与者(Actor)管理

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

2.2 消息(Message)管理

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

2.3 交互功能实现

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

三、高级功能扩展

3.1 自动布局优化

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

3.2 导出图片功能

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导入导出

完整代码已包含所有核心功能,读者可以直接复制使用或根据需求进行扩展。 “`

推荐阅读:
  1. php用原生还是框架好
  2. 原生和jQuery的ajax怎么用

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

html css js

上一篇:jQuery中$this.index()怎么用

下一篇:如何使用jQuery样式操作css

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》