洛阳市葬花

HTML5 Canvas 实现在线签字功能(示例代码)

2026-04-15 20:48:01 浏览次数:1
详细信息

HTML5 Canvas 在线签字功能实现

我将创建一个完整的在线签字应用,包含画布签字、颜色选择、线条粗细调整、清除、撤销、保存和下载功能。

思路分析

使用HTML5 Canvas作为绘制区域 监听鼠标/触摸事件捕获用户手势 实现平滑的线条绘制 添加工具控制:颜色、粗细、清除、撤销 提供签名保存功能

下面是完整实现代码:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>HTML5 Canvas 在线签字板</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif;
        }

        body {
            background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
            min-height: 100vh;
            display: flex;
            justify-content: center;
            align-items: center;
            padding: 20px;
        }

        .container {
            max-width: 1000px;
            width: 100%;
            background-color: white;
            border-radius: 16px;
            box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
            overflow: hidden;
        }

        header {
            background: linear-gradient(90deg, #2c3e50, #4a6491);
            color: white;
            padding: 20px 30px;
            text-align: center;
        }

        h1 {
            font-size: 28px;
            margin-bottom: 8px;
            letter-spacing: 1px;
        }

        .subtitle {
            font-size: 16px;
            opacity: 0.85;
            font-weight: 300;
        }

        .main-content {
            display: flex;
            flex-wrap: wrap;
            padding: 25px;
            gap: 25px;
        }

        .canvas-container {
            flex: 1;
            min-width: 300px;
            border-radius: 10px;
            overflow: hidden;
            box-shadow: 0 5px 15px rgba(0, 0, 0, 0.08);
            background-color: #f8f9fa;
            border: 1px solid #eaeaea;
        }

        #signatureCanvas {
            width: 100%;
            height: 400px;
            cursor: crosshair;
            display: block;
            background-color: white;
        }

        .tools-panel {
            width: 280px;
            display: flex;
            flex-direction: column;
            gap: 20px;
        }

        .panel {
            background-color: #f8f9fa;
            border-radius: 10px;
            padding: 20px;
            box-shadow: 0 4px 8px rgba(0, 0, 0, 0.05);
            border: 1px solid #eaeaea;
        }

        .panel h3 {
            color: #2c3e50;
            margin-bottom: 16px;
            padding-bottom: 10px;
            border-bottom: 1px solid #e0e0e0;
            font-size: 18px;
        }

        .color-options {
            display: flex;
            flex-wrap: wrap;
            gap: 10px;
            margin-bottom: 10px;
        }

        .color-option {
            width: 32px;
            height: 32px;
            border-radius: 50%;
            cursor: pointer;
            border: 3px solid transparent;
            transition: transform 0.2s, border 0.2s;
        }

        .color-option:hover {
            transform: scale(1.1);
        }

        .color-option.active {
            border-color: #2c3e50;
            transform: scale(1.1);
        }

        .brush-size {
            margin-top: 15px;
        }

        .size-options {
            display: flex;
            gap: 12px;
            align-items: center;
        }

        .size-option {
            width: 24px;
            height: 24px;
            border-radius: 50%;
            background-color: #2c3e50;
            cursor: pointer;
            transition: transform 0.2s;
        }

        .size-option:hover {
            transform: scale(1.1);
        }

        .size-option.active {
            outline: 2px solid #3498db;
            outline-offset: 2px;
        }

        .size-option.small {
            width: 16px;
            height: 16px;
        }

        .size-option.medium {
            width: 24px;
            height: 24px;
        }

        .size-option.large {
            width: 32px;
            height: 32px;
        }

        .action-buttons {
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 12px;
        }

        button {
            padding: 12px 16px;
            border: none;
            border-radius: 8px;
            cursor: pointer;
            font-weight: 600;
            font-size: 15px;
            transition: all 0.2s;
            display: flex;
            align-items: center;
            justify-content: center;
            gap: 8px;
        }

        .primary-btn {
            background-color: #3498db;
            color: white;
        }

        .primary-btn:hover {
            background-color: #2980b9;
        }

        .secondary-btn {
            background-color: #e0e0e0;
            color: #333;
        }

        .secondary-btn:hover {
            background-color: #d0d0d0;
        }

        .danger-btn {
            background-color: #e74c3c;
            color: white;
        }

        .danger-btn:hover {
            background-color: #c0392b;
        }

        .success-btn {
            background-color: #2ecc71;
            color: white;
        }

        .success-btn:hover {
            background-color: #27ae60;
        }

        .info-section {
            margin-top: 10px;
            font-size: 14px;
            color: #666;
            line-height: 1.5;
            background-color: #f0f7ff;
            padding: 15px;
            border-radius: 8px;
            border-left: 4px solid #3498db;
        }

        .signature-preview {
            text-align: center;
            margin-top: 20px;
            padding: 15px;
            border-top: 1px dashed #ddd;
        }

        #signatureImage {
            max-width: 100%;
            max-height: 120px;
            margin-top: 10px;
            border: 1px solid #eee;
            border-radius: 4px;
            display: none;
        }

        .mobile-notice {
            display: none;
            background-color: #fff9e6;
            padding: 10px 15px;
            border-radius: 8px;
            margin-bottom: 15px;
            font-size: 14px;
            color: #8a6d3b;
            border-left: 4px solid #f0ad4e;
        }

        @media (max-width: 768px) {
            .main-content {
                flex-direction: column;
            }

            .tools-panel {
                width: 100%;
            }

            .mobile-notice {
                display: block;
            }
        }

        footer {
            text-align: center;
            padding: 20px;
            color: #666;
            font-size: 14px;
            border-top: 1px solid #eee;
            background-color: #f8f9fa;
        }
    </style>
</head>
<body>
    <div class="container">
        <header>
            <h1>HTML5 Canvas 在线签字板</h1>
            <div class="subtitle">支持鼠标/触摸绘制 • 可调整颜色和线条粗细 • 支持保存签名</div>
        </header>

        <div class="main-content">
            <div class="canvas-container">
                <div class="mobile-notice">
                    📱 在移动设备上,请用手指在下方区域直接绘制
                </div>
                <canvas id="signatureCanvas">
                    您的浏览器不支持HTML5 Canvas,请使用现代浏览器如Chrome、Firefox等。
                </canvas>
            </div>

            <div class="tools-panel">
                <div class="panel">
                    <h3>画笔颜色</h3>
                    <div class="color-options">
                        <div class="color-option active" style="background-color: #000000;" data-color="#000000"></div>
                        <div class="color-option" style="background-color: #e74c3c;" data-color="#e74c3c"></div>
                        <div class="color-option" style="background-color: #3498db;" data-color="#3498db"></div>
                        <div class="color-option" style="background-color: #2ecc71;" data-color="#2ecc71"></div>
                        <div class="color-option" style="background-color: #f39c12;" data-color="#f39c12"></div>
                        <div class="color-option" style="background-color: #9b59b6;" data-color="#9b59b6"></div>
                        <div class="color-option" style="background-color: #34495e;" data-color="#34495e"></div>
                        <div class="color-option" style="background-color: #e84393;" data-color="#e84393"></div>
                    </div>

                    <div class="brush-size">
                        <h3>线条粗细</h3>
                        <div class="size-options">
                            <div class="size-option small active" data-size="2"></div>
                            <div class="size-option medium" data-size="4"></div>
                            <div class="size-option large" data-size="6"></div>
                        </div>
                    </div>
                </div>

                <div class="panel">
                    <h3>操作工具</h3>
                    <div class="action-buttons">
                        <button id="clearBtn" class="danger-btn">
                            <span>🗑️</span> 清除
                        </button>
                        <button id="undoBtn" class="secondary-btn">
                            <span>↶</span> 撤销
                        </button>
                        <button id="saveBtn" class="success-btn">
                            <span>💾</span> 保存
                        </button>
                        <button id="downloadBtn" class="primary-btn">
                            <span>⬇️</span> 下载
                        </button>
                    </div>

                    <div class="info-section">
                        <p><strong>使用提示:</strong></p>
                        <p>1. 在画布区域拖动鼠标/手指进行签名</p>
                        <p>2. 点击"保存"可预览签名图像</p>
                        <p>3. 点击"下载"可将签名保存为PNG图片</p>
                    </div>
                </div>

                <div class="signature-preview">
                    <h3>签名预览</h3>
                    <img id="signatureImage" alt="签名预览">
                    <p id="previewText">签名保存后将在此显示预览</p>
                </div>
            </div>
        </div>

        <footer>
            <p>© 2023 HTML5 Canvas 签字板 | 使用HTML5 Canvas API实现</p>
        </footer>
    </div>

    <script>
        // 获取Canvas元素和上下文
        const canvas = document.getElementById('signatureCanvas');
        const ctx = canvas.getContext('2d');

        // 设置Canvas尺寸为容器尺寸
        function resizeCanvas() {
            const container = canvas.parentElement;
            canvas.width = container.clientWidth;
            canvas.height = container.clientHeight;

            // 设置绘制样式
            ctx.lineCap = 'round';
            ctx.lineJoin = 'round';
            ctx.strokeStyle = '#000000';
            ctx.lineWidth = 2;

            // 绘制初始背景
            drawBackground();
        }

        // 绘制网格背景
        function drawBackground() {
            ctx.fillStyle = '#ffffff';
            ctx.fillRect(0, 0, canvas.width, canvas.height);

            // 绘制浅色网格
            ctx.strokeStyle = '#f0f0f0';
            ctx.lineWidth = 1;

            const gridSize = 20;
            // 垂直线
            for (let x = 0; x < canvas.width; x += gridSize) {
                ctx.beginPath();
                ctx.moveTo(x, 0);
                ctx.lineTo(x, canvas.height);
                ctx.stroke();
            }

            // 水平线
            for (let y = 0; y < canvas.height; y += gridSize) {
                ctx.beginPath();
                ctx.moveTo(0, y);
                ctx.lineTo(canvas.width, y);
                ctx.stroke();
            }

            // 重置线条样式
            ctx.strokeStyle = document.querySelector('.color-option.active').getAttribute('data-color');
            ctx.lineWidth = parseInt(document.querySelector('.size-option.active').getAttribute('data-size'));
        }

        // 初始化变量
        let isDrawing = false;
        let lastX = 0;
        let lastY = 0;
        let drawingHistory = [];
        let currentHistory = null;

        // 开始绘制
        function startDrawing(e) {
            isDrawing = true;
            [lastX, lastY] = getCoordinates(e);

            // 开始新的绘制路径
            currentHistory = {
                points: [],
                color: ctx.strokeStyle,
                width: ctx.lineWidth
            };
        }

        // 绘制中
        function draw(e) {
            if (!isDrawing) return;

            e.preventDefault();

            const [x, y] = getCoordinates(e);

            // 添加点到当前绘制历史
            currentHistory.points.push({x, y});

            // 绘制线条
            ctx.beginPath();
            ctx.moveTo(lastX, lastY);
            ctx.lineTo(x, y);
            ctx.stroke();

            [lastX, lastY] = [x, y];
        }

        // 结束绘制
        function stopDrawing() {
            if (!isDrawing) return;

            isDrawing = false;

            // 保存当前绘制历史
            if (currentHistory && currentHistory.points.length > 0) {
                drawingHistory.push(currentHistory);
                currentHistory = null;
            }
        }

        // 获取坐标(支持触摸事件)
        function getCoordinates(e) {
            let x, y;

            if (e.type.includes('touch')) {
                const touch = e.touches[0] || e.changedTouches[0];
                const rect = canvas.getBoundingClientRect();
                x = touch.clientX - rect.left;
                y = touch.clientY - rect.top;
            } else {
                x = e.offsetX;
                y = e.offsetY;
            }

            return [x, y];
        }

        // 清除画布
        function clearCanvas() {
            if (confirm('确定要清除所有绘制内容吗?')) {
                drawingHistory = [];
                currentHistory = null;
                drawBackground();

                // 隐藏预览
                document.getElementById('signatureImage').style.display = 'none';
                document.getElementById('previewText').textContent = '签名保存后将在此显示预览';
            }
        }

        // 撤销上一步
        function undoLast() {
            if (drawingHistory.length > 0) {
                drawingHistory.pop();

                // 重新绘制所有历史
                drawBackground();

                // 重新绘制历史记录
                drawingHistory.forEach(history => {
                    ctx.strokeStyle = history.color;
                    ctx.lineWidth = history.width;

                    if (history.points.length > 0) {
                        ctx.beginPath();
                        ctx.moveTo(history.points[0].x, history.points[0].y);

                        for (let i = 1; i < history.points.length; i++) {
                            ctx.lineTo(history.points[i].x, history.points[i].y);
                        }

                        ctx.stroke();
                    }
                });

                // 恢复当前画笔设置
                ctx.strokeStyle = document.querySelector('.color-option.active').getAttribute('data-color');
                ctx.lineWidth = parseInt(document.querySelector('.size-option.active').getAttribute('data-size'));

                // 更新预览
                updatePreview();
            } else {
                alert('没有可撤销的操作');
            }
        }

        // 保存签名(预览)
        function saveSignature() {
            if (drawingHistory.length === 0) {
                alert('请先绘制签名');
                return;
            }

            // 创建临时Canvas用于保存
            const tempCanvas = document.createElement('canvas');
            const tempCtx = tempCanvas.getContext('2d');

            // 设置临时Canvas尺寸
            tempCanvas.width = canvas.width;
            tempCanvas.height = canvas.height;

            // 绘制白色背景
            tempCtx.fillStyle = '#ffffff';
            tempCtx.fillRect(0, 0, tempCanvas.width, tempCanvas.height);

            // 复制原始Canvas内容
            tempCtx.drawImage(canvas, 0, 0);

            // 转换为数据URL
            const dataURL = tempCanvas.toDataURL('image/png');

            // 显示预览
            const previewImg = document.getElementById('signatureImage');
            previewImg.src = dataURL;
            previewImg.style.display = 'block';

            document.getElementById('previewText').textContent = '签名预览:';

            // 保存到本地存储
            try {
                localStorage.setItem('signatureData', dataURL);
                showMessage('签名已保存到本地存储', 'success');
            } catch (e) {
                console.warn('本地存储失败:', e);
                showMessage('签名预览已生成,但本地存储失败', 'warning');
            }
        }

        // 下载签名
        function downloadSignature() {
            if (drawingHistory.length === 0) {
                alert('请先绘制签名');
                return;
            }

            // 创建临时Canvas用于下载
            const tempCanvas = document.createElement('canvas');
            const tempCtx = tempCanvas.getContext('2d');

            // 设置临时Canvas尺寸
            tempCanvas.width = canvas.width;
            tempCanvas.height = canvas.height;

            // 绘制白色背景
            tempCtx.fillStyle = '#ffffff';
            tempCtx.fillRect(0, 0, tempCanvas.width, tempCanvas.height);

            // 复制原始Canvas内容
            tempCtx.drawImage(canvas, 0, 0);

            // 创建下载链接
            const link = document.createElement('a');
            link.download = `signature_${new Date().getTime()}.png`;
            link.href = tempCanvas.toDataURL('image/png');
            link.click();

            showMessage('签名已下载', 'success');
        }

        // 显示临时消息
        function showMessage(message, type) {
            // 创建消息元素
            const messageEl = document.createElement('div');
            messageEl.textContent = message;
            messageEl.style.cssText = `
                position: fixed;
                top: 20px;
                right: 20px;
                padding: 12px 20px;
                border-radius: 8px;
                color: white;
                font-weight: 600;
                z-index: 1000;
                box-shadow: 0 4px 12px rgba(0,0,0,0.15);
                animation: slideIn 0.3s ease-out;
            `;

            // 根据类型设置颜色
            if (type === 'success') {
                messageEl.style.backgroundColor = '#2ecc71';
            } else if (type === 'warning') {
                messageEl.style.backgroundColor = '#f39c12';
            } else {
                messageEl.style.backgroundColor = '#3498db';
            }

            document.body.appendChild(messageEl);

            // 3秒后移除消息
            setTimeout(() => {
                messageEl.style.animation = 'slideOut 0.3s ease-out';
                setTimeout(() => {
                    document.body.removeChild(messageEl);
                }, 300);
            }, 3000);
        }

        // 更新预览
        function updatePreview() {
            if (drawingHistory.length === 0) {
                document.getElementById('signatureImage').style.display = 'none';
                document.getElementById('previewText').textContent = '签名保存后将在此显示预览';
            }
        }

        // 初始化事件监听
        function initEventListeners() {
            // 鼠标事件
            canvas.addEventListener('mousedown', startDrawing);
            canvas.addEventListener('mousemove', draw);
            canvas.addEventListener('mouseup', stopDrawing);
            canvas.addEventListener('mouseout', stopDrawing);

            // 触摸事件(移动设备支持)
            canvas.addEventListener('touchstart', (e) => {
                e.preventDefault();
                startDrawing(e);
            });

            canvas.addEventListener('touchmove', (e) => {
                e.preventDefault();
                draw(e);
            });

            canvas.addEventListener('touchend', (e) => {
                e.preventDefault();
                stopDrawing();
            });

            // 颜色选择
            document.querySelectorAll('.color-option').forEach(option => {
                option.addEventListener('click', function() {
                    document.querySelectorAll('.color-option').forEach(opt => {
                        opt.classList.remove('active');
                    });
                    this.classList.add('active');
                    ctx.strokeStyle = this.getAttribute('data-color');
                });
            });

            // 线条粗细选择
            document.querySelectorAll('.size-option').forEach(option => {
                option.addEventListener('click', function() {
                    document.querySelectorAll('.size-option').forEach(opt => {
                        opt.classList.remove('active');
                    });
                    this.classList.add('active');
                    ctx.lineWidth = parseInt(this.getAttribute('data-size'));
                });
            });

            // 按钮事件
            document.getElementById('clearBtn').addEventListener('click', clearCanvas);
            document.getElementById('undoBtn').addEventListener('click', undoLast);
            document.getElementById('saveBtn').addEventListener('click', saveSignature);
            document.getElementById('downloadBtn').addEventListener('click', downloadSignature);

            // 窗口大小改变时调整Canvas尺寸
            window.addEventListener('resize', resizeCanvas);

            // 尝试从本地存储加载签名
            try {
                const savedSignature = localStorage.getItem('signatureData');
                if (savedSignature) {
                    const img = new Image();
                    img.onload = function() {
                        ctx.drawImage(img, 0, 0);

                        // 由于无法解析历史,我们创建一个占位历史
                        drawingHistory.push({
                            points: [],
                            color: '#000000',
                            width: 2
                        });

                        // 显示预览
                        const previewImg = document.getElementById('signatureImage');
                        previewImg.src = savedSignature;
                        previewImg.style.display = 'block';
                        document.getElementById('previewText').textContent = '从本地存储加载的签名:';
                    };
                    img.src = savedSignature;
                }
            } catch (e) {
                console.warn('无法从本地存储加载签名:', e);
            }
        }

        // 初始化应用
        function init() {
            resizeCanvas();
            initEventListeners();

            // 添加CSS动画
            const style = document.createElement('style');
            style.textContent = `
                @keyframes slideIn {
                    from { transform: translateX(100%); opacity: 0; }
                    to { transform: translateX(0); opacity: 1; }
                }

                @keyframes slideOut {
                    from { transform: translateX(0); opacity: 1; }
                    to { transform: translateX(100%); opacity: 0; }
                }
            `;
            document.head.appendChild(style);
        }

        // 页面加载完成后初始化
        window.addEventListener('load', init);
    </script>
</body>
</html>

功能说明

绘制功能

自定义选项

操作工具

其他特性

使用说明

在画布区域使用鼠标或手指绘制签名 选择画笔颜色和线条粗细 使用操作工具进行清除、撤销、保存或下载 签名会自动保存到浏览器本地存储,下次打开页面时会自动加载

这个实现使用了纯HTML5 Canvas API,没有依赖任何外部库,可以直接复制代码到HTML文件中运行。

相关推荐