0


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

文章目录

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

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

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

script 代码

let bigBoxHeight =ref(0);// 获取到元素let bigBox = document.querySelector(".bigBox");// 设置高度为 auto
bigBox.style.height ="auto";// 获取 offsetHeightconst height = bigBox.offsetHeight;// 设置值
bigBoxHeight.value = height;

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

template 代码

<divclass="bigBox":style="{ height: bigBoxHeight + 'px' }"><divclass="contractBox"><divv-html="printData"></div></div><!-- 遮罩层,返回的printData里设置了可编辑,但是这里只是展示用,且修改了也不会有影响,所以就简单的加个遮罩就行了 --><divclass="markBox":style="{ height: bigBoxHeight + 'px' }"></div></div>

获取元素设置的 transform

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

// 获取设置了transform的元素let contractBox = document.querySelector(".contractBox");// 获取浏览器计算后的结果let st = window.getComputedStyle(contractBox,null);// 从结算后的结果中找到 transform,也可以直接 st.transformvar 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 代码

<divclass="bigBox":style="{ height: bigBoxHeight + 'px' }"><divclass="contractBox":style="{ marginLeft: isScale ? bigBoxmargin + 'px' : 'auto' }"><divv-html="printData"></div></div><divclass="markBox":style="{ height: bigBoxHeight + 'px' }"></div></div>

签字效果

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

这里主要粘贴签字的代码

<script setup>import{ ref, onMounted, nextTick }from"vue";// eslint-disable-next-lineconst props =defineProps({dialogVisible:{type: Boolean,default:false,},});// eslint-disable-next-lineconst emit =defineEmits(["closeEvent"]);// 关闭弹窗functionhandleClose(){emit("closeEvent",false);// 解除禁止页面滚动
  document.body.removeEventListener("touchmove", preventDefault);}const dialogBox =ref();functioncloseDialog(){
  dialogBox.value.resetFields();}// 禁止页面滚动functionpreventDefault(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()=>{awaitnextTick();// 获取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);// 初始化constinit=(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);};// 绘制constdraw=(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();};// 结束绘制constcloaseDraw=()=>{// 结束绘制
  ctx.closePath();// 移除鼠标移动或手势移动监听器
  window.removeEventListener("mousemove", draw);};// 创建鼠标/手势按下监听器
window.addEventListener(mobileStatus ?"touchstart":"mousedown", init);// 创建鼠标/手势 弹起/离开 监听器
window.addEventListener(mobileStatus ?"touchend":"mouseup", cloaseDraw);// 取消-清空画布constcancel=()=>{// 清空当前画布上的所有绘制内容
  ctx.clearRect(0,0, config.width, config.height);};// 保存-将画布内容保存为图片constsave=()=>{// 将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="签字"
      ref="dialogBox":modelValue="dialogVisible":before-close="handleClose"
      @close="closeDialog":close-on-click-modal="false":destroy-on-close="true"
      top="0"
      width="100%"><canvas class="canvas"></canvas><template #footer><div><el-button type="primary" @click="save">保存</el-button><el-button @click="cancel">清除</el-button><el-button @click="handleClose">关闭</el-button></div></template></el-dialog></div></template><style lang="scss">.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 中代码

constsave=()=>{// 将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继续修改为

constsave=()=>{// 将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 =newImage();
  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 打印出来是一个元素:

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

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

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

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

template

<template><div><el-dialogtitle="签字"ref="dialogBox":modelValue="dialogVisible":before-close="handleClose"@close="closeDialog":close-on-click-modal="false":destroy-on-close="true"top="0"width="100%"><divclass="canvasBox":style="{ height: config.height + 'px' }"><canvasclass="canvas"></canvas><divclass="tipTxt"v-show="showTip"><p>示例文字!</p><pclass="redTxt">请按示例文字方向,正楷清晰书写。谢谢!</p></div></div><template#footer><div><el-buttontype="primary"@click="save">保存</el-button><el-button@click="cancel">清除</el-button><el-button@click="handleClose(false)">关闭</el-button></div></template></el-dialog></div></template><stylelang="scss">.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-lineconst props =defineProps({dialogVisible:{type: Boolean,default:false,},});// eslint-disable-next-lineconst emit =defineEmits(["closeEvent"]);// 关闭弹窗functionhandleClose(imgEl){
  console.log(imgEl);emit("closeEvent", imgEl);// 禁止页面滚动
  document.body.removeEventListener("touchmove", preventDefault);}const dialogBox =ref();functioncloseDialog(){
  dialogBox.value.resetFields();}// 禁止页面滚动functionpreventDefault(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()=>{awaitnextTick();// 获取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);// 初始化constinit=(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);};// 绘制constdraw=(event)=>{// 获取当前坐标点位const{ pageX, pageY }= mobileStatus ? event.changedTouches[0]: event;// 超出范围不监听if(pageY > config.height){return;}
  showTip.value =false;// 修改最后一次绘制的坐标点
  client.endX = pageX;
  client.endY = pageY;// 根据坐标点位移动添加线条
  ctx.lineTo(pageX, pageY);// 绘制
  ctx.stroke();};// 结束绘制constcloaseDraw=()=>{// 结束绘制
  ctx.closePath();// 移除鼠标移动或手势移动监听器
  window.removeEventListener("mousemove", draw);};// 创建鼠标/手势按下监听器
window.addEventListener(mobileStatus ?"touchstart":"mousedown", init);// 创建鼠标/手势 弹起/离开 监听器
window.addEventListener(mobileStatus ?"touchend":"mouseup", cloaseDraw);// 取消-清空画布constcancel=()=>{// 清空当前画布上的所有绘制内容
  ctx.clearRect(0,0, config.width, config.height);
  showTip.value =true;};// 保存-将画布内容保存为图片constsave=()=>{// 将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-lineElMessage({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 =newImage();
  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>

效果:
在这里插入图片描述
后续其实还有更多需求,但是菜鸟感觉再写就太复杂了,增加了读者的负担,而且也不一定用得上,所以就不再写了!


本文转载自: https://blog.csdn.net/langwang_100/article/details/140872616
版权归原作者 PBitW 所有, 如有侵权,请联系我们删除。

“前端实现签字效果+合同展示”的评论:

还没有评论