deepseek v3-0324 化学键线式Canvas编辑器设计

化学键线式Canvas编辑器设计

下面是一个基于HTML5 Canvas的化学键线式编辑器的基本实现方案。这个编辑器允许用户绘制有机化学中常见的键线式结构。

基本HTML结构

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>化学键线式编辑器</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            margin: 20px;
        }

        #editor-container {
            display: flex;
            flex-direction: column;
            width: 800px;
            margin: 0 auto;
        }

        #toolbar {
            display: flex;
            margin-bottom: 10px;
            padding: 10px;
            background: #f0f0f0;
            border-radius: 5px;
        }

        .tool-btn {
            margin-right: 10px;
            padding: 5px 10px;
            cursor: pointer;
        }

        .tool-btn.active {
            background: #4CAF50;
            color: white;
        }

        #canvas-container {
            border: 1px solid #ccc;
            margin-bottom: 10px;
        }

        canvas {
            background: white;
            display: block;
        }

        #status {
            font-size: 12px;
            color: #666;
        }

        /* 添加到style部分 */
        #presets {
            margin: 15px 0;
            padding: 10px;
            background: #f8f8f8;
            border-radius: 5px;
        }

        #presets h3 {
            margin-top: 0;
        }

        .preset-btn {
            display: inline-block;
            margin: 5px;
            padding: 5px 10px;
            background: #e0e0e0;
            border-radius: 3px;
            cursor: pointer;
        }

        .preset-btn:hover {
            background: #d0d0d0;
        }

        #export-buttons {
            margin-top: 15px;
        }

        #export-buttons button {
            margin-right: 10px;
            padding: 8px 15px;
            background: #4CAF50;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
        }

        #export-buttons button:hover {
            background: #45a049;
        }
    </style>
</head>

<body>
    <div id="editor-container">
        <h1>化学键线式编辑器</h1>
        <div id="toolbar">
            <div class="tool-btn active" data-tool="select">选择</div>
            <div class="tool-btn" data-tool="bond">单键</div>
            <div class="tool-btn" data-tool="double-bond">双键</div>
            <div class="tool-btn" data-tool="triple-bond">三键</div>
            <div class="tool-btn" data-tool="wedge">楔形键</div>
            <div class="tool-btn" data-tool="dash">虚线键</div>
            <div class="tool-btn" data-tool="text">文本</div>
            <div class="tool-btn" data-tool="erase">橡皮擦</div>
            <div class="tool-btn" data-tool="clear">清空</div>
        </div>
        <!-- 在toolbar div后面添加以下内容 -->
        <div id="presets">
            <h3>常用结构</h3>
            <div class="preset-btn" data-preset="benzene">苯环</div>
            <div class="preset-btn" data-preset="cyclohexane">环己烷</div>
            <div class="preset-btn" data-preset="cyclopentane">环戊烷</div>
            <div class="preset-btn" data-preset="methane">甲烷</div>
            <div class="preset-btn" data-preset="ethane">乙烷</div>
            <div class="preset-btn" data-preset="ethene">乙烯</div>
            <div class="preset-btn" data-preset="ethyne">乙炔</div>
        </div>

        <div id="export-buttons">
            <button id="export-png">导出PNG</button>
            <button id="export-svg">导出SVG</button>
            <button id="copy-clipboard">复制到剪贴板</button>
        </div>
        <div id="canvas-container">
            <canvas id="chem-canvas" width="800" height="500"></canvas>
        </div>
        <div id="status">就绪</div>
    </div>

    <script src="editor.js"></script>
</body>

</html>

JavaScript实现 (editor.js)

javascript 复制代码
document.addEventListener('DOMContentLoaded', function() {
    const canvas = document.getElementById('chem-canvas');
    const ctx = canvas.getContext('2d');
    const statusDiv = document.getElementById('status');
    const toolButtons = document.querySelectorAll('.tool-btn');
    
    let currentTool = 'select';
    let isDrawing = false;
    let startX, startY;
    let elements = [];
    let selectedElement = null;
    
    // 工具按钮点击事件
    toolButtons.forEach(button => {
        button.addEventListener('click', function() {
            toolButtons.forEach(btn => btn.classList.remove('active'));
            this.classList.add('active');
            currentTool = this.dataset.tool;
            statusDiv.textContent = `当前工具: ${this.textContent}`;
        });
    });
    
    // 鼠标事件处理
    canvas.addEventListener('mousedown', startDrawing);
    canvas.addEventListener('mousemove', draw);
    canvas.addEventListener('mouseup', stopDrawing);
    canvas.addEventListener('mouseout', stopDrawing);
    
    function startDrawing(e) {
        const rect = canvas.getBoundingClientRect();
        startX = e.clientX - rect.left;
        startY = e.clientY - rect.top;
        
        if (currentTool === 'select') {
            // 检查是否点击了现有元素
            selectedElement = getElementAtPosition(startX, startY);
            if (selectedElement) {
                isDrawing = true;
                return;
            }
        }
        
        isDrawing = true;
        redrawCanvas();
    }
    
    function draw(e) {
        if (!isDrawing) return;
        
        const rect = canvas.getBoundingClientRect();
        const mouseX = e.clientX - rect.left;
        const mouseY = e.clientY - rect.top;
        
        redrawCanvas();
        
        if (currentTool === 'select' && selectedElement) {
            // 移动选中的元素
            const dx = mouseX - startX;
            const dy = mouseY - startY;
            selectedElement.x += dx;
            selectedElement.y += dy;
            startX = mouseX;
            startY = mouseY;
            redrawCanvas();
            return;
        }
        
        // 绘制预览
        ctx.strokeStyle = '#999';
        ctx.lineWidth = 2;
        ctx.setLineDash([5, 5]);
        
        switch(currentTool) {
            case 'bond':
                ctx.beginPath();
                ctx.moveTo(startX, startY);
                ctx.lineTo(mouseX, mouseY);
                ctx.stroke();
                break;
                
            case 'double-bond':
                drawDoubleBond(startX, startY, mouseX, mouseY, true);
                break;
                
            case 'triple-bond':
                drawTripleBond(startX, startY, mouseX, mouseY, true);
                break;
                
            case 'wedge':
                drawWedgeBond(startX, startY, mouseX, mouseY, true);
                break;
                
            case 'dash':
                drawDashBond(startX, startY, mouseX, mouseY, true);
                break;
        }
        
        ctx.setLineDash([]);
    }
    
    function stopDrawing(e) {
        if (!isDrawing) return;
        
        const rect = canvas.getBoundingClientRect();
        const endX = e.clientX - rect.left;
        const endY = e.clientY - rect.top;
        
        if (currentTool === 'select') {
            isDrawing = false;
            selectedElement = null;
            return;
        }
        
        // 只添加元素如果移动了足够的距离
        if (Math.abs(endX - startX) > 5 || Math.abs(endY - startY) > 5) {
            switch(currentTool) {
                case 'bond':
                    elements.push({
                        type: 'bond',
                        x1: startX,
                        y1: startY,
                        x2: endX,
                        y2: endY,
                        width: 2
                    });
                    break;
                    
                case 'double-bond':
                    elements.push({
                        type: 'double-bond',
                        x1: startX,
                        y1: startY,
                        x2: endX,
                        y2: endY,
                        width: 2
                    });
                    break;
                    
                case 'triple-bond':
                    elements.push({
                        type: 'triple-bond',
                        x1: startX,
                        y1: startY,
                        x2: endX,
                        y2: endY,
                        width: 2
                    });
                    break;
                    
                case 'wedge':
                    elements.push({
                        type: 'wedge',
                        x1: startX,
                        y1: startY,
                        x2: endX,
                        y2: endY,
                        width: 8
                    });
                    break;
                    
                case 'dash':
                    elements.push({
                        type: 'dash',
                        x1: startX,
                        y1: startY,
                        x2: endX,
                        y2: endY,
                        width: 2
                    });
                    break;
                    
                case 'text':
                    const text = prompt('输入原子符号或文本:', 'C');
                    if (text) {
                        elements.push({
                            type: 'text',
                            text: text,
                            x: startX,
                            y: startY,
                            fontSize: 16
                        });
                    }
                    break;
            }
        }
        
        isDrawing = false;
        redrawCanvas();
    }
    
    function redrawCanvas() {
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        
        // 绘制所有元素
        elements.forEach(element => {
            switch(element.type) {
                case 'bond':
                    drawBond(element.x1, element.y1, element.x2, element.y2, element.width);
                    break;
                    
                case 'double-bond':
                    drawDoubleBond(element.x1, element.y1, element.x2, element.y2);
                    break;
                    
                case 'triple-bond':
                    drawTripleBond(element.x1, element.y1, element.x2, element.y2);
                    break;
                    
                case 'wedge':
                    drawWedgeBond(element.x1, element.y1, element.x2, element.y2);
                    break;
                    
                case 'dash':
                    drawDashBond(element.x1, element.y1, element.x2, element.y2);
                    break;
                    
                case 'text':
                    drawText(element.text, element.x, element.y, element.fontSize);
                    break;
            }
        });
        
        // 高亮选中的元素
        if (selectedElement) {
            ctx.strokeStyle = '#4285F4';
            ctx.lineWidth = 2;
            ctx.setLineDash([3, 3]);
            
            switch(selectedElement.type) {
                case 'bond':
                case 'double-bond':
                case 'triple-bond':
                case 'wedge':
                case 'dash':
                    ctx.beginPath();
                    ctx.moveTo(selectedElement.x1, selectedElement.y1);
                    ctx.lineTo(selectedElement.x2, selectedElement.y2);
                    ctx.stroke();
                    break;
                    
                case 'text':
                    ctx.strokeRect(
                        selectedElement.x - 5, 
                        selectedElement.y - selectedElement.fontSize, 
                        ctx.measureText(selectedElement.text).width + 10, 
                        selectedElement.fontSize + 5
                    );
                    break;
            }
            
            ctx.setLineDash([]);
        }
    }
    
    // 绘制单键
    function drawBond(x1, y1, x2, y2, width = 2) {
        ctx.strokeStyle = '#000';
        ctx.lineWidth = width;
        ctx.beginPath();
        ctx.moveTo(x1, y1);
        ctx.lineTo(x2, y2);
        ctx.stroke();
    }
    
    // 绘制双键
    function drawDoubleBond(x1, y1, x2, y2, isPreview = false) {
        const angle = Math.atan2(y2 - y1, x2 - x1);
        const offset = isPreview ? 3 : 2;
        
        // 计算垂直方向的偏移
        const dx = -Math.sin(angle) * offset;
        const dy = Math.cos(angle) * offset;
        
        // 绘制两条平行线
        drawBond(x1 + dx, y1 + dy, x2 + dx, y2 + dy);
        drawBond(x1 - dx, y1 - dy, x2 - dx, y2 - dy);
    }
    
    // 绘制三键
    function drawTripleBond(x1, y1, x2, y2, isPreview = false) {
        const angle = Math.atan2(y2 - y1, x2 - x1);
        const offset = isPreview ? 5 : 3;
        
        // 计算垂直方向的偏移
        const dx = -Math.sin(angle) * offset;
        const dy = Math.cos(angle) * offset;
        
        // 绘制三条平行线
        drawBond(x1, y1, x2, y2);
        drawBond(x1 + dx, y1 + dy, x2 + dx, y2 + dy);
        drawBond(x1 - dx, y1 - dy, x2 - dx, y2 - dy);
    }
    
    // 绘制楔形键
    function drawWedgeBond(x1, y1, x2, y2, isPreview = false) {
        const angle = Math.atan2(y2 - y1, x2 - x1);
        const width = isPreview ? 8 : 6;
        
        // 计算楔形的两个端点
        const dx = -Math.sin(angle) * width / 2;
        const dy = Math.cos(angle) * width / 2;
        
        ctx.fillStyle = isPreview ? 'rgba(0, 0, 0, 0.3)' : '#000';
        ctx.beginPath();
        ctx.moveTo(x1, y1);
        ctx.lineTo(x2 + dx, y2 + dy);
        ctx.lineTo(x2 - dx, y2 - dy);
        ctx.closePath();
        ctx.fill();
    }
    
    // 绘制虚线键
    function drawDashBond(x1, y1, x2, y2, isPreview = false) {
        ctx.strokeStyle = isPreview ? 'rgba(0, 0, 0, 0.3)' : '#000';
        ctx.lineWidth = 2;
        ctx.setLineDash([5, 3]);
        ctx.beginPath();
        ctx.moveTo(x1, y1);
        ctx.lineTo(x2, y2);
        ctx.stroke();
        ctx.setLineDash([]);
    }
    
    // 绘制文本
    function drawText(text, x, y, fontSize = 16) {
        ctx.font = `${fontSize}px Arial`;
        ctx.fillStyle = '#000';
        ctx.textAlign = 'center';
        ctx.textBaseline = 'middle';
        ctx.fillText(text, x, y);
    }
    
    // 获取指定位置的元素
    function getElementAtPosition(x, y) {
        // 首先检查文本元素
        for (let i = elements.length - 1; i >= 0; i--) {
            const element = elements[i];
            
            if (element.type === 'text') {
                ctx.font = `${element.fontSize}px Arial`;
                const textWidth = ctx.measureText(element.text).width;
                const textHeight = element.fontSize;
                
                if (x >= element.x - textWidth/2 - 5 && 
                    x <= element.x + textWidth/2 + 5 && 
                    y >= element.y - textHeight/2 - 5 && 
                    y <= element.y + textHeight/2 + 5) {
                    return element;
                }
            }
        }
        
        // 然后检查键
        for (let i = elements.length - 1; i >= 0; i--) {
            const element = elements[i];
            
            if (element.type !== 'text') {
                // 计算点到线段的距离
                if (isPointNearLine(x, y, element.x1, element.y1, element.x2, element.y2, 8)) {
                    return element;
                }
            }
        }
        
        return null;
    }
    
    // 检查点是否靠近线段
    function isPointNearLine(px, py, x1, y1, x2, y2, threshold) {
        // 线段长度
        const lineLength = Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
        
        // 计算点到线段的投影
        const u = ((px - x1) * (x2 - x1) + (py - y1) * (y2 - y1)) / (lineLength * lineLength);
        
        // 如果投影在线段外,计算到端点的距离
        if (u < 0) {
            return Math.sqrt((px - x1) * (px - x1) + (py - y1) * (py - y1)) <= threshold;
        } else if (u > 1) {
            return Math.sqrt((px - x2) * (px - x2) + (py - y2) * (py - y2)) <= threshold;
        }
        
        // 计算投影点
        const projX = x1 + u * (x2 - x1);
        const projY = y1 + u * (y2 - y1);
        
        // 计算点到投影点的距离
        const distance = Math.sqrt((px - projX) * (px - projX) + (py - projY) * (py - projY));
        
        return distance <= threshold;
    }
    
    // 清空画布
    document.querySelector('[data-tool="clear"]').addEventListener('click', function() {
        if (confirm('确定要清空画布吗?')) {
            elements = [];
            redrawCanvas();
        }
    });
    
    // 橡皮擦工具
    document.querySelector('[data-tool="erase"]').addEventListener('click', function() {
        currentTool = 'erase';
        canvas.style.cursor = 'url("data:image/svg+xml;utf8,<svg xmlns=\'http://www.w3.org/2000/svg\' width=\'16\' height=\'16\' viewBox=\'0 0 16 16\'><circle cx=\'8\' cy=\'8\' r=\'8\' fill=\'red\' opacity=\'0.5\'/></svg>") 8 8, auto';
    });
    
    canvas.addEventListener('click', function(e) {
        if (currentTool === 'erase') {
            const rect = canvas.getBoundingClientRect();
            const x = e.clientX - rect.left;
            const y = e.clientY - rect.top;
            
            const element = getElementAtPosition(x, y);
            if (element) {
                elements = elements.filter(el => el !== element);
                redrawCanvas();
            }
        }
    });
    // 在DOMContentLoaded事件监听器内添加以下代码

// 预设结构
const presets = {
    benzene: {
        name: "苯环",
        elements: [
            // 六边形的六个边
            // { type: 'bond', x1: 200, y1: 100, x2: 250, y2: 100, width: 2 },
            { type: 'bond', x1: 250, y1: 100, x2: 275, y2: 135, width: 2 },
            // { type: 'bond', x1: 275, y1: 135, x2: 250, y2: 170, width: 2 },
            { type: 'bond', x1: 250, y1: 170, x2: 200, y2: 170, width: 2 },
            // { type: 'bond', x1: 200, y1: 170, x2: 175, y2: 135, width: 2 },
            { type: 'bond', x1: 175, y1: 135, x2: 200, y2: 100, width: 2 },
            
            // 三个交替的双键
            { type: 'double-bond', x1: 200, y1: 100, x2: 250, y2: 100, width: 2 },
            { type: 'double-bond', x1: 275, y1: 135, x2: 250, y2: 170, width: 2 },
            { type: 'double-bond', x1: 200, y1: 170, x2: 175, y2: 135, width: 2 },
            
            // 可选:添加圆圈表示芳香性
            // { type: 'aromatic-circle', cx: 225, cy: 135, radius: 30, width: 1 }
        ]
    },
    cyclohexane: {
        name: "环己烷",
        elements: [
            { type: 'bond', x1: 200, y1: 100, x2: 250, y2: 100, width: 2 },
            { type: 'bond', x1: 250, y1: 100, x2: 275, y2: 140, width: 2 },
            { type: 'bond', x1: 275, y1: 140, x2: 250, y2: 180, width: 2 },
            { type: 'bond', x1: 250, y1: 180, x2: 200, y2: 180, width: 2 },
            { type: 'bond', x1: 200, y1: 180, x2: 175, y2: 140, width: 2 },
            { type: 'bond', x1: 175, y1: 140, x2: 200, y2: 100, width: 2 }
        ]
    },
    cyclopentane: {
        name: "环戊烷",
        elements: [
            { type: 'bond', x1: 200, y1: 100, x2: 240, y2: 100, width: 2 },
            { type: 'bond', x1: 240, y1: 100, x2: 260, y2: 130, width: 2 },
            { type: 'bond', x1: 260, y1: 130, x2: 220, y2: 160, width: 2 },
            { type: 'bond', x1: 220, y1: 160, x2: 180, y2: 130, width: 2 },
            { type: 'bond', x1: 180, y1: 130, x2: 200, y2: 100, width: 2 }
        ]
    },
    methane: {
        name: "甲烷",
        elements: [
            { type: 'bond', x1: 200, y1: 100, x2: 200, y2: 150, width: 2 },
            { type: 'bond', x1: 200, y1: 150, x2: 180, y2: 180, width: 2 },
            { type: 'bond', x1: 200, y1: 150, x2: 220, y2: 180, width: 2 },
            { type: 'wedge', x1: 200, y1: 150, x2: 190, y2: 120, width: 6 },
            { type: 'dash', x1: 200, y1: 150, x2: 210, y2: 120, width: 2 }
        ]
    },
    ethane: {
        name: "乙烷",
        elements: [
            { type: 'bond', x1: 150, y1: 150, x2: 200, y2: 150, width: 2 },
            { type: 'bond', x1: 200, y1: 150, x2: 250, y2: 150, width: 2 },
            { type: 'bond', x1: 150, y1: 150, x2: 130, y2: 180, width: 2 },
            { type: 'bond', x1: 150, y1: 150, x2: 170, y2: 180, width: 2 },
            { type: 'bond', x1: 250, y1: 150, x2: 230, y2: 180, width: 2 },
            { type: 'bond', x1: 250, y1: 150, x2: 270, y2: 180, width: 2 }
        ]
    },
    ethene: {
        name: "乙烯",
        elements: [
            { type: 'bond', x1: 150, y1: 150, x2: 200, y2: 150, width: 2 },
            { type: 'double-bond', x1: 200, y1: 150, x2: 250, y2: 150, width: 2 },
            { type: 'bond', x1: 150, y1: 150, x2: 150, y2: 180, width: 2 },
            { type: 'bond', x1: 250, y1: 150, x2: 250, y2: 180, width: 2 }
        ]
    },
    ethyne: {
        name: "乙炔",
        elements: [
            { type: 'bond', x1: 150, y1: 150, x2: 200, y2: 150, width: 2 },
            { type: 'triple-bond', x1: 200, y1: 150, x2: 250, y2: 150, width: 2 }
        ]
    }
};

// 预设按钮点击事件
document.querySelectorAll('.preset-btn').forEach(btn => {
    btn.addEventListener('click', function() {
        const presetName = this.dataset.preset;
        if (presets[presetName]) {
            elements = JSON.parse(JSON.stringify(presets[presetName].elements));
            redrawCanvas();
            statusDiv.textContent = `已加载预设: ${presets[presetName].name}`;
        }
    });
});

// 导出PNG
document.getElementById('export-png').addEventListener('click', function() {
    const dataURL = canvas.toDataURL('image/png');
    const link = document.createElement('a');
    link.download = 'chemical-structure.png';
    link.href = dataURL;
    link.click();
});

// 导出SVG
document.getElementById('export-svg').addEventListener('click', function() {
    const svgData = generateSVG();
    const blob = new Blob([svgData], {type: 'image/svg+xml'});
    const url = URL.createObjectURL(blob);
    const link = document.createElement('a');
    link.download = 'chemical-structure.svg';
    link.href = url;
    link.click();
});

// 复制到剪贴板
document.getElementById('copy-clipboard').addEventListener('click', function() {
    canvas.toBlob(function(blob) {
        navigator.clipboard.write([
            new ClipboardItem({
                'image/png': blob
            })
        ]).then(() => {
            statusDiv.textContent = '图像已复制到剪贴板';
        }).catch(err => {
            statusDiv.textContent = '复制失败: ' + err;
        });
    });
});

// 生成SVG
function generateSVG() {
    const width = canvas.width;
    const height = canvas.height;
    
    let svg = `<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}">
    <rect width="100%" height="100%" fill="white"/>
`;
    
    elements.forEach(element => {
        switch(element.type) {
            case 'bond':
                svg += `    <line x1="${element.x1}" y1="${element.y1}" x2="${element.x2}" y2="${element.y2}" stroke="black" stroke-width="${element.width}"/>\n`;
                break;
                
            case 'double-bond':
                const angle1 = Math.atan2(element.y2 - element.y1, element.x2 - element.x1);
                const dx1 = -Math.sin(angle1) * 2;
                const dy1 = Math.cos(angle1) * 2;
                svg += `    <line x1="${element.x1 + dx1}" y1="${element.y1 + dy1}" x2="${element.x2 + dx1}" y2="${element.y2 + dy1}" stroke="black" stroke-width="${element.width}"/>\n`;
                svg += `    <line x1="${element.x1 - dx1}" y1="${element.y1 - dy1}" x2="${element.x2 - dx1}" y2="${element.y2 - dy1}" stroke="black" stroke-width="${element.width}"/>\n`;
                break;
                
            case 'triple-bond':
                const angle2 = Math.atan2(element.y2 - element.y1, element.x2 - element.x1);
                const dx2 = -Math.sin(angle2) * 3;
                const dy2 = Math.cos(angle2) * 3;
                svg += `    <line x1="${element.x1}" y1="${element.y1}" x2="${element.x2}" y2="${element.y2}" stroke="black" stroke-width="${element.width}"/>\n`;
                svg += `    <line x1="${element.x1 + dx2}" y1="${element.y1 + dy2}" x2="${element.x2 + dx2}" y2="${element.y2 + dy2}" stroke="black" stroke-width="${element.width}"/>\n`;
                svg += `    <line x1="${element.x1 - dx2}" y1="${element.y1 - dy2}" x2="${element.x2 - dx2}" y2="${element.y2 - dy2}" stroke="black" stroke-width="${element.width}"/>\n`;
                break;
                
            case 'wedge':
                const angle3 = Math.atan2(element.y2 - element.y1, element.x2 - element.x1);
                const dx3 = -Math.sin(angle3) * element.width / 2;
                const dy3 = Math.cos(angle3) * element.width / 2;
                svg += `    <path d="M${element.x1},${element.y1} L${element.x2 + dx3},${element.y2 + dy3} L${element.x2 - dx3},${element.y2 - dy3} Z" fill="black"/>\n`;
                break;
                
            case 'dash':
                svg += `    <line x1="${element.x1}" y1="${element.y1}" x2="${element.x2}" y2="${element.y2}" stroke="black" stroke-width="${element.width}" stroke-dasharray="5,3"/>\n`;
                break;
                
            case 'text':
                svg += `    <text x="${element.x}" y="${element.y}" font-family="Arial" font-size="${element.fontSize}" text-anchor="middle" dominant-baseline="middle">${element.text}</text>\n`;
                break;
        }
    });
    
    svg += '</svg>';
    return svg;
}
    // 初始化
    redrawCanvas();
});

功能说明

这个化学键线式编辑器提供以下功能:

  1. 基本工具

    • 选择工具:选择和移动已有元素
    • 单键、双键、三键工具
    • 楔形键(实心三角形)和虚线键工具
    • 文本工具:添加原子符号或注释
    • 橡皮擦工具:删除元素
    • 清空画布
  2. 交互功能

    • 点击拖动绘制化学键
    • 选择并移动已有元素
    • 点击删除元素
    • 实时预览绘制效果
  3. 化学键表示

    • 正确绘制不同类型的化学键
    • 楔形键表示立体化学
    • 虚线键表示远离观察者的键
相关推荐
白雪讲堂43 分钟前
AI搜索品牌曝光资料包(精准适配文心一言/Kimi/DeepSeek等场景)
大数据·人工智能·搜索引擎·ai·文心一言·deepseek
王亭_6664 小时前
Ollama+open-webui搭建私有本地大模型详细教程
人工智能·大模型·ollama·openwebui·deepseek
几米哥5 小时前
OpenManus进阶指南:如何配置DeepSeek模型和百度搜索提升中文体验
开源·aigc·deepseek
奥德元5 小时前
【Ktransformers+Deepseek R1】再来一遍,以验证安装流程
deepseek
奥德元5 小时前
Ktransformer+Deepseek R1 671B实操
deepseek
DevUI团队6 小时前
通过DeepSeek学CSS - Flex和Grid布局优缺点对比
前端·deepseek
Mr.zwX7 小时前
【完整版】DeepSeek-R1大模型学习笔记(架构、训练、Infra、复现代码)
大模型·deepseek
全栈小59 小时前
【C#】.net core 6.0 依赖注入常见问题之一,在构造函数使用的类,都需要注入到容器里,否则会提示如下报错,让DeepSeek找找原因,看看效果
c#·.netcore·依赖注入·deepseek
Wnq1007214 小时前
智能巡检机器人在化工企业的应用研究
运维·计算机视觉·机器人·智能硬件·deepseek
1alisa16 小时前
Sublime Text for Mac v4【注册汉化版】代码编辑器
macos·编辑器·sublime text