H5 Canvas 签名板

签名板介绍

在最近的一个项目中,最后的一个功能是实现一个签名板供客户签名使用。这需要用到canvas来实现。

我将这个功能分成实现和优化两个阶段。首先来看实现

实现

对于一个canvas签名板,首先我们要完成最最最基本的功能,签名!

步骤为下:

1、提供画布:

通过canvas的API,很容易定义出一块画布。

2、定义画笔

实际情况上是没有画笔这个东西的,所以需要自己来假定一个画笔。

画笔具有三个状态,准备画,画,画完了。用一个flag和三个方法来表示这一过程:

mousePress:false  // 这个flag表示是否开始画了,准备画的时候这个标志位置为true,画完置为false。
beginDraw (e) { ... } // 对应准备画的方法
drawing (e) {...} // 对应画的方法
endDraw (e) {...} // 对应画完的方法 。。。 这三个方法的实现下面会具体讲,这里假定已经实现了。
3、实现签名

现在距离实现签名就差一步,那就是记录下鼠标每次经过的位置并用ctx.stroke()方法将它画出来即可。
这里需要知道:画过的路径不会变,将要画的路径不知道,正在画的地方能获取。所以定义一个叫做last的对象来记录上一次鼠标画过的点的位置并把它画到现在的位置,以此类推,就可以画出一条鼠标经过的线路了。现在来具体实现画画这一过程:

a、beginDraw

  beginDraw(e) {
        mousePress = true;
    },  // 没什么好说的  

b、endDraw

  endDraw  (e) {
        e.preventDefault();
        mousePress = false;
        last = null;
  } // 也没什么好说的,画完了标志位和记录对象都该还原了。

c、drawing

  drawing (e) {
        e.preventDefault();
        if (!this.mousePress) {
            return; // 这个虽然不会触发,但是好像需要这个逻辑也就写上去了
        }
        var xy = this.getCoordinate(e);  // 这个方法是获取当前的鼠标的位置并将它赋值给一个叫做xy的对象
        if (this.last != null) {
            this.context.beginPath();
            this.context.moveTo(this.last.x, this.last.y);
            this.context.lineTo(xy.x, xy.y);
            this.context.stroke();
        }
        // 开始移动,将坐标赋值给last。那么下次再移动就会通过上面的操作从上一个xy移动到当前的xy处
        this.last = xy;
  }

d、getCoordinate 获取当前鼠标位置

getCoordinate(e) {
        var x, y;
        x = e.offsetX + e.target.offsetLeft;
        y = e.offsetY + e.target.offsetTo; // 获取当前x,y坐标
        return {
           x,y
        } //es6语法多简洁
    },
4、事件绑定

接下来只需要在canvas上绑定事件去对应准备画,画和结束画三个方法就可以了。

canvas.onmousedown = beginDraw; // 鼠标按下事件
canvas.onmouseup = endDraw; // 鼠标松开的事件
canvas.onmousemove = drawing; // 鼠标移动事件

好了,如果你稍微了解一些canvas的基础,补全这些代码,现在你的签名板就实现了。

优化

签名板,在电脑上签名,用鼠标签名,怎么说都显得怪异。

优化1、移动端的签名:

首先,把移动端的事件绑定到对应的三个方法:

 canvas.addEventListener('touchstart',beginDraw,false)
canvas.addEventListener('touchmove',drawing,false)
canvas.addEventListener('touchend',endDraw,false)

然后,在移动端试了试,不行。那是因为在getCoordinate 方法中获取的当前位置有问题,在移动端,要用另一种方法来获取。为了区分移动端和PC,需要用一个控制语句来区分它们。如下

getCoordinate(e) {
        var x, y;
        if (this.isTouch(e)) {  // 判断当前事件是移动端事件还是PC端事件
            x = e.touches[0].pageX; 
            y = e.touches[0].pageY;
        }
        else {
            x = e.offsetX + e.target.offsetLeft;
            y = e.offsetY + e.target.offsetTop;
        }
        return {
            x,y
        }
    },

  isTouch(e) { // 判断当前事件是移动端事件还是PC端事件
        var type = e.type;
        if (type.indexOf('touch') >= 0) {
            return true;
        } else {
            return false;
        }
    },

OK,现在签名板以及可以兼容PC端和移动端两个部分了。

优化2、个性化签名

当然,这个名还是要自己签的,不过可以自己设定签名的线条的粗细,颜色等。这些样式设定放在beginDraw这个方法处即可。具体实现就不说了。

优化3、用Vue来实现。

用Vue的话,更方便地控制这个项目中需要用的变量等。具体改变就是把上述需要定义的变量放在vue的data中,方法房子啊methods中。绑定事件在canvas处,如图:

canvas.png

这样也是非常的方便。

优化4、移动端横竖屏画布大小调整

在PC端的话,设定固定大小的画布即可。但对于移动端来说,画布的宽度需要撑满屏幕才勉强可以做到签名。这时候,需要实现将手机变成横屏时,监听手机的变化,改变画布的大小。这里,初始化画布宽度为手机屏幕的宽度,高度为width*380/750。

这时候,需要用到window.onorientationchange来监听横竖屏的转变和window.orientation来获取横竖屏转变的参数,如图所示:

监听横竖屏.png
获取参数.png

0表示正常,90和-90表示横屏,180就是把手机倒过来。然后把orientationChange方法下面开始方案。

方案1、获取手机屏幕大小,直接进行改变(宽变高,高变宽)。
这是我最初的实现方案,这种直接改变画布宽和高是销毁了原来的画布重新生成了一张画布。确实达到了横屏签名的效果,但这却存在很大缺陷,那就是横竖屏转变重置画布会消除画布原有的内容。在实际场景中,有些客户如果想要横屏签字,竖屏再查看一下自己写的什么样子,就不满足于客户需求了。

方案2、由于横竖屏转变,画布必然会被重置,所以就不需要考虑如何让原有的签名内容不变保留过来了。解决方案就是在每次横竖屏转变的时候,获取原有签名板上内容数据,在横竖屏转变之后,将数据放入到新生成的画布中。这里,我将画布重置和画布内容导入封装到了一个叫做resize的方法中,代码如下:

  /**
     * 重置画布操作
     * @param {*} w 画布宽 
     * @param {*} h 画布高
     * @param {*} flag 横竖屏标志
     */  
    resize(w, h, flag) {
        var that = this;
        var nc = document.createElement("canvas"); // nc用来保留原有画布数据
        nc.width = that.canvas.width;
        nc.height = that.canvas.height;
        nc.getContext("2d").drawImage(that.canvas, 0, 0); // 将原有画布数据放到同样大小的nc画布中,就像是copy了一份
        this.canvas.width = w; 
        this.canvas.height = h; //画布大小重置
        // 横转竖,原有数据放入到新的画布中。等比放置签名数据。
        if (flag) {
            // crossWidth是我设置的变量,表示横屏情况下,屏幕的宽度,verticalWidth表示竖屏情况下屏幕的宽度。
            this.context.drawImage(nc, 0, 0, this.crossWidth, this.crossHeight, 0, 0, w, h); 
        }
        // 竖转横
        else {
            this.context.drawImage(nc, 0, 0, this.verticalWidth, this.verticalHeight, 0, 0, w, h); 
        }
    },
优化5、高清屏适配。

通常对于canvas来说,我们会在html中设置

 <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1, user-scalable=no">

这样一串东西来规定这个页面不能缩放,缩放比为1,这样我们在开发canvas的时候就不会因为不同手机的dpr不同导致画布呈现的有大有小,很不合理。当然,设置这样的一个约定不但不会影响签名板的视觉呈现(毕竟名字线条单一没那么复杂),也会很方便整个流程的开发。可是还是想让IOS的高清屏可以显示的更加清晰。

方案1、

由于项目采用的是手淘的flexible布局,这里,我们把上面的那个meta标签注释掉,如果你画布的宽度是这样定义的:

this.verticalWidth = screen.width。// 因为我会将这个verticalWidth赋值给canvas.width,见resize()方法。

如果你的是安卓机,那么画布没有变化,因为flexible默认设置的安卓dpr = 1,也就是initial-scale=1/dpr=1,和上面meta标签的配置是一样的。所以不会有变化。

如果你的是IPhone,那么你会发现你的画布大小变小了。这是因为initial-scale不再是1了,而是1/dpr。这时候,我们只需要在设置屏幕宽度的时候再乘上当前iPhone手机的dpr即可还原画布大小。

this.verticalWidth = screen.width*dpr  // dpr需要自己获取

下面晒图,采用高清的IPhone的3像素的签名和不采用高清适配的签名对比

高清.png
非高清.png

好吧,在签名这个功能处高清是真的没什么优势。。。。但是可能在其他的canvas功能中,这种高清适配会让canvas内容更加清晰。

对于安卓来说,暂时的想法就是将dpr为整数的安卓机在flexible中设置为其本身的dpr(dpr>=3 设置为3),这样就也可以让一大部分dpr比较正常的安卓机也可以像IPhone一样使用这种高清。

优化6、兼容性问题

在网上拜读了一位大佬对于orientation事件兼容性处理的方法后 文章,在签名板上也加入了orientation兼容性处理。

由于部分低端手机不支持orientation事件,所以要让他们也可以横屏签名,那么就要做一些改动。

解决方案:

保留原有方法不变,加上判断语句,如下代码:

var isOrientation = ('orientation' in window && 'onorientationchange' in window);
    if (isOrientation) {
        // 注册横竖屏事件,方案1;
        window.onorientationchange = this.orientationChange;
        this.orientationChange();            
    }

对于不支持orientation事件的机型,做resize事件处理(为了防止与我自己写的resize方法冲突,所以我把自己在前面写的resize方法修改成了resizeCanvas):

      else {
        // 使用 resize 来做监听机制。方案2:
        window.addEventListener('resize',function(e){
            var orientation=(window.innerWidth > window.innerHeight)? "landscape":"portrait"; // 判断横竖屏
            if(orientation === 'portrait'){
                // 竖屏 do something ……
                that.screenCtrl = true;
                setTimeout(function(){
                    that.height = that.getWarnHeight();                
                },500)
                that.resizeCanvas(that.verticalWidth, that.verticalHeight, true);
                return;
            } 
            else {
                // 横屏 do something else ……
                that.screenCtrl = false;
                setTimeout(function(){
                    that.height = that.getBtnAreaHeight();
                    that.crossHeight = window.innerHeight - that.height;
                    that.resizeCanvas(that.crossWidth, that.crossHeight, false);              
                },500)
                that.resizeCanvas(that.crossWidth, that.crossHeight, false);  
            }
        },false)
    }

在加上上面的兼容处理后,基本所有的机型都可以使用了。

这一块会不定时更新如何优化,我还在学习怎样使canvas内容更加清晰。

额外补充

从上图可以看出,canvas画布左上角相对于页面而言不一定是(0,0)的位置。那么如果在drawing方法中不做些改变的话,在签名的时候笔迹和画出来的线条位置会不一样,这时候就需要计算出canvas左上角相对于页面左上角直接的差值,并改变drawing方法内的代码。补充代码,获取提示区域高度:

getWarnHeight() {
        var warn = document.getElementById('warn');
        var height = warn.offsetHeight;
        warn = null;
        return height;
    },

总结

签名板 JS代码地址

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,236评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,867评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,715评论 0 340
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,899评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,895评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,733评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,085评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,722评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,025评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,696评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,816评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,447评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,057评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,254评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,204评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,561评论 2 343

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,478评论 25 707
  • 本文首发于我的个人博客:http://cherryblog.site/github项目地址:https://git...
    sunshine小小倩阅读 1,973评论 1 8
  • ‘你在哪里,我很担心’ 最近在一个广告里看到这句话,一个妻子发给他加班的丈夫。看到之后突然在想这句话真正在表达的意...
    简心安阅读 251评论 0 0
  • 最舒服的状态,躺在床上,静静的听着窗外沥沥的雨声,慢慢享受这独处的时光。安静,舒适,没有其他嘈杂的声音,任凭大脑放...
    谁与光阅读 197评论 0 0
  • 急聘人事助理一名(朝阳区朝阳路) 【应聘要求】 2年以上人事工作经验(员工关系、招聘经验为佳) 本科及以上学历,人...
    愚鸿说阅读 475评论 1 0