100字范文,内容丰富有趣,生活中的好帮手!
100字范文 > 微信小程序webview页面使用painter生成海报

微信小程序webview页面使用painter生成海报

时间:2022-10-14 04:07:21

相关推荐

微信小程序webview页面使用painter生成海报

微信小程序webview页面使用painter生成海报

因为要在webview下生成海报,需要使用cover-view,根据接口返回数据动态更新海报内容,微信小程序生成海报组件有wxa-plugin-canvas、painter等,这里我们使用painter来生成海报图片,ui如下图:

该分享海报动态数据包括背景banner图、标题、副标题、设计师名字及二维码

分享弹窗及按钮UI及代码请见:webview页面分享弹窗

webview页面

wxml代码如下:

<cover-view class="preview-poster-container" wx:if="{{showPosterPreview}}"><cover-view class="shade" catchtouchmove="poptouchmove"></cover-view><cover-view class="content flex flex-col" style="max-height: {{maxHeight}}px"><cover-view class="close-bar"><cover-view class="close-btn" catchtap="closePreviewPop"><cover-image class="close-icon" src="/icons/cover-view/close-white.png"></cover-image></cover-view></cover-view><cover-view class="poster-box flex-1" style="overflow-y: scroll;height: {{imgHeight}}px"><cover-image wx:if="{{tempFilePath}}" src="{{tempFilePath}}" class="poster" mode="widthFix"></cover-image></cover-view>><cover-view class="save-btn" catchtap="savePoster" >保存图片</cover-view></cover-view></cover-view><painter palette="{{posterData}}" use2D="{{true}}" customStyle='position: absolute; left: -9999rpx;' bind:imgOK="onImgOK" bind:imgErr="onImgErr" /><!--获取文字高度使用 此处注意在真机调试下获取该canvas会报错--><canvas type="2d" id="heightCanvas" style="position: absolute; left: -9999rpx;"></canvas>

js代码如下

// pages/details/introduction/introduction.jsimport sharePosterObj from '../../../components/common/share/page-poster-data/introduction';const app = getApp();Page({/*** 页面的初始数据*/data: {/*分享相关参数 */showShareSheet: false, //是否显示分享弹窗shareData: null, //默认为nullshowPosterPreview: false, //是否展示海报预览弹窗posterData: null, //生成海报数据树tempFilePath: '',maxHeight: 525, // 海报预览的最大高度imgHeight: '', //海报的高度},/*** 生命周期函数--监听页面加载*/onLoad: function (options) {let self = this;wx.showLoading({title: '加载中',mask: true,});//获取动态数据,根据实际情况调整self.getBaseRequestData();app.getSystemInfo(function (systemMsg) {self.data.screenWidth = systemMsg.screenWidth;// 海报预览盒子最大高度 根据实际情况调整const maxHeight = Math.floor(systemMsg.availableHeight - (68 * systemMsg.screenWidth) / 750);self.setData({maxHeight,});});},// 请求接口、获取动态数据getBaseRequestData() {const self = this;baseUtil.baseConfigData().then(data => {self.data.shareData = self.data.shareData || {showImg: '',title: '',subordinateTitle: '',qrCodeImg: '',posterDescribe: '',};// 在这里对shareData 重新赋值并设置setData// ... 代码省略 根据具体情况设置// 给painter组件设置参数self.setPosterData();});},/********分享相关开始********/// 点击分享按钮shareAction() {this.setData({showShareSheet: true,});},poptouchmove: function () {return false;},//点击取消按钮quickAction() {this.data.showShareSheet = false;this.setData({showShareSheet: false,});},/********生成海报相关开始********///获取ctxgetTextHeightCtx() {const self = this;return new Promise(resolve => {const query = wx.createSelectorQuery().in(self);query.select('#heightCanvas').fields({ node: true, size: true }).exec(res => {const canvasNode = res[0].node;const ctx = canvasNode.getContext('2d');resolve(ctx);});});},async setPosterData() {const self = this;if (!self.textHeightCtx) {const textHeightCtx = await self.getTextHeightCtx();self.textHeightCtx = textHeightCtx;}sharePosterObj.createPosterData(self.textHeightCtx, self.data.shareData).then(res => {self.setData({posterData: res,});}).catch(error => {wx.hideLoading();wx.showToast({title: (error && error.errMsg) || '生成失败,请重试',icon: 'none',});});},// 保存图片到本地savePoster() {this.isWritePhotosAlbum();},//判断是否授权(访问相册)isWritePhotosAlbum: function () {let self = this;//判断是否授权wx.getSetting({success: function (res) {let writePhotosAlbum = res.authSetting['scope.writePhotosAlbum'];if (writePhotosAlbum) {//已授权 //trueself.saveImageToPhoto();} else if (writePhotosAlbum != undefined && writePhotosAlbum != true) {//拒绝授权了 //falseself.data.savePhotoIng = false;wx.showModal({title: '',content: '您还未授权保存图片到相册,请确认授权',showCancel: true,cancelText: '取消',confirmText: '确认',success: function (e) {//点了查看规则if (e.confirm) {//针对用户保存图片的时候可能会拒绝授权,再次点击时需要调起授权窗口self.openSetting();} else {//取消}},});} else {//第一次授权 //undefinedself.saveImageToPhoto();}},});},//授权操作(访问相册)openSetting: function () {let self = this;//调起授权弹窗wx.openSetting({success(res) {//同意授权if (res.authSetting['scope.writePhotosAlbum']) {self.saveImageToPhoto();}},});},toPx(rpx, int) {if (int) {return parseInt(rpx * this.factor * this.pixelRatio);}return rpx * this.factor * this.pixelRatio;},toRpx(px, int) {if (int) {return parseInt(px / this.factor);}return px / this.factor;},//点击生成海报按钮createPosterAction() {if (!this.data.tempFilePath) {this.setData({clickCreatePosterBtn: true,showPosterPreview: false,showShareSheet: false,});if (!this.data.shareData) {// this.data.isCreating = false;// this.data.clickedCreateBtn = false;wx.showToast({title: '分享数据不存在',icon: 'none',});return;}wx.showLoading({title: '生成中...',mask: true,});if (!this.data.posterData) {this.setPosterData();}return;}this.setData({showPosterPreview: true,clickCreatePosterBtn: false,showShareSheet: false,});},onImgOK(e) {console.log('onImgOK', e);const self = this;self.data.tempFilePath = e.detail.path;//获取海报图高度wx.getImageInfo({src: e.detail.path,success(res) {const { width, height } = res;let scaleHeight = Math.floor((self.data.screenWidth * 0.67 * height) / width);if (scaleHeight > self.data.maxHeight) {scaleHeight = self.data.maxHeight;}self.setData({imgHeight: scaleHeight,});},fail() {},});self.setData({tempFilePath: self.data.tempFilePath,});wx.hideLoading();if (self.data.clickCreatePosterBtn) {self.setData({showPosterPreview: true,});self.data.clickCreatePosterBtn = false;}},onImgErr(err) {console.log('onImgErr', err);wx.hideLoading();},closePreviewPop() {this.setData({showPosterPreview: false,});},//保存图片到相册saveImageToPhoto: function () {let self = this;const tempFilePath = self.data.tempFilePath;wx.saveImageToPhotosAlbum({filePath: tempFilePath,success(res) {wx.showToast({title: '保存成功',icon: 'success',});self.setData({showPosterPreview: false,});},fail: function (res) {if (res.errMsg === 'saveImageToPhotosAlbum:fail auth deny') {toast('您拒绝了授权无法保存图片 ');} else {if (res.errMsg === 'saveImageToPhotosAlbum:fail:auth canceled' ||res.errMsg === 'saveImageToPhotosAlbum:fail cancel') {console.log('用户取消');} else {wx.showToast({title: '保存失败,请重试',icon: 'none',});}}},complete: function (res) {console.log('保存图片到本地 结束', res);},});},});

less样式如下:

其中引用的两个样式可以参考webview页面分享弹窗

@import '../../../components/common/share/share-sheet/share-sheet.wxss';@import '../../../components/common/share/poster-share/poster-share.wxss';.bottom-fixed-bar {position: fixed;box-sizing: content-box;bottom: 0;left: 0;width: 100%;padding-bottom: env(safe-area-inset-bottom);z-index: 100;background-color: #fff;.icon {margin-right: 16rpx;width: 32rpx;height: 32rpx;}.bar-info {width: 100%;height: 112rpx;padding: 0 48rpx;background-color: #fff;}.share-btn {position: relative;margin-right: 24rpx;width: 214rpx;height: 80rpx;border-radius: 40rpx;background: #ffffff;border: 2rpx solid #222222;text-align: center;line-height: 79rpx;font-size: 30rpx;}.share-main-btn {position: absolute;top: 0;left: 0;right: 0;bottom: 0;z-index: 2;width: 214rpx;height: 80rpx;line-height: 79rpx;color: rgba(0, 0, 0, 0);&::after {display: none;}}.collect-btn {display: flex;align-items: center;justify-content: center;text-align: center;height: 80rpx;line-height: 79rpx;border-radius: 40rpx;background: #0c0c0c;color: #fff;font-size: 30rpx;}}

生成painter json数据树的js “page-poster-data/introduction“:

//作品详情页面的postr data 数据import shareUtils from '../share-utils';const app = getApp();const createPosterData = (ctx, shareData) => {return new Promise(async (resolve, reject) => {const userInfo = app.globalData.userInfo || wx.getStorageSync('userInfo');const { showImg, title, qrCodeImg, subordinateTitle, posterDescribe } = shareData;const titleView = {// id: 'title',type: 'text',text: title,css: {left: '48rpx',width: '450rpx',fontWeight: '500',color: '#fff',fontSize: '50rpx',lineHeight: '72rpx',scalable: true,maxLines: 2,},};const subordinateTitleView = {type: 'text',text: subordinateTitle,css: {left: '48rpx',bottom: '334rpx',width: '450rpx',color: '#fff',fontSize: '26rpx',lineHeight: '40rpx',scalable: true,maxLines: 2,},};let scaleImgHeight = 0;try {const imgPositionData = await shareUtils.getImgPositionData(showImg);if (imgPositionData) {scaleImgHeight = imgPositionData.scaleHeight;}} catch (error) {reject(error);return;}let titleH = 0;const titlePositionData = await shareUtils.getTextPositionData({ ctx, view: titleView });if (titlePositionData) {titleH = shareUtils.toRpx(titlePositionData.height);}let subordinateTitleH = 0;const subordinateTitlePositionData = await shareUtils.getTextPositionData({ctx,view: subordinateTitleView,});if (subordinateTitlePositionData) {subordinateTitleH = shareUtils.toRpx(subordinateTitlePositionData.height);}const paddingBottom = 380; //从副标题往下的距离 rpxconst titleDistance = 40; //主标题到副标题之间的距离 rpxtitleView.css.bottom = `${paddingBottom + subordinateTitleH + titleDistance}rpx`;const posterHeight = Math.ceil(scaleImgHeight + (titleH * 2) / 3 + titleDistance + subordinateTitleH + paddingBottom);const distanceTop = Math.ceil(scaleImgHeight * 0.38); // 渐变色距离顶部的距离 rpx//渐变背景的高度const linearGradientHeight = Math.ceil(distanceTop + (titleH * 2) / 3 + titleDistance + subordinateTitleH + paddingBottom);const startLinear =Math.ceil((1 - distanceTop / linearGradientHeight).toFixed(2) * 100) + '%';// 宽度和高度必须 位置必须rpx才能生效,否则默认左上角 0 0 位置 渐变后面的百分数必须写const data = {width: '750rpx',height: `${posterHeight}rpx`,background: '#060419',views: [// 顶栏图片{type: 'image',url: showImg,css: {top: '0rpx',left: '0rpx',width: '750rpx',mode: 'widthFix',scalable: true,},},//渐变矩形{type: 'rect',css: {left: '0rpx',bottom: '0rpx',width: '750rpx',height: `${linearGradientHeight}rpx`,//从下向上的渐变color: `linear-gradient(180deg, rgba(6, 4, 25, 1) 0%, rgba(7,4,26,1) ${startLinear}, transparent 100%)`,scalable: true,},},//标题titleView,// 副标题subordinateTitleView,//分割线{type: 'rect',css: {left: '50rpx',bottom: '250rpx',width: '320rpx',height: '2rpx',color: 'rgba(196, 196, 196, 0.3)',},},// 个人头像{type: 'image',url: (userInfo && userInfo.headPortraitUrl) || '',css: {bottom: '142rpx',left: '50rpx',width: '42rpx',height: '42rpx',scalable: true,borderRadius: '4rpx',},},//个人昵称{type: 'text',text: (userInfo && userInfo.nickName) || '',css: {left: '110rpx',bottom: '142rpx',color: '#fff',fontSize: '28rpx',align: 'left',scalable: true,lineHeight: '42rpx',},},// 邀请您一起做客直播间{type: 'text',text: posterDescribe,css: {left: '50rpx',bottom: '78rpx',width: '440rpx',color: 'rgba(255,255,255,0.5)',fontSize: '28rpx',lineHeight: '30rpx',scalable: true,maxLines: 1,},},// 二维码{type: 'image',// url: codeImg,url: qrCodeImg,css: {bottom: `50rpx`,right: '48rpx',backgroundColor: '#fff',width: '160rpx',height: '160rpx',scalable: true,borderRadius: '160rpx',},},],};resolve(data);});};module.exports.createPosterData = createPosterData;

引用计算文本高度的组件 js “share-utils“ :

//获取text类型的行高 行数等const screenWidth = wx.getSystemInfoSync().screenWidth;/*text:文案fontSize:字体大小 只支持rpx 数值类型maxLines: 文案最大行数 可不传 数值类型width: 文案宽度 可不传 只支持rpx 数值类型padding: 文案padding值为数组格式 只支持rpx值 可不传*/const getTextPositionData = ({ ctx, view }) => {let paddings = doPaddings(view.css.padding);const textArray = String(view.text).split('\n');// 处理多个连续的'\n'for (let i = 0; i < textArray.length; ++i) {if (textArray[i] === '') {textArray[i] = ' ';}}if (!view.css.fontSize) {view.css.fontSize = '20rpx';}const fontWeight = view.css.fontWeight || '400';const textStyle = view.css.textStyle || 'normal';ctx.font = `${textStyle} ${fontWeight} ${toPx(view.css.fontSize)}px "${view.css.fontFamily || 'sans-serif'}"`;// 计算行数let lines = 0;// let totalWidth = 0const linesArray = [];for (let i = 0; i < textArray.length; ++i) {const textLength = ctx.measureText(textArray[i]).width;const minWidth = toPx(view.css.fontSize) + paddings[1] + paddings[3];let partWidth = view.css.width? toPx(view.css.width) - paddings[1] - paddings[3]: textLength;if (partWidth < minWidth) {partWidth = minWidth;}const calLines = Math.ceil(textLength / partWidth);// 取最长的作为 width// totalWidth = partWidth > totalWidth ? partWidth : totalWidth;lines += calLines;linesArray[i] = calLines;}lines = view.css.maxLines < lines ? view.css.maxLines : lines;const lineHeight = view.css.lineHeight ? toPx(view.css.lineHeight) : toPx(view.css.fontSize);const height = lineHeight * lines;return {lines, //行数height, //高度};};//计算rpx px转换用const toPx = (rpx, int, factor = screenWidth / 750, pixelRatio = 1) => {rpx = rpx.replace('rpx', '');if (int) {return parseInt(rpx * factor * pixelRatio);}return rpx * factor * pixelRatio;};const toRpx = (px, int, factor = screenWidth / 750) => {if (int) {return parseInt(px / factor);}return px / factor;};const doPaddings = padding => {let pd = [0, 0, 0, 0];if (padding) {const pdg = padding.split(/\s+/);if (pdg.length === 1) {const x = toPx(pdg[0]);pd = [x, x, x, x];}if (pdg.length === 2) {const x = toPx(pdg[0]);const y = toPx(pdg[1]);pd = [x, y, x, y];}if (pdg.length === 3) {const x = toPx(pdg[0]);const y = toPx(pdg[1]);const z = toPx(pdg[2]);pd = [x, y, z, y];}if (pdg.length === 4) {const x = toPx(pdg[0]);const y = toPx(pdg[1]);const z = toPx(pdg[2]);const a = toPx(pdg[3]);pd = [x, y, z, a];}}return pd;};// 获取图片宽高以及750像素下的高度export const getImgPositionData = img => {return new Promise((resolve, reject) => {if (img) {wx.getImageInfo({src: img,success(res) {const { width, height } = res;const scaleHeight = Math.ceil((750 * height) / width);resolve({width,height,scaleHeight,});},fail() {reject({errMsg: '获取图片信息失败, 请重试',});},});} else {// wx.hideLoading();reject({errMsg: '图片不存在',});}});};export default {getTextPositionData,getImgPositionData,toPx,toRpx,};

注意:

提示:以下均为真机测试,实际情况可能会随着时间改变,请注意自测

painter 高度和宽度必须给定

painter 位置必须添加单位,否则默认左上角0 0,例如: top值不能直接写0,需要写0rpx

painter 渐变属性名为color 不支持to bottom格式

painter cnpm方式安装的版本非最新版本,demo里的版本为最新

painter git提交代码时painter目录会被忽略,需要更改忽略文件配置或者直接上传该目录

手机预览及本地预览可以正确通过createSelectorQuery获取canvas但是真机调试会报错,上线后也正常

webview下个别时候setData不生效,具体原因未知

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。