体验svg的厉害之处-制作签名板

343 阅读1分钟

前言

使用svg制作签名板,并转成pngbase64

如果文章对你有帮助的话,记得一键三连哟。有问题和疑惑的话也可以在评论区留言。我会第一时间回复大家,如果觉得我的文章哪里有知识点错误的话,也恳请能够告知,把错的东西理解成对的,无论在什么行业,都是致命的。

日常博客记录在语雀,欢迎关注 语雀文档

更新

2022-08-06 vue新增背景文案

image.png

原生代码

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <style>
    body {
      overflow: hidden;
      display: flex;
      justify-content: center;
      flex-direction: column;
    }
    
    #svg {
      margin: 0 auto;
    }
  </style>
  <body>
    <img id="myImg" src="" />
    <button onclick="svgToPng()">下载</button>
    <br>
    <button onclick="clearSvgChild()">清除svg元素</button>
    <br>
    <svg width="300" height="200" id="svg" style="border: 1px solid blue"></svg>
  </body>
  <script>
    
    let svgCanvas = document.querySelector("#svg");
    let svgPolyline;
    
    svgCanvas.addEventListener("touchstart", startDrawTouch, false);
    svgCanvas.addEventListener("touchmove", continueDrawTouch, false);
    svgCanvas.addEventListener("touchend", endDrawTouch, false);
    
    function startDrawTouch(event) {
      svgPolyline = createSvgElement("polyline");
      svgPolyline.setAttribute("fill", "none"); //填充颜色
      svgPolyline.setAttribute("shape-rendering", "geometricPrecision"); //指定渲染模式
      svgPolyline.setAttribute("stroke-linejoin", "round"); //结束时的收边
      svgPolyline.setAttribute("stroke", "#000000"); //轮廓颜色
      svgCanvas.appendChild(svgPolyline);
      continueDrawTouch(event);
    }
    
    function continueDrawTouch(event) {
      if (svgPolyline) {
        let touch = event.changedTouches[0];
        let point = svgPolyline.ownerSVGElement.createSVGPoint();
        point.x = touch.clientX;
        point.y = touch.clientY;
        let ctm = event.target.getScreenCTM(); //svgObj.getScreenCTM(); 该矩阵将svg坐标转换为屏幕坐标
        if ((ctm = ctm.inverse())) {
          point = point.matrixTransform(ctm);
        }
        svgPolyline.points.appendItem(point);
      }
    }
    
    function endDrawTouch(event) {
      continueDrawTouch(event);
      svgPolyline = null;
    }
    
    function createSvgElement(tagName) {
      return document.createElementNS("http://www.w3.org/2000/svg", tagName);
    }
    
    function svgToPng() {
      const myImg = document.getElementById("myImg"); // 获取Img
      const s = new XMLSerializer().serializeToString(svgCanvas);
      const src = `data:image/svg+xml;base64,${window.btoa(s)}`;
      const img = new Image(); // 创建图片容器承载过渡
      img.src = src;
      img.onload = () => {
        // 图片创建后再执行,转Base64过程
        const canvas = document.createElement("canvas");
        canvas.width = img.width;
        canvas.height = img.height;
        const context = canvas.getContext("2d");
        context.drawImage(img, 0, 0);
        const ImgBase64 = canvas.toDataURL("image/png");
        console.log(ImgBase64, "Svg 转 png");
        myImg.src = ImgBase64;
      };
    }
    
    function svgBase64() {
      const myImg = document.getElementById("myImg"); // 获取Img
      const s = new XMLSerializer().serializeToString(svgCanvas);
      const ImgBase64 = `data:image/svg+xml;base64,${window.btoa(s)}`;
      console.log(ImgBase64, "Svg 转 Base64");
      myImg.src = ImgBase64;
    }
    
    function clearSvgChild() {
      // SVG DOM是XML DOM的超集,所以我知道我可以做这样的事情:
      while (svgCanvas.lastChild) {
        svgCanvas.removeChild(svgCanvas.lastChild);
      }
    }
  </script>
</html>

Vue组件

<template>
  <div class="signature" :style="{width:width+'px',height:height+'px'}">
    <svg  :id="id" ref="svgCanvas" width="100%" height="100%">
      <text class="bg_title" :x="width/2" :y="height/2" >{{bgTitle}}</text>
    </svg>
  </div>
  <div @click="exportSign('base64')">1122</div>
</template>

<script setup name="signature">
import { useEventListener } from '@vueuse/core'

const props = defineProps({
  bgTitle:{
    type: String,
    default: '签名区'
  },
  width: {
    type: String,
    default: '200'
  },
  height: {
    type: String,
    default: '100'
  },
  background: {
    type: String,
    default: '#F2F3F5'
  },
  id: {
    type: String,
    default: 'svg'
  },
})
defineExpose({clearSvgChild,exportSign})
const {width, height, id} = toRefs(props)
let svgCanvas = ref(null)
let svgPolyline=null
useEventListener(svgCanvas,'touchstart', (event) => {
      startDrawTouch(event)
    });
useEventListener(svgCanvas,'touchmove', (event) => {
      continueDrawTouch(event)
    });
useEventListener(svgCanvas,'touchend', (event) => {
      endDrawTouch(event)
    });
function startDrawTouch(event) {
  svgPolyline = createSvgElement("polyline");
  svgPolyline.setAttribute("fill", "none");//填充颜色
  svgPolyline.setAttribute("shape-rendering", "geometricPrecision");//指定渲染模式
  svgPolyline.setAttribute("stroke-linejoin", "round");//结束时的收边
  svgPolyline.setAttribute("stroke", "#000000");//轮廓颜色
  svgCanvas.value.appendChild(svgPolyline);
  continueDrawTouch(event);
}

function continueDrawTouch(event) {
  if (svgPolyline) {
    let touch = event.changedTouches[0];
    let point = svgPolyline.ownerSVGElement.createSVGPoint();
    point.x = touch.clientX;
    point.y = touch.clientY;
    let ctm = event.target.getScreenCTM(); //svgObj.getScreenCTM(); 该矩阵将svg坐标转换为屏幕坐标
    if (ctm = ctm.inverse()) {
      point = point.matrixTransform(ctm);
    }
    svgPolyline.points.appendItem(point);
  }
}

function endDrawTouch(event) {
  continueDrawTouch(event);
  svgPolyline = null;
}

function createSvgElement(tagName) {
  return document.createElementNS("http://www.w3.org/2000/svg", tagName);
}

function exportSign(type='base64') {
  return new Promise((resolve, reject)=>{
    // 导出图片不需要显示背景元素
    let svg=svgCanvas.value
    let bgTitleDom=document.querySelector('.bg_title')
    let clone=svg.removeChild(bgTitleDom);
    const s = new XMLSerializer().serializeToString(svgCanvas.value);
    const src = `data:image/svg+xml;base64,${window.btoa(s)}`;
    svg.insertBefore(clone, svg.children[0]);
    if(type==='base64'){
      resolve(src)
    }else{
      const img = new Image(); // 创建图片容器承载过渡
      img.src = src;
      img.onload = () => {
        // 图片创建后再执行,转Base64过程
        const canvas = document.createElement('canvas');
        canvas.width = img.width;
        canvas.height = img.height;
        const context = canvas.getContext('2d');
        context.drawImage(img, 0, 0);
        resolve(canvas.toDataURL('image/png'))
      }
    }
  })
}

//清除签名
function clearSvgChild() {
  let svg=svgCanvas.value
  // 背景元素不用删除
  while (svg.lastChild && svg.lastChild.className.baseVal!=='bg_title') {
     svg.removeChild(svg.lastChild);
  }
}

</script>

<style scoped lang="scss">
.signature{
  background: #F2F3F5;
  .bg_title{
    font-size: 18px;
    fill: #B2BAD1;
    text-anchor: middle;
    dominant-baseline: middle;

  }
}
/*当前页面根class * {*/
/*  -webkit-touch-callout:none; !*系统默认菜单被禁用*!*/
/*  -webkit-user-select:none; !*webkit浏览器*!*/
/*  -khtml-user-select:none; !*早期浏览器*!*/
/*  -moz-user-select:none;!*火狐*!*/
/*  -ms-user-select:none; !*IE10*!*/
/*  user-select:none;*/
/*}*/
</style>

效果

引用

首发于语雀文档@is_tao
关于javascript:是否有清除SVG元素内容的简单方法?
浅入深出,原生API实现SVG 转 BASE64
Draw svg path with mouse