项目需求
-
需求一: 通过原生js绘制canvas页面
- 如何绘制圆型图片?
- 如何绘制圆角矩形图片?
- 如何绘制圆角矩形?
-
需求二: 使用qrcode插件生成二维码,实现分享
-
需求三:全部代码实现
加入新公司,之前从来没有使用过canvas绘制过需求,对于这方面的小白来说真是被难倒了,经过几天的熟悉终于使用canvas绘制出了设计效果,在此作出分享
UI效果图如下:
需求一:通过原生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圆弧组成,拆解如下。
代码实现:
// 绘制矩形圆角
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]绘制分享海报的页面组件
代码还没有优化,小伙伴们可以按自己的喜欢自行优化哈,欢迎三连