今天要做的是将海报与微信分享二维码贴在一起生成完整的海报,并且要有切换图片的效果。一开始想着直接海报和分享二维码通过样式搞在一起,但是因为有缩小的特效,会导致切换图片时海报与二维码分离。所以我就换了个思路。
将图片和二维码加入到canvas,然后canvas转换成图片,这样整张的图片就不会有什么问题了。
<template>
<div class="s-container">
<div class="s-title">
<text>和好友一起推广,一起赚奖励</text>
</div>
<div class="s-main"
:style="{ 'padding-left': (screenWidth - basicNum) / 2 + 'px',
'padding-right': (screenWidth - basicNum) / 2 + 'px' }">
<div
class="s-item"
:class="{ active: currentIndex === index }"
v-for="(item, index) in shareInfo"
:key="index"
:style="{
right: item.right + '%',
transform:
(currentIndex === index ? 'scale(1)' : 'scale(.9)') +
' translateX(' +
deltaX +
'rpx)',
transition: deltaX === 0 ? 'all .3s' : 'none',
width: shareWidth + 'px', height: shareHeight + 'px',
}"
@click="setCurrentIndex(index)"
@touchstart="touchstart"
@touchmove="touchmove"
@touchend="touchend"
>
<image
:src="item.gestaltPosterImg ? item.gestaltPosterImg : item.posterImg"
></image>
<canvas
:style="{ width: shareWidth + 'px', height: shareHeight + 'px', }"
:id="'shareCanvas' + index"
:canvas-id="'shareCanvas' + index"
></canvas>
<div class="s-current-info" v-show="currentIndex === index">
<text>{{ currentIndex + 1 }}/{{ shareInfo.length }}</text>
</div>
</div>
</div>
<div class="s-footer">
<div :class="{ 's-generate-bad': this.shareInfo.length === 0 }" class="s-generate" @tap="generatePosterImg">
<image src="../../static/images/icon/picture.png" />
<text>生成海报</text>
</div>
</div>
<view class="code-popup" v-if="wxCodeShow" @tap="closeSharePopup()">
<image class="code-main"
:class="{ 'fade-in': showExecShare, 'fade-out': !showExecShare }"
:src="shareInfo[currentIndex].gestaltPosterImg" />
<view
@tap.stop
class="share-main"
:class="{ 'share-open': showExecShare, 'share-close': !showExecShare }"
>
<view class="share-close-btn" @tap="closeSharePopup()"></view>
<view class="share-type">
<button class="share-wechat" open-type="share">
<image src="../../static/images/icon/share1.png"></image>
<span>发送给朋友</span>
</button>
<button class="share-download" @tap="saveShareImg">
<image src="../../static/images/icon/share3.png"></image>
<span>保存至相册</span>
</button>
</view>
</view>
</view>
</div>
</template>
<script>
var config = require("../../../utils/config.js");
var http = require("../../../utils/http.js");
export default {
onLoad() {
uni.setNavigationBarTitle({
title: "分享中心",
});
const systemInfo = uni.getSystemInfoSync();
this.screenWidth = systemInfo.screenWidth;
},
async onShow() {
uni.showLoading();
this.shareInfo = await this.initShareInfo();
if (this.shareInfo.length) {
this.shareWxCode = await this.createShareQrCodeImg();
this.shareCodeImg = await this.getShareQrCodeImg();
const rect = await this.getShareCodeBoxSize();
this.initRate(rect);
await this.initShareCanvas();
this.initShareCanvasToImgs();
}
uni.hideLoading();
console.log(this.shareInfo);
},
onShareAppMessage() {
this.closeSharePopup();
return {
title: "有蜜会员MALL",
path: "/pages/index/index?scene" + wx.getStorageSync("distCardNo"),
imageUrl: this.shareInfo[this.currentIndex].gestaltPosterImg
}
},
data() {
return {
currentIndex: 0,
screenWidth: 0,
shareInfo: [],
shareCodeImg: "",
shareWxCode: "",
startX: 0, // 触摸开始时的 X 坐标
currentX: 0, // 触摸移动时的 X 坐标
deltaX: 0, // 滑动的距离
shareWidth: 0, //图片的宽度 * resultRate
shareHeight: 0, //图片的高度 * resultRate
basicRate: 0.72, //基准比例值
resultRate: 0, //结果比例值
basicNum: 0, //basicRate * screenWidth
wxCodeShow: false,
showExecShare: false,
};
},
methods: {
initShareInfo() {
return new Promise((resolve) => {
http.request({
url: "/poster/list",
method: "GET",
data: {},
callBack: (res) => {
res.forEach((v) => {
v.right = 0;
v.gestaltPosterImg = "";
});
resolve(res);
},
errCallBack: (err) => {
resolve([]);
},
});
});
},
setCurrentIndex(index) {
if (this.currentIndex !== index) {
this.currentIndex = index;
}
},
touchstart(event) {
// 获取触摸开始时的 X 坐标
this.startX = event.touches[0].clientX;
},
touchmove(event) {
// 获取触摸移动时的 X 坐标
this.currentX = event.touches[0].clientX;
// 计算滑动的距离
this.deltaX = this.currentX - this.startX;
},
touchend() {
if (Math.abs(this.deltaX) > 70) {
// 向右滑动
if (this.deltaX > 0 && this.currentIndex > 0) {
setTimeout(() => {
this.currentIndex--;
});
}
// 向左滑动
if (this.deltaX < 0 && this.currentIndex < this.shareInfo.length - 1) {
setTimeout(() => {
this.currentIndex++;
});
}
}
this.deltaX = 0;
},
getShareQrCodeImg() {
return new Promise((resolve) => {
var cardno = wx.getStorageSync("distCardNo");
wx.downloadFile({
header: {
Authorization: wx.getStorageSync("token"),
},
url:
config.domain +
"/p/distribution/qrCode/invitation?page=pages/index/index&scene=" +
cardno,
success: (res) => {
resolve(res.tempFilePath);
},
fail: (err) => {
resolve("");
},
});
});
},
async createShareQrCodeImg() {
return new Promise((resolve) => {
const cardNo = wx.getStorageSync("distCardNo");
const content = JSON.stringify({
scene: cardNo,
});
const params = {
url: "/qrcodeTicket/miniQrCode",
method: "GET",
responseType: "arraybuffer",
data: {
type: 2,
content: content,
},
callBack: async (res) => {
resolve("data:image/jpg;base64," + wx.arrayBufferToBase64(res));
},
errCallBack: (err) => {
resolve("");
},
};
http.request(params);
});
},
initRate(boxSize) {
this.basicNum = this.screenWidth * this.basicRate;
this.resultRate = (this.basicNum / boxSize.width).toFixed(2);
this.shareWidth = boxSize.width * this.resultRate;
this.shareHeight = boxSize.height * this.resultRate;
},
getShareCodeBoxSize() {
return new Promise(resolve => {
const img = this.shareInfo[0].posterImg;
uni.getImageInfo({
src: img,
success: (res) => {
resolve({
width: res.width,
height: res.height
})
},
});
})
},
async initShareCanvas() {
for (let i = 0; i < this.shareInfo.length; i++) {
const id = "shareCanvas" + i;
await this.createShareCanvas(id, i);
}
},
async createShareCanvas(canvasId, idx) {
const ctx = uni.createCanvasContext(canvasId, this);
ctx.setFillStyle("#ffffff");
this.roundRect(ctx, 0, 0, this.shareWidth, this.shareHeight, 10);
ctx.fill();
const img = await this.getImageInfo(this.shareInfo[idx].posterImg);
this.roundRectImg(ctx, 0, 0, this.shareWidth, this.shareHeight, 10, img);
const size = 0.26 * this.shareWidth;
const widthRate = this.shareInfo[idx].leftAbsolute;
const heightRate = this.shareInfo[idx].topAbsolute;
this.roundRectImg(
ctx,
widthRate * this.resultRate,
heightRate * this.resultRate,
size,
size,
size / 2,
this.shareCodeImg
);
ctx.draw();
},
roundRect(ctx, x, y, width, height, radius) {
ctx.beginPath();
ctx.moveTo(x + radius, y);
ctx.arcTo(x + width, y, x + width, y + height, radius);
ctx.arcTo(x + width, y + height, x, y + height, radius);
ctx.arcTo(x, y + height, x, y, radius);
ctx.arcTo(x, y, x + width, y, radius);
ctx.closePath();
},
roundRectImg(ctx, x, y, width, height, radius, img) {
ctx.save();
this.roundRect(ctx, x, y, width, height, radius);
ctx.clip();
ctx.drawImage(img, x, y, width, height);
ctx.restore();
},
getImageInfo(path) {
return new Promise((resolve, reject) => {
uni.getImageInfo({
src: path,
success: (res) => {
resolve(res.path);
},
fail: (err) => {
reject(err);
},
});
});
},
initShareCanvasToImgs() {
const query = uni.createSelectorQuery();
for (let i = 0; i < this.shareInfo.length; i++) {
const id = "shareCanvas" + i;
query
.select("#" + id)
.node()
.exec((res) => {
const canvasNode = res[0].node;
// 将canvas内容转换为临时文件路径
uni.canvasToTempFilePath(
{
canvasId: id,
success: (res) => {
this.shareInfo[i].gestaltPosterImg = res.tempFilePath;
},
fail: (err) => {
console.log("转换失败", err);
},
},
canvasNode
);
});
}
},
async saveShareImg() {
uni.saveImageToPhotosAlbum({
filePath: this.shareInfo[this.currentIndex].gestaltPosterImg,
success: () => {
uni.hideLoading();
uni.showToast({
title: "保存成功",
});
},
fail: (err) => {
uni.hideLoading();
uni.showToast({
title: "保存失败",
icon: "none",
});
},
});
},
generatePosterImg() {
if (this.shareInfo.length === 0) {
uni.showToast({
title: "暂时还没有海报哦",
icon: "none",
});
return;
}
this.openSharePopup();
},
closeSharePopup() {
this.showExecShare = false;
setTimeout(() => {
this.setData({
wxCodeShow: false,
});
}, 300);
},
openSharePopup() {
this.wxCodeShow = true;
setTimeout(() => {
this.showExecShare = true;
}, 300);
},
},
watch: {
currentIndex(newValue, oldValue) {
if (oldValue > newValue) {
this.shareInfo.forEach((v) => (v.right -= 100));
}
if (oldValue < newValue) {
this.shareInfo.forEach((v) => (v.right += 100));
}
},
},
};
</script>
<style>
page {
background: linear-gradient(to bottom, #fff4fa, #f4f4f4);
overflow: hidden;
}
</style>
<style scoped>
.s-container {
width: 100%;
overflow: hidden;
}
.s-title {
text-align: center;
margin: 32rpx;
background: linear-gradient(to right, #f02850, #ff280f);
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.s-main {
display: flex;
width: 100%;
box-sizing: border-box;
overflow: hidden;
position: relative;
}
.s-item {
flex-shrink: 0;
position: relative;
}
.s-item > canvas {
width: 100%;
height: 100%;
position: absolute;
left: 9999px;
}
.s-item > image {
width: 100%;
height: 100%;
}
.s-item::after {
content: "";
display: block;
position: absolute;
width: 100%;
height: 100%;
top: 0;
border-radius: 20rpx;
background-color: rgba(0, 0, 0, 0.4);
}
.s-item:last-child {
margin: 0;
}
.active::after {
content: none;
}
.s-footer {
position: fixed;
bottom: 0;
width: 100%;
background-color: #fff;
height: 160rpx;
display: flex;
justify-content: center;
align-items: center;
}
.s-footer > .s-generate {
width: 92%;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
padding: 20rpx 0;
border-radius: 62rpx;
background: linear-gradient(to right, #f02850, #ff280f);
}
.s-footer > .s-generate-bad {
background: none;
background-color: #666;
}
.s-footer > .s-generate > image {
width: 42rpx;
height: 42rpx;
margin-right: 12rpx;
}
.s-current-info {
position: absolute;
right: 20rpx;
bottom: 10rpx;
font-size: 22rpx;
color: #fff;
padding: 6rpx 22rpx;
border-radius: 32rpx;
background-color: rgba(0, 0, 0, 0.6);
}
.code-popup {
position: fixed;
top: 0;
left: 0;
background: rgba(0, 0, 0, 0.7);
width: 100%;
height: 100%;
z-index: 9999;
}
.code-main {
transition: all .4s ;
margin: auto;
left: 0;
right: 0;
top: 200rpx;
position: fixed;
width: 560rpx;
height: 848rpx;
box-sizing: border-box;
}
.fade-in {
opacity: 1;
}
.fade-out {
opacity: 0;
}
.share-main {
width: 100%;
height: 260rpx;
padding-top: 28rpx;
background-color: #fff;
position: absolute;
bottom: -300rpx;
border-top-left-radius: 32rpx;
border-top-right-radius: 32rpx;
transition: all .3s;
}
.share-open {
bottom: 0;
}
.share-close {
bottom: -300rpx;
}
.share-close-btn {
position: absolute;
right: 32rpx;
width: 24px;
height: 24px;
}
.share-close-btn::before,
.share-close-btn::after {
content: "";
position: absolute;
top: 50%;
left: 50%;
width: 24px;
height: 2px;
background-color: #EEE;
transform: translate(-50%, -50%);
}
.share-close-btn::before {
transform: translate(-50%, -50%) rotate(45deg);
}
.share-close-btn::after {
transform: translate(-50%, -50%) rotate(-45deg);
}
.share-type {
height: 100%;
display: flex;
align-items: center;
justify-content: space-around;
}
.share-type > button {
padding: 0;
margin: 0;
border: none;
outline: none;
border-radius: none;
background-color: transparent;
font-size: 26rpx;
color: #ccc;
display: flex;
flex-direction: column;
align-items: center;
}
.share-type > button::after {
content: none !important;
}
.share-type image {
width: 62rpx;
height: 62rpx;
border-radius: 50%;
}
</style>