likes
comments
collection
share

纯正的代码水文--多码警告

作者站长头像
站长
· 阅读数 22

写这个文章主要是为了记录开发中用到的相关方法,避免以后再遇到的时候再去一通度娘搜索(虽然以后遇到还是很大概率直接去搜索...),同时也希望能帮助有需要的小伙伴们。

本文主要记录三个内容,一个是前端生成带印章和文字的图片;二是微信和支付宝内h5分享;三是微信和支付宝内点击图片查看大图功能。

一、前端生成带公章的图片

此处需求是:前端生成一个带有文字和公章的图片,并能在微信和支付宝里长按保存,其中各种文字和公章内容都是动态的。

先上代码,看下整体的结构,此处数据均为实例数据,真实需求中均为接口获取。

<!-- 转化为图片内容 -->
<div class="zy_my_certificate_end_box">
    // 最终生成的图片
    <img :src="dataObj.imgSrc" />
</div>
<!-- 实体html内容 -->
<div class="zy_my_certificate_box" id="ImgRef">
    <img :src="getAssets('gongyi/certificate.png')" />
    <p class="certificate_number">证书编号:123456789</p>
    <div class="certificate_content">
        <p>亲爱的张三同学</p>
        <p>恭喜你获得全班第一名的好成绩,仅此奖状,以资鼓励</p>
    </div>
    <div class="certificate_bottom">
        // 此处为公章位置
        <div class="certificate_icon">
          <canvas id="certificate_icon_id" width="80" height="80"></canvas>
        </div>
        <p>东南西北第一实验小学</p>
        <p>2023年5月9日</p>
    </div>
</div>

针对这个结构,就只说一点,实体的html内容,也就是用来生成图片的内容,不能设置任何opacity:0, visibility: hidden之类的属性,那样生成的图片都是大白纸一张,此处只能把它fixed、absolute到屏幕之外的地方。

接下来看关于公章的生成,主要涉及环形文字的生成和五角星的绘制,相关说明在代码注释里有写明。

// 绘制印章
const createSeal = () => {
  return new Promise((resolve)=>{
    var canvas = document.getElementById('certificate_icon_id');
    var context = canvas.getContext('2d');
    // 绘制印章边框   
    var width = canvas.width / 2;
    var height = canvas.height / 2;
    context.lineWidth = 2;    // 边框宽度
    context.strokeStyle = "#f00";    // 边框颜色
    context.beginPath();
    context.arc(width, height, 38, 0, Math.PI * 2);    // 绘制个圆圈,不理解的可以查下api
    context.stroke();

    //画五角星,此处的第四个参数,根据自己需要的大小进行修改
    create5star(context, width, height, 10, "#f00", 0);

    // 绘制印章名称,就是公章星星下边那一排小字   
    context.font = '7px SimSun';
    context.textBaseline = 'middle';  //设置文本的垂直对齐方式
    context.textAlign = 'center';   //设置文本的水平对对齐方式
    context.lineWidth = 1;
    context.fillStyle = '#f00';
    let name = "学校专用章"
    context.fillText(name, width, height + 24);   // 此处最后一个参数表示距离底部位置,可以根据情况自行设定

    // 绘制印章单位   
    context.translate(width, height);  // 平移到此位置,
    context.font = '8px SimSun'
    let count = dataObj.certificateData!.merName.length;  // 字数   
    let angle = 4 * Math.PI / (3 * (count - 1));  // 字间角度   
    let chars = dataObj.certificateData!.merName.split("");
    let c;
    for (let i = 0; i < count; i++) {
      c = chars[i];  // 需要绘制的字符   
      if (i == 0){
        context.rotate(5 * Math.PI / 6);
      } else {
        context.rotate(angle);
      }
      
      context.save();
      context.translate(31, 0);  // 平移到此位置,此时字和x轴垂直,关于这个位置,个人也是多次尝试确定好的,考虑可能和绘制半径有一定的关系。这里一定要根据自己图章大小修改,否则可能看不到任何文字!   
      context.rotate(Math.PI / 2);  // 旋转90度,让字平行于x轴   
      context.fillText(c, 0, 0);  // 此点为字的中心点   
      context.restore();
    }
    resolve(true)

    //绘制五角星,这部分不用修改,可以拿来直接用  
    /** 
     * 创建一个五角星形状. 该五角星的中心坐标为(sx,sy),中心到顶点的距离为radius,rotate=0时一个顶点在对称轴上 
     * rotate:绕对称轴旋转rotate弧度 
    */
    function create5star(context, sx, sy, radius, color, rotato) {
      context.save();
      context.fillStyle = color;
      context.translate(sx, sy);  //移动坐标原点  
      context.rotate(Math.PI + rotato);  //旋转  
      context.beginPath();   //创建路径  
      var x = Math.sin(0);
      var y = Math.cos(0);
      var dig = Math.PI / 5 * 4;
      for (var i = 0; i < 5; i++) {//画五角星的五条边  
        var x = Math.sin(i * dig);
        var y = Math.cos(i * dig);
        context.lineTo(x * radius, y * radius);
      }
      context.closePath();
      context.stroke();
      context.fill();
      context.restore();
    }
  })
}

此处使用了promise,因为后需要将文字和图章一起转化为图片,需要确保图章已经生成完成才去转换。

后边的内容就比较简单了,直接用html2canvas转换就可以了,当然此处没有做缩放的处理,因为下载出来的图片效果还是可以的,如果图片模糊,可能需要先放大再缩小的操作。

// 将html转化成图片
const htmlToImg = ()=>{
  let imgid = document.getElementById('ImgRef')!;
  html2canvas(imgid, {
    allowTaint: true,
    useCORS: true,
    logging: true,
  }).then((canvas) => {
    //  转成图片,生成图片地址
    let imgUrl = canvas.toDataURL('image/jpeg')
    dataObj.imgSrc = imgUrl
  })
}

// 调用绘制公章和转换图片
createSeal().then(()=>{
    htmlToImg()
})

最后来看下生成的图片最终效果,数据和上边的数据不一样哦,只是看下整体效果~

纯正的代码水文--多码警告

二、微信和支付宝h5页面的分享

这个应该属于老生常谈的问题了,相关功能,可以使用微信/支付宝环境内置JSBridge,也可以使用相关的jssdk,这里使用的是sdk方法,JSBridge方法在下边的图片查看里有使用。

此处以微信为例,如果你问为啥不用支付宝做例子,因为支付宝h5根本不允许自定义分享,调用sdk的share方法,会报错,提示无权限使用,配置也不会生效;而支付宝JSBridge里根本没有相关的方法。

// main.js
import wx from 'weixin-js-sdk'

let app = createApp(App)
app.use(router)
app.config.globalProperties.$wx = wx

// utils/index.js
//判断微信、支付宝环境。
export const isWxOrZfb = () => {
  var ua = window.navigator.userAgent.toLowerCase();
  if (ua.match(/MicroMessenger/i) == "micromessenger") {
    return 'wx';
  }
  if (ua.match(/AlipayClient/i) == "alipayclient") {
    return "zfb";
  }
}

// 组件内使用
import { getCurrentInstance } from 'vue'

const proxy = getCurrentInstance().proxy;
// 分享
const shareFun = ()=>{
  const shareUrl = window.location.href;

  if(isWxOrZfb() === 'wx'){
    const params = {
      shareUrl,
      debug: false,
      jsApiList: "updateAppMessageShareData,onMenuShareAppMessage,updateTimelineShareData,onMenuShareTimeline,previewImage"
    }
    // 此处调用后端接口,因为使用jssdk方法前,一定要用config进行配置,并在ready里才可以使用
    sceneHttp.getWxShare(params).then((res) => {
      if (res.code === 200 && res.data) {
        proxy.$wx.config({
          debug: false,
          appId: res.data.appId,  // 必填,公众号的唯一标识
          timestamp: res.data.timestamp,  // 必填,生成签名的时间戳
          nonceStr: res.data.nonceStr,  // 必填,生成签名的随机串
          signature: res.data.signature,  // 必填,签名
          jsApiList: ["updateAppMessageShareData", "onMenuShareAppMessage","updateTimelineShareData","onMenuShareTimeline","previewImage"]
        });

        setTimeout(() => {
          proxy.$wx.ready(() => {
            //分享给朋友的
            proxy.$wx.updateAppMessageShareData({
              title: '我是标题', // 分享标题
              desc: '我是描述文字',
              link: 'https://www.aaaaa.com', // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
              imgUrl: '',   // 分享出去所展示的小图
              success: function () {
                console.log('分享到朋友成功')
              },
            });
            //分享给朋友圈
            proxy.$wx.updateTimelineShareData({
              title: '我是标题',  // 分享标题
              link: 'https://www.aaaaa.com', // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
              imgUrl: '',   // 分享出去所展示的小图
              success: function () {
                console.log('分享到朋友圈成功')
              },
              fail: function () { }
            });
          });
        }, 1000)
      } else {
        console.log('准备失败');
      }
    }, function (err: any) {
      console.log('准备失败', err);
    });
  } else if(isWxOrZfb() === 'zfb'){
    console.log('对不起,支付宝不支持自定义,再见吧!👋🏻')
  }
}

三、微信和支付宝点击小图打开大图预览

这个功能也是属于微信/支付宝内置的功能,可以通过api调用直接实现。此处主要说一下,如果一篇文章中有很多图片,而文章的内容又是通过html格式从接口里获取的,那此时要想预览图片该如何操作呢?

我们知道js原生可以通过事件冒泡或者事件捕获,获取到目标元素,此处我们可以使用这个方法。

同时通过查看两端的相关api我们发现,除了获取到当前需要展示的图片外,还有一个数组用来存储所有的需要查看的图片,此处我们就在点击事件开始时,顺带把所有的图片都加到数组里,避免了后续某些诡异的情况(这里主要说的是支付宝的init参数,如果值不正确的话,可能会出现,点击第二个图但是弹出来的确实第一张图,滑动才能看到第二张图)

// 文字加图片内容,数据从接口里获取html片段
<div class="zy_contribution_content_mes" id="zy_contribution_parent">
    <div v-html="dataObj.contributionMes.graphicDetails" @click="mesShowImg"></div>
</div>


// 详情点击中的图片查看大图
const mesShowImg = (e)=>{
  // 只有点击的元素是img时,才进行后边的操作
  if(e.target.tagName.toLowerCase() === 'img'){
    let imgUrl = e.target.getAttribute('src')
    // 获取所有图片的列表
    let imgList = document.getElementById('zy_contribution_parent')!.getElementsByTagName('img')
    // 当前打开的是第几张图,支付宝使用
    let idx = 0
    
    if(isWxOrZfb() === 'wx'){
      if(dataObj.contentImgList.includes(imgUrl)){
        dataObj.contentImgList.push(imgUrl)
      }
      wxJsReady(function(){
        WeixinJSBridge.invoke("imagePreview", {
          urls: dataObj.contentImgList,
          current: imgUrl,
        })
      })
    } else if(isWxOrZfb() === 'zfb'){
      // 如果数组里没有图,就循环添加进去
      if(!dataObj.contentImgList.length){
        for(let i = 0; i < imgList.length; i++){
          let zfbObj: ObjAny = {}
          let item = imgList[i].src
          zfbObj.u = item
          zfbObj.t = ''
          dataObj.contentImgList.push(zfbObj)
          if(imgList[i].src === imgUrl){
            idx = i
          }
        }
      } else {
        // 如果有图,就看展示第几张
        for(let i = 0; i < imgList.length; i++){
          if(imgList[i].src === imgUrl){
            idx = i
          }
        }
      }

      alJsReady(function() {
        AlipayJSBridge.call('imageViewer', {
          images: dataObj.contentImgList,
          init: idx
        },
        function(result: any) {
          console.log('imgview', result);
        });
      })
    }
  }
}

此处顺带附上,关于JSBridge是否成功注入的方法,调用api之前最好确定已经成功注入。

// 阿里jsbridge是否注入
export const alJsReady = (callback: Function) => {
  // 如果jsbridge已经注入则直接调用
  if ((window as any).AlipayJSBridge) {
    callback && callback();
  } else {
    // 如果没有注入则监听注入的事件
    (document as any).addEventListener('AlipayJSBridgeReady', callback, false);
  }
}

// 微信jsbridge是否注入
export const wxJsReady = (callback: Function) => {
  if (typeof WeixinJSBridge == "object" && typeof WeixinJSBridge.invoke == "function") {
     callback && callback();
  } else {
    if ((document as any).addEventListener) {
      (document as any).addEventListener("WeixinJSBridgeReady", callback, false);
    } else if ((document as any).attachEvent) {
      (document as any).attachEvent("WeixinJSBridgeReady", callback);
      (document as any).attachEvent("onWeixinJSBridgeReady", callback);
    }
  }
}

水文结束,有错误的地方,欢迎指正,反正我也不会改😁😄😄