前端实现签字效果+合同展示

PBitW 2024-08-11 08:03:01 阅读 50

文章目录

获取一个高度会变的元素的高度获取元素设置的 transform适配手机transform-origin: 5% 0; 的原因修改后

签字效果取消el-dialog的头部+边距为什么禁止界面滚动vue3 使用 nextTick实现效果

签字判断是横是竖canvas 去掉空白部分canvas裁剪图片最终完善代码,可以直接使用

最近菜鸟公司要做一个这样的功能,后端返回一个合同的整体html,前端进行签字,以下是一些重要思路!

注:本文章是给自己看的,读者酌情考虑!

获取一个高度会变的元素的高度

script 代码

<code>let bigBoxHeight = ref(0);

// 获取到元素

let bigBox = document.querySelector(".bigBox");

// 设置高度为 auto

bigBox.style.height = "auto";

// 获取 offsetHeight

const height = bigBox.offsetHeight;

// 设置值

bigBoxHeight.value = height;

注:

offsetHeight:返回一个元素的高度,包括其padding和border,但不包括其margin。

template 代码

<div class="bigBox" :style="{ height: bigBoxHeight + 'px' }">code>

<div class="contractBox">code>

<div v-html="printData"></div>code>

</div>

<!-- 遮罩层,返回的printData里设置了可编辑,但是这里只是展示用,且修改了也不会有影响,所以就简单的加个遮罩就行了 -->

<div class="markBox" :style="{ height: bigBoxHeight + 'px' }"></div>code>

</div>

获取元素设置的 transform

感谢:原生js获取元素transform的scale和rotate

// 获取设置了transform的元素

let contractBox = document.querySelector(".contractBox");

// 获取浏览器计算后的结果

let st = window.getComputedStyle(contractBox, null);

// 从结算后的结果中找到 transform,也可以直接 st.transform

var tr = st.getPropertyValue("transform");

if (tr === "none") { -- -->

// 为none表示未设置

bigBox.style.height = "auto";

const height = bigBox.offsetHeight + 50;

bigBoxHeight.value = height;

} else {

bigBox.style.height = "auto";

// 缩放需要 * 缩放比例 + 边距(margin/padding)

const height = bigBox.offsetHeight * 0.5 + 50;

bigBoxHeight.value = height;

}

getComputedStyle 可以学习我的博客:看 Javascript实战详解 收获一

适配手机

上面设置transform是因为返回的html文档不是那么的自适应,所以菜鸟就在手机端,让其渲染700px,但是再缩小0.5倍去展示,即可解决!

css 代码

@media screen and (max-width: 690px) {

.contractBox {

width: 700px !important;

transform: scale(0.5);

// 防止延中心点缩放而导致上面留白很多(合同很长,7000px左右)

transform-origin: 5% 0;

}

}

.bigBox {

position: relative;

// 设置是因为 scale 缩放了但是元素还是占本身那么大,所以要超出隐藏

overflow: hidden;

.markBox {

width: 100%;

position: absolute;

left: 0;

bottom: 0;

top: 0;

bottom: 0;

}

}

.contractBox {

width: 70%;

margin: 50px auto 0px;

overflow: hidden;

}

transform-origin: 5% 0; 的原因

这里设置 5% 是为了居中,因为这里有个问题就是不能设置bigBox为display:flex,不然里面的内容就是按照width:100%然后缩放0.5,而不是width:700px来缩放的!

是flex搞的鬼,菜鸟这里就用了个简单办法。

其实正统做法应该是获取宽度,再用窗口宽度减去获取的宽度 / 2,然后通过该值设置margin!

修改后

菜鸟既然想到了上面的居中方式,那就直接实现了,这里给上代码!

script 代码

// 是否缩放,来确定margin-left取值

let isScale = ref(false);

let bigBoxmargin = ref(0);

let bigBox = document.querySelector(".bigBox");

let contractBox = document.querySelector(".contractBox");

let st = window.getComputedStyle(contractBox, null);

var tr = st.getPropertyValue("transform");

if (tr === "none") {

isScale.value = false;

bigBox.style.height = "auto";

const height = bigBox.offsetHeight + 50;

bigBoxHeight.value = height;

} else {

isScale.value = true;

bigBox.style.height = "auto";

// 缩放需要 * 缩放比例 + 边距(margin/padding)

const height = bigBox.offsetHeight * 0.5 + 50;

// 不用 st.witdh 是因为 st.witdh 获取的值是 700px,不能直接运算,这里菜鸟就偷懒了,不想处理了

bigBoxmargin.value = (window.innerWidth - 700 * 0.5) / 2;

bigBoxHeight.value = height;

}

template 代码

<div class="bigBox" :style="{ height: bigBoxHeight + 'px' }">code>

<div class="contractBox" :style="{ marginLeft: isScale ? bigBoxmargin + 'px' : 'auto' }">code>

<div v-html="printData"></div>code>

</div>

<div class="markBox" :style="{ height: bigBoxHeight + 'px' }"></div>code>

</div>

签字效果

这里签字效果,菜鸟是使用 el-dialog 实现的,el-dialog 的使用方式见:element plus 使用细节

这里主要粘贴签字的代码

<script setup>

import { -- --> ref, onMounted, nextTick } from "vue";

// eslint-disable-next-line

const props = defineProps({

dialogVisible: {

type: Boolean,

default: false,

},

});

// eslint-disable-next-line

const emit = defineEmits(["closeEvent"]);

// 关闭弹窗

function handleClose() {

emit("closeEvent", false);

// 解除禁止页面滚动

document.body.removeEventListener("touchmove", preventDefault);

}

const dialogBox = ref();

function closeDialog() {

dialogBox.value.resetFields();

}

// 禁止页面滚动

function preventDefault(e) {

e.preventDefault();

}

document.body.addEventListener("touchmove", preventDefault, { passive: false });

// 签名

// 配置内容

const config = {

width: window.innerWidth, // 宽度

height: window.innerHeight - 300, // 高度,减300是为了给dialog的footer一点空间显示

lineWidth: 5, // 线宽

strokeStyle: "red", // 线条颜色

lineCap: "round", // 设置线条两端圆角

lineJoin: "round", // 线条交汇处圆角

};

let canvas = null;

let ctx = null;

onMounted(async () => {

await nextTick();

// 获取canvas 实例

canvas = document.querySelector(".canvas");

// 设置宽高

canvas.width = config.width;

canvas.height = config.height;

// 设置一个边框

canvas.style.border = "1px solid #000";

// 创建上下文

ctx = canvas.getContext("2d");

// 设置填充背景色

ctx.fillStyle = "transparent";

// 绘制填充矩形

ctx.fillRect(

0, // x 轴起始绘制位置

0, // y 轴起始绘制位置

config.width, // 宽度

config.height // 高度

);

});

// 保存上次绘制的 坐标及偏移量

const client = {

offsetX: 0, // 偏移量

offsetY: 0,

endX: 0, // 坐标

endY: 0,

};

// 判断是否为移动端

const mobileStatus = /Mobile|Android|iPhone/i.test(navigator.userAgent);

// 初始化

const init = (event) => {

// 获取偏移量及坐标

const { offsetX, offsetY, pageX, pageY } = mobileStatus ? event.changedTouches[0] : event;

// 修改上次的偏移量及坐标

client.offsetX = offsetX;

client.offsetY = offsetY;

client.endX = pageX;

client.endY = pageY;

// 清除以上一次 beginPath 之后的所有路径,进行绘制

ctx.beginPath();

// 根据配置文件设置相应配置

ctx.lineWidth = config.lineWidth;

ctx.strokeStyle = config.strokeStyle;

ctx.lineCap = config.lineCap;

ctx.lineJoin = config.lineJoin;

// 设置画线起始点位

ctx.moveTo(client.endX, client.endY);

// 监听 鼠标移动或手势移动

window.addEventListener(mobileStatus ? "touchmove" : "mousemove", draw);

};

// 绘制

const draw = (event) => {

console.log(event);

// 获取当前坐标点位

const { pageX, pageY } = mobileStatus ? event.changedTouches[0] : event;

// 超出范围不监听

if (pageY > config.height) {

return;

}

// 修改最后一次绘制的坐标点

client.endX = pageX;

client.endY = pageY;

// 根据坐标点位移动添加线条

ctx.lineTo(pageX, pageY);

// 绘制

ctx.stroke();

};

// 结束绘制

const cloaseDraw = () => {

// 结束绘制

ctx.closePath();

// 移除鼠标移动或手势移动监听器

window.removeEventListener("mousemove", draw);

};

// 创建鼠标/手势按下监听器

window.addEventListener(mobileStatus ? "touchstart" : "mousedown", init);

// 创建鼠标/手势 弹起/离开 监听器

window.addEventListener(mobileStatus ? "touchend" : "mouseup", cloaseDraw);

// 取消-清空画布

const cancel = () => {

// 清空当前画布上的所有绘制内容

ctx.clearRect(0, 0, config.width, config.height);

};

// 保存-将画布内容保存为图片

const save = () => {

// 将canvas上的内容转成blob流

canvas.toBlob((blob) => {

// 获取当前时间并转成字符串,用来当做文件名

const date = Date.now().toString();

// 创建一个 a 标签

const a = document.createElement("a");

// 设置 a 标签的下载文件名

a.download = `${ date}.png`;

// 设置 a 标签的跳转路径为 文件流地址

a.href = URL.createObjectURL(blob);

// 手动触发 a 标签的点击事件

a.click();

// 移除 a 标签

a.remove();

});

handleClose();

};

</script>

<template>

<div>

<el-dialog

title="签字"code>

ref="dialogBox"code>

:modelValue="dialogVisible"code>

:before-close="handleClose"code>

@close="closeDialog"code>

:close-on-click-modal="false"code>

:destroy-on-close="true"code>

top="0"code>

width="100%"code>

>

<canvas class="canvas"></canvas>code>

<template #footer>

<div>

<el-button type="primary" @click="save">保存</el-button>code>

<el-button @click="cancel">清除</el-button>code>

<el-button @click="handleClose">关闭</el-button>code>

</div>

</template>

</el-dialog>

</div>

</template>

<style lang="scss">code>

.el-dialog__header { -- -->

display: none;

}

.el-dialog__body {

padding: 0 !important;

}

</style>

取消el-dialog的头部+边距

因为这里的 client 设置的偏移量都是 0,菜鸟不会改(感觉应该加上el-dialog的头部+边框的偏移量),如果不取消的话,就是错位着写的!

为什么禁止界面滚动

这里禁止是因为手机端,签名时写 “竖” 操作时,容易触发下拉整个界面的事件!导致写字中断,体验感极差,所以弹窗弹出时阻止事件,关闭后移除!

这里函数 preventDefault 必须提出,不然会取消不掉!

vue3 使用 nextTick

获取元素必须在 onMounted 中,但是 el-dialog 即使写在 onMounted 里面也不行,需要加上 nextTick !

实现效果

在这里插入图片描述

签字判断是横是竖

今天菜鸟又遇见了大麻烦,就是这个签字不能知道别人是横屏横着写的还是竖屏横着写的,eg:

在这里插入图片描述

在这里插入图片描述

菜鸟的思路就是获取到签字部分,然后如果横着签字就直接截取那部分设置样式,如果竖着签字就设置样式旋转 -90deg,那如何获取签字部分的大小呢?

canvas 去掉空白部分

canvas 去掉空白部分

修改上面 save 中代码

<code>const save = () => { -- -->

// 将canvas上的内容转成blob流

var imgData = ctx.getImageData(0, 0, canvas.width, canvas.height).data;

var lOffset = canvas.width,

rOffset = 0,

tOffset = canvas.height,

bOffset = 0;

for (var i = 0; i < canvas.width; i++) {

for (var j = 0; j < canvas.height; j++) {

var pos = (i + canvas.width * j) * 4;

if (imgData[pos] > 0 || imgData[pos + 1] > 0 || imgData[pos + 2] || imgData[pos + 3] > 0) {

// 说第j行第i列的像素不是透明的

// 楼主貌似底图是有背景色的,所以具体判断RGBA的值可以根据是否等于背景色的值来判断

bOffset = Math.max(j, bOffset); // 找到有色彩的最底部的纵坐标

rOffset = Math.max(i, rOffset); // 找到有色彩的最右端

tOffset = Math.min(j, tOffset); // 找到有色彩的最上端

lOffset = Math.min(i, lOffset); // 找到有色彩的最左端

}

}

}

}

canvas.getContext(‘2d’).getImageData(0, 0, 宽, 高) 会返回一个当前 canvas 的图像数据对象,其中有一个data属性,是一个一维数组,这个一维数组,每4个下标分别代表了一个像素点的 R,G,B,A 的值,只需要遍历这些值就能找到边界了。

感谢:canvas 裁剪空白区域

canvas裁剪图片

canvas裁剪图片

但是获取到了区域并不行,因为我还需要将其截取,然后转成图片传给后端,且还要让后端知道到底是横着放 html 模板里还是竖着放,思来想去,感觉直接返回 base64 的 img 元素给后端更好,因为我就可以直接设置style,后端只需要放到对应的地方就行,所以save继续修改为

const save = () => {

// 将canvas上的内容转成blob流

var imgData = ctx.getImageData(0, 0, canvas.width, canvas.height).data;

var lOffset = canvas.width,

rOffset = 0,

tOffset = canvas.height,

bOffset = 0;

for (var i = 0; i < canvas.width; i++) {

for (var j = 0; j < canvas.height; j++) {

var pos = (i + canvas.width * j) * 4;

if (imgData[pos] > 0 || imgData[pos + 1] > 0 || imgData[pos + 2] || imgData[pos + 3] > 0) {

// 说第j行第i列的像素不是透明的

// 楼主貌似底图是有背景色的,所以具体判断RGBA的值可以根据是否等于背景色的值来判断

bOffset = Math.max(j, bOffset); // 找到有色彩的最底部的纵坐标

rOffset = Math.max(i, rOffset); // 找到有色彩的最右端

tOffset = Math.min(j, tOffset); // 找到有色彩的最上端

lOffset = Math.min(i, lOffset); // 找到有色彩的最左端

}

}

}

// 重新创建一个canvas,将之前的canvas上的图片,按照获取到的大小去截取

const trimmedWidth = rOffset - lOffset + 1;

const trimmedHeight = bOffset - tOffset + 1;

const trimmedCanvas = document.createElement("canvas");

trimmedCanvas.width = trimmedWidth;

trimmedCanvas.height = trimmedHeight;

const trimmedContext = trimmedCanvas.getContext("2d");

trimmedContext.putImageData(

ctx.getImageData(lOffset, tOffset, trimmedWidth, trimmedHeight),

0,

0

);

// 将截取后的生成图片,并设置样式

console.log(trimmedWidth);

console.log(trimmedHeight);

var newUrl = trimmedCanvas.toDataURL();

var newImage = new Image();

newImage.src = newUrl;

console.log(trimmedWidth < trimmedHeight);

if (trimmedWidth < trimmedHeight) {

newImage.style.height = "100px";

newImage.style.transform = "rotate(-" + 90 + "deg)";

} else {

newImage.style.width = "100px";

}

console.log(newImage);

handleClose();

};

newImage 打印出来是一个元素:

<img src="data:image/png;base64,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" style="transform: rotate(-90deg);">code>

至此算是完成了整个签字功能!

最终完善代码,可以直接使用

其实这里还有一个问题,就是不知道横屏后的用户到底是哪边横屏,可能要旋转-90deg,也可能是正90deg,这里菜鸟想的是一个简单办法,就是给个示例文字,让用户根据示例文字进行签字!

template

<template>

<div>

<el-dialog

title="签字"code>

ref="dialogBox"code>

:modelValue="dialogVisible"code>

:before-close="handleClose"code>

@close="closeDialog"code>

:close-on-click-modal="false"code>

:destroy-on-close="true"code>

top="0"code>

width="100%"code>

>

<div class="canvasBox" :style="{ height: config.height + 'px' }">code>

<canvas class="canvas"></canvas>code>

<div class="tipTxt" v-show="showTip">code>

<p>示例文字!</p>

<p class="redTxt">请按示例文字方向,正楷清晰书写。谢谢!</p>code>

</div>

</div>

<template #footer>

<div>

<el-button type="primary" @click="save">保存</el-button>code>

<el-button @click="cancel">清除</el-button>code>

<el-button @click="handleClose(false)">关闭</el-button>code>

</div>

</template>

</el-dialog>

</div>

</template>

<style lang="scss">code>

.el-dialog__header { -- -->

display: none;

}

.el-dialog__body {

padding: 0 !important;

}

.canvasBox {

position: relative;

}

.canvas,

.tipTxt {

position: absolute;

top: 0;

left: 0;

bottom: 0;

right: 0;

}

.tipTxt {

display: flex;

flex-direction: column;

align-items: center;

justify-content: center;

}

.tipTxt p {

color: #999;

font-size: 80px;

}

.tipTxt .redTxt {

color: rgba(255, 0, 0, 0.5);

font-size: 50px;

}

@media screen and (max-width: 690px) {

.tipTxt {

transform: rotate(90deg);

}

.tipTxt p {

font-size: 50px;

}

.tipTxt .redTxt {

font-size: 18px;

}

}

</style>

js 控制 showTip 的展示

<script setup>

import { ref, onMounted, nextTick } from "vue";

// eslint-disable-next-line

const props = defineProps({

dialogVisible: {

type: Boolean,

default: false,

},

});

// eslint-disable-next-line

const emit = defineEmits(["closeEvent"]);

// 关闭弹窗

function handleClose(imgEl) {

console.log(imgEl);

emit("closeEvent", imgEl);

// 禁止页面滚动

document.body.removeEventListener("touchmove", preventDefault);

}

const dialogBox = ref();

function closeDialog() {

dialogBox.value.resetFields();

}

// 禁止页面滚动

function preventDefault(e) {

e.preventDefault();

}

document.body.addEventListener("touchmove", preventDefault, { passive: false });

// 签名

// 配置内容

const config = {

width: window.innerWidth, // 宽度

height: window.innerHeight - 150, // 高度

lineWidth: 5, // 线宽

strokeStyle: "red", // 线条颜色

lineCap: "round", // 设置线条两端圆角

lineJoin: "round", // 线条交汇处圆角

};

let canvas = null;

let ctx = null;

onMounted(async () => {

await nextTick();

// 获取canvas 实例

canvas = document.querySelector(".canvas");

console.log(canvas);

// 设置宽高

canvas.width = config.width;

canvas.height = config.height;

// 设置一个边框

canvas.style.border = "1px solid #000";

// 创建上下文

ctx = canvas.getContext("2d");

// 设置填充背景色

ctx.fillStyle = "transparent";

// 绘制填充矩形

ctx.fillRect(

0, // x 轴起始绘制位置

0, // y 轴起始绘制位置

config.width, // 宽度

config.height // 高度

);

});

// 保存上次绘制的 坐标及偏移量

const client = {

offsetX: 0, // 偏移量

offsetY: 0,

endX: 0, // 坐标

endY: 0,

};

// 判断是否为移动端

const mobileStatus = /Mobile|Android|iPhone/i.test(navigator.userAgent);

// 初始化

const init = (event) => {

// 获取偏移量及坐标

const { offsetX, offsetY, pageX, pageY } = mobileStatus ? event.changedTouches[0] : event;

// 修改上次的偏移量及坐标

client.offsetX = offsetX;

client.offsetY = offsetY;

client.endX = pageX;

client.endY = pageY;

// 清除以上一次 beginPath 之后的所有路径,进行绘制

ctx.beginPath();

// 根据配置文件设置相应配置

ctx.lineWidth = config.lineWidth;

ctx.strokeStyle = config.strokeStyle;

ctx.lineCap = config.lineCap;

ctx.lineJoin = config.lineJoin;

// 设置画线起始点位

ctx.moveTo(client.endX, client.endY);

// 监听 鼠标移动或手势移动

window.addEventListener(mobileStatus ? "touchmove" : "mousemove", draw);

};

// 绘制

const draw = (event) => {

showTip.value = false;

// 获取当前坐标点位

const { pageX, pageY } = mobileStatus ? event.changedTouches[0] : event;

// 修改最后一次绘制的坐标点

client.endX = pageX;

client.endY = pageY;

// 根据坐标点位移动添加线条

ctx.lineTo(pageX, pageY);

// 绘制

ctx.stroke();

};

// 结束绘制

const cloaseDraw = () => {

// 结束绘制

ctx.closePath();

// 移除鼠标移动或手势移动监听器

window.removeEventListener("mousemove", draw);

};

// 创建鼠标/手势按下监听器

window.addEventListener(mobileStatus ? "touchstart" : "mousedown", init);

// 创建鼠标/手势 弹起/离开 监听器

window.addEventListener(mobileStatus ? "touchend" : "mouseup", cloaseDraw);

// 取消-清空画布

const cancel = () => {

// 清空当前画布上的所有绘制内容

ctx.clearRect(0, 0, config.width, config.height);

showTip.value = true;

};

// 保存-将画布内容保存为图片

const save = () => {

// 将canvas上的内容转成blob流

var imgData = ctx.getImageData(0, 0, canvas.width, canvas.height).data;

var lOffset = canvas.width,

rOffset = 0,

tOffset = canvas.height,

bOffset = 0;

for (var i = 0; i < canvas.width; i++) {

for (var j = 0; j < canvas.height; j++) {

var pos = (i + canvas.width * j) * 4;

if (imgData[pos] > 0 || imgData[pos + 1] > 0 || imgData[pos + 2] || imgData[pos + 3] > 0) {

// 说第j行第i列的像素不是透明的

// 楼主貌似底图是有背景色的,所以具体判断RGBA的值可以根据是否等于背景色的值来判断

bOffset = Math.max(j, bOffset); // 找到有色彩的最底部的纵坐标

rOffset = Math.max(i, rOffset); // 找到有色彩的最右端

tOffset = Math.min(j, tOffset); // 找到有色彩的最上端

lOffset = Math.min(i, lOffset); // 找到有色彩的最左端

}

}

}

if (lOffset === config.width && rOffset === 0 && tOffset === config.height && bOffset === 0) {

// eslint-disable-next-line

ElMessage({

message: "请签名后保存!",

type: "warning",

});

return;

}

const trimmedWidth = rOffset - lOffset + 1;

const trimmedHeight = bOffset - tOffset + 1;

const trimmedCanvas = document.createElement("canvas");

trimmedCanvas.width = trimmedWidth;

trimmedCanvas.height = trimmedHeight;

const trimmedContext = trimmedCanvas.getContext("2d");

trimmedContext.putImageData(

ctx.getImageData(lOffset, tOffset, trimmedWidth, trimmedHeight),

0,

0

);

const newUrl = trimmedCanvas.toDataURL();

const newImage = new Image();

newImage.src = newUrl;

console.log(trimmedWidth < trimmedHeight);

if (trimmedWidth < trimmedHeight) {

newImage.style.height = "100px";

newImage.style.transform = "rotate(-" + 90 + "deg)";

} else {

newImage.style.width = "100px";

}

// console.log(newImage.outerHTML + "</img>");

handleClose(newImage.outerHTML + "</img>");

};

// 示例文字

let showTip = ref(true);

</script>

效果:

在这里插入图片描述



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。