The project requirements

  • Requirement 1: Draw canvas page with native JS

    • How to draw round pictures?
    • How to draw a rounded rectangle picture?
    • How do I draw rounded rectangles?
  • Requirement 2: Use qrcode plug-in to generate two-dimensional code and realize sharing

  • Requirement three: all code implementation

After joining the new company, I have never used canvas to draw requirements before, which is really a puzzle for the white guy in this field. After several days of familiarity, I finally drew the design effect using canvas, and I would like to share here

The UI effect is as follows:

Requirement 1: Draw canvas page with native JS

How to draw round pictures

First let’s get familiar with the Canvas Arc drawing Api. There are two methods to draw arcs:

1. Arc (x, y, R, startAngle, endAngle, anticlockwise): Take (x, y) as the center of the circle and r as the radius, from startAngle radian to endAngle radian. Anticlosewise is a Boolean value, with true meaning anticlosewise and false meaning clockwise (the default is clockwise).

1. The degrees here are radians. 0 radians is the positive x direction of PI.

Radians =(math.pi /180)*degrees // Convert Angle to radi. arc(50, 50, 40, 0, math.pi / 2, false);Copy the code

2. Use Canvas to draw a circle first, then position the picture to the center of the circle for cutting, and remove the part beyond the circle to form a circle

Code implementation:

/* * * CTX Canvas instance * img image address * x x coordinates * y y coordinates * R radius */ circleImgOne(CTX, img, x, y, r, w, H) {// If there are other elements that need to be drawn after the drawing, start the save() and restore() methods. Otherwise, the clip() methods will render all elements invisible. // Save () : save the current Canvas state. 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(); },Copy the code

How do I draw rounded corners

1. ArcTo (X1, y1, X2, y2, RADIUS): Draw an arc according to the given control point and radius, and finally connect the two control points with a straight line.

ArcTo (200, 50, 200, 200, 100); CTX. ArcTo (200, 50, 200, 200, 100);Copy the code

Code implementation:

/* * CTX Canvas instance * img image address * x x coordinates * y y coordinates * W width * H height * R radian size */ 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(); },Copy the code

How do I draw rounded rectangles

  • FillRect (x, y, width, height) : Draw a filled rectangle.
  • StrokeRect (x, y, width, height) : Draw the border of a rectangle.
  • 3, clearRect(x, y, widh, height) : Clear the specified rectangle area, then the area becomes fully transparent. \

The rounded rectangle is made up of four lines and four 1/4 arcs, disassembled as follows.

Code implementation:

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(); },Copy the code

Requirement 2: Use qrcode plug-in to generate two-dimensional code and realize sharing

1. Download

npm install qrcode --save-dev	
Copy the code

2. The introduction of

import QRCode from "qrcode"; 
Copy the code

Example 3.

GetQRCode () {let opts = {errorCorrectionLevel: "H", // error tolerance level type: "image/ PNG ", // 0.3, // qR code quality margin: 12, // QR code white margin width: 200, // width height: 180, // high text: "http://www.xxx.com", // qR code content color: {dark: "#333333", // Light: "# FFF ", // background color},}; this.QRCodeMsg = "http://www.baidu.com"; Js let MSG = document.getelementbyid ("QRCode_header"); Qrcode.tocanvas (MSG, this.qrcodemsg, opts, function (error) {conso.log(error); }); },Copy the code

Code implementation:

/ / get the page url / / everyone's business requirements, specific parameters to change the let curPath = window. The location. The 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(" get code error ", e); }}Copy the code

Requirement three: all code implementation

<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>
Copy the code

We have now completed the page component for drawing the shared poster on canvas

Code has not been optimized, partners can be optimized according to their own likes ha, welcome three