
canvasdrawer 组件
Component({
properties: {
painting: {
type: Object,
value: {view: []},
observer (newVal, oldVal) {
if (!this.data.isPainting) {
if (JSON.stringify(newVal) !== JSON.stringify(oldVal)) {
if (newVal && newVal.width && newVal.height) {
this.setData({
showCanvas: true,
isPainting: true
})
this.readyPigment()
}
} else {
if (newVal && newVal.mode !== 'same') {
this.triggerEvent('getImage', {errMsg: 'canvasdrawer:samme params'})
}
}
}
}
}
},
data: {
showCanvas: false,
width: 100,
height: 100,
tempFileList: [],
isPainting: false
},
ctx: null,
cache: {},
ready () {
wx.removeStorageSync('canvasdrawer_pic_cache')
this.cache = wx.getStorageSync('canvasdrawer_pic_cache') || {}
this.ctx = wx.createCanvasContext('canvasdrawer', this)
},
methods: {
readyPigment () {
const { width, height, views } = this.data.painting
this.setData({
width,
height
})
const inter = setInterval(() => {
if (this.ctx) {
clearInterval(inter)
this.ctx.clearActions()
this.ctx.save()
this.getImagesInfo(views)
}
}, 100)
},
getImagesInfo (views) {
const imageList = []
for (let i = 0; i < views.length; i++) {
if (views[i].type === 'image') {
imageList.push(this.getImageInfo(views[i].url))
}
}
const loadTask = []
for (let i = 0; i < Math.ceil(imageList.length / 8); i++) {
loadTask.push(new Promise((resolve, reject) => {
Promise.all(imageList.splice(i * 8, 8)).then(res => {
resolve(res)
}).catch(res => {
reject(res)
})
}))
}
Promise.all(loadTask).then(res => {
let tempFileList = []
for (let i = 0; i < res.length; i++) {
tempFileList = tempFileList.concat(res[i])
}
this.setData({
tempFileList
})
this.startPainting()
})
},
startPainting () {
const { tempFileList, painting: { views } } = this.data
console.log(tempFileList)
for (let i = 0, imageIndex = 0; i < views.length; i++) {
if (views[i].type === 'image') {
this.drawImage({
...views[i],
url: tempFileList[imageIndex]
})
imageIndex++
} else if (views[i].type === 'text') {
if (!this.ctx.measureText) {
wx.showModal({
title: '提示',
content: '当前微信版本过低,无法使用 measureText 功能,请升级到最新微信版本后重试。'
})
this.triggerEvent('getImage', {errMsg: 'canvasdrawer:version too low'})
return
} else {
this.drawText(views[i])
}
} else if (views[i].type === 'rect') {
this.drawRect(views[i])
}
}
this.ctx.draw(false, () => {
wx.setStorageSync('canvasdrawer_pic_cache', this.cache)
const system = wx.getSystemInfoSync().system
if (/ios/i.test(system)) {
this.saveImageToLocal()
} else {
setTimeout(() => {
this.saveImageToLocal()
}, 800)
}
})
},
drawImage (params) {
this.ctx.save()
const { url, top = 0, left = 0, width = 0, height = 0, borderRadius = 0, deg = 0 } = params
if (deg !== 0) {
this.ctx.translate(left + width/2, top + height/2)
this.ctx.rotate(deg * Math.PI / 180)
this.ctx.drawImage(url, -width/2, -height/2, width, height)
} else {
this.ctx.drawImage(url, left, top, width, height)
}
this.ctx.restore()
},
drawText (params) {
this.ctx.save()
const {
MaxLineNumber = 2,
breakWord = false,
color = 'black',
content = '',
fontSize = 16,
top = 0,
left = 0,
lineHeight = 20,
textAlign = 'left',
width,
bolder = false,
textDecoration = 'none'
} = params
this.ctx.beginPath()
this.ctx.setTextBaseline('top')
this.ctx.setTextAlign(textAlign)
this.ctx.setFillStyle(color)
this.ctx.setFontSize(fontSize)
if (!breakWord) {
this.ctx.fillText(content, left, top)
this.drawTextLine(left, top, textDecoration, color, fontSize, content)
} else {
let fillText = ''
let fillTop = top
let lineNum = 1
for (let i = 0; i < content.length; i++) {
fillText += [content[i]]
if (this.ctx.measureText(fillText).width > width) {
if (lineNum === MaxLineNumber) {
if (i !== content.length) {
fillText = fillText.substring(0, fillText.length - 1) + '...'
this.ctx.fillText(fillText, left, fillTop)
this.drawTextLine(left, fillTop, textDecoration, color, fontSize, fillText)
fillText = ''
break
}
}
this.ctx.fillText(fillText, left, fillTop)
this.drawTextLine(left, fillTop, textDecoration, color, fontSize, fillText)
fillText = ''
fillTop += lineHeight
lineNum ++
}
}
this.ctx.fillText(fillText, left, fillTop)
this.drawTextLine(left, fillTop, textDecoration, color, fontSize, fillText)
}
this.ctx.restore()
if (bolder) {
this.drawText({
...params,
left: left + 0.3,
top: top + 0.3,
bolder: false,
textDecoration: 'none'
})
}
},
drawTextLine (left, top, textDecoration, color, fontSize, content) {
if (textDecoration === 'underline') {
this.drawRect({
background: color,
top: top + fontSize * 1.2,
left: left - 1,
width: this.ctx.measureText(content).width + 3,
height: 1
})
} else if (textDecoration === 'line-through') {
this.drawRect({
background: color,
top: top + fontSize * 0.6,
left: left - 1,
width: this.ctx.measureText(content).width + 3,
height: 1
})
}
},
drawRect (params) {
this.ctx.save()
const { background, top = 0, left = 0, width = 0, height = 0 } = params
this.ctx.setFillStyle(background)
this.ctx.fillRect(left, top, width, height)
this.ctx.restore()
},
getImageInfo (url) {
console.log(url,'========url')
return new Promise((resolve, reject) => {
if (this.cache[url]) {
resolve(this.cache[url])
} else {
const objExp = new RegExp(/^http(s)?:\/\/([\w-]+\.)+[\w-]+(\/[\w- .\/?%&=]*)?/)
console.log(url,'========url')
if (objExp.test(url)) {
wx.getImageInfo({
src: url,
complete: res => {
if (res.errMsg === 'getImageInfo:ok') {
this.cache[url] = res.path
resolve(res.path)
} else {
this.triggerEvent('getImage', {errMsg: 'canvasdrawer:download fail'})
reject(new Error('getImageInfo fail'))
}
}
})
} else {
this.cache[url] = url
resolve(url)
}
}
})
},
saveImageToLocal () {
const { width, height } = this.data
wx.canvasToTempFilePath({
x: 0,
y: 0,
width,
height,
canvasId: 'canvasdrawer',
complete: res => {
if (res.errMsg === 'canvasToTempFilePath:ok') {
this.setData({
showCanvas: false,
isPainting: false,
tempFileList: []
})
this.triggerEvent('getImage', {tempFilePath: res.tempFilePath, errMsg: 'canvasdrawer:ok'})
} else {
this.triggerEvent('getImage', {errMsg: 'canvasdrawer:fail'})
}
}
}, this)
}
}
})
wxml 文件
<canvas canvas-id="canvasdrawer" style="width:{{width}}px;height:{{height}}px;" class="board" wx:if="{{showCanvas}}"></canvas>
wxss 文件
.board {
position: fixed;
top: 10000rpx;
}
如何使用页面需要引入改组件
"usingComponents": {
"canvasdrawer": "/components/canvasdrawer/canvasdrawer"
}
<!--packageHome/pages/ShareBill/ShareBill.wxml
测试数据
appPosterConfig/detail返回报文:>>{"code":"200","message":"","data":{"appNo":"A003","posterUrls":["https://v3-cs.obs.cn-south-1.myhuaweicloud.com:443/boss/app_config/2024/04/337_1675069394379_1713149938541.jpg?AccessKeyId=4QVR19MB9WULK1AUMG9R&Expires=2068767627&Signature=8ZKjZKJJ%2BqG6NC7z8dPk0sobEtw%3D"],"descMessage":"<p>测试测试</p><p><span style=\"color: rgb(247, 89, 171); background-color: rgb(255, 255, 255); font-size: 19px;\">拓展代理商推广海报配置</span></p><p><span style=\"color: rgb(83, 29, 171); background-color: rgb(255, 255, 255); font-size: 24px;\">拓展代理说明文案配置</span></p><p><span style=\"color: rgb(83, 29, 171); background-color: rgb(255, 255, 255); font-size: 24px;\"> </span><a href=\"http://www.baidu.com\" target=\"_blank\">百度</a> </p>","agentNo":"8121749390","agentName":"联盟代理商A003-2","qrCodeUrl":"https://zxy001-agent-h5-expand.51ydmw.com/agentExpand?appNo=A003&userId=100122120&businessType=POS","defaultStatus":"1"}}
-->
<pageViewThemeColor>
<view class="mani_content">
<view class="bannerswiper">
<swiper bindchange="handleBindchange" indicator-dots="{{true}}" autoplay="{{false}}" interval="{{2000}}" duration="{{500}}" indicator-color="#bbb" indicator-active-color="#333333">
<swiper-item wx:for="{{appPosterConfig.posterUrls}}" wx:key="*this">
<image class="swiper-image" src="{{item}}" mode="scaleToFill" />
</swiper-item>
</swiper>
<view class="kzexpend" bind:tap="handleExpend" wx:if="{{appPosterConfig.descMessage}}">
<view class="label">拓展说明<text class="iconfont icon-jiantou-you" style="color: white;"></text></view>
</view>
</view>
<view class="sharebillwrap" style="background-image: url({{baseImgUrl+'sharebill_bg@2x.png'}});">
<view class="leftpersion">
<image class="icon_persion" src="{{baseImgUrl+'icon_persion@2x.png'}}" mode="widthFix" />
<view class="persioon_name">{{appPosterConfig.agentName}}</view>
</view>
<image class="qrcode" mode="widthFix" src="{{paymentCodeImgPath}}"></image>
</view>
<view class="bottom_wrap2" style="padding-bottom: {{isIPhoneX?74:40}}rpx;">
<button class="comfit" bind:tap="goMyPolicyClick" wx:if="{{tag !== 'wifi' }}">默认政策</button>
<button class="comfit" bindtap="bindClick" style="{{tag=='wifi'? 'width: 630rpx;':''}}">分享</button>
</view>
</view>
<van-toast id="van-toast" />
<canvas class="canvas-qrcode" type="2d" id='qrcode'></canvas>
<canvasdrawer painting="{{painting}}" class="canvasdrawer" bind:getImage="eventGetImage"/>
<van-popup show="{{ isShow }}" round position="bottom" custom-style="van-popup" bind:close="onClose">
<view class="share_main_content">
<view class="share_title">分享或保存</view>
<view class="share_item" bindtap="handleSaveImage">
<image class="icon" src="{{baseImgUrl+'icon_save_img@2x.png'}}" mode="widthFix"></image>
<view class="item_label">保存到相册</view>
</view>
<view class="cancel_wrap" bind:tap="bindCancel">取消</view>
</view>
</van-popup>
</pageViewThemeColor>
const app = getApp()
import Toast from '@vant/weapp/toast/toast';
import {
appPosterConfig,
showM,
showT
} from "../../../utils/api.js";
import {
encode,
decode,
isNull,
loadImgSync,
canvasToUrlSync,
formatTime,
getMessageTitleAndDateHtml
} from "../../../utils/util"
import drawQrcode from '../../../utils/weapp.qrcode.esm.js'
Page({
data: {
tag: '',
baseUrl: app.globalData.baseUrl,
baseImgUrl: app.globalData.baseImgUrl,
isIPhoneX: app.globalData.isIPhoneX,
index: 0,
isShow: false,
canvasHiddenQrcode: true,
canvasHiddenBigImg: true,
appPosterConfig: '',
paymentCodeImgPath: '',
wxBgUrlPath: '',
bigPosterImgWidth: '',
bigPosterImgHeigth: '',
wxLogoUrlPath: '',
wxLogoUrlPathWidth: '',
wxLogoUrlPathHeigth: '',
wxBottomImgbgPath: '',
wxBottomImgbgPathWidth: '',
wxBottomImgbgPathHeight: '',
painting: {},
shareImage: '',
screenWidth: 750,
screenHeight: 0,
},
onLoad(options) {
let tag = options.tag || ''
this.setData({
tag
})
this.appPosterConfig()
},
eventDraw() {
let that = this
const {
index,
appPosterConfig
} = this.data
var bgUrl = appPosterConfig.posterUrls[index];
var rate = that.data.bigPosterImgHeigth / that.data.bigPosterImgWidth
var imgHigth = that.data.screenWidth * rate
var bottomBgRate = that.data.wxBottomImgbgPathHeight / that.data.wxBottomImgbgPathWidth
var bottomImgHigth = that.data.screenWidth * bottomBgRate
var wxLogoUrlPathW = 108
var wxLogoUrlPathH = 108
var wxLogoUrlPathX = (that.data.screenWidth - wxLogoUrlPathW - 604)
var wxLogoUrlPathY = (imgHigth + bottomImgHigth / 2 - wxLogoUrlPathH / 2)
var agentFontSize = 32
var agentNameY = imgHigth + bottomImgHigth / 2 - agentFontSize / 2
var agentNameX = wxLogoUrlPathX + wxLogoUrlPathW + 16
var wxQrCodePathW = 160;
var wxQrCodePathH = 160;
var qrCodeY = imgHigth + bottomImgHigth / 2 - wxQrCodePathH / 2
var qrCodeX = that.data.screenWidth - 44 - wxQrCodePathW
that.data.screenHeight = imgHigth + bottomImgHigth;
this.setData({
painting: {
width: that.data.screenWidth,
height: that.data.screenHeight,
clear: true,
views: [{
type: 'image',
url: bgUrl,
top: 0,
left: 0,
width: that.data.screenWidth,
height: imgHigth
},
{
type: 'image',
url: this.data.wxBottomImgbgPath,
top: imgHigth,
left: 0,
width: this.data.screenWidth,
height: bottomImgHigth
},
{
type: 'image',
url: this.data.wxLogoUrlPath,
top: wxLogoUrlPathY,
left: wxLogoUrlPathX,
width: wxLogoUrlPathW,
height: wxLogoUrlPathH
},
{
type: 'text',
content: that.data.appPosterConfig.agentName,
fontSize: agentFontSize,
color: '#333333',
textAlign: 'left',
top: agentNameY,
breakWord: true,
width: 360,
lineHeight: 40,
MaxLineNumber: 3,
left: agentNameX,
bolder: false
},
{
type: 'image',
url: that.data.paymentCodeImgPath,
top: qrCodeY,
left: qrCodeX,
width: wxQrCodePathW,
height: wxQrCodePathH
},
]
}
})
},
eventSave() {
wx.saveImageToPhotosAlbum({
filePath: this.data.shareImage,
success: (res) => {
wx.hideLoading()
this.setData({
isShow: false
})
showT("保存相册成功")
console.log("已保存到相册")
},
fail: (err) => {
wx.hideLoading()
showT("保存相册失败")
console.log("保存失败" + JSON.stringify(err))
}
})
},
eventGetImage(event) {
const {
tempFilePath,
errMsg
} = event.detail
if (errMsg === 'canvasdrawer:ok') {
this.setData({
shareImage: tempFilePath
})
this.checkPhotoAlbumPerm()
}
},
goMyPolicyClick() {
wx.navigateTo({
url: '/packagePolicy/pages/mypolicy/index',
})
},
appPosterConfig() {
wx.showLoading({
title: '加载中..',
mask: true
})
let params = {
businessType: this.data.tag == 'wifi' ? 'WIFI' : 'POS',
}
appPosterConfig(params).then((res) => {
wx.hideLoading()
if (res.code == '200') {
var appPosterConfig = res.data
if (!isNull(appPosterConfig.qrCodeUrl)) {
this.saveImageQrcode()
}
this.setData({
appPosterConfig,
})
} else {
Toast(res.message)
}
}).catch(res => {
console.log(res);
wx.hideLoading()
});
},
saveImageQrcode() {
const that = this;
that.setData({
canvasHiddenQrcode: false
})
const systemInfo = wx.getSystemInfoSync()
const width = 160 * systemInfo.windowWidth / 375
const height = width;
const query = wx.createSelectorQuery()
query.select('#qrcode')
.fields({
node: true,
size: true
})
.exec((res) => {
var canvas = res[0].node
drawQrcode({
canvas: canvas,
canvasId: 'qrcode',
width: height,
height: height,
padding: 1,
background: '#ffffff',
foreground: '#000000',
correctLevel: 1,
text: that.data.appPosterConfig.qrCodeUrl,
})
wx.canvasToTempFilePath({
fileType: 'jpg',
canvas: canvas,
canvasId: 'qrcode',
success: function (res) {
console.log("canvasToTempFilePath 保存二维码成功")
console.log("saveImageQrcode res.tempFilePath=" + res.tempFilePath)
wx.hideLoading()
wx.setStorage({
key: "codeLinkImg",
data: res.tempFilePath,
});
that.setData({
canvasHiddenQrcode: true,
paymentCodeImgPath: res.tempFilePath
})
},
fail: function (res) {
console.log("saveImageQrcode res.tempFilePath 保存二维码失败" + JSON.stringify(res))
wx.hideLoading()
that.setData({
canvasHiddenQrcode: true
})
wx.setStorage({
key: "codeLinkImg",
data: "",
});
},
complete: function (res) {
console.log("complete=" + JSON.stringify(res))
}
}, that)
})
},
handleExpend(e) {
var title = "拓展说明"
var tempUrl = encodeURIComponent(this.data.appPosterConfig.descMessage)
wx.navigateTo({
url: `/pages/webview/richWebview?url=${tempUrl}&title=${title}`,
})
},
handleBindchange(e) {
const index = e.detail.current
console.log("滑动索引:" + index);
this.setData({
index,
})
},
handleSaveImage(e) {
wx.showLoading({
title: '保存中...',
mask: true
})
this.saveDownLoadImg()
},
async saveDownLoadImg() {
this.data.wxBgUrlPath = await this.downLoadBigBg()
if (isNull(this.data.wxBgUrlPath)) {
wx.hideLoading()
showT("保存失败")
return
}
this.data.wxLogoUrlPath = await this.downLoadHeadImg()
if (isNull(this.data.wxLogoUrlPath)) {
wx.hideLoading()
showT("保存失败")
return
}
this.data.wxBottomImgbgPath = await this.downLoadBottomImgbg()
if (isNull(this.data.wxBottomImgbgPath)) {
wx.hideLoading()
showT("保存失败")
return
}
this.eventDraw()
},
checkPhotoAlbumPerm() {
var that = this
wx.getSetting({
success(res) {
console.log('res', res)
if (!res.authSetting['scope.writePhotosAlbum']) {
wx.authorize({
scope: 'scope.writePhotosAlbum',
success() {
setTimeout(() => {
that.createCanvas()
}, 0)
},
fail() {
showM("温馨提示", "您还未授权保存到相册,是否现在去授权?", "确定", "取消").then(() => {
wx.openSetting({})
}).catch(() => {
})
}
})
} else {
setTimeout(() => {
that.createCanvas()
}, 0)
}
}
})
},
createCanvas() {
this.eventSave()
},
downLoadBigBg() {
var that = this
const {
index,
appPosterConfig
} = this.data
var bgUrl = appPosterConfig.posterUrls[index];
return new Promise((resolve, reject) => {
wx.getImageInfo({
src: bgUrl,
type: 'png',
success: function (res) {
that.data.bigPosterImgWidth = res.width;
that.data.bigPosterImgHeigth = res.height
console.log("downLoadBigBg 保存背景图成功 图片高=" + that.data.bigPosterImgHeigth + "图片宽=" + that.data.bigPosterImgWidth)
resolve(res.path)
},
fail: function (res) {
console.log("downLoadBigBg 保存背景图失敗")
wx.hideLoading()
showT("保存失败")
reject("")
}
})
})
},
downLoadHeadImg() {
var that = this
var logoUrl = this.data.baseImgUrl + 'icon_persion@2x.png';
return new Promise((resolve, reject) => {
wx.getImageInfo({
src: logoUrl,
success: function (res) {
that.data.wxLogoUrlPathWidth = res.width;
that.data.wxLogoUrlPathHeigth = res.height
console.log("downLoadHeadImg 保存头部成功")
resolve(res.path)
},
fail: function (res) {
console.log("downLoadHeadImg 保存头部失敗")
wx.hideLoading()
showT("保存失败")
reject("")
}
})
})
},
downLoadBottomImgbg() {
var that = this
var bottomImgbg = this.data.baseImgUrl + 'sharebill_bg@2x.png';
return new Promise((resolve, reject) => {
wx.getImageInfo({
src: bottomImgbg,
success: function (res) {
that.data.wxBottomImgbgPathWidth = res.width;
that.data.wxBottomImgbgPathHeight = res.height
console.log("downLoadBottomImgbg 保存底部背景图成功wxBottomImgbgPathWidth=" + that.data.wxBottomImgbgPathWidth + "wxBottomImgbgPathHeigth=" + that.data.wxBottomImgbgPathHeight)
resolve(res.path)
},
fail: function (res) {
console.log("downLoadBottomImgbg 保存底部背景图失敗")
wx.hideLoading()
showT("保存失败")
reject("")
}
})
})
},
bindClick(e) {
this.setData({
isShow: true
})
},
bindCancel(e) {
this.setData({
isShow: false
})
},
async getCanvasUrl(canvas) {
return await canvasToUrlSync(canvas)
},
roundRect(ctx, x, y, w, h, r, colorBackground, strokeColor) {
ctx.beginPath();
ctx.fillStyle = colorBackground;
ctx.strokeStyle = strokeColor
ctx.arc(x + r, y + r, r, Math.PI, Math.PI * 1.5);
ctx.arc(x + w - r, y + r, r, Math.PI * 1.5, Math.PI * 2);
ctx.arc(x + w - r, y + h - r, r, 0, Math.PI * 0.5);
ctx.arc(x + r, y + h - r, r, Math.PI * 0.5, Math.PI);
ctx.lineTo(x, y + r);
ctx.fill();
ctx.stroke();
ctx.closePath();
ctx.restore();
},
onReady() {
},
onShow() {
},
onHide() {
},
onUnload() {
},
onPullDownRefresh() {
},
onReachBottom() {
},
})