HTML&CSS: 在线电子签名工具

该 HTML 文件是一个功能完整、跨设备兼容的在线电子签名工具,基于 HTML5 Canvas 实现签名绘制核心功能,搭配直观的样式控制(颜色、线条粗细)与操作按钮(清除、保存、下载),同时支持鼠标与触摸屏交互,整体设计简洁易用且视觉友好。


大家复制代码时,可能会因格式转换出现错乱,导致样式失效。建议先少量复制代码进行测试,若未能解决问题,私信回复源码两字,我会发送完整的压缩包给你。

演示效果

HTML&CSS

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>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
        }

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

        .container {
            max-width: 800px;
            width: 100%;
            background: white;
            border-radius: 15px;
            box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
            padding: 30px;
            margin-top: 20px;
        }

        header {
            text-align: center;
            margin-bottom: 30px;
        }

        h1 {
            color: #2c3e50;
            margin-bottom: 10px;
            font-size: 2.2rem;
        }

        .subtitle {
            color: #7f8c8d;
            font-size: 1.1rem;
        }

        .signature-area {
            border: 2px dashed #bdc3c7;
            border-radius: 10px;
            margin-bottom: 25px;
            position: relative;
            background-color: #f9f9f9;
            overflow: hidden;
        }

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

        .canvas-placeholder {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            display: flex;
            justify-content: center;
            align-items: center;
            color: #95a5a6;
            font-size: 1.2rem;
            pointer-events: none;
        }

        .controls {
            display: flex;
            flex-wrap: wrap;
            gap: 15px;
            margin-bottom: 25px;
        }

        .btn {
            padding: 12px 20px;
            border: none;
            border-radius: 6px;
            font-size: 1rem;
            cursor: pointer;
            transition: all 0.3s ease;
            display: flex;
            align-items: center;
            justify-content: center;
            gap: 8px;
        }

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

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

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

        .btn:hover {
            transform: translateY(-2px);
            box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
        }

        .btn:active {
            transform: translateY(0);
        }

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

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

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

        .color-picker {
            display: flex;
            align-items: center;
            gap: 10px;
            margin-bottom: 20px;
        }

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

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

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

        .line-width {
            display: flex;
            align-items: center;
            gap: 10px;
            margin-bottom: 20px;
        }

        .line-width input {
            width: 100%;
        }

        .signature-preview {
            margin-top: 30px;
            text-align: center;
        }

        .preview-title {
            margin-bottom: 15px;
            color: #2c3e50;
        }

        #signaturePreview {
            max-width: 100%;
            border: 1px solid #bdc3c7;
            border-radius: 5px;
            display: none;
        }

        footer {
            margin-top: 30px;
            text-align: center;
            color: #7f8c8d;
            font-size: 0.9rem;
        }

        @media (max-width: 600px) {
            .container {
                padding: 20px;
            }

            h1 {
                font-size: 1.8rem;
            }

            .controls {
                flex-direction: column;
            }

            .btn {
                width: 100%;
            }
        }

        .instructions {
            background-color: #f8f9fa;
            border-left: 4px solid #3498db;
            padding: 15px;
            margin-bottom: 25px;
            border-radius: 0 8px 8px 0;
        }

        .instructions h3 {
            color: #2c3e50;
            margin-bottom: 10px;
        }

        .instructions ul {
            padding-left: 20px;
        }

        .instructions li {
            margin-bottom: 8px;
        }
    </style>
</head>

<body>
    <header>
        <h1>在线电子签名工具</h1>
        <p class="subtitle">使用鼠标或触摸屏创建您的电子签名</p>
    </header>

    <div class="container">
        <div class="instructions">
            <h3>使用说明</h3>
            <ul>
                <li>在下方画布区域使用鼠标或手指(触摸屏设备)绘制您的签名</li>
                <li>可以使用下方的颜色和线条粗细选项调整签名样式</li>
                <li>完成后可以保存签名或下载为PNG图片</li>
                <li>如需重新开始,请点击"清除签名"按钮</li>
            </ul>
        </div>

        <div class="signature-area">
            <canvas id="signatureCanvas"></canvas>
            <div class="canvas-placeholder">请在此处绘制您的签名</div>
        </div>

        <div class="color-picker">
            <span>选择颜色:</span>
            <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>

        <div class="line-width">
            <span>线条粗细:</span>
            <input type="range" id="lineWidth" min="1" max="10" value="2">
            <span id="widthValue">2px</span>
        </div>

        <div class="controls">
            <button class="btn btn-clear" id="clearBtn">
                <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
                    <path
                        d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6z" />
                    <path fill-rule="evenodd"
                        d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1zM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118zM2.5 3V2h11v1h-11z" />
                </svg>
                清除签名
            </button>
            <button class="btn btn-save" id="saveBtn">
                <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
                    <path
                        d="M2 1a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1H9.5a1 1 0 0 0-1 1v7.293l2.646-2.647a.5.5 0 0 1 .708.708l-3.5 3.5a.5.5 0 0 1-.708 0l-3.5-3.5a.5.5 0 1 1 .708-.708L7.5 9.293V2a2 2 0 0 1 2-2H14a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2h2.5a.5.5 0 0 1 0 1H2z" />
                </svg>
                保存签名
            </button>
            <button class="btn btn-download" id="downloadBtn">
                <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
                    <path
                        d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5z" />
                    <path
                        d="M7.646 11.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 10.293V1.5a.5.5 0 0 0-1 0v8.793L5.354 8.146a.5.5 0 1 0-.708.708l3 3z" />
                </svg>
                下载签名
            </button>
        </div>

        <div class="signature-preview">
            <h3 class="preview-title">签名预览</h3>
            <img id="signaturePreview" alt="签名预览">
        </div>
    </div>

    <footer>
        <p>© 2023 在线电子签名工具 | 使用HTML5 Canvas和JavaScript构建</p>
    </footer>

    <script>
        document.addEventListener('DOMContentLoaded', function () {
            // 获取Canvas元素和上下文
            const canvas = document.getElementById('signatureCanvas');
            const ctx = canvas.getContext('2d');
            const placeholder = document.querySelector('.canvas-placeholder');

            // 获取控制元素
            const clearBtn = document.getElementById('clearBtn');
            const saveBtn = document.getElementById('saveBtn');
            const downloadBtn = document.getElementById('downloadBtn');
            const colorOptions = document.querySelectorAll('.color-option');
            const lineWidthInput = document.getElementById('lineWidth');
            const widthValue = document.getElementById('widthValue');
            const signaturePreview = document.getElementById('signaturePreview');

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

                // 重新绘制内容(如果有)
                redrawSignature();
            }

            // 初始化变量
            let isDrawing = false;
            let lastX = 0;
            let lastY = 0;
            let currentColor = '#000000';
            let currentLineWidth = 2;
            let signatureData = [];

            // 初始化Canvas
            resizeCanvas();
            window.addEventListener('resize', resizeCanvas);

            // 设置初始绘制样式
            ctx.strokeStyle = currentColor;
            ctx.lineWidth = currentLineWidth;
            ctx.lineJoin = 'round';
            ctx.lineCap = 'round';

            // 颜色选择
            colorOptions.forEach(option => {
                option.addEventListener('click', function () {
                    // 移除所有active类
                    colorOptions.forEach(opt => opt.classList.remove('active'));
                    // 添加active类到当前选项
                    this.classList.add('active');
                    // 更新当前颜色
                    currentColor = this.getAttribute('data-color');
                    ctx.strokeStyle = currentColor;
                });
            });

            // 线条粗细
            lineWidthInput.addEventListener('input', function () {
                currentLineWidth = this.value;
                ctx.lineWidth = currentLineWidth;
                widthValue.textContent = `${currentLineWidth}px`;
            });

            // 开始绘制
            function startDrawing(e) {
                isDrawing = true;
                [lastX, lastY] = getCoordinates(e);
                placeholder.style.display = 'none';

                // 开始新的路径
                signatureData.push([]);
            }

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

                e.preventDefault();

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

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

                // 保存绘制数据
                signatureData[signatureData.length - 1].push({ x, y });

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

            // 结束绘制
            function stopDrawing() {
                isDrawing = false;
            }

            // 获取坐标(兼容鼠标和触摸事件)
            function getCoordinates(e) {
                let x, y;

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

                return [x, y];
            }

            // 重新绘制签名(用于Canvas尺寸调整后)
            function redrawSignature() {
                ctx.clearRect(0, 0, canvas.width, canvas.height);

                if (signatureData.length === 0) {
                    placeholder.style.display = 'flex';
                    return;
                }

                ctx.beginPath();

                signatureData.forEach(stroke => {
                    if (stroke.length > 0) {
                        ctx.moveTo(stroke[0].x, stroke[0].y);

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

                ctx.stroke();
            }

            // 清除签名
            function clearSignature() {
                ctx.clearRect(0, 0, canvas.width, canvas.height);
                signatureData = [];
                placeholder.style.display = 'flex';
                signaturePreview.style.display = 'none';
            }

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

                const dataURL = canvas.toDataURL('image/png');
                signaturePreview.src = dataURL;
                signaturePreview.style.display = 'block';

                // 平滑滚动到预览区域
                signaturePreview.scrollIntoView({ behavior: 'smooth' });
            }

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

                const dataURL = canvas.toDataURL('image/png');
                const link = document.createElement('a');
                link.download = '电子签名.png';
                link.href = dataURL;
                link.click();
            }

            // 事件监听器
            canvas.addEventListener('mousedown', startDrawing);
            canvas.addEventListener('mousemove', draw);
            canvas.addEventListener('mouseup', stopDrawing);
            canvas.addEventListener('mouseout', stopDrawing);

            // 触摸事件支持
            canvas.addEventListener('touchstart', startDrawing);
            canvas.addEventListener('touchmove', draw);
            canvas.addEventListener('touchend', stopDrawing);

            clearBtn.addEventListener('click', clearSignature);
            saveBtn.addEventListener('click', saveSignature);
            downloadBtn.addEventListener('click', downloadSignature);

            // 防止触摸设备上的滚动
            canvas.addEventListener('touchmove', function (e) {
                if (isDrawing) {
                    e.preventDefault();
                }
            }, { passive: false });
        });
    </script>
</body>

</html>

HTML

  • container: 核心功能容器:包裹使用说明、签名画布、样式控制、操作按钮与预览区,集中承载工具功能
  • instructions: 使用说明区:通过列表清晰告知用户操作步骤(绘制、调整样式、保存等),降低使用门槛
  • signature-area: 签名绘制区:包含 Canvas 画布与占位提示,是工具的核心交互区域
  • signatureCanvas:签名绘制核心:基于 HTML5 Canvas 实现手写签名功能,支持像素级绘制
  • canvas-placeholder:画布占位提示:未绘制时显示 "请在此处绘制您的签名",引导用户操作,绘制后隐藏
  • color-picker:颜色选择区:提供 5 种预设颜色(黑、红、蓝、绿、橙),支持点击切换签名颜色
  • color-option:单个颜色选项:圆形色块,active 类标识当前选中颜色,data-color 存储颜色值
  • line-width:线条粗细控制区:包含滑块输入框与数值显示,支持 1-10px 的粗细调节
  • lineWidth:粗细调节滑块:min=1/max=10/value=2,实时同步线条粗细
  • controls:操作按钮容器:横向排列 "清除""保存""下载" 三个核心功能按钮,方便用户操作
  • btn:功能按钮:每个按钮绑定不同操作,内嵌 SVG 图标增强视觉辨识度(如 "清除" 对应垃圾桶图标)
  • svg:按钮图标:使用内联 SVG 绘制功能图标(清除、保存、下载),无需外部图片资源,加载更快
  • signature-preview:签名预览区:显示保存后的签名图片,提供可视化反馈,未保存时隐藏
  • signaturePreview":预览图片容器:src 动态赋值为 Canvas 导出的图片数据,display 控制显示隐藏

CSS

  • container 功能容器:1. 尺寸:最大宽度 800px、100% 自适应宽度,确保大屏幕不拉伸、小屏幕全屏;2. 视觉:白色背景(white)、圆角 15px、阴影(0 10px 30px rgba(0,0,0,0.1)),模拟卡片效果,与渐变背景区分,突出功能区;3. 内边距:padding: 30px,确保内部元素不拥挤。

  • .header 头部信息区:1. 布局:text-align: center 使标题 / 副标题居中,margin-bottom: 30px 与下方功能区分隔;2. 文字:标题(h1)颜色深灰蓝(#2c3e50)、字号 2.2rem,副标题(.subtitle)颜色浅灰(#7f8c8d)、字号 1.1rem,层级分明。

  • .instructions 使用说明区:1. 视觉:浅灰背景(#f8f9fa)、左侧 4px 蓝色边框(#3498db)、圆角(0 8px 8px 0),与其他区域区分,引导用户注意;2. 内边距:padding: 15px,列表左 padding 20px,确保文字不贴边。

  • .signature-area 签名绘制区:1. 边框:2px 虚线(dashed #bdc3c7)、圆角 10px,明确绘制范围;2. 背景:浅灰(#f9f9f9),与 Canvas 白色背景区分;3. 定位:position: relative,为绝对定位的占位提示提供父容器;4. 溢出:overflow: hidden,防止 Canvas 内容超出容器。

  • #signatureCanvas 签名画布:1. 尺寸:宽度 100%、高度 300px,填满父容器;2. 交互:cursor: crosshair,鼠标悬停时显示 "十字准星",提示可绘制;3. 背景:白色(white),模拟纸质签名效果。

  • .canvas-placeholder 画布占位提示:1. 定位:absolute 覆盖整个 Canvas,top/left: 0、width/height: 100%;2. 布局:flex 居中显示文字,pointer-events: none 确保不遮挡 Canvas 交互;3. 文字:浅灰(#95a5a6)、字号 1.2rem,提示用户操作。

  • .color-picker/.line-width 样式控制区:1. 布局:display: flex+align-items: center 使标签与选项垂直居中,gap: 10px 分隔元素,margin-bottom: 20px 与其他区域分隔;2. 颜色选项(.color-option):圆形(border-radius: 50%)、30×30px 尺寸,active 类添加深灰边框(#2c3e50),明确选中状态。

  • .controls 操作按钮容器:1. 布局:display: flex+flex-wrap: wrap(支持换行)、gap: 15px,适配不同屏幕宽度;2. 响应式:小屏幕自动换行,确保按钮不溢出。

  • .btn 功能按钮:1. 基础样式:padding: 12px 20px、无边框、圆角 6px、字号 1rem,display: flex+align-items: center+gap: 8px 使图标与文字居中;2. 颜色区分:清除(红#e74c3c)、保存(绿#2ecc71)、下载(蓝#3498db),通过颜色暗示功能(红 = 删除、绿 = 保存、蓝 = 下载);3. 交互:transition: all 0.3s ease,hover 时上移 2px(translateY(-2px))+ 阴影,active 时复位,提供点击反馈。

  • .signature-preview 签名预览区:1. 布局:text-align: center 使预览图居中,margin-top: 30px 与操作区分隔;2. 预览图(#signaturePreview):max-width: 100%避免拉伸,边框(1px solid #bdc3c7)+ 圆角 5px,默认 display: none,保存后显示。

  • footer 页脚信息区:1. 布局:margin-top: 30px、text-align: center,颜色浅灰(#7f8c8d)、字号 0.9rem,不抢占视觉焦点;2. 内容:版权 + 技术栈说明,增强工具可信度。

JavaScript

  1. 初始化与元素获取
javascript 复制代码
document.addEventListener('DOMContentLoaded', function () {
    // 1. 获取Canvas与上下文(核心绘制对象)
    const canvas = document.getElementById('signatureCanvas');
    const ctx = canvas.getContext('2d'); // 2D绘制上下文,用于绘制签名
    const placeholder = document.querySelector('.canvas-placeholder');

    // 2. 获取控制元素(样式控制、操作按钮、预览区)
    const clearBtn = document.getElementById('clearBtn');
    const saveBtn = document.getElementById('saveBtn');
    const downloadBtn = document.getElementById('downloadBtn');
    const colorOptions = document.querySelectorAll('.color-option');
    const lineWidthInput = document.getElementById('lineWidth');
    const widthValue = document.getElementById('widthValue');
    const signaturePreview = document.getElementById('signaturePreview');

    // 3. 初始化状态变量
    let isDrawing = false; // 是否正在绘制(防止鼠标离开后继续绘制)
    let lastX = 0, lastY = 0; // 上一个绘制点的坐标(用于连接线条)
    let currentColor = '#000000'; // 默认签名颜色(黑色)
    let currentLineWidth = 2; // 默认线条粗细(2px)
    let signatureData = []; // 存储签名数据(用于Canvas resize后重新绘制)
});
  1. Canvas 尺寸适配(核心兼容逻辑)

解决窗口 resize 时 Canvas 内容丢失问题,通过保存绘制数据重新渲染:

javascript 复制代码
function resizeCanvas() {
    const container = canvas.parentElement;
    // 1. 更新Canvas尺寸为父容器宽度(高度固定300px)
    canvas.width = container.clientWidth;
    canvas.height = 300;
    // 2. 重新绘制签名(如果有数据)
    redrawSignature();
}

// 重新绘制签名:遍历保存的绘制数据,还原签名
function redrawSignature() {
    ctx.clearRect(0, 0, canvas.width, canvas.height); // 清空画布
    if (signatureData.length === 0) { // 无签名数据时显示占位提示
        placeholder.style.display = 'flex';
        return;
    }

    // 遍历每一段绘制路径(每段对应一次鼠标按下到抬起的绘制)
    signatureData.forEach(stroke => {
        if (stroke.length > 0) {
            ctx.beginPath(); // 开始新路径
            ctx.moveTo(stroke[0].x, stroke[0].y); // 移动到第一段的起点
            // 连接后续所有点,还原线条
            for (let i = 1; i < stroke.length; i++) {
                ctx.lineTo(stroke[i].x, stroke[i].y);
            }
        }
    });
    ctx.stroke(); // 执行绘制
}

// 初始化时调用一次,窗口 resize 时重新适配
resizeCanvas();
window.addEventListener('resize', resizeCanvas);
  1. 签名绘制逻辑(核心交互)

通过监听鼠标 / 触摸事件,实现 "按下 - 移动 - 抬起" 的完整绘制流程,兼容 PC 与移动端:

javascript 复制代码
// 1. 初始化绘制样式:圆角线条(避免尖锐边缘)
ctx.strokeStyle = currentColor; // 线条颜色
ctx.lineWidth = currentLineWidth; // 线条粗细
ctx.lineJoin = 'round'; // 线条连接点圆角
ctx.lineCap = 'round'; // 线条端点圆角

// 2. 开始绘制(鼠标按下/触摸开始)
function startDrawing(e) {
    isDrawing = true; // 标记为正在绘制
    [lastX, lastY] = getCoordinates(e); // 获取初始坐标(兼容鼠标/触摸)
    placeholder.style.display = 'none'; // 隐藏占位提示
    signatureData.push([]); // 新增一段绘制数据(存储当前笔画)
}

// 3. 绘制中(鼠标移动/触摸滑动)
function draw(e) {
    if (!isDrawing) return; // 未标记绘制时跳过
    e.preventDefault(); // 阻止触摸设备上的默认滚动

    const [x, y] = getCoordinates(e); // 获取当前坐标
    // 绘制线条:从上次坐标到当前坐标
    ctx.beginPath();
    ctx.moveTo(lastX, lastY);
    ctx.lineTo(x, y);
    ctx.stroke();

    // 保存当前坐标到绘制数据(用于后续重新渲染)
    signatureData[signatureData.length - 1].push({ x, y });
    [lastX, lastY] = [x, y]; // 更新上次坐标
}

// 4. 结束绘制(鼠标抬起/触摸结束)
function stopDrawing() {
    isDrawing = false; // 取消绘制标记
}

// 5. 坐标转换(兼容鼠标与触摸事件)
function getCoordinates(e) {
    let x, y;
    if (e.type.includes('touch')) { // 触摸事件:获取触摸点相对于Canvas的坐标
        x = e.touches[0].clientX - canvas.getBoundingClientRect().left;
        y = e.touches[0].clientY - canvas.getBoundingClientRect().top;
    } else { // 鼠标事件:直接获取相对于Canvas的偏移坐标
        x = e.offsetX;
        y = e.offsetY;
    }
    return [x, y];
}

// 6. 绑定绘制事件(鼠标+触摸)
canvas.addEventListener('mousedown', startDrawing);
canvas.addEventListener('mousemove', draw);
canvas.addEventListener('mouseup', stopDrawing);
canvas.addEventListener('mouseout', stopDrawing); // 鼠标离开画布时结束绘制

canvas.addEventListener('touchstart', startDrawing);
canvas.addEventListener('touchmove', draw);
canvas.addEventListener('touchend', stopDrawing);

// 防止触摸绘制时页面滚动(passive: false 允许 preventDefault)
canvas.addEventListener('touchmove', function (e) {
    if (isDrawing) e.preventDefault();
}, { passive: false });
  1. 样式控制逻辑(颜色 + 粗细)

实现签名样式的实时调整,视觉反馈即时:

javascript 复制代码
// 颜色选择:点击颜色块切换签名颜色
colorOptions.forEach(option => {
    option.addEventListener('click', function () {
        // 移除所有选项的active类,避免多选中
        colorOptions.forEach(opt => opt.classList.remove('active'));
        // 为当前选中选项添加active类,视觉标识
        this.classList.add('active');
        // 更新当前颜色,并同步到Canvas绘制样式
        currentColor = this.getAttribute('data-color');
        ctx.strokeStyle = currentColor;
    });
});

// 线条粗细:拖动滑块调整,实时显示数值
lineWidthInput.addEventListener('input', function () {
    currentLineWidth = this.value;
    ctx.lineWidth = currentLineWidth; // 同步到Canvas绘制样式
    widthValue.textContent = `${currentLineWidth}px`; // 更新数值显示
});

各位互联网搭子,要是这篇文章成功引起了你的注意,别犹豫,关注、点赞、评论、分享走一波,让我们把这份默契延续下去,一起在知识的海洋里乘风破浪!

相关推荐
biomooc2 小时前
D3.js 与数据可视化
开发语言·javascript·信息可视化
前端Hardy2 小时前
告别抽象!可视化动画带你学习算法——选择排序
前端·javascript·css
毕设十刻2 小时前
基于vue的考研信息系统6kv17(程序 + 源码 + 数据库 + 调试部署 + 开发环境配置),配套论文文档字数达万字以上,文末可获取,系统界面展示置于文末
前端·数据库·vue.js
望获linux3 小时前
论文解读:利用中断隔离技术的 Linux 亚微秒响应性能优化
java·linux·运维·前端·arm开发·数据库·性能优化
brzhang3 小时前
ChatGPT Pulse来了:AI 每天替你做研究,这事儿你该高兴还是该小心?
前端·后端·架构
正义的大古3 小时前
OpenLayers地图交互 -- 章节八:平移交互详解
javascript·vue.js·交互·openlayers
泉城老铁3 小时前
springboot+vue 文件下载,实现大文件的分片压缩和下载,避免内存溢出
前端·spring boot·后端
用户203735549813 小时前
Vue+Node+MongoDB高级全栈开发视频教程 完整版
前端
我是天龙_绍3 小时前
setup 函数 和 setup 语法糖
前端