移动端VUE-H5手写电子签名的实现(生成base64图片)- 横版
vue
vuejs/vue: 是一个用于构建用户界面的 JavaScript 框架,具有简洁的语法和丰富的组件库,可以用于开发单页面应用程序和多页面应用程序。
项目地址:https://gitcode.com/gh_mirrors/vu/vue
免费下载资源
·
实现形式:html页面中引入vue方式
一、核心代码 - html部分
<div id="app" v-cloak>
<!-- 旋转前 -->
<img src="" id="mysignature">
<!-- 旋转后 -->
<img :src="signature" id="mysignatureCopy">
<van-popup v-model="showUserSign" class="buyShow">
<user-sign :show-user-sign="showUserSign" @changepopup="changePopup" @signature="getSignature"></user-sign>
</van-popup>
</div>
二、核心代码 - js部分
<script>
/**
* 用户签名组件
*/
/* 注册全局组件 */
Vue.component('user-sign', {
template: `
<div class="signBox">
<canvas ref="copyCanvas" id="copyCanvas"></canvas>
<div class="signature">
<div id="sign" ref="fRoot">
<div class="mask">
<div class="qianmings">
<span>本人签名</span>
</div>
</div>
</div>
<div class="signBtn">
<button @click="closeUserSign">关闭</button>
<button @click="clearCanvas">清除</button>
<button @click="saveCanvas">保存</button>
</div>
</div>
</div>
`,
props: ['showUserSign'],
data() {
return {
linewidth: 4,
color: '#404040',
background: 'transparent',
drawStatus: false,
cxt: '',
cxtCopy: '',
copy: '',
canvasStyle: {
width: '',
height: ''
}
};
},
mounted() {
this.initCanvas()
},
methods: {
closeUserSign() {
// api.closeWin()
this.$emit('changepopup', false)
},
initCanvas() {
let canvas = document.createElement("canvas")
this.canvas = canvas
this.$refs.fRoot.appendChild(canvas)
let cxt = canvas.getContext("2d")
this.cxt = cxt
let width = this.$refs.fRoot.clientWidth
let height = this.$refs.fRoot.clientHeight
cxt.fillStyle = this.background;
canvas.width = width;
canvas.height = height;
this.canvasStyle.width = width;
this.canvasStyle.height = height;
// cxt.fillRect(0, 0, width, height);
cxt.strokeStyle = this.color;
cxt.lineWidth = this.linewidth;
cxt.lineCap = "round";
this.cxtCopy = this.$refs.copyCanvas.getContext("2d");
this.copy = this.$refs.copyCanvas
const offset = 24;
//开始绘制
canvas.addEventListener(
"touchstart",
function (e) {
this.drawStatus = true
cxt.beginPath();
cxt.moveTo(
e.changedTouches[0].pageX - offset,
e.changedTouches[0].pageY - offset
);
}.bind(this),
false
);
//绘制中
canvas.addEventListener(
"touchmove",
function (e) {
cxt.lineTo(
e.changedTouches[0].pageX - offset,
e.changedTouches[0].pageY - offset
);
cxt.stroke();
}.bind(this),
false
);
//结束绘制
canvas.addEventListener(
"touchend",
function () {
cxt.closePath();
}.bind(this),
false
);
},
clearCanvas() {
// 清除画布
this.cxt.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.drawStatus = false
},
saveCanvas() {
// 保存图片,直接转base64
let imgBase64Copy = ''
let signature = ''
const imgBase64 = this.canvas.toDataURL();
const img = document.querySelector("#mysignature");
img.src = imgBase64;
// console.log(imgBase64)
img.onload = () => {
// 图片宽高
let iw = img.width
let ih = img.height
console.log(iw,ih)
// canvas宽高
// let cw = 763
let cw = 145
let ch = 86
// let ch = 346
const opt = this.getXY(cw, ch, ih, iw)
this.cxtCopy.clearRect(0, 0, opt.rw, opt.rh)
this.cxtCopy.translate(0, opt.rh + opt.points[1])
this.cxtCopy.rotate(-Math.PI / 2)
this.cxtCopy.drawImage(img, opt.points[1], opt.points[0], opt.rh, opt.rw)
// 将有效部分图片截取
let frame = this.cxtCopy.getImageData(0, 0, opt.rw, opt.rh)
// 将canvas保持与图片大小相同
this.copy.height = opt.rh
this.copy.width = opt.rw
// 清除画布
this.cxtCopy.clearRect(0, 0, opt.rw, opt.rh)
// 重新绘制
this.cxtCopy.putImageData(frame, 0, 0)
imgBase64Copy = this.copy.toDataURL();
// console.log(imgBase64Copy)
if (this.drawStatus) {
signature = imgBase64Copy;
} else {
signature = ''
}
this.drawStatus = false
this.clearCanvas()
this.$emit('signature', signature)
setTimeout(() => {
// api.closeWin()
this.uploadOss(imgBase64Copy)
}, 3000)
}
},
/**
* @description:
* @param {*} cw canvas宽度
* @param {*} ch canvas高度
* @param {*} iw img宽度
* @param {*} ih img高度
* @return {*}
*/
getXY(cw, ch, iw, ih) {
// 比较宽高比例,大的一边撑满
let li = iw / ih
let lc = cw / ch
// console.log('图片宽高比', li, 'canvas宽高比', lc);
// 需要绘制的宽高起点
let points = [0, 0]
let rw, rh
let wall = false // 是否宽度占满
if (li > lc) {
// 宽度100%,等比缩放
rw = cw
rh = rw / li
points[0] = 0
points[1] = (ch - rh) / 2
wall = true
} else {
rh = ch
rw = rh * li
points[0] = (cw - rw) / 2
points[1] = 0
wall = false
}
rw = Math.floor(rw)
rh = Math.floor(rh)
// console.log('canvas', cw, ch, 'img', iw, ih, '绘制', rw, rh);
return {
points,
rw,
rh
}
},
uploadOss(imgBase64Copy) {
// 1.先转为 blob格式 file.content是此文件的base64格式
let blob = this.dataURLtoBlob(imgBase64Copy);
// 拿到文件名
// let fileName = file.file.name;
// 2,在转为 file类型
let file1 = this.blobToFile(blob, '签名.png');
console.log("file1:", file1);
const formData = new FormData();//将选择上传的name="pdfFile"的文件赋值给formData
// formData.append("bizType", '1');//接口还需要一个用户名参数,通过append附加到formData上
formData.append("file", file1);//接口还需要一个用户名参数,通过append附加到formData上
var token = api.getPrefs({
sync: true,
key: 'token'
})
console.log(token, url.uploadFile())
$.ajax({
type: 'post',
url: url.uploadFile(),
data: formData,
processData: false,
contentType: false,
dataType: 'json',
headers: {
'X-Token': token
},
success: function (res) {
console.log('上传成功', JSON.stringify(res));
api.toast({
msg: '上传成功',
duration: 3000,
location: 'middle'
});
},
error: function (err) {
api.hideProgress();
api.toast({
msg: '上传失败',
duration: 3000,
location: 'middle'
});
}
});
},
//1,先将base64转换为blob
dataURLtoBlob(dataurl) {
var arr = dataurl.split(','),
mime = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]),
n = bstr.length,
u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new Blob([u8arr], { type: mime });
},
//2,再将blob转换为file
blobToFile(theBlob, fileName) {
theBlob.lastModifiedDate = new Date(); // 文件最后的修改日期
theBlob.name = fileName;
console.log(fileName) // 文件名
return new File([theBlob], fileName, { type: theBlob.type, lastModified: Date.now() });
},
}
});
</script>
<script>
new Vue({
el: '#app',
data() {
return {
showUserSign: true,
signature: ''
}
},
methods: {
getSignature(val) {
this.signature = val
this.showUserSign = false
},
goSign() {
this.showUserSign = true
},
changePopup(val) {
this.showUserSign = val
if (!val) {
api.closeWin()
}
}
}
})
</script>
三、全部代码(html文件)
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport"
content="maximum-scale=1.0, minimum-scale=1.0, user-scalable=0, initial-scale=1.0, width=device-width" />
<meta name="format-detection" content="telephone=no, email=no, date=no, address=no">
<title>Hello APP</title>
<link rel="stylesheet" type="text/css" href="../../css/api.css" />
<link rel="stylesheet" type="text/css" href="../../css/aui/aui.css">
<script type="text/javascript" src="../../script/vue.js"></script>
<link rel="stylesheet" href="../../css/index.css" />
<link rel="stylesheet" href="../../css/base.css" />
<script src="../../script/vant.min.js"></script>
<style>
.signature,
#sign,
.signature button {
box-sizing: border-box;
}
#sign {
position: relative;
}
.signBtn {
flex-shrink: 0;
height: 70px;
display: flex;
align-items: center;
justify-content: space-around;
}
#sign canvas {
width: 100%;
height: 100%;
}
.copyCanvas {
display: none;
}
.address {
margin: 1rem;
padding-bottom: 1rem;
margin-bottom: 0rem;
display: flex;
align-items: center;
justify-content: space-between;
}
.qianming img {
width: 1rem;
float: right;
margin-top: 4px;
}
.qianming button {
width: 1rem;
float: right;
display: flex;
justify-content: flex-end;
}
.qianming {
align-items: flex-start;
}
.signature {
background-color: #fff;
/* width: calc(100% - 2rem);
height: calc(100% - 2rem);
margin: 1rem; */
height: 100%;
width: calc(100vw - 2rem);
display: flex;
flex-direction: column;
justify-content: space-between;
}
.signature>div:first-child {
height: calc(100% - 70px - (0.6*16*2px));
border: 1px dashed #c1c1c1;
margin: 0.6rem;
}
.signature button {
color: #404040;
font-size: 1.2rem;
width: initial;
transform: rotate(90deg);
text-align: center;
width: 100%;
height: 100%;
border: none;
outline: none;
background-color: transparent;
padding: 0;
}
.mask {
position: absolute;
width: 100%;
height: 100%;
z-index: 1;
display: flex;
text-align: right;
}
canvas {
position: relative;
z-index: 2;
}
.mask div {
flex: 1;
position: relative;
}
.mask div span {
transform: rotate(90deg);
display: inline-block;
color: #c1c1c1;
position: relative;
right: 0;
top: 40px;
font-weight: 500;
}
.qianmings {
border-right: 1px dashed #c1c1c1;
}
.btns button {
padding: 0.7rem;
background-color: #5c92ff;
border-radius: 1.5rem;
font-size: 1.1rem;
box-shadow: 0px 2px 5px rgb(0 0 0 / 20%);
color: #fff;
width: 100%;
box-sizing: border-box;
margin-top: 15px;
}
.btns {
margin: 1rem;
}
.signBox {
width: calc(100vw - 2rem);
height: calc(100vh - 2rem);
overflow: hidden;
}
#copyCanvas {
display: none;
}
</style>
</head>
<body>
<div id="app" v-cloak>
<!-- 旋转前 -->
<img src="" id="mysignature">
<!-- 旋转后 -->
<img :src="signature" id="mysignatureCopy">
<van-popup v-model="showUserSign" class="buyShow">
<user-sign :show-user-sign="showUserSign" @changepopup="changePopup" @signature="getSignature"></user-sign>
</van-popup>
</div>
</body>
<script type="text/javascript" src="../../script/api.js"></script>
<script type="text/javascript" src="../../script/jquery.min.js"></script>
<script type="text/javascript" src="../../script/mui.min.js"></script>
<script type="text/javascript" src="../../script/config.js"></script>
<script>
/**
* 用户签名组件
*/
/* 注册全局组件 */
Vue.component('user-sign', {
template: `
<div class="signBox">
<canvas ref="copyCanvas" id="copyCanvas"></canvas>
<div class="signature">
<div id="sign" ref="fRoot">
<div class="mask">
<div class="qianmings">
<span>本人签名</span>
</div>
</div>
</div>
<div class="signBtn">
<button @click="closeUserSign">关闭</button>
<button @click="clearCanvas">清除</button>
<button @click="saveCanvas">保存</button>
</div>
</div>
</div>
`,
props: ['showUserSign'],
data() {
return {
linewidth: 4,
color: '#404040',
background: 'transparent',
drawStatus: false,
cxt: '',
cxtCopy: '',
copy: '',
canvasStyle: {
width: '',
height: ''
}
};
},
mounted() {
this.initCanvas()
},
methods: {
closeUserSign() {
// api.closeWin()
this.$emit('changepopup', false)
},
initCanvas() {
let canvas = document.createElement("canvas")
this.canvas = canvas
this.$refs.fRoot.appendChild(canvas)
let cxt = canvas.getContext("2d")
this.cxt = cxt
let width = this.$refs.fRoot.clientWidth
let height = this.$refs.fRoot.clientHeight
cxt.fillStyle = this.background;
canvas.width = width;
canvas.height = height;
this.canvasStyle.width = width;
this.canvasStyle.height = height;
// cxt.fillRect(0, 0, width, height);
cxt.strokeStyle = this.color;
cxt.lineWidth = this.linewidth;
cxt.lineCap = "round";
this.cxtCopy = this.$refs.copyCanvas.getContext("2d");
this.copy = this.$refs.copyCanvas
const offset = 24;
//开始绘制
canvas.addEventListener(
"touchstart",
function (e) {
this.drawStatus = true
cxt.beginPath();
cxt.moveTo(
e.changedTouches[0].pageX - offset,
e.changedTouches[0].pageY - offset
);
}.bind(this),
false
);
//绘制中
canvas.addEventListener(
"touchmove",
function (e) {
cxt.lineTo(
e.changedTouches[0].pageX - offset,
e.changedTouches[0].pageY - offset
);
cxt.stroke();
}.bind(this),
false
);
//结束绘制
canvas.addEventListener(
"touchend",
function () {
cxt.closePath();
}.bind(this),
false
);
},
clearCanvas() {
// 清除画布
this.cxt.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.drawStatus = false
},
saveCanvas() {
// 保存图片,直接转base64
let imgBase64Copy = ''
let signature = ''
const imgBase64 = this.canvas.toDataURL();
const img = document.querySelector("#mysignature");
img.src = imgBase64;
// console.log(imgBase64)
img.onload = () => {
// 图片宽高
let iw = img.width
let ih = img.height
console.log(iw,ih)
// canvas宽高
let cw = 145
let ch = 86
const opt = this.getXY(cw, ch, ih, iw)
this.cxtCopy.clearRect(0, 0, opt.rw, opt.rh)
this.cxtCopy.translate(0, opt.rh + opt.points[1])
this.cxtCopy.rotate(-Math.PI / 2)
this.cxtCopy.drawImage(img, opt.points[1], opt.points[0], opt.rh, opt.rw)
// 将有效部分图片截取
let frame = this.cxtCopy.getImageData(0, 0, opt.rw, opt.rh)
// 将canvas保持与图片大小相同
this.copy.height = opt.rh
this.copy.width = opt.rw
// 清除画布
this.cxtCopy.clearRect(0, 0, opt.rw, opt.rh)
// 重新绘制
this.cxtCopy.putImageData(frame, 0, 0)
imgBase64Copy = this.copy.toDataURL();
// console.log(imgBase64Copy)
if (this.drawStatus) {
signature = imgBase64Copy;
} else {
signature = ''
}
this.drawStatus = false
this.clearCanvas()
this.$emit('signature', signature)
setTimeout(() => {
// api.closeWin()
this.uploadOss(imgBase64Copy)
}, 3000)
}
},
/**
* @description:
* @param {*} cw canvas宽度
* @param {*} ch canvas高度
* @param {*} iw img宽度
* @param {*} ih img高度
* @return {*}
*/
getXY(cw, ch, iw, ih) {
// 比较宽高比例,大的一边撑满
let li = iw / ih
let lc = cw / ch
// console.log('图片宽高比', li, 'canvas宽高比', lc);
// 需要绘制的宽高起点
let points = [0, 0]
let rw, rh
let wall = false // 是否宽度占满
if (li > lc) {
// 宽度100%,等比缩放
rw = cw
rh = rw / li
points[0] = 0
points[1] = (ch - rh) / 2
wall = true
} else {
rh = ch
rw = rh * li
points[0] = (cw - rw) / 2
points[1] = 0
wall = false
}
rw = Math.floor(rw)
rh = Math.floor(rh)
// console.log('canvas', cw, ch, 'img', iw, ih, '绘制', rw, rh);
return {
points,
rw,
rh
}
},
uploadOss(imgBase64Copy) {
// 1.先转为 blob格式 file.content是此文件的base64格式
let blob = this.dataURLtoBlob(imgBase64Copy);
// 拿到文件名
// let fileName = file.file.name;
// 2,在转为 file类型
let file1 = this.blobToFile(blob, '签名.png');
console.log("file1:", file1);
const formData = new FormData();//将选择上传的name="pdfFile"的文件赋值给formData
// formData.append("bizType", '1');//接口还需要一个用户名参数,通过append附加到formData上
formData.append("file", file1);//接口还需要一个用户名参数,通过append附加到formData上
var token = api.getPrefs({
sync: true,
key: 'token'
})
console.log(token, url.uploadFile())
$.ajax({
type: 'post',
url: url.uploadFile(),
data: formData,
processData: false,
contentType: false,
dataType: 'json',
headers: {
'X-Token': token
},
success: function (res) {
console.log('上传成功', JSON.stringify(res));
api.toast({
msg: '上传成功',
duration: 3000,
location: 'middle'
});
},
error: function (err) {
api.hideProgress();
api.toast({
msg: '上传失败',
duration: 3000,
location: 'middle'
});
}
});
},
//1,先将base64转换为blob
dataURLtoBlob(dataurl) {
var arr = dataurl.split(','),
mime = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]),
n = bstr.length,
u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new Blob([u8arr], { type: mime });
},
//2,再将blob转换为file
blobToFile(theBlob, fileName) {
theBlob.lastModifiedDate = new Date(); // 文件最后的修改日期
theBlob.name = fileName;
console.log(fileName) // 文件名
return new File([theBlob], fileName, { type: theBlob.type, lastModified: Date.now() });
},
}
});
</script>
<script>
new Vue({
el: '#app',
data() {
return {
showUserSign: true,
signature: ''
}
},
methods: {
getSignature(val) {
this.signature = val
this.showUserSign = false
},
goSign() {
this.showUserSign = true
},
changePopup(val) {
this.showUserSign = val
if (!val) {
api.closeWin()
}
}
}
})
</script>
</html>
四、实现效果
1.签名页面
2.签名结束页面
3.处理后签名图片
GitHub 加速计划 / vu / vue
207.55 K
33.66 K
下载
vuejs/vue: 是一个用于构建用户界面的 JavaScript 框架,具有简洁的语法和丰富的组件库,可以用于开发单页面应用程序和多页面应用程序。
最近提交(Master分支:2 个月前 )
73486cb5
* chore: fix link broken
Signed-off-by: snoppy <michaleli@foxmail.com>
* Update packages/template-compiler/README.md [skip ci]
---------
Signed-off-by: snoppy <michaleli@foxmail.com>
Co-authored-by: Eduardo San Martin Morote <posva@users.noreply.github.com> 4 个月前
e428d891
Updated Browser Compatibility reference. The previous currently returns HTTP 404. 5 个月前
更多推荐
已为社区贡献4条内容
所有评论(0)