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

avatar
作者
筋斗云
阅读量:0

文章目录

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

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

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

script 代码

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

template 代码

<div class="bigBox" :style="{ height: bigBoxHeight + 'px' }">   <div class="contractBox">     <div v-html="printData"></div>   </div>   <!-- 遮罩层,返回的printData里设置了可编辑,但是这里只是展示用,且修改了也不会有影响,所以就简单的加个遮罩就行了 -->   <div class="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.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";   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' }">   <div class="contractBox" :style="{ marginLeft: isScale ? bigBoxmargin + 'px' : 'auto' }">     <div v-html="printData"></div>   </div>   <div class="markBox" :style="{ height: bigBoxHeight + 'px' }"></div> </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, // 高度   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) => {   console.log(event);   // 获取当前坐标点位   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); }; // 保存-将画布内容保存为图片 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="签字"       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 !

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!