微信小程序 非webview分享给好友及分享海报

UI展示


点击分享显示分享sheet:

点击生成海报,展示海报预览图片:

组件目录结构:


代码

works文件

woks.json中引入: "share-sheet": "/components/common/share/share-sheet/share-sheet",
works.wxml中引入:<share-sheet show="{{showShareSheet}}" share-data="{{shareData}}" />
works.js中相关:

data: {// .../*分享相关参数 */showShareSheet: false, //是否显示分享弹窗shareData: null, //默认为null// shareData: {//     showImg: '', //分享图片//     title: '', //标题//     subordinateTitle: '',  //次标题// designerHeadPortrait: '', //设计师头像//          designerName: '', //设计师名字//     qrCodeImg: '', //分享二维码图片// }, //获取分享的相关数据},getWorkDetail(id) {// ...//异步请求数据设置分享数据this.setData({shareData})/ ...},// 点击分享按钮shareAction() {this.setData({showShareSheet: true,});},
该处使用的url网络请求的数据。

share-sheet文件

share-sheet组件见https://blog.csdn.net/feiyupang/article/details/125605602

poster-share文件

json文件引入:"painter":"/components/painter/painter"
wxml:

<painter palette="{{posterData}}" use2D="{{true}}"  customStyle='position: absolute; left: -9999rpx;'  bind:imgOK="onImgOK"  bind:imgErr="onImgErr" />
<!-- use2D -->
<view class="preview-poster-container" hidden="{{ !show }}" catchtouchmove="poptouchmove"><view class="shade" ></view><view class="content" style="max-height: {{maxHeight}}px"><view class="close-bar"><view class="close-btn" catchtap="closePreviewPop"><image class="close-icon" src="/icons/cover-view/close-white.png"></image></view></view><scroll-view scroll-y class="poster-box" style="height: {{imgHeight}}px"><image wx:if="{{tempFilePath}}" src="{{tempFilePath}}" class="poster" mode="widthFix" bindload="filePathLoaded" binderror="filePathError"></image></scroll-view><view class="save-btn" catchtap="savePoster" >保存图片</view></view>
</view>
<!--获取文字高度使用-->
<canvas type="2d" id="heightCanvas" style="position: absolute; left: -9999rpx;"></canvas>

js:

const app = getApp();
Component({options: {addGlobalClass: true,},/*** 组件的属性列表*/properties: {show: {type: Boolean,value: false},shareData: {type: Object,value: null,observer(val) {if (val) {this.data.isCreating = true;this.setPosterData();}},},// 是否在生成海报之前点击了生成海报按钮clickedCreateBtn: {type: Boolean,value: false,observer(val) {if (val) {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.isCreating) {this.setPosterData();}}},},},lifetimes: {attached: function () {this.attachedMethod();},},attached: function () {this.attachedMethod();},/*** 组件的初始数据*/data: {// 分享相关参数posterData: null,tempFilePath: '', //生成的海报图片临时存储路径imgHeight: 0, // 盛放图片的盒子高度maxHeight: 0, // 海报预览的最大高度isCreating: false, //是否正在生成中},/*** 组件的方法列表*/methods: {attachedMethod() {let self = this;let pages = getCurrentPages(),currentPage = pages[pages.length - 1];self.data.currentPage = currentPage;app.getSystemInfo(function (systemMsg) {// 根据实际情况调整const maxHeight = Math.floor(systemMsg.height -systemMsg.navigationBarHeight -(68 * systemMsg.screenWidth) / 750);/* 海报预览盒子中海报的最大高度 根据实际情况调整*/const maxImgHeight = Math.floor(maxHeight - ((68 + 156) * systemMsg.screenWidth) / 750);self.data.screenWidth = systemMsg.screenWidth;self.data.maxImgHeight = maxImgHeight || 0;self.setData({maxHeight,});});},//阻止滑动事件 防止page滑动poptouchmove: function () {return 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;}//作品详情页if (self.data.currentPage.route === 'pages/details/work/work') {import('../page-poster-data/work').then(({ createPosterData }) => {createPosterData(self.textHeightCtx, self.data.shareData).then(res => {self.setData({posterData: res,});}).catch(error => {wx.hideLoading();self.data.isCreating = false;self.data.clickedCreateBtn = false;wx.showToast({title: (error && error.errMsg) || '生成失败,请重试',icon: 'none',});});}).catch(error => {console.log('createPosterData 失败', error);wx.hideLoading();self.data.isCreating = false;this.data.clickedCreateBtn = false;});}},// 保存图片到本地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();}},});},//保存图片到相册saveImageToPhoto: function () {let self = this;const tempFilePath = self.data.tempFilePath;wx.saveImageToPhotosAlbum({filePath: tempFilePath,success(res) {wx.showToast({title: '保存成功',icon: 'success',});self.setData({show: 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);},});},onImgOK(e) {console.log('onImgOK', e);this.data.isCreating = false;this.data.tempFilePath = e.detail.path;this.setData({tempFilePath: this.data.tempFilePath,});this.triggerEvent('posterStatus', e.detail.path);wx.hideLoading();if (this.data.clickedCreateBtn) {this.setData({show: true,});this.data.clickedCreateBtn = false;}},onImgErr(err) {this.data.isCreating = false;console.log('onImgErr', err);wx.hideLoading();},closePreviewPop() {this.setData({show: false,});},//预览海报图片加载完成filePathLoaded(event) {const { width, height } = event.detail;//计算出预览海报的在页面中的展示高度const scaleImgHeight = Math.floor((this.data.screenWidth * 0.67 * height) / width);this.data.imgHeight =scaleImgHeight > this.data.maxImgHeight ? this.data.maxImgHeight : scaleImgHeight;this.setData({imgHeight: this.data.imgHeight,});},filePathError(event) {console.log('图片加载失败error', event);},},
});

less样式:

.preview-poster-container {position: fixed;top: 0;left: 0;right: 0;bottom: 0;z-index: 1002;.shade {position: absolute;top: 0;left: 0;right: 0;bottom: 0;background: rgba(0, 0, 0, 0.6);z-index: 1;}.content {position: absolute;top: 50%;left: 50%;transform: translate(-50%, -50%);width: 67%;max-height: 90%;z-index: 2;//关闭按钮不算在垂直居中的里面 margint-top值为关闭按钮的高度的一半margin-top: -34rpx;.close-bar {text-align: right;width: 100%;}.close-btn {display: inline-block;padding: 20rpx 30rpx;margin-right: -30rpx;}.close-icon {width: 28rpx;height: 28rpx;}.poster-box {width: 100%;overflow: auto;}.poster {width: 100%;height: auto;}.save-btn {display: block;width: 400rpx;height: 112rpx;background: rgba(0, 0, 0, 0.9);color: #fff;text-align: center;line-height: 111rpx;margin: 44rpx auto 0 auto;font-size: 32rpx;font-weight: 500;letter-spacing: 3rpx;border-radius: 99rpx;}}
}

page-poster-data文件

work.js

import shareUtils from '../share-utils';
//获取图片的高度 750rpx对应的高度
const app = getApp();
const createPosterData = (ctx, shareData) => {return new Promise(async (resolve, reject) => {const { showImg, title, qrCodeImg, subordinateTitle, designerHeadPortrait, designerName } =shareData;const titleView = {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);}titleView.css.bottom = `${334 + subordinateTitleH + 40}rpx`;//底部padding值const paddingBottom = 338; //从副标题往下的距离 rpxconst titleDistance = 40; //主标题到副标题之间的距离 rpxconst 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,},},//渐变矩形 //不认 to bottom 、to top等格式{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: '270rpx',width: '320rpx',height: '2rpx',// opacity: '0.3',// color: '#C4C4C4', //rect 下背景色用colorcolor: 'rgba(196, 196, 196, 0.3)',},},//设计师{type: 'text',text: '设计师 ',css: {left: '50rpx',bottom: '200rpx',color: '#fff',fontSize: '28rpx',align: 'left',scalable: true,lineHeight: '40rpx',},},// 设计师头像{type: 'image',url: designerHeadPortrait,css: {bottom: '142rpx',left: '50rpx',width: '42rpx',height: '42rpx',scalable: true,borderRadius: '4rpx',},},//设计师姓名{type: 'text',text: designerName,css: {left: '110rpx',bottom: '142rpx',color: '#fff',fontSize: '28rpx',align: 'left',scalable: true,lineHeight: '42rpx',},},// 优秀作品 即可点击收藏{type: 'text',text: '优秀作品,即刻点击收藏',css: {left: '50rpx',bottom: '78rpx',color: 'rgba(255,255,255,0.5)',fontSize: '28rpx',scalable: true,lineHeight: '40rpx',},},// 二维码{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;

share-utils js

公共share-utils js文件,计算文字高度、px/rpx简单换算等:

//获取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,
};

微信小程序 非webview分享给好友及生成分享海报相关推荐

  1. 微信小程序使用html2canvas,微信小程序canvas 2d 引入本地图片并生成分享图

    在小程序基础库 v2.9.0 正式开放一套全新的 Canvas 接口.该接口符合 HTML Canvas 2D 的标准,实现上采用 GPU 硬件加速,渲染性能相比于现有的 Canvas 接口有一倍左右 ...

  2. 微信小程序与webview H5交互(内嵌H5跳转原生页面)

    在开发中,使用web-view组件内嵌H5页面是非常常见的,但很多人不知道webview内嵌H5如何与原生小程序 交互.下面介绍下实现微信小程序与webview H5交互的方法. web-view功能 ...

  3. webview 个人小程序_微信小程序新增Webview它是什么东西?

    原标题:微信小程序新增Webview,它是什么东西? 今天刚刚给客户做完案子,正准备去睡觉.2017 今天刚刚给客户做完案子,正准备去睡觉.2017年11月3日 11:29看到了微信公众平台推送的小程 ...

  4. 全新实用工具证件照制作微信小程序源码下载支持多种证件生成与制作

    这是一款证件照制作的微信小程序,里面也支持直接微信公众号版本生成安装 支持多种尺寸制作 支持相册上传于直接相机拍摄 支持多种类型的证件制作如,职业证件,公务员证件,身份证等各种类型 支持电子照存档等等 ...

  5. 微信小程序:炫酷手持滚动弹幕生成小工具

    这是一款滚动弹幕生成微信小程序源码 让弹幕文字在手机屏幕上跑起来,LED弹幕 手机弹幕,告白神奇,等 支持多种模板,每一种模板都支持自定义颜色等等 字体跳动,字体表白等等 另外用户也可以支持自定义文字 ...

  6. 微信小程序:星际旅行飞船乘坐票制作生成

    这是一款非常票制作生成的一款微信小程序源码 当然啦并不是说制作了就真的能坐飞船一样 这只是一种娱乐啊,请勿当真 支持制作 情侣票,闺蜜票,单人票 安装方法: 使用微信开发者工具打开源码 然后提交上传审 ...

  7. 微信小程序在web-view页面做分享,并且把分享的参数传递给小程序

    QQ技术交流群 173683866 526474645 欢迎加入交流讨论,打广告的一律飞机票 本demo实现的功能,微信小程序给h5传参,h5给小程序传参 实现代码: <!--index.wxm ...

  8. 微信小程序中WebView中原生组件限制问题解析

    背景 在微信的文档中有一个章节说明了『 原生组件的使用限制 』有这么一段话 『由于原生组件脱离在 WebView 渲染流程外,因此在使用时有以下限制:原生组件的层级是最高的,所以页面中的其他组件无论设 ...

  9. 微信小程序通过web-view网页授权获取用户公众号OpenID

    小程序中实现网页授权获取微信公众号OpenID 1.准备工作 2.应用场景说明 3.实现步骤 结语 1.准备工作 第一步: 通过该地址https://mp.weixin.qq.com/debug/cg ...

最新文章

  1. 良好的用户体验应该...
  2. 开启 Truffle Ganache  MetaMask交互
  3. Windows环境安装Gradle6.4.1
  4. 要成为年薪五十万的数据分析师,除了技术还需要什么?
  5. 关于如何在word中写公式的方法
  6. 转:C++反汇编揭秘2 – VC编译器的运行时错误检查(RTC)
  7. Leetcode-952 Largest Component Size by Common Factor(按公因数计算最大组件大小)
  8. 3.9 Spark 键值对RDD编程
  9. 计算机组成原理试题解析pdf,计算机组成原理试题集.pdf
  10. 史上最全的工控类软件链接 快收藏备用吧
  11. fw313r手机登录_fw313r路由器设置
  12. 在Word中上下两张表格合并不了的解决办法
  13. Robot Framework Selenium UI自动化测试 --- 进阶篇
  14. 《剑指Offer》力扣刷题笔记(03-10)
  15. 技术科普:虚拟现实系统
  16. 英语日常口语对话(2)
  17. Android权限详解,权限整理
  18. Android安全启动学习(五):Android Verified Boot 2.0
  19. ag-Grid 超丰富的表格插件(1)——简易使用
  20. Prometheus -Grafana部署及部署告警

热门文章

  1. 本以为大厂无望,结果陆续收到京东/滴滴/爱奇艺offer的我迷茫了
  2. 综合Synthesis
  3. maven(视频学习)
  4. 问卷中多选题该怎么分析?
  5. 研究:骇客又在合法的苹果Xcode专案上植入恶意程式
  6. iOS指纹识别(解锁)
  7. Cisco 2960 3750交换机端口流量限速(QOS)
  8. Google Spanner原理:地球上最大的单一数据库
  9. 【金猿信创展】数新网络——国内云数智操作系统信创领导者
  10. IT项目管理 第四章 习题