vue手写签名组件封装

8/12/2020 vue.js

最近业务上需要用到移动端手写签名, 找了几个组件都不太如意, 遂自己封装一个组件

设计图如下:

在这里插入图片描述

本组件基于Signature Pad库, 在Signature Pad库的基础上完成上方设计图中的手写签名组件, 所以需要先引入Signature Pad npm包 --------- 点我跳转查看(Signature Pad) (opens new window)

npm install --save signature_pad
  1. 创建手写签名组件 components/autograph/index.vue
<template>
<div class="xhy-autograph-box">
    <div class="autograph-box">
        <div class="inside">
            <van-icon class="icon-full" name="enlarge" color="#008cff" @click="fullScreenShow" />
            <canvas class="xhy-canvas" />
            <span @click="againSignature">重新签名</span>
        </div>
    </div>
    <van-popup v-model="isShowFull" @close="closeFull" :close-on-click-overlay="false" close-on-popstate safe-area-inset-bottom closeable close-icon-position="bottom-right" position="left" :style="{ height: '100%', width: '100%' }">
        <div class="popup-box">
            <canvas class="xhy-canvasFull" />
            <div class="panel-full">
                <van-button class="btn-again" type="info" @click="againFull">重新签名</van-button>
                <van-button class="btn-confirm" plain type="primary" @click="confirmFull">确认签名</van-button>
            </div>
        </div>
    </van-popup>
</div>
</template>

<script>
import SignaturePad from 'signature_pad';
export default {
    name: "xhy-autograph",
    props: {
        config: {
            type: Object,
            default: {
                penColor: 'green', //笔刷颜色
                minWidth: 1 //最小宽度
            }
        }
    },
    data() {
        return {
            isShowFull: false, // 是否横屏显示
            signaturePad: null, // 存放竖屏SignaturePad对象
            signaturePadFull: null, // 存放横屏SignaturePad对象
            value: '',
            fullValue: ''
        }
    },
    mounted() {
        this.init();
    },
    methods: {
        init() {
            if (!this.signaturePad) {
                let canvas = document.querySelector('.xhy-canvas');
                this.signaturePad = new SignaturePad(canvas, this.config);
                this.signaturePad.onEnd = () => {
                    this.$emit('receive', this.signaturePad.toDataURL()); //通知父组件改变。
                }
                canvas.height = document.body.clientHeight / 3;
                canvas.width = document.body.clientWidth - 30;
            }
        },
        initFull() {
            if (!this.signaturePadFull) {
                let canvas = document.querySelector('.xhy-canvasFull');
                this.signaturePadFull = new SignaturePad(canvas, this.config);
                canvas.height = document.body.clientHeight * 0.82;
                canvas.width = document.body.clientWidth;
            }
        },
        closeFull() {

        },
        againFull() {
            this.signaturePadFull.clear();
        },
        confirmFull() {
            try {
                let _flag = this.signaturePadFull.isEmpty();
                this.signaturePad.clear();
                if (!_flag) {
                    this.fullValue = this.signaturePadFull.toDataURL();
                    this.rotateBase64Img(this.fullValue, 270, res => {
                        let _option = {
                            width: document.body.clientWidth - 30,
                            height: document.body.clientHeight / 3
                        }
                        this.signaturePad.fromDataURL(res, _option);
                        this.$emit('receive', res); //通知父组件改变。
                        this.isShowFull = false;
                    })
                }
            } catch (error) {
                console.log(error);
                this.isShowFull = false;
            }
        },
        fullScreenShow() {
            this.isShowFull = true;
            setTimeout(() => {
                this.initFull();
                let _flag = this.signaturePad.isEmpty();
                this.signaturePadFull.clear();
                if (!_flag) {
                    this.value = this.signaturePad.toDataURL();
                    this.rotateBase64Img(this.value, 90, res => {
                        let _option = {
                            width: document.body.clientWidth,
                            height: document.body.clientHeight * 0.82
                        }
                        this.signaturePadFull.fromDataURL(res, _option);
                    })
                }
            }, 100)
        },
        againSignature() {
            this.signaturePad.clear();
            this.$emit('receive', ''); //通知父组件改变。
        },
        // base64图片旋转方法
        rotateBase64Img(src, edg, callback) {
            var canvas = document.createElement("canvas");
            var ctx = canvas.getContext("2d");
            var imgW; //图片宽度
            var imgH; //图片高度
            var size; //canvas初始大小
            if (edg % 90 != 0) {
                console.error("旋转角度必须是90的倍数!");
                throw '旋转角度必须是90的倍数!';
            }
            (edg < 0) && (edg = (edg % 360) + 360)
            const quadrant = (edg / 90) % 4; //旋转象限
            const cutCoor = {
                sx: 0,
                sy: 0,
                ex: 0,
                ey: 0
            }; //裁剪坐标
            var image = new Image();
            image.crossOrigin = "anonymous"
            image.src = src;
            image.onload = function () {
                imgW = image.width;
                imgH = image.height;
                size = imgW > imgH ? imgW : imgH;
                canvas.width = size * 2;
                canvas.height = size * 2;
                switch (quadrant) {
                    case 0:
                        cutCoor.sx = size;
                        cutCoor.sy = size;
                        cutCoor.ex = size + imgW;
                        cutCoor.ey = size + imgH;
                        break;
                    case 1:
                        cutCoor.sx = size - imgH;
                        cutCoor.sy = size;
                        cutCoor.ex = size;
                        cutCoor.ey = size + imgW;
                        break;
                    case 2:
                        cutCoor.sx = size - imgW;
                        cutCoor.sy = size - imgH;
                        cutCoor.ex = size;
                        cutCoor.ey = size;
                        break;
                    case 3:
                        cutCoor.sx = size;
                        cutCoor.sy = size - imgW;
                        cutCoor.ex = size + imgH;
                        cutCoor.ey = size + imgW;
                        break;
                }
                ctx.translate(size, size);
                ctx.rotate(edg * Math.PI / 180);
                ctx.drawImage(image, 0, 0);
                var imgData = ctx.getImageData(cutCoor.sx, cutCoor.sy, cutCoor.ex, cutCoor.ey);
                if (quadrant % 2 == 0) {
                    canvas.width = imgW;
                    canvas.height = imgH;
                } else {
                    canvas.width = imgH;
                    canvas.height = imgW;
                }
                ctx.putImageData(imgData, 0, 0);
                callback(canvas.toDataURL())
            };
        }
    }

}
</script>

<style lang="scss" scoped>
.xhy-autograph-box {
    @include wh(100%, auto);

    .autograph-box {
        padding: 15px;
        width: 100%;
        height: auto;
        background-color: #fff;

        .inside {
            display: flex;
            position: relative;
            @include wh(100%, 100%);
            background-color: rgb(242, 242, 242);

            .icon-full {
                position: absolute;
                right: 0;
                top: 0;
                padding: 6px;
            }

            .xhy-canvas {}

            span {
                position: absolute;
                right: 0;
                bottom: 0;
                @include sc(12px, #008cff); // font-size  color
                padding: 6px;
            }
        }
    }

    .popup-box {
        @include wh(100%, 100%);
        background-color: rgb(242, 242, 242);

        .xhy-canvasFull {
            @include wh(100%, 82%);
            border-bottom: 1px solid #ccc;
        }

        .panel-full {
            @include wh(100%, 18%);
            position: relative;

            /deep/ .van-button {
                position: absolute;
                transform: rotate(90deg);
            }

            .btn-again {
                bottom: 40px;
                left: 66px;
            }

            .btn-confirm {
                bottom: 40px;
                left: 0px;
            }
        }
    }
}
</style>

  1. 引入组件使用
// template
<XhyAutograph :config="options" @receive="receiveQmValue"></XhyAutograph>

// script
import XhyAutograph from "@/components/autograph";

components: {
    XhyAutograph
},
data() {
    return {
      options: {
                penColor: 'black', //笔刷颜色
                minWidth: 2 //最小宽度
        },
        qmValue: ''
    }
},
methods: {
  receiveQmValue(val) {
       this.qmValue = val;
        console.log(this.qmValue);
    }
}

最后receiveQmValue方法返回的是图片的base64

到这就结束了, 如有不懂的问题可以咨询.