推荐收藏[canvas]绘制分享海报

1,473 阅读4分钟

项目需求

  • 需求一: 通过原生js绘制canvas页面

    • 如何绘制圆型图片?
    • 如何绘制圆角矩形图片?
    • 如何绘制圆角矩形?
  • 需求二: 使用qrcode插件生成二维码,实现分享

  • 需求三:全部代码实现

加入新公司,之前从来没有使用过canvas绘制过需求,对于这方面的小白来说真是被难倒了,经过几天的熟悉终于使用canvas绘制出了设计效果,在此作出分享

UI效果图如下:

ui.png

需求一:通过原生js绘制canvas页面

如何绘制圆型图片

首先我们先熟悉一下canvas绘制圆弧Api,有两个方法可以绘制圆弧:

1、arc(x, y, r, startAngle, endAngle, anticlockwise): 以(x, y) 为圆心,以r 为半径,从 startAngle 弧度开始到endAngle弧度结束。anticlosewise 是布尔值,true 表示逆时针,false 表示顺时针(默认是顺时针)。

1、这里的度数都是弧度0 弧度是指的 x 轴正方向。

radians=(Math.PI/180)*degrees   //角度转换成弧度
ctx.arc(50, 50, 40, 0, Math.PI / 2, false);

2、利用 Canvas 先画出一个圆形,然后将图片定位到圆形中心位置进行剪切,将超出圆形的部分去掉,就会形成一个圆形

代码实现:

     /*
      *  参数说明
      *  ctx Canvas实例
      *  img 图片地址
      *   x  x轴坐标
      *   y  y轴坐标
      *   r  圆形半径
     */

   circleImgOne(ctx, img, x, y, r, w, h) {
      // 如果在绘制图片之后还有需要绘制别的元素,需启动 save() 、restore() 方法,否则 clip() 方法会导致之后元素都不可见
      // save():保存当前 Canvas 画布状态
      // restore():恢复到保存时的状态
      let d = r * 2;
      let cx = x + r;
      let cy = y + r;
      ctx.arc(cx, cy, r, 0, 2 * Math.PI);
      ctx.strokeStyle = "#FFFFFF";
      ctx.stroke();
      ctx.clip();
      ctx.drawImage(img, x, y, d, d);
      ctx.restore();
    },

如何绘制圆角图片

1、arcTo(x1, y1, x2, y2, radius): 根据给定的控制点和半径画一段圆弧,最后再以直线连接两个控制点。

//参数1、2:控制点1坐标 参数3、4:控制点2坐标 参数4:圆弧半径 
ctx.arcTo(200, 50, 200, 200, 100);

代码实现:

  /*
     *  绘制矩形图片
     *  参数说明
     *  ctx Canvas实例
     *  img 图片地址
     *   x  x轴坐标
     *   y  y轴坐标
     *   w  宽度
     *   h  高度
     *   r  弧度大小
     */
    circleImgTwo(ctx, img, x, y, w, h, r) {
      if (w < 2 * r) r = w / 2;
      if (h < 2 * r) r = h / 2;
      ctx.beginPath();
      ctx.moveTo(x + r, y);
      ctx.arcTo(x + w, y, x + w, y + h, r);
      ctx.arcTo(x + w, y + h, x, y + h, r);
      ctx.arcTo(x, y + h, x, y, r);
      ctx.arcTo(x, y, x + w, y, r);
      ctx.closePath();
      ctx.strokeStyle = "#FFFFFF";
      ctx.stroke();
      ctx.clip();
      ctx.drawImage(img, x, y, w, h);
      ctx.restore();
    },

如何绘制圆角矩形

  • 1、fillRect(x, y, width, height):绘制一个填充的矩形。
  • 2、strokeRect(x, y, width, height):绘制一个矩形的边框。
  • 3、clearRect(x, y, widh, height):清除指定的矩形区域,然后这块区域会变的完全透明。
    圆角矩形是由四段线条和四个1/4圆弧组成,拆解如下。
    2016322111336083.jpg (600×425)

代码实现:

// 绘制矩形圆角
    roundReck(ctx, x, y, width, height, radius) {
      ctx.beginPath();
      ctx.arc(x + radius, y + radius, radius, Math.PI, (Math.PI * 3) / 2);
      ctx.lineTo(width - radius + x, y);
      ctx.arc(width - radius + x, radius + y, radius, (Math.PI * 3) / 2, Math.PI * 2);
      ctx.lineTo(width + x, height + y - radius);
      ctx.arc(width - radius + x, height - radius + y, radius, 0, (Math.PI * 1) / 2);
      ctx.lineTo(radius + x, height + y);
      ctx.arc(radius + x, height - radius + y, radius, (Math.PI * 1) / 2, Math.PI);
      ctx.closePath();
    },

需求二:使用qrcode插件生成二维码,实现分享

1.下载

npm install qrcode --save-dev	

2.引入

import QRCode from "qrcode"; 

3.示例

getQRCode() {
      let opts = {
        errorCorrectionLevel: "H", //容错级别
        type: "image/png", //生成的二维码类型
        quality: 0.3, //二维码质量
        margin: 12, //二维码留白边距
        width: 200, //宽
        height: 180, //高
        text: "http://www.xxx.com", //二维码内容
        color: {
          dark: "#333333", //前景色
          light: "#fff", //背景色
        },
      };
      this.QRCodeMsg = "http://www.baidu.com"; //生成的二维码为URL地址js
      let msg = document.getElementById("QRCode_header");
      // 将获取到的数据(val)画到msg(canvas)上
      QRCode.toCanvas(msg, this.QRCodeMsg, opts, function (error) {
        conso.log(error);
      });
    },

代码实现:

// 获取页面url
// 每个人的业务需求不一样,具体参数自行更改
let curPath = window.location.href.split("#")[0];
      curPath =
        curPath.indexOf("?") > -1
          ? `${curPath.split("?")[0]}?storyId=${info.storyId}`
          : `${curPath}?storyId=${info.storyId}`;
      let qr = new QRCode("qrcodeStory", {
        text: curPath,
        width: 150,
        height: 150,
        colorDark: "#333333",
      });
      let qrimg = "";
      qrimg = qr._el.childNodes[1];
       if (isMiniapp() && this.$store.state.vipCompanyId) {
        try {
          let { result } = await this.getMiniAPPCode();
          qrimg = { src: result };
        } catch (e) {
          console.error("获取小程序二维码错误", e);
        }
      }

需求三:全部代码实现

<template>
  <van-popup class="share-story-content" v-model="show" @open="createImage">
    <div id="qrcodeStory"></div>
    <img class="closeBtn" src="../../assets/img/close.png" @click="close()" alt="" />
    <img class="resultUrl" :src="cardUrl" alt="" />
    <div class="bottom-btn" v-if="isFinished">长按保存图片,发送至好友或朋友圈</div>
    <loading v-show="isShowLoading"></loading>
  </van-popup>
</template>
<script>
import Cookies from "js-cookie";
import { QRCode } from "utils/qrcode";
import MDCanvas from "@/utils/canvas";
import { getStoryCode } from "../../api/api";
import { isMiniapp, getRatio, visitRecord } from "../../utils/tool";
export default {
  name: "dialogStoryShare",
  props: {
    info: {
      type: Object,
      default() {
        return {};
      },
    },
    // 隐藏显示弹框
    show: {
      type: Boolean,
      default: false,
    },
  },
  data() {
    return {
      showPop: false,
      isFinished: true,
      isShowLoading: false,
      cardUrl: "",
      getUserName: "",
      infoNum: 0,
      storyUrl: {
        img: "https://q.plusx.cn/wechat/live_m/source/image/storyTitle.png",
      },
      isBigImg: [],
    };
  },
  watch: {
    show(val) {
      if (val) {
        this.createImage();
      }
    },
  },
  mounted() {
    let _this = this;
    for (let i = 0; i < this.info.storyAlbums.length; i++) {
      let element1 = this.info.storyAlbums[i].pics;
      for (const key in element1) {
        _this.infoNum++;
        if (!element1[key].bigImg) return "";
        _this.isBigImg.push(element1[key]);
      }
    }
  },
  created() {},
  methods: {
    close() {
      this.$parent.close("showShare");
    },
    async createImage() {
      let getUserName = Cookies.get("user_name");
      let info = this.info;
      let curPath = window.location.href.split("#")[0];
      curPath =
        curPath.indexOf("?") > -1
          ? `${curPath.split("?")[0]}?storyId=${info.storyId}`
          : `${curPath}?storyId=${info.storyId}`;
      let qr = new QRCode("qrcodeStory", {
        text: curPath,
        width: 150,
        height: 150,
        colorDark: "#333333",
      });
   
      let qrimg = "";
      qrimg = qr._el.childNodes[1];
      if (isMiniapp() && this.$store.state.vipCompanyId) {
        try {
          let { result } = await this.getMiniAPPCode();
          qrimg = { src: result };
        } catch (e) {
          console.error("获取小程序二维码错误", e);
        }
      }
      let infoParams = {
        userHeadImg: info.headImg,
        userTitile: info.title,
        userName: info.name,
      };

      var canvas = document.createElement("canvas");
      var ctx = canvas.getContext("2d");
      let ratio = getRatio(ctx);
      canvas.width = 750 * ratio;
      let img1 = { img: "", width: 750, height: 0 };
      let bgImage = this.isBigImg[0].bigImg;
      let img2 = await this.readImage("https:" + bgImage, ctx);
      let img3 = await this.readImage(infoParams.userHeadImg, ctx);
      let img4 = await this.readImage(this.storyUrl.img, ctx);
      canvas.height = img1.height + img2.height + 320 * ratio;
      this.roundReck(ctx, 0, 0, canvas.width, canvas.height, 50);
      ctx.fillStyle = "#fff";
      ctx.fill();
      ctx.save();
      this.circleImgTwo(ctx, img2.img, 0, img1.height, img2.width, img2.height, 50);
      ctx.save();
      this.circleImgOne(ctx, img3.img, 90, img2.height + 70 * ratio, 55 * ratio, 55 * ratio);
      // 二维码
      ctx.drawImage(
        qrimg,
        canvas.width - 190 * ratio,
        img2.height + 55 * ratio,
        140 * ratio,
        140 * ratio
      );
      ctx.save();
      ctx.font = "115px PingFangSC-Semibold, PingFang SC";
      ctx.fillStyle = "#333";
      ctx.fillText(getUserName, (img2.width + 50) / 5, img2.height + 110 * ratio);
      ctx.save();
      ctx.font = "95px PingFangSC-Semibold, PingFang SC";
      ctx.fillStyle = "#888";
      ctx.fillText(
        `故事中有我${this.infoNum}张图片`,
        (img2.width + 50) / 5,
        img2.height + 170 * ratio
      );
      ctx.save();
      ctx.fillStyle = "#eee";
      ctx.fillRect(100, img2.height + 220 * ratio, canvas.width - 220, 220);
      ctx.save();
      ctx.font = "100px PingFangSC-Semibold, PingFang SC";
      ctx.fillStyle = "#666";
      ctx.fillText(
        `微信扫一扫,查看我的精彩故事`,
        (img2.width + 10) / 5,
        img2.height + 270 * ratio
      );
      ctx.save();
      // 我的图片故事边框背景
      // ctx.fillRect(120, img2.height - 150 * ratio, canvas.width - 260, 500);
      this.roundReck(ctx, 120, img2.height - 123 * ratio, canvas.width - 260, 430, 50);
      ctx.fillStyle = "#fff";
      ctx.shadowColor = "#e8e8e8";
      ctx.shadowBlur = 30;
      ctx.fill();
      ctx.stroke();
      ctx.restore();
      // 我的图片故事--png
      ctx.drawImage(
        img4.img,
        (canvas.width + 300) / 5,
        img2.height - 86 * ratio,
        img4.width / 2,
        img4.height / 2
      );
      ctx.restore();
      var x = canvas.width - 390;
      var y = img2.height + 203 * ratio;
      var r = 60;
      ctx.beginPath();
      ctx.moveTo(x, y + r);
      ctx.lineTo(x + r, y);
      ctx.lineTo(x + (r + 60), y + r);
      ctx.strokeStyle = "#eee";
      ctx.fillStyle = "#eee";
      ctx.closePath();
      ctx.stroke(); //开始绘制直线
      ctx.fill(); //填充颜色
      ctx.restore();
      ctx.scale(ratio, ratio);
      this.cardUrl = canvas.toDataURL("image/png", 1);
      this.isShowLoading = false;
    },
    // 绘制矩形圆角
    roundReck(ctx, x, y, width, height, radius) {
      ctx.beginPath();
      ctx.arc(x + radius, y + radius, radius, Math.PI, (Math.PI * 3) / 2);
      ctx.lineTo(width - radius + x, y);
      ctx.arc(width - radius + x, radius + y, radius, (Math.PI * 3) / 2, Math.PI * 2);
      ctx.lineTo(width + x, height + y - radius);
      ctx.arc(width - radius + x, height - radius + y, radius, 0, (Math.PI * 1) / 2);
      ctx.lineTo(radius + x, height + y);
      ctx.arc(radius + x, height - radius + y, radius, (Math.PI * 1) / 2, Math.PI);
      ctx.closePath();
    },
    readImage(url, ctx) {
      return new Promise((resolve) => {
        let img = new Image();
        let winWidth = 750 * getRatio(ctx);
        img.crossOrigin = "anonymous";
        img.onload = function () {
          let obj = {
            img: img,
            width: winWidth,
            height: parseInt(img.height * (winWidth / img.width).toFixed(2)),
          };
          resolve(obj);
        };
        img.src = url;
      });
    },
    // 绘制圆形图片
    circleImgOne(ctx, img, x, y, r, w, h) {
      // ctx.save()
      let d = r * 2;
      let cx = x + r;
      let cy = y + r;
      ctx.arc(cx, cy, r, 0, 2 * Math.PI);
      ctx.strokeStyle = "#FFFFFF";
      ctx.stroke();
      ctx.clip();
      ctx.drawImage(img, x, y, d, d);
      ctx.restore();
    },
    /*
     *  绘制矩形图片
     *  参数说明
     *  ctx Canvas实例
     *  img 图片地址
     *   x  x轴坐标
     *   y  y轴坐标
     *   w  宽度
     *   h  高度
     *   r  弧度大小
     */
    circleImgTwo(ctx, img, x, y, w, h, r) {
      if (w < 2 * r) r = w / 2;
      if (h < 2 * r) r = h / 2;
      ctx.beginPath();
      ctx.moveTo(x + r, y);
      ctx.arcTo(x + w, y, x + w, y + h, r);
      ctx.arcTo(x + w, y + h, x, y + h, 0);
      ctx.arcTo(x, y + h, x, y, 0);
      ctx.arcTo(x, y, x + w, y, r);
      ctx.closePath();
      ctx.strokeStyle = "#FFFFFF";
      ctx.stroke();
      ctx.clip();
      ctx.drawImage(img, x, y, w, h);
      ctx.restore();
    },
    getMiniAPPCode() {
      let params = {
        param: encodeURIComponent(
          `acNo=${this.$store.state.activityNo}&storyId=${this.info.storyId}`
        ),
        path: encodeURIComponent("pages/index/index"),
        vipFrom: "vipMapp",
        companyNo: this.$store.state.vipCompanyId,
        vipMappTicket: this.$store.state.vipMappTicket,
      };
      return getStoryCode(params);
    },
  },
};
</script>

<style lang="scss" scoped>
.share-story-content {
  width: 100%;
  height: 100vh;
  background-color: rgb(51, 49, 49);
  /* opacity: 0.9; */
  overflow-y: auto;
  .closeBtn {
    width: 0.5rem;
    height: 0.5rem;
    position: absolute;
    right: 0.2rem;
    top: 0.8rem;
    font-size: 0.3rem;
    z-index: 2;
  }
  .bottom-btn {
    width: 100%;
    height: 1rem;
    background-color: #dd4e4e;
    text-align: center;
    line-height: 1rem;
    font-size: 0.3rem;
    color: #fff;
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    z-index: 2;
  }
  .resultUrl {
    width: 100%;
    margin-top: 0.85rem;
    padding: 0 0.85rem;
  }
}
</style>

//具体页面调用
  <dialog-story-share :info="info" :show="showShare"></dialog-story-share>

至此我们就完成了[canvas]绘制分享海报的页面组件

代码还没有优化,小伙伴们可以按自己的喜欢自行优化哈,欢迎三连