利用 HTML5 Canvas 实现在线签字功能

news2025/1/13 10:56:03

目录

前言

一、HTML5 Canvas 简介

二、签字功能的实现

效果演示

完整代码


前言

        在现代互联网应用中,有时我们需要让用户在网页上进行签字操作,比如确认文件、填写电子表格或者签署合同。利用 HTML5 的 canvas 画布,我们可以轻松地实现这一功能,为用户提供方便快捷的在线签字体验。

一、HTML5 Canvas 简介

HTML5 的 canvas 元素是一种强大的图形渲染工具,它允许开发者使用 JavaScript 在网页上绘制各种图形、动画和交互式内容。通过 canvas,开发者可以创建丰富多彩的视觉效果,并实现复杂的用户交互体验。

HTML5 Canvas的关键特性:

  1. 图形绘制能力:Canvas 元素提供了绘制路径、矩形、圆形、直线、文本等基本图形的功能,同时还支持图像的绘制和变换操作,使得开发者能够轻松地创建各种视觉效果。

  2. 动画和交互:借助 JavaScript,开发者可以在 Canvas 上创建复杂的动画效果,并添加交互式的操作。这使得 Canvas 成为开发游戏、数据可视化和其他需要动态效果的应用的理想选择。

  3. 性能优势:由于 Canvas 是基于 GPU 加速的,因此它具有良好的性能表现,能够处理大量的图形元素和动画效果,而不会对页面的整体性能产生太大影响。

  4. 灵活性:Canvas 元素可以轻松地与其他 HTML 元素结合使用,使得开发者可以在页面上创建复杂的混合媒体效果,同时还可以响应用户的交互操作。

二、签字功能的实现

效果演示

完整代码

HTML代码

<!DOCTYPE html>
<html class="no-js">
<head>
    <meta name="viewport"
        content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0,user-scalable=no,viewport-fit=cover">
    <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
    <meta http-equiv="expires" CONTENT="Wed, 26 Feb 1997 08:21:57 GMT">
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta http-equiv="Pragma" content="no-cache">
    <meta http-equiv="Cache" content="no-cache">
    <meta http-equiv="Expires" content="0">
    <meta charset="utf-8">
    <title>画图</title>
    <link rel="stylesheet" href="css/bootstrap.css">
    <style>
        * {
            margin: 0;
            padding: 0;
        }

        html,
        body {
            width: 100%;
            height: 100%;
            text-align: center;
        }

        canvas {
            max-width: 100%;
            border: 2px dotted #ccc;
        }
    </style>
</head>

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

    <script>
        //初始化
        var sign = new Draw( {
            // canvas:document.getElementById('canvas'),
            lineWidth: 10, // 线条宽度
            width: 400, // canvas 宽
            height: 400, //canvas 高
            strokeStyle: '#333333' // 线条颜色
        } );

        window.onload = function () {
            // 点击输出图片
            document.querySelector( '.ouput' ).onclick = function () {
                var img = new Image();
                img.style.width = '200px';
                img.src = sign.ouput();
                img.onload = function () {
                    document.body.appendChild( img );
                }
                document.querySelector( 'img' ) && document.querySelector( 'img' ).remove();
            }
            // 点击清除
            document.querySelector( '.clear' ).onclick = function () {
                sign.clear();
            }
            // 点击撤销
            document.querySelector( '.undo' ).onclick = function () {
                if ( sign.state.undopath.length > 0 ) {
                    sign.undo();
                } else {
                    console.log( '还没有签名' );
                }
            }
        }
    </script>
    <div class="buttons">
        <button type="button" class="btn btn-primary ouput">生成图片</button>
        <button type="button" class="btn btn-light undo">撤销</button>
        <button type="button" class="btn btn-light clear">清除画布</button>
    </div>
</body>

</html>

js代码

( function ( global, factory ) {
    typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
        typeof define === 'function' && define.amd ? define( factory ) :
            ( global = global || self, global.Draw = factory() );
}( this, ( function () {
    'use strict';

    var classCallCheck = function ( instance, Constructor ) {
        if ( !( instance instanceof Constructor ) ) {
            throw new TypeError( "Cannot call a class as a function" );
        }
    };

    var createClass = function () {
        function defineProperties ( target, props ) {
            for ( var i = 0; i < props.length; i++ ) {
                var descriptor = props[i];
                descriptor.enumerable = descriptor.enumerable || false;
                descriptor.configurable = true;
                if ( "value" in descriptor ) descriptor.writable = true;
                Object.defineProperty( target, descriptor.key, descriptor );
            }
        }

        return function ( Constructor, protoProps, staticProps ) {
            if ( protoProps ) defineProperties( Constructor.prototype, protoProps );
            if ( staticProps ) defineProperties( Constructor, staticProps );
            return Constructor;
        };
    }();

    /**
     * 
     * @description  手写签字版
     */
    var Draw = function () {
        function Draw () {
            var params = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
            classCallCheck( this, Draw );

            this.el = params.el || document.createElement( 'canvas' );
            this.state = {
                undopath: [],
                index: -1,
                old: void 0,
                isStart: false,
                width: params.width || 400,
                height: params.height || 400,
                lineWidth: params.lineWidth || 1,
                isTouch: 'ontouchstart' in window,
                strokeStyle: params.strokeStyle || '#333333'
            };
            var _state = this.state,
                width = _state.width,
                height = _state.height,
                lineWidth = _state.lineWidth;

            this.el.width = width * 2;
            this.el.height = height * 2;
            document.body.appendChild( this.el );
            this.ctx = this.el.getContext( '2d' );
            this.ctx.scale( 2, 2 );
            this.ctx.lineWidth = lineWidth;
            this.ctx.lineJoin = 'round';
            this.ctx.lineCap = 'round';
            this.init();
        }

        createClass( Draw, [{
            key: 'onStart',
            value: function onStart () {
                ++this.state.index;
                this.state.isStart = true;
            }
        }, {
            key: 'onMove',
            value: function onMove ( e ) {
                e.preventDefault();
                if ( !this.state.isStart ) return;
                var pos = this.pos( e );
                var index = this.state.index;

                this.ctx.strokeStyle = this.state.strokeStyle;
                if ( this.state.old ) {
                    this.ctx.beginPath();
                    this.ctx.moveTo( this.state.old.x, this.state.old.y );
                    this.ctx.lineTo( pos.x, pos.y );
                    this.ctx.stroke();
                }
                this.state.old = pos;
                if ( this.state.undopath[index] ) {
                    this.state.undopath[index].push( { x: this.state.old.x, y: this.state.old.y } );
                } else {
                    this.state.undopath[index] = [{
                        x: this.state.old.x,
                        y: this.state.old.y,
                        strokeStyle: this.ctx.strokeStyle,
                        lineWidth: this.ctx.lineWidth
                    }];
                }
            }
        }, {
            key: 'onEnd',
            value: function onEnd () {
                this.state.old = void 0;
                this.state.isStart = false;
            }
        }, {
            key: 'pos',
            value: function pos ( e ) {
                var x = 0,
                    y = 0;
                if ( e.touches ) {
                    x = e.touches[0].pageX;
                    y = e.touches[0].pageY;
                } else {
                    x = e.offsetX / 2;
                    y = e.offsetY / 2;
                }
                return { x: x, y: y };
            }
        }, {
            key: 'ouput',
            value: function ouput () {
                // 输出图片
                return this.el.toDataURL();
            }
        }, {
            key: 'init',
            value: function init () {
                // 绑定事件
                var isTouch = this.state.isTouch;

                this.el.addEventListener( isTouch ? 'touchstart' : 'mousedown', this.onStart.bind( this ), false );
                this.el.addEventListener( isTouch ? 'touchmove' : 'mousemove', this.onMove.bind( this ), false );
                this.el.addEventListener( isTouch ? 'touchend' : 'mouseup', this.onEnd.bind( this ), false );
                this.el.addEventListener( isTouch ? 'touchcancel' : 'mouseout', this.onEnd.bind( this ), false );
            }
        }, {
            key: 'destroyed',
            value: function destroyed () {
                if ( this.el ) {
                    var isTouch = this.state.isTouch;
                    this.el.removeEventListener( isTouch ? 'touchstart' : 'mousedown', this.onStart.bind( this ) );
                    this.el.removeEventListener( isTouch ? 'touchmove' : 'mousemove', this.onMove.bind( this ) );
                    this.el.removeEventListener( isTouch ? 'touchend' : 'mouseup', this.onEnd.bind( this ) );
                    this.el.removeEventListener( isTouch ? 'touchcancel' : 'mouseout', this.onEnd.bind( this ) );
                }
            }
        }, {
            key: 'clear',
            value: function clear () {
                // 清除画布
                this.state.index = -1;
                this.state.undopath = [];
                this.ctx.clearRect( 0, 0, this.el.width, this.el.height );
            }
        }, {
            key: 'undo',
            value: function undo () {
                // 撤销
                this.state.index >= 0 && --this.state.index;
                var undopath = this.state.undopath;
                this.state.undopath.pop();
                this.ctx.clearRect( 0, 0, this.el.width, this.el.height );
                if ( undopath ) {
                    this.ctx.beginPath();
                    for ( var z = 0; z < undopath.length; ++z ) {
                        this.ctx.moveTo( undopath[z][0].x, undopath[z][0].y );
                        this.ctx.lineWidth = undopath[z][0].lineWidth;
                        this.ctx.strokeStyle = undopath[z][0].strokeStyle;
                        for ( var i = 0; i < undopath[z].length; ++i ) {
                            this.ctx.lineTo( undopath[z][i].x, undopath[z][i].y );
                        }
                    }
                    this.ctx.stroke();
                    this.ctx.closePath();
                } else {
                    this.state.undopath = [];
                }
            }
        }] );
        return Draw;
    }();

    return Draw;

} ) ) );

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1810887.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

一、【源码】实现一个简单的Bean容器

实现一个简单的Bean容器 实现流程&#xff1a; 1.创建一个BeanFactory容器&#xff0c;用于管理bean 2.定义一个BeanDefinition&#xff0c;指定UserService类 3.将BeanDefinition注册到BeanFactory中 4.从工厂中获取bean实例&#xff0c;执行方法 注意&#xff1a; BeanDefin…

orbslam2代码解读(3):localmapping局部建图线程

书接上回&#xff0c;介绍完了跟踪线程&#xff0c;已经得到了当前帧相机的位姿&#xff0c;并且当判断需要产生关键帧的时候&#xff0c;tracking线程把新创建的关键帧插入到mpLocalMapper这个线程的mlNewKeyFrames容器中。所以这时候局部线程就根据这个新的关键帧来进行局部建…

三星系统因何而成?或许是因为吞噬了第四颗恒星

相比于其他的类似星体&#xff0c;这个特殊的三星系统拥有更大更紧密的星体。 三星 天文学家发现了前所未见的三星系统。相比于其他典型的三星系统&#xff0c;这一三星系统拥有更大的体积&#xff0c;并且排列也更加紧密&#xff0c;这也使得这一系统更加特别。科学家推测&am…

8、项目目录结构创建

项目目录结构创建 8.1 三层架构 在spring-boot 的web项目中大都是按照这个思路来的: controller层 —> service层(serviceImpl实现service接口)—> mapper层—> mapper.xml文件 创建目录 commen:存放公共代码的 config:存放配置代码的 controller:后端控制器,…

[office] excel表格中双击鼠标左键有什么快捷作用- #经验分享#媒体

excel表格中双击鼠标左键有什么快捷作用? excel表格中双击鼠标左键有什么快捷作用&#xff1f;不要小看鼠标左键双击的作用&#xff0c;在excel中双击鼠标左键可以实现六个功能&#xff0c;提高工作效率&#xff0c;到底是那六个功能呢&#xff1f;请看下文详细介绍 在表格中…

MAVEN仓库和Nexus私服

仓库 仓库的类型 本地&#xff08;local&#xff09; Maven 的本地仓库&#xff0c;在安装 Maven 后并不会创建&#xff0c;它是在第一次执行 maven 命令的时候才被创建 中央&#xff08;central&#xff09; Maven 中央仓库是由 Maven 社区提供的仓库&#xff0c;其中包含了大…

爱普生32.768kHz晶振FC-135:小型化为高端产品带来的卓越优势

在电子设备快速发展的时代&#xff0c;晶振作为时钟源的核心元件&#xff0c;其性能和尺寸对产品的整体表现至关重要。爱普生32.768kHz晶振FC-135凭借其小型化设计和出色的性能&#xff0c;为高端产品带来了显著的优势。本文将介绍32.768kHz晶振FC-135的特点及其在高端产品中的…

Tiny Time Mixers (TTM)轻量级时间序列基础模型:无需注意力机制,并且在零样本预测方面表现出色

大语言模型的发展让研究人员专注于建立尽可能大的模型。但是其实较小的模型在某些任务中表现会优于较大的模型时&#xff0c;例如&#xff1a;Llama 3-8B在MMLU任务上的表现优于较大的Llama 2-70B ! 这就说明大模型并不是万能的&#xff0c;在一些特定任务中&#xff0c;小模型…

RK3568-修改fiq-debugger调试串口

瑞芯微SDK默认将uart2_m0作为调试串口,以下方法将调试串口修改为uart5_m1。修改bootloader 修改/OK3568-linux-source/rkbin/tools/ddrbin_param.txt文件,5表示串口5。1表示复用m1。执行./ddrbin_tool ddrbin_param.txt ../bin/rk35/rk3568_ddr_1560MHz_v1.11.bin命令修改ub…

Windows环境下搭建RocketMq集群(双主双从)

一、官网下载Rocket安装包 下载地址&#xff1a;https://rocketmq.apache.org/dowloading/releases/下载 | RocketMQhttps://rocketmq.apache.org/dowloading/releases/ 博主这里下载的是4.9.8版本的&#xff0c;大家根据自己的需要下载对应的版本即可。 二、环境变量配置 环…

再度牵手,制造升级 | 毅达科技IMS OS+通用产品集+行业套件项目正式启动!

在数字化与智能制造的浪潮中&#xff0c;制造业企业纷纷加快转型步伐&#xff0c;力求通过技术创新实现生产效率与质量的双重提升。近日&#xff0c;广东毅达医疗科技股份有限公司&#xff08;以下简称“毅达科技”&#xff09;再次携手盘古信息&#xff0c;正式启动了IMS 数字…

背包问题—动态规划

01背包问题&#xff1a;没有物品&#xff08;元素&#xff09;只能选择1次 【模板】01背包_牛客题霸_牛客网 (nowcoder.com) #include <array> #include <cstring> #include <iostream> #include<vector> using namespace std; int n,V; int dp[1001…

全球微型光谱仪市场规模逐渐扩大 智能手机为最大应用领域

全球微型光谱仪市场规模逐渐扩大 智能手机为最大应用领域 光谱仪又称为分光仪&#xff0c;是用来测量光谱成分的一种仪器&#xff0c;可以检测特定波长的电磁辐射形成的光谱&#xff0c;从而获取有关物质的结构和动力学等信息。微型光谱仪是光谱仪的细分产品之一&#xff0c;具…

原腾讯云副总裁张纾翔加入矩阵起源,共筑人工智能新篇章

近日&#xff0c;原腾讯云副总裁张纾翔先生正式加入矩阵起源&#xff0c;担任合伙人兼高级副总裁&#xff0c;全面负责矩阵起源商业化工作。 矩阵起源成立于2021年。公司创始团队来自腾讯云、Snowflake等国内外一流的互联网企业、软件公司、数字化企业和开源社区&#xff0c;核…

使用 Transformer 完成 IMDB 情感分类任务

前言 本文使用简单的 Transformer Block 实现对影评文本数据 IMDB 的情感分类任务。 数据 这里定义了两个关键的超参数&#xff1a; vocab_size&#xff1a;表示词汇表的大小&#xff0c;即允许在文本数据中使用的不同的单词数量。maxlen&#xff1a;表示文本序列的最大长度&…

AI大模型-机器学习中的集成学习

机器学习中的集成学习 集成学习概述及主要研究领域 1.1 集成学习概述&#x1f4a5; “众人拾柴火焰高”、“三个臭皮匠顶个诸葛亮”等词语都在表明着群体智慧的力量&#xff0c;所谓的“群体智慧”指的就是一群对某个主题具有平均知识的人集中在一起可以对某一些问题提供出更…

觅瑞集团两年亏损9亿:现金流承压营销成本近亿,应收账款周期过长

《港湾商业观察》黄懿 4月30日&#xff0c;Mirxes Holding Company Limited&#xff08;下称“觅瑞集团”&#xff09;递表港交所&#xff0c;拟在香港主板挂牌上市&#xff0c;中金和建银国际为联席保荐人&#xff0c;这是继2023年7月25日递表失效后的再一次申请。觅瑞集团国…

记录vue一个echarts页面 柱状图加平均分横线 双柱状图 横向双柱状图

<template><div class"app-container"><el-form :model"queryParams" ref"queryForm" size"small" v-show"showSearch" label-width"85px"><el-form-item label"园所名称" prop&q…

【Vue】智慧商城

步骤一般都是&#xff1a; 静态结构 > 封装接口 > 路由获取参数 > 获取数据 动态渲染 先封装接口再路由获取参数的原因是因为&#xff0c;只有先封装好了接口&#xff0c;才能知道我们需要哪些参数 接口文档&#xff1a;https://apifox.com/apidoc/shared-12ab6b18-a…

LabVIEW结构体内部缺陷振动检测

结构体内部缺陷会改变其振动特性&#xff0c;通过振动分析可以检测并定位这些缺陷。本文详细分析内部缺陷对振动的影响&#xff0c;从频谱分析、时域分析和模态分析等多角度探讨基于LabVIEW的检测方法&#xff0c;提供实施步骤和注意事项&#xff0c;帮助工程师有效利用LabVIEW…