当前端遇见了强制横屏签字的需求...

 
 

大厂技术  高级前端  Node进阶

点击上方 程序员成长指北,关注公众号
回复1,加入高级Node交流群

作者:爱泡澡的小萝卜

原文:https://juejin.cn/post/7260697932173590565

序言

人的一生就是进行尝试,尝试的越多,生活就越美好。——爱默生

在前一阶段的工作中,突然接到了这个需求:_手写签批的页面在移动端竖屏时强制页面横屏展示进行签字_,一开始我觉着只要将页面使用 CSS3 的 transform 进行 rotate 一下就可以了,但是当我尝试后发现并不是像我想象的那样简单。

vue2实现手写签批

在介绍横屏签字之前,我想先说明一下我实现签批使用的插件以及插件所调用的方法,这样在之后说到横屏签字的时候,大佬们不会感觉唐突。

vue-signature-pad

项目使用 vue-signature-pad [1]插件进行签名功能实现,强调一下如果使用vue2进行开发,安装的 vue-signature-pad 的版本我自测 2.0.5 是可以的

安装
npm i vue-signature-pad@2.0.5
引入
js
复制代码
// main.js
import Vue from 'vue'
import App from './App.vue'

import VueSignaturePad from 'vue-signature-pad'

Vue.use(VueSignaturePad)

Vue.config.productionTip = false

new Vue({
  render: (h) => h(App),
}).$mount('#app')

使用 vue-signature-pad 完成签批功能

这里我为了方便直接写了一个demo放到App.vue中,没有封装成组件

// app.vue
<template>
  <div id="app">
    <div style="background: #fff">
      <vue-signature-pad
        id="signature"
        width="95%"
        height="400px"
        ref="signaturePad"
        :options="options"
      />
    </div>

    <button @click="save">保存</button>
    <button @click="resume">重置</button>
  </div>
</template>

<script>
export default {
  name: 'App',
  data() {
    return {
      options: {
        penColor: '#000',
      },
    }
  },
  methods: {
    save() {
      const { isEmpty, data } = this.$refs.signaturePad.saveSignature()
      console.log(isEmpty)
      console.log(data)
    },

    //清除重置
    resume() {
      this.$refs.signaturePad.clearSignature()
    },
  },
}
</script>

<style lang="scss">
html,
body {
  padding: 0;
  margin: 0;
}
#app {
  width: 100vw;
  height: 100vh;
  background: #ececec;
}
</style>

代码比较通俗易懂,就是调用组件封装好的方法,保存后能够解构出data为base64编码的图片 7ae925e04b5eb93f5864b89567ee2f10.jpeg 之后需要将base64编码格式转换成File文件格式的图片最后进行接口请求,那么转换方法如下展示👇🏻

<template>
  <div id="app">
    <div style="background: #fff">
      <vue-signature-pad
        id="signature"
        width="95%"
        height="300px"
        ref="signaturePad"
        :options="options"
      />
    </div>

    <div v-for="(item, index) in imgList" :key="index">
      <img :src="item.src" alt="" width="100" />
    </div>

    <button @click="save" class="btn">保存</button>
    <button @click="resume" class="btn">重置</button>
  </div>
</template>

<script>
export default {
  name: 'App',
  data() {
    return {
      options: {
        penColor: '#000',
      },
      imgList: [],
    }
  },
  methods: {
    save() {
      const { isEmpty, data } = this.$refs.signaturePad.saveSignature()
      this.imgList.push({
        src: data,
      })
      let res = this.dataURLtoFile(data, 'demo')
      console.log(res)
    },

    // 清除重置
    resume() {
      this.$refs.signaturePad.clearSignature()
    },

    // 将base64转换为文件
    dataURLtoFile(dataurl, filename) {
      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 File([u8arr], filename, { type: mime })
    },
  },
}
</script>

<style lang="scss">
html,
body {
  padding: 0;
  margin: 0;
}
#app {
  width: 100vw;
  height: 100vh;
  background: #ececec;
}

.btn {
  width: 35%;
  color: #fff;
  background: #5daaf3;
  border: none;
  height: 40px;
  border-radius: 20px;
  margin-top: 20px;
  margin-left: 40px;
}
</style>

调用后,打印出转换成文件的图片如图

5bce826b93683654c8c5feae638f31b6.jpeg 之后根据需求调用接口将文件图片作为入参即可。

阶段总结

经过上面的操作,我们就实现了前端的签批的完整流程,还是比较容易理解的。


新的需求

在实现这个功能不久之后,客户那边提出了新的需求:手机竖屏时将签字功能进行横屏展示。

错误思路

刚开始接到这个需求的时候,通过我所掌握的技术首先就是想到用CSS3的transform:rotate方法进行页面90deg的旋转,将签字组件也进行旋转之后进行签名;由于我对canvas不是很了解,所以我把包裹在签字组件外的div标签进行了旋转后签字发现落笔点位置错乱。

<div style="background: #fff; transform: rotate(-90deg)">
      <vue-signature-pad
        id="signature"
        width="95%"
        height="300px"
        ref="signaturePad"
        :options="options"
      />
    </div>
5455c9b0f7c94e24f40640923c335783.jpeg

改变思路

既然不能旋转外层的div,那我想到一种欺骗方式:不旋转div,样式修改成与横屏样式相似,然后将生成的图片进行一个旋转,这样就ok了!那么我们的目标就明确了,找到能够旋转bas64编码的方法然后返回一个旋转后的base64图片在转换成file文件传递给后端问题就解决了。
经过一个苦苦寻找,终于找到了方法并实现了这个功能,话不多说,先撸为敬(样式大佬们自己改下,我这里展示下转换后的图片)。

<template>
  <div id="app">
    <div style="background: #fff">
      <vue-signature-pad
        id="signature"
        width="95%"
        height="300px"
        ref="signaturePad"
        :options="options"
      />
    </div>

    <div v-for="(item, index) in imgList" :key="index">
      <img :src="item.src" alt="" width="100" />
    </div>

    <div class="buttons">
      <button @click="save" class="btn">保存</button>
      <button @click="resume" class="btn">重置</button>
    </div>
  </div>
</template>

<script>
export default {
  name: 'App',
  data() {
    return {
      options: {
        penColor: '#000',
      },
      imgList: [],
      fileList: [],
    }
  },
  methods: {
    save() {
      const { isEmpty, data } = this.$refs.signaturePad.saveSignature()

      this.rotateBase64Img(data, 90, (res) => {
        console.log(res) // 旋转后的base64图片src
        this.fileList.push({
          file: this.dataURLtoFile(res, 'sign'),
          name: 'sign',
        })
        this.imgList.push({
          src: res,
        })
      })
    },

    // 清除重置
    resume() {
      this.$refs.signaturePad.clearSignature()
    },

    // 将base64转换为文件
    dataURLtoFile(dataurl, filename) {
      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 File([u8arr], filename, { type: mime })
    },

    // 通过canvas旋转图片
    rotateBase64Img(src, edg, callback) {
      var canvas = document.createElement('canvas')
      var ctx = canvas.getContext('2d')

      var imgW //图片宽度
      var imgH //图片高度
      var size //canvas初始大小

      if (edg % 90 != 0) {
        console.error('旋转角度必须是90的倍数!')
        throw '旋转角度必须是90的倍数!'
      }
      edg < 0 && (edg = (edg % 360) + 360)
      const quadrant = (edg / 90) % 4 //旋转象限
      const cutCoor = { sx: 0, sy: 0, ex: 0, ey: 0 } //裁剪坐标

      var image = new Image()

      image.crossOrigin = 'anonymous'
      image.src = src

      image.onload = function () {
        imgW = image.width
        imgH = image.height
        size = imgW > imgH ? imgW : imgH

        canvas.width = size * 2
        canvas.height = size * 2
        switch (quadrant) {
          case 0:
            cutCoor.sx = size
            cutCoor.sy = size
            cutCoor.ex = size + imgW
            cutCoor.ey = size + imgH
            break
          case 1:
            cutCoor.sx = size - imgH
            cutCoor.sy = size
            cutCoor.ex = size
            cutCoor.ey = size + imgW
            break
          case 2:
            cutCoor.sx = size - imgW
            cutCoor.sy = size - imgH
            cutCoor.ex = size
            cutCoor.ey = size
            break
          case 3:
            cutCoor.sx = size
            cutCoor.sy = size - imgW
            cutCoor.ex = size + imgH
            cutCoor.ey = size + imgW
            break
        }

        ctx.translate(size, size)
        ctx.rotate((edg * Math.PI) / 180)
        ctx.drawImage(image, 0, 0)

        var imgData = ctx.getImageData(
          cutCoor.sx,
          cutCoor.sy,
          cutCoor.ex,
          cutCoor.ey
        )

        if (quadrant % 2 == 0) {
          canvas.width = imgW
          canvas.height = imgH
        } else {
          canvas.width = imgH
          canvas.height = imgW
        }
        ctx.putImageData(imgData, 0, 0)
        callback(canvas.toDataURL())
      }
    },
  },
}
</script>

<style lang="scss">
html,
body {
  padding: 0;
  margin: 0;
}
#app {
  width: 100vw;
  height: 100vh;
  background: #ececec;
}

.btn {
  width: 35%;
  color: #fff;
  background: #5daaf3;
  border: none;
  height: 40px;
  border-radius: 20px;
  margin-top: 20px;
  margin-left: 40px;
}
</style>

那么经过翻转后当我们横屏移动设备时,保存出的图片会进行90度旋转,传递给后端的图片就是正常的了✅(代码可直接食用) 0c0a5fd93546bcfd7ee58f3e37f84229.jpeg

处理细节

后来我发现签字的笔锋太细了,打印出来的效果很差,于是通过查阅,只要设置 options 中的 minWidth和maxWidth 大一些即可 357fcb83698b5375cb5c11d8fc04e183.jpeg 到此所有需求就已经都解决了。

总结

其实平时开发中没有对canvas用到很多,导致对这块的知识很薄弱,我在查阅的时候找到过用原生实现此功能,不过因为时间不够充裕,为了完成需求耍了一个小聪明,后续应该对canvas更多的了解一下,在深入了解上面的旋转方法具体是如何实现的,希望这篇文章能够对遇到这种需求并且时间紧迫的你有所帮助!

参考资料

[1]

https://www.npmjs.com/package/vue-signature-pad: https://link.juejin.cn?target=https%3A%2F%2Fwww.npmjs.com%2Fpackage%2Fvue-signature-pad

Node 社群

 
 

我组建了一个氛围特别好的 Node.js 社群,里面有很多 Node.js小伙伴,如果你对Node.js学习感兴趣的话(后续有计划也可以),我们可以一起进行Node.js相关的交流、学习、共建。下方加 考拉 好友回复「Node」即可。

eeee430a2ee206f550878737bcd44921.png

“分享、点赞、在看” 支持一下
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: docker.md 是一种文档文件的格式,通常用于存储和分享有关 Docker 容器技术的相关信息。在技术领域中,狂神说是一位非常知名的程序员/技术博主,他在网络上分享了许多关于软件开发和技术方面的知识和经验。 如果我在 docker.md 文件中遇到了狂神说,那么我可能会高兴地发现这个文件中包含了关于 Docker 容器的宝贵信息。狂神说的经验和见解一直很受人们的欢迎,因此他的观点和建议对于使用 Docker 技术的人来说会非常有价值。 在 docker.md 文件中,狂神说可能会分享一些与 Docker 相关的教程、示例代码、最佳实践和经验分享。这对于初学者来说将是一个宝贵的资源,可以帮助他们更好地理解和运用 Docker 技术。 此外,狂神说可能还会分享一些关于 Docker 生态系统中其他相关技术和工具的使用和整合的知识。这可能包括与容器编排系统(如 Kubernetes)的集成、镜像构建工具、网络管理和存储等方面的内容。 总之,如果我在 docker.md 文件中遇到了狂神说,我会感到非常幸运,因为我相信他的经验和知识将对我的 Docker 学习和实践非常有帮助。深入学习和使用 Docker 技术可以提高软件开发和部署的效率,并帮助我更好地理解和应用容器化技术的优势。 ### 回答2: 《docker.md 遇见狂神说》是一本书名,书中可能是关于Docker技术的介绍、应用和实践经验的内容。Docker是一个开源的容器化技术,它可以让开发人员将应用程序及其依赖封装到一个独立的容器中,保证应用程序可以在不同的环境中运行。 在《docker.md 遇见狂神说》中,狂神可能是书中的作者,他可能是一位经验丰富的开发者或专家,对Docker技术有深入的了解和实践经验。 这本书可能会详细介绍Docker的背景和原理,包括Docker的运行方式、容器的创建和管理,以及容器与宿主机的交互等。另外,书中可能会介绍Docker在开发、测试和部署过程中的应用场景和最佳实践,帮助读者更好地掌握和应用Docker技术。 此外,书中可能还会探讨Docker与其他相关技术的结合,比如在容器编排工具Kubernetes中的应用,以及与持续集成和持续部署工具的集成等。 总之,《docker.md 遇见狂神说》可能是一本教学实践结合的书籍,通过狂神的经验和案例,帮助读者深入理解Docker技术,并在实际应用中取得更好的效果。无论是初学者还是有经验的开发者,都可以从这本书中获得有关Docker的全面知识和实践指导。 ### 回答3: 《docker.md遇见狂神说》是一篇关于Docker容器化技术的文章。文章首先介绍了Docker的背景和起源,以及它在应用开发和部署中的重要性。接着,文章详细讲解了Docker的基本概念、架构和工作原理,包括镜像、容器和仓库的概念以及Docker引擎的组成部分。 在接下来的内容中,狂神从实践的角度出发,分享了他个人在使用Docker过程中的一些经验和技巧。他提到了如何选择和构建适合的Docker镜像,如何在容器中安装和配置应用程序,以及如何运行和管理容器等方面的问题。此外,他还介绍了一些常用的Docker命令,用于管理和监控容器、镜像和仓库。 文章还提到了Docker的一些高级特性,例如Docker网络和存储,以及Docker Compose和Swarm等工具的使用。狂神通过详细的示例和实践操作,帮助读者更好地理解和掌握这些概念和技术。 最后,文章总结了Docker的优势和应用场景,以及使用Docker所带来的效益和挑战。狂神提醒读者在使用Docker时要注意安全性和性能方面的问题,并鼓励读者通过实践来深入学习和掌握这一技术。 总的来说,《docker.md遇见狂神说》通过简明清晰的语言和实践案例,全面而深入地介绍了Docker的基本概念、工作原理和实际应用。读者可以通过阅读这篇文章,快速入门和深入理解Docker技术,从而在实际工作中更好地应用和运用这一强大的容器化技术。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值