canvas 手机端横屏绘制签名

前景:这个签名之前就存在了一个web端的鼠标绘制,但是因反馈操作不便,后又迭代了一个手机可以进行扫码签名了h5页面。
不得不说,这个签名颠倒真的是折磨了wo超级无敌久...期间我还去请教了之前写过类似签名的同事,参考了下之前的代码。。。但是当时实在是被折磨了太久了,思维一直局限在自己最开始写出来那种想法。

最近主要是在跟着修复问题,也是遇到瓶颈了,就想再看下这个问题。才发现。。。我把两种不同的想法糅合在一起,那一定会有问题存在的!只想告诫自己,切记要跳出固定思维,多思考,哪里不会不明白就console哪里。

一、二维码的实现


二、h5页面签名的绘制

总的来讲,也是借用了不知名大佬的好的方法,握拳感谢。

  • getHorizontalStyle方法
  • 引入的draw.js

主要代码如下:

<template>
   <div class="sign-wrapper">
     <div 
      class="canvas-box" 
      id="canvasBig"
      :style="getHorizontalStyle">
      <canvas></canvas>
      <!-- 画布操作按钮 -->
      <div class="btn_box">
        <span class="btn clear-btn"   
          @touchstart.stop="clear"
          @mousedown.stop="clear"
        >清空
        </span>
        <span class="btn commit-btn"  
          @touchstart.stop="submitPNG"
          @mousedown.stop="submitPNG"
          >提交
        </span>
      </div>
     </div>
   </div>
</template>
<script>
// import VConsole from 'vconsole/dist/vconsole.min.js' //引入vconsole
// let vConsole = new VConsole() // 初始化
import Draw from "@/utils/draw"; 

// 上传图片到阿里云
let OSS = require('ali-oss')
let client = new OSS({
  region: 'oss-cn-beijing',
  accessKeyId: 'XXXXX',
  accessKeySecret: 'XXXXXXXXXXXXX',
  bucket: 'vueupdate'
})

/**  解决ios 12版本的post请求报错问题 */   //补充,这个仍然没有解决。。。。。
import axios from 'axios'; 
import qs from "qs";
import base from '@/api/base';
/**  End   */  

// 引入vant中的轻提示组件
import Toast from 'vant/lib/toast'; 
import 'vant/lib/toast/style';
export default {
  name: '',
  components: {
  },
  data() {
    return {
      viewScanCodeUrl: '',  // 签名地址
      orderListId: '',      //订单id
      degree: 90,           //旋转度数
    }
  },
  mounted() {
    console.log('========进入挂载==== mounted')
    this.canvasBox = document.getElementById('canvasBig');
    this.initCanvas();
  },

getHorizontalStyle()方法如下:

  computed: {
    //计算画布大小
    getHorizontalStyle() {
      const d = document;
      const w = window.innerWidth || d.documentElement.clientWidth || d.body.clientWidth;
      const h = window.innerHeight || d.documentElement.clientHeight || d.body.clientHeight;
      let length = (h - w) / 2;
      let width = w;
      let height = h;

      switch (this.degree) {
        case -90:
          length = -length;
        case 90:
          width = h;
          height = w;
          break;
        default:
          length = 0;
      }

      if (this.canvasBox) {
        this.canvasBox.removeChild(document.querySelector('canvas'));
        this.canvasBox.appendChild(document.createElement('canvas'));
        console.log('this.canvasBox333333:',this.canvasBox)
        setTimeout(() => {
          this.initCanvas();
        }, 200);
      }
      return {
        transform: `rotate(${this.degree}deg) translate(${length}px,${length}px)`,
        width: `${width}px`,
        height: `${height}px`,
        transformOrigin: 'center center',
      };
    },
  },
  methods: {
    //初始化签名画布
    initCanvas() {
      let canvas = document.querySelector('canvas');
      this.draw = new Draw(canvas, -this.degree);
      console.log('初始化画布11111canvas-------',canvas)
      console.log('this.draw111111111',this.draw)
    },
    //下载图片
    download() {
     this.draw.downloadPNGImage(this.draw.getPNGImage());
    },
    //清空画布
    clear() {
      this.draw.clear();
    },
    //上传签名
    async submitPNG() {
      console.log('点击进行提交1111:',this.draw.isDraw);
      if (!this.draw.isDraw) {
        Toast('请先进行签署');
        return
      }
      this.signImage = this.draw.getPNGImage();
      // 返回blob文件对象
      const dataURLtoFile = (dataurl, filename) => {
        const arr = dataurl.split(',');
        const mime = arr[0].match(/:(.*?);/)[1];
        const bstr = atob(arr[1]);
        let n = bstr.length;
        const u8arr = new Uint8Array(n);
        while (n--) {
          u8arr[n] = bstr.charCodeAt(n);
        }
        return new Blob([u8arr], { type: mime });
      };
      //上传base64
      const uploadBase64Img = async () => {
        const filename = `${new Date().getTime()}img.png`;
        const imgfile = dataURLtoFile(this.signImage, filename);
        const {res:{requestUrls:[imgUrl]}} = await client.multipartUpload(`${new Date().getTime()}img.png`, imgfile);
        return imgUrl
      }
      const imgUrl = await uploadBase64Img();
      this.viewScanCodeUrl = imgUrl;    //签名地址
      console.log('上传的签名地址:',this.viewScanCodeUrl);
      this.uploadPhoto(); //上传图片

    },
    //提交签名
    uploadPhoto() {
      // let uploadData = {
      //   orderViewId: this.orderListId,
      //   autographPhoto: this.viewScanCodeUrl
      // }
      console.log('上传图片111111--uploadPhoto')
      let _that = this
      console.log('uploadData====点击提交上传的图片')
      // 调取提交
      var apiUrl = `${base.Microservice}/cloud/consultation/api/v1/consultation/report/upLoadAutographPhoto`;
      axios.post(apiUrl, qs.stringify({
        orderViewId: this.orderListId,
        autographPhoto: this.viewScanCodeUrl
      }), {
        headers: {
          'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'
        }
      }).then(res => {
        console.log('headers====请求成功--上传签名成功',res)
        _that.$router.push({
          path: "/signSuccess",
          query: {
            id: _that.orderListId, //订单id
          }
        })
       }).catch(err => {
        console.log('请求失败=========',err)
        return Toast('提交失败,请重新提交');
       }
      )
    }
  },
  created() {
    this.orderListId = this.$route.query.id
    console.log('this.$route.query.id',this.$route.query.id)
    //console.log('进入签名created=====横屏竖屏',window.orientation,this.orderListId)
    //判断手机横竖屏状态:   
    //window.addEventListener("onorientationchange" in window ? "orientationchange" : "resize", function() {   
     // if (window.orientation === 180 || window.orientation === 0) {    
        //console.log('竖屏状态!')
     //}    
      //if (window.orientation === 90 || window.orientation === -90 ){    
        //console.log('横屏状态!')
      //}     
    //}, false);  
  }
}
</script>
<style lang="less" scoped>
.sign-wrapper {
  height: 100%;
  width: 100%;
  background: #fff;
  //手写签名 canvas
  .canvas-box {
    height: 100%;
    display: flex;
  }
}
canvas {
  flex: 1;
  // cursor: crosshair;
}
// 操作按钮
.btn {
  width: 10.8rem;
  height: 4.5rem;
  position: absolute;
  right: 3.6rem;
  bottom: 2.3rem;
  display: inline-block;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: .25rem;
  color: #fff;
  font: 1.6rem '苹方-简';
  z-index: 999;
  // width: 8.8rem;
  // height: 3.5rem;
  // position: absolute;
  // right: 1.875rem;
  // bottom: 1.25rem;
  border: none
}
.clear-btn {
  background: #999999;
  right: 16rem;
}
.commit-btn {
  background: #3978E1
} 
</style>

引入的draw.js,这个我后来也有搜到别人有用,应该是某位大佬写的,非常感谢。但是我也不确定来源,我搜索了下louizhai都不太确定作者,总之感谢

/**
 * Created by louizhai on 17/6/30.
 * description: Use canvas to draw.
 */

function Draw(canvas, degree, config = {}) {
  this.isDraw = false;
  if (!(this instanceof Draw)) {
    return new Draw(canvas, config);
  }
  if (!canvas) {
    return;
  }
  let { width, height } = window.getComputedStyle(canvas, null);
  width = width.replace('px', '');
  height = height.replace('px', '');

  this.canvas = canvas;
  this.context = canvas.getContext('2d');
  this.width = width;
  this.height = height;
  const context = this.context;

  // 根据设备像素比优化canvas绘图
  const devicePixelRatio = window.devicePixelRatio;
  if (devicePixelRatio) {
    canvas.style.width = `${width}px`;
    canvas.style.height = `${height}px`;
    canvas.height = height * devicePixelRatio;
    canvas.width = width * devicePixelRatio;
    context.scale(devicePixelRatio, devicePixelRatio);
  } else {
    canvas.width = width;
    canvas.height = height;
  }

  context.lineWidth = 6;
  context.strokeStyle = 'black';
  context.lineCap = 'round';
  context.lineJoin = 'round';
  Object.assign(context, config);

  const { left, top } = canvas.getBoundingClientRect();
  const point = {};
  const isMobile = /phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone/i.test(navigator.userAgent);
  // 移动端性能太弱, 去掉模糊以提高手写渲染速度
  if (!isMobile) {
    context.shadowBlur = 1;
    context.shadowColor = 'black';
  }
  let pressed = false;

  const paint = (signal) => {
    switch (signal) {
      case 1:
        context.beginPath();
        context.moveTo(point.x, point.y);
      case 2:
        context.lineTo(point.x, point.y);
        context.stroke();
        break;
      default:
    }
  };
  const create = signal => (e) => {
    this.isDraw = true;
    e.preventDefault();
    if (signal === 1) {
      pressed = true;
    }
    if (signal === 1 || pressed) {
      e = isMobile ? e.touches[0] : e;
      point.x = e.clientX - left;
      point.y = e.clientY - top;
      paint(signal);
    }
  };
  const start = create(1);
  const move = create(2);
  const requestAnimationFrame = window.requestAnimationFrame;
  const optimizedMove = requestAnimationFrame ? (e) => {
    requestAnimationFrame(() => {
      move(e);
    });
  } : move;

  if (isMobile) {
    canvas.addEventListener('touchstart', start);
    canvas.addEventListener('touchmove', optimizedMove);
  } else {
    canvas.addEventListener('mousedown', start);
    canvas.addEventListener('mousemove', optimizedMove);
    ['mouseup', 'mouseleave'].forEach((event) => {
      canvas.addEventListener(event, () => {
        pressed = false;
      });
    });
  }

  // 重置画布坐标系
  if (typeof degree === 'number') {
    this.degree = degree;
    context.rotate((degree * Math.PI) / 180);
    switch (degree) {
      case -90:
        context.translate(-height, 0);
        break;
      case 90:
        context.translate(0, -width);
        break;
      case -180:
      case 180:
        context.translate(-width, -height);
        break;
      default:
    }
  }
}
Draw.prototype = {
  scale(width, height, canvas = this.canvas) {
    const w = canvas.width;
    const h = canvas.height;
    width = width || w;
    height = height || h;
    if (width !== w || height !== h) {
      const tmpCanvas = document.createElement('canvas');
      const tmpContext = tmpCanvas.getContext('2d');
      tmpCanvas.width = width;
      tmpCanvas.height = height;
      tmpContext.drawImage(canvas, 0, 0, w, h, 0, 0, width, height);
      canvas = tmpCanvas;
    }
    return canvas;
  },
  rotate(degree, image = this.canvas) {
    degree = ~~degree;
    if (degree !== 0) {
      const maxDegree = 180;
      const minDegree = -90;
      if (degree > maxDegree) {
        degree = maxDegree;
      } else if (degree < minDegree) {
        degree = minDegree;
      }

      const canvas = document.createElement('canvas');
      const context = canvas.getContext('2d');
      const height = image.height;
      const width = image.width;
      const degreePI = (degree * Math.PI) / 180;

      switch (degree) {
        // 逆时针旋转90°
        case -90:
          canvas.width = height;
          canvas.height = width;
          context.rotate(degreePI);
          context.drawImage(image, -width, 0);
          break;
        // 顺时针旋转90°
        case 90:
          canvas.width = height;
          canvas.height = width;
          context.rotate(degreePI);
          context.drawImage(image, 0, -height);
          break;
        // 顺时针旋转180°
        case 180:
          canvas.width = width;
          canvas.height = height;
          context.rotate(degreePI);
          context.drawImage(image, -width, -height);
          break;
        default:
      }
      image = canvas;
    }
    return image;
  },
  getPNGImage(canvas = this.canvas) {
    return canvas.toDataURL('image/png');
  },
  getJPGImage(canvas = this.canvas) {
    return canvas.toDataURL('image/jpeg', 0.5);
  },
  downloadPNGImage(image) {
    const url = image.replace('image/png', 'image/octet-stream;Content-Disposition:attachment;filename=test.png');
    window.location.href = url;
  },
  dataURLtoBlob(dataURL) {
    const arr = dataURL.split(',');
    const mime = arr[0].match(/:(.*?);/)[1];
    const bStr = atob(arr[1]);
    let n = bStr.length;
    const u8arr = new Uint8Array(n);
    while (n--) {
      u8arr[n] = bStr.charCodeAt(n);
    }
    return new Blob([u8arr], { type: mime });
  },
  clear() {
    this.isDraw = false;
    let width;
    let height;
    switch (this.degree) {
      case -90:
      case 90:
        width = this.height;
        height = this.width;
        break;
      default:
        width = this.width;
        height = this.height;
    }
    this.context.clearRect(0, 0, width, height);
  },
  upload(blob, url, success, failure) {
    const formData = new FormData();
    const xhr = new XMLHttpRequest();
    xhr.withCredentials = true;
    formData.append('image', blob, 'sign');

    xhr.open('POST', url, true);
    xhr.onload = () => {
      if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
        success(xhr.responseText);
      } else {
        failure();
      }
    };
    xhr.onerror = (e) => {
      if (typeof failure === 'function') {
        failure(e);
      } else {
        console.log(`upload img error: ${e}`);
      }
    };
    xhr.send(formData);
  },
};
export default Draw;

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容