采用canvas进行电子签名,并旋转保存

记录一下电子签名旋转并合并图片的是实现过程

昨天产品提了个需求,在操作流程上需要添加上用户签名,并且添加到图片上对应的位置.

下面说一下自己思路:

首先想到的就是canvas,因为之前的程序中已经使用过,所以签名很好搞,

首先是样式  html

<view class="signa">
        <view class="btn">
            <view class="cancel-btn" @click="clear">重写</view>

            <view class="save-btn" @click="save">保存</view>

            <view class="cancel-bth" @click="colse">关闭</view>
        </view>
        <canvas class="canvas" disable-scroll="true" :style="{ width: width + 'px', height: height + 'px' }" canvas-id="designature" @touchstart="starts" @touchmove="moves" @touchend="end"></canvas>

    </view>

css样式:此样式采用全屏暂时,需要的执行修改里面的height以及三个标签对应的margin-top

.signa {
    position: relative;
    overflow: hidden;
    background-color: #f1efef;
    height: 96vh;
    width: 100vw;
    .canvas {
        background-color: #ffffff;
        position: absolute;
        z-index: 9999;
        left: 50px;
        top: 6px;
        border: 1px solid #d6d6d6;
    }

    .btn {
        height: 96vh;
        position: absolute;
        font-size: 32rpx;
        .cancel-btn {
            width: 10vh;
            border: 1rpx solid #a9a1a1;
            transform: rotate(90deg);
            color: #666;
            margin-left: -2vh;
            margin-top: 10vh;
            height: 65rpx;
            line-height: 65rpx;
            border-radius: 3px;
            text-align: center;
            justify-content: center;
        }

        .save-btn {
            margin-top: 31vh;
            margin-left: -2vh;
            transform: rotate(90deg);
            background: #007aff;
            width: 10vh;
            border-radius: 3px;
            border: 1rpx solid #007aff;
            color: #fff;
            height: 65rpx;
            line-height: 65rpx;
            text-align: center;
        }

        .cancel-bth {
            background: #de7f7f;
            text-align: center;
            color: #fff;
            width: 10vh;
            height: 65rpx;
            line-height: 65rpx;
            transform: rotate(90deg);
            margin-top: 35vh;
            border-radius: 3px;
            border: 1rpx solid #de7f7f;
            margin-left: -2vh;
        }
    }
}

接下来就是重要的js了,我采用的是uni-app进行编写的,其它的应该也差不多(这里不多说)

<script>
export default {
    components: {},
    data() {
        return {
            dom: null,
            line: [],
            radius: 0,
            width: 0,
            height: 0
        };
    },
    onLoad() {},
    computed: {},
    created() {
        uni.getSystemInfo({
            success: res => {
                this.width = res.windowWidth-60;
                console.log(this.width)
                this.height = res.windowHeight+100;
                console.log(this.height)
            }
        });
        this.dom = uni.createCanvasContext('designature', this);
    },
    onShow() {},
    methods: {
        end(e) {},
        distance(a, b) {
            let x = b.x - a.x;
            let y = b.y - a.y;
            return Math.sqrt(x * x + y * y);
        },
        // 开始
        starts(e) {
            this.line.push({
                points: [
                    {
                        time: new Date().getTime(),
                        x: e.touches[0].x,
                        y: e.touches[0].y,
                        dis: 0
                    }
                ]
            });
            let currentPoint = {
                x: e.touches[0].x,
                y: e.touches[0].y
            };
            this.currentPoint = currentPoint;
            this.drawer(this.line[this.line.length - 1]);
        },
        // 滑动
        moves(e) {
            let point = {
                x: e.touches[0].x,
                y: e.touches[0].y
            };
            (this.lastPoint = this.currentPoint), (this.currentPoint = point);
            this.line[this.line.length - 1].points.push({
                time: new Date().getTime(),
                x: e.touches[0].x,
                y: e.touches[0].y,
                dis: this.distance(this.currentPoint, this.lastPoint)
            });
            this.drawer(this.line[this.line.length - 1]);
        },
        // 书写
        drawer(item) {
            let x1,
                x2,
                y1,
                y2,
                len,
                radius,
                r,
                cx,
                cy,
                t = 0.5,
                x,
                y;
            var time = 0;
            if (item.points.length > 2) {
                let lines = item.points[item.points.length - 3];
                let line = item.points[item.points.length - 2];
                let end = item.points[item.points.length - 1];
                x = line.x;
                y = line.y;
                x1 = lines.x;
                y1 = lines.y;
                x2 = end.x;
                y2 = end.y;
                var dis = 0;
                time = line.time - lines.time + (end.time - line.time);
                dis = line.dis + lines.dis + end.dis;
                var dom = this.dom;
                var or = Math.min((time / dis) * this.linePressure + this.lineMin, this.lineMax);
                cx = (x - Math.pow(1 - t, 2) * x1 - Math.pow(t, 2) * x2) / (2 * t * (1 - t));
                cy = (y - Math.pow(1 - t, 2) * y1 - Math.pow(t, 2) * y2) / (2 * t * (1 - t));
                dom.setLineCap('round');
                dom.beginPath();
                dom.setStrokeStyle('black');
                dom.setLineWidth(5);
                dom.moveTo(x1, y1);
                dom.quadraticCurveTo(cx, cy, x2, y2);
                dom.stroke();
                dom.draw(true);
            }
        },
        // 清除
        clear() {
            this.dom.clearRect(0, 0, 1000, 1000);
            this.dom.draw();
        },
        //关闭
        colse(e) {
            this.$emit('colse', false);
        },
        // 保存图片
        save() {
            var t = this;
            uni.canvasToTempFilePath(
                {
                    canvasId: 'designature',
                    fileType: 'png',
                    quality: 1, //图片质量
                    success: function(res) {
                        t.$emit('getImg',res.tempFilePath);
                        // uni.navigateBack({
                        //     delta:1
                        // })
                    },
                    fail(e) {
                        console.log(e);
                    }
                },
                this
            );
        },
    }
};
</script>

上面就是签名的实现了.需要了直接封装成组件就好;

最后拿到的效果就是这样的:

开源的电子签名框架 java 前端电子签名_Math

 

 

 最后保存的样式

开源的电子签名框架 java 前端电子签名_电子签名_02

 

 

 结果这样的不是产品要的结果,产品需要的是竖屏签字,横屏显示,直接原地裂开,

由于签名已经好了  懒得改,所以就产生了第二个canvas用来进行图片的旋转.上代码:

首先是html,更改后的代码如下

<view class="signa">
        <view class="btn">
            <view class="cancel-btn" @click="clear">重写</view>

            <view class="save-btn" @click="save">保存</view>

            <view class="cancel-bth" @click="colse">关闭</view>
        </view>
        <canvas class="canvas" disable-scroll="true" :style="{ width: width + 'px', height: height + 'px' }" canvas-id="designature" @touchstart="starts" @touchmove="moves" @touchend="end"></canvas>

        <canvas canvas-id="camCacnvs" :style="{ width: height + 'px', height: width + 'px' }" class="canvsborder"></canvas>
    </view>

因为图片旋转不需要用户看到,所以想到了定位到屏幕外面,添加css

.canvsborder {
        border: 1rpx solid #333;
        position: fixed;
        top: 0;
        left: 10000rpx;
    }

接下来就是旋转保存了

next(path) {
            console.log(8888,path)
            const that = this;
            const ctx = uni.createCanvasContext('camCacnvs', that);
            ctx.translate(that.width/2, that.height/2.5);//设置旋转点
            ctx.rotate((-90 * Math.PI) / 180);//旋转角度
            ctx.drawImage(path, 0, 0);//画图片
            ctx.draw();
            setTimeout(() => {
                uni.canvasToTempFilePath({
                        canvasId: 'camCacnvs',
                        success: function(res) {
                            var tempFilePath = res.tempFilePath;
                            console.log(6666,tempFilePath)
                            that.$emit('getImg', tempFilePath);
                        },
                        fail: err => {
                            console.log('fail', err);
                        uni.showToast({
                            title:'签名图片生成失败!',
                            duration:2000
                        })
                        uni.hideLoading()
                        }
                    },this);
            }, 200);
        }

这里就是旋转的逻辑,需要注意的一点是,上面的有个地方需要改一下,需要把save里面的t.$emit()修改为t.next(res.tempFilePath)

开源的电子签名框架 java 前端电子签名_Math_03

 

 

 然后我门再试一下,结果发现图片旋转了,终于达到了要求

开源的电子签名框架 java 前端电子签名_Math_04

 

 

 到此 签名以及旋转就结束了.

图片合并在下一篇

完整代码如下:

<template>
    <!-- 
        签名组件
        LYH
        横屏组件
     -->
    <view class="signa">
        <view class="btn">
            <view class="cancel-btn" @click="clear">重写</view>

            <view class="save-btn" @click="save">保存</view>

            <view class="cancel-bth" @click="colse">关闭</view>
        </view>
        <canvas class="canvas" disable-scroll="true" :style="{ width: width + 'px', height: height + 'px' }" canvas-id="designature" @touchstart="starts" @touchmove="moves" @touchend="end"></canvas>

        <canvas canvas-id="camCacnvs" :style="{ width: height + 'px', height: width + 'px' }" class="canvsborder"></canvas>
    </view>
</template>

<script>
export default {
    components: {},
    data() {
        return {
            dom: null,
            line: [],
            radius: 0,
            width: 0,
            height: 0
        };
    },
    onLoad() {},
    computed: {},
    created() {
        uni.getSystemInfo({
            success: res => {
                this.width = res.windowWidth-60;
                console.log(this.width)
                this.height = res.windowHeight+100;
                console.log(this.height)
            }
        });
        this.dom = uni.createCanvasContext('designature', this);
    },
    onShow() {},
    methods: {
        end(e) {},
        distance(a, b) {
            let x = b.x - a.x;
            let y = b.y - a.y;
            return Math.sqrt(x * x + y * y);
        },
        // 开始
        starts(e) {
            this.line.push({
                points: [
                    {
                        time: new Date().getTime(),
                        x: e.touches[0].x,
                        y: e.touches[0].y,
                        dis: 0
                    }
                ]
            });
            let currentPoint = {
                x: e.touches[0].x,
                y: e.touches[0].y
            };
            this.currentPoint = currentPoint;
            this.drawer(this.line[this.line.length - 1]);
        },
        // 滑动
        moves(e) {
            let point = {
                x: e.touches[0].x,
                y: e.touches[0].y
            };
            (this.lastPoint = this.currentPoint), (this.currentPoint = point);
            this.line[this.line.length - 1].points.push({
                time: new Date().getTime(),
                x: e.touches[0].x,
                y: e.touches[0].y,
                dis: this.distance(this.currentPoint, this.lastPoint)
            });
            this.drawer(this.line[this.line.length - 1]);
        },
        // 书写
        drawer(item) {
            let x1,
                x2,
                y1,
                y2,
                len,
                radius,
                r,
                cx,
                cy,
                t = 0.5,
                x,
                y;
            var time = 0;
            if (item.points.length > 2) {
                let lines = item.points[item.points.length - 3];
                let line = item.points[item.points.length - 2];
                let end = item.points[item.points.length - 1];
                x = line.x;
                y = line.y;
                x1 = lines.x;
                y1 = lines.y;
                x2 = end.x;
                y2 = end.y;
                var dis = 0;
                time = line.time - lines.time + (end.time - line.time);
                dis = line.dis + lines.dis + end.dis;
                var dom = this.dom;
                var or = Math.min((time / dis) * this.linePressure + this.lineMin, this.lineMax);
                cx = (x - Math.pow(1 - t, 2) * x1 - Math.pow(t, 2) * x2) / (2 * t * (1 - t));
                cy = (y - Math.pow(1 - t, 2) * y1 - Math.pow(t, 2) * y2) / (2 * t * (1 - t));
                dom.setLineCap('round');
                dom.beginPath();
                dom.setStrokeStyle('black');
                dom.setLineWidth(5);
                dom.moveTo(x1, y1);
                dom.quadraticCurveTo(cx, cy, x2, y2);
                dom.stroke();
                dom.draw(true);
            }
        },
        // 清除
        clear() {
            this.dom.clearRect(0, 0, 1000, 1000);
            this.dom.draw();
        },
        //关闭
        colse(e) {
            console.log(88888)
            this.$emit('Back');
        },
        // 保存图片
        save() {
            uni.showLoading({
                title:'图片生成中...'
            })
            var t = this;
            uni.canvasToTempFilePath(
                {
                    canvasId: 'designature',
                    fileType: 'png',
                    quality: 1, //图片质量
                    success: function(res) {
                        t.next(res.tempFilePath);
                        // uni.navigateBack({
                        //     delta:1
                        // })
                    },
                    fail(e) {
                        console.log(e);
                        uni.showToast({
                            title:'签名图片生成失败!',
                            duration:2000
                        })
                        uni.hideLoading()
                    }
                },
                this
            );
        },
        next(path) {
            console.log(8888,path)
            const that = this;
            const ctx = uni.createCanvasContext('camCacnvs', that);
            ctx.translate(that.width/2, that.height/2.5);
            ctx.rotate((-90 * Math.PI) / 180);
            ctx.drawImage(path, 0, 0);
            ctx.draw();
            setTimeout(() => {
                uni.canvasToTempFilePath({
                        canvasId: 'camCacnvs',
                        success: function(res) {
                            var tempFilePath = res.tempFilePath;
                            console.log(6666,tempFilePath)
                            that.$emit('getImg', tempFilePath);
                        },
                        fail: err => {
                            console.log('fail', err);
                        uni.showToast({
                            title:'签名图片生成失败!',
                            duration:2000
                        })
                        uni.hideLoading()
                        }
                    },this);
            }, 200);
        }
    }
};
</script>

<style scoped lang="less">
.signa {
    position: relative;
    overflow: hidden;
    background-color: #f1efef;
    height: 96vh;
    width: 100vw;
    .canvsborder {
        border: 1rpx solid #333;
        position: fixed;
        top: 0;
        left: 10000rpx;
    }
    .canvas {
        background-color: #ffffff;
        position: absolute;
        z-index: 9999;
        left: 50px;
        top: 6px;
        border: 1px solid #d6d6d6;
    }

    .btn {
        height: 96vh;
        position: absolute;
        font-size: 32rpx;
        .cancel-btn {
            width: 10vh;
            border: 1rpx solid #a9a1a1;
            transform: rotate(90deg);
            color: #666;
            margin-left: -2vh;
            margin-top: 10vh;
            height: 65rpx;
            line-height: 65rpx;
            border-radius: 3px;
            text-align: center;
            justify-content: center;
        }

        .save-btn {
            margin-top: 31vh;
            margin-left: -2vh;
            transform: rotate(90deg);
            background: #007aff;
            width: 10vh;
            border-radius: 3px;
            border: 1rpx solid #007aff;
            color: #fff;
            height: 65rpx;
            line-height: 65rpx;
            text-align: center;
        }

        .cancel-bth {
            background: #de7f7f;
            text-align: center;
            color: #fff;
            width: 10vh;
            height: 65rpx;
            line-height: 65rpx;
            transform: rotate(90deg);
            margin-top: 35vh;
            border-radius: 3px;
            border: 1rpx solid #de7f7f;
            margin-left: -2vh;
        }
    }
}
</style>

上面采用组件形式:调用如下:

<QM @getImg="getImg" @Back="Back"></QM>
import QM  from "@/components/auto.vue"
components:{
            QM
        },
getImg(e){
        //这里是图片的临时路径  可以用来上传
                console.log(e)
            },
            Back(e){
                uni.navigateBack({
                    url:1
                })
            }

到此结束.