1. 全局自定义分享的需求分析与方案选型
做微信小程序开发的朋友一定都遇到过这个尴尬场景:用户在小程序里看到一篇好内容,想转发给微信好友,结果随手一点右上角的菜单,默认分享卡片只有小程序首页的截图和一行系统自动生成的标题,既不美观,也带不上任何上下文信息。用户要么懒得转,要么转出去之后对方点开看到的页面和预期完全不一致,转化效果大打折扣。
这个问题的本质在于:微信小程序的原生转发能力虽然开箱即用,但默认行为只覆盖“当前页面路径+页面标题+页面截图”这三件套,而且截图是在用户点击转发按钮的瞬间由微信系统截取的,开发者根本控制不了截图上有什么。对于内容型、电商型、工具型小程序来说,这就等于把最关键的传播入口交给了运气。
所以我说的“全局自定义分享”,指的是在项目层面统一接管小程序的分享行为——不管是转发给好友、分享到朋友圈,还是用户复制链接自行传播,都能做到:
- 分享卡片图片完全自定义,不再截取当前页面乱七八糟的实时画面。
- 分享标题、描述、跳转路径全部可动态控制,不同页面、不同内容可以生成不同的分享文案。
- 兼容微信纯原生转发能力,不依赖任何第三方分享插件或SDK。
- 提供复制链接的兜底方案,覆盖用户无法直接转发或想转发到其他平台的场景。
这套方案适合谁?适合所有正在开发或已经上线的小程序项目,尤其是遇到以下情况的团队:页面数量多、分享需求分散、分享内容随业务数据变化(比如商品、文章、订单详情)、以及那些被默认分享卡片丑到忍无可忍的开发者。
方案的核心思路其实很简单:微信小程序所有的页面分享行为最终都由onShareAppMessage和onShareTimeline这两个生命周期方法控制。与其在每个页面里复制粘贴一份分享逻辑,不如在全局统一封装一份可配置的分享处理器,再通过页面配置项来决定每个页面具体分享什么内容。这样代码写一次,全项目复用,以后改分享样式只需要动一个文件。
2. 微信原生转发机制的核心细节拆解
2.1 onShareAppMessage的触发时机与参数结构
onShareAppMessage是微信小程序里控制“转发给好友”行为的核心钩子。它的触发时机是用户点击右上角菜单中的“转发”按钮,或页面内通过open-type="share"的按钮组件主动发起转发。
这个方法的返回值直接决定了分享卡片长什么样,关键字段有三个:
- title:分享标题,最长限制在30个字符以内,超出部分会被截断。
- path:分享出去的页面路径,可以带query参数,对方点击卡片后会带着这些参数进入小程序。
- imageUrl:分享卡片的封面图,支持网络图片和本地路径,但要求必须是5:4比例的图片,并且大小不能超过200KB。如果不传,微信默认截取当前页面顶部区域作为封面。
imageUrl这个字段是最容易踩坑的地方。很多人图省事直接传一个几百KB的高清大图,结果微信直接报错或者分享出去显示失败。微信对分享图片的大小和比例校验非常严格,不是“建议”而是“必须”,超了就失败,没有任何商量余地。我自己处理这类问题通常会在后端加一个图片处理接口,统一把分享图裁剪成5:4比例并压缩到200KB以内再返回给前端。
另外需要特别说明的是,onShareAppMessage只能在页面级配置,App级没有这个生命周期。所以“全局自定义分享”的真正含义并不是在一个地方设置一次就完事,而是通过统一封装的方式让每个页面都自动具备自定义分享能力。
2.2 onShareTimeline的差异与限制
onShareTimeline是分享到朋友圈的入口,从基础库2.4.3开始支持。它的触发场景是用户点击右上角菜单中的“分享到朋友圈”按钮(需要小程序已开通朋友圈分享功能)。
这个方法的返回值结构和onShareAppMessage有一些区别:
- title:同样控制标题,但朋友圈分享的标题展示样式和好友会话里不一样。
- imageUrl:朋友圈分享卡的图片,同样有5:4比例和200KB大小限制。
- query:朋友圈分享没有path字段,只有query字符串,用于对方点击后进入小程序时的参数传递。
还有个细节需要注意:onShareTimeline不能像onShareAppMessage那样通过按钮组件主动触发,只能是用户从右上角菜单发起。还有一个限制是,目前微信对朋友圈分享的小程序卡片展示做了收紧,很多类目的小程序即使开发了朋友圈分享功能,实际分享出去的卡片样式也可能与预期不符。所以朋友圈分享我更倾向于把它当成“锦上添花”的能力,核心传播路径还是放在好友转发和复制链接上。
2.3 分享菜单的隐藏与显示控制
微信右上角菜单的转发入口,可以通过wx.hideShareMenu()隐藏(比如某些不适合转发的页面,像支付结果页、登录页),也可以配置wx.showShareMenu()配合menus参数来显示或隐藏“分享到朋友圈”选项。
// 只显示转发给好友,不显示分享到朋友圈 wx.showShareMenu({ menus: ['shareAppMessage'] }) // 两个都显示 wx.showShareMenu({ menus: ['shareAppMessage', 'shareTimeline'] })这个控制在全局分享方案里有明确的用处:不是所有页面都适合开放所有分享渠道。比如带有强隐私属性的个人中心页面,只保留分享给好友就够了,朋友圈分享反而容易造成隐私泄露。这类页面级的渠道控制,同样可以通过页面的路由配置来统一管理。
3. 全局分享模块的设计与实现
3.1 思路:配置驱动,页面零重复代码
全局分享模块的设计目标非常明确:页面代码里不出现或者极少出现分享逻辑,所有分享行为由统一模块根据页面路由和上下文来决策。
具体来说,我设计了这么三层结构:
- 第一层:全局分享配置表,放在一个独立的JS文件里。配置表以页面路由路径为key,每一项配置页面分享时的标题、图片、跳转参数生成规则。
- 第二层:全局分享注入器,提供install方法。该方法遍历所有已注册页面,为每一个页面实例动态挂载onShareAppMessage和onShareTimeline方法。
- 第三层:兜底策略。页面如果不在配置表里,使用全局默认分享方案;如果页面配置了自定义处理函数,则优先执行该函数获取动态分享数据。
这样做的好处非常明显:分享逻辑与页面业务代码解耦。页面开发者只需要关心自己页面的数据怎么展示,不需要关心分享卡片长什么样;分享的样式、文案、图片调整全部集中在配置文件里,运营改文案不用动业务代码,只需要改配置文件然后重新发版。
3.2 全局分享注入器完整实现
先来看注入器部分。这个模块的核心作用是重写Page构造器,给每个页面实例注入统一的分享处理方法。
// utils/shareInjector.js const DEFAULT_SHARE_IMAGE = '/assets/images/default-share.png' // 递归获取页面栈最顶层页面实例(即当前显示页) function getCurrentPage() { const pages = getCurrentPages() return pages[pages.length - 1] || null } // 全局默认分享配置(可被页面级配置覆盖) const globalShareConfig = { title: '这是一个不错的小程序,推荐给你', imageUrl: DEFAULT_SHARE_IMAGE, // 默认跳转首页,可用函数动态生成 path: '/pages/index/index', // query 统一拼接参数,比如渠道追踪 queryMap: { shareFrom: 'global' } } // 合并 query 对象为标准字符串 function buildSharePath(path, queryMap = {}) { const queryString = Object.keys(queryMap) .map(key => `${key}=${encodeURIComponent(queryMap[key])}`) .join('&') return queryString ? `${path}?${queryString}` : path } // 页面级分享配置读取器 function getPageShareConfig(route) { const page = getCurrentPage() if (!page) return null const ownConfig = page.shareConfig || null // 页面可以提供一个 shareData 函数来动态返回分享配置 if (typeof ownConfig === 'function') { return ownConfig.call(page) } // 页面可以提供一个静态对象作为 shareConfig if (ownConfig && typeof ownConfig === 'object') { return ownConfig } return null } // 生成最终的分享配置(合并全局默认 + 页面自定义) function resolveShareOptions(config = {}) { const finalConfig = { title: config.title || globalShareConfig.title, imageUrl: config.imageUrl || globalShareConfig.imageUrl } // 容错处理:非法图片路径一律回退默认图 if (!finalConfig.imageUrl || finalConfig.imageUrl.length === 0) { finalConfig.imageUrl = globalShareConfig.imageUrl } // path 与 query 分开处理,页面配置只需要给 query,path 默认当前页面 const path = config.path || getCurrentPage().route || '/pages/index/index' const queryMap = { ...globalShareConfig.queryMap, ...(config.queryMap || {}) } finalConfig.path = buildSharePath(path, queryMap) return finalConfig } // 核心:重写 Page 构造器,注入全局分享处理方法 function installGlobalShare() { const originalPage = Page Page = function (pageConfig) { // 保存页面原始分享方法(如果页面自己定义了) const originalShareAppMessage = pageConfig.onShareAppMessage const originalShareTimeline = pageConfig.onShareTimeline // 统一注入 onShareAppMessage pageConfig.onShareAppMessage = function (res) { // 优先执行页面自己的业务分享逻辑 if (typeof originalShareAppMessage === 'function') { const customResult = originalShareAppMessage.call(this, res) if (customResult && typeof customResult === 'object') { return resolveShareOptions(customResult) } } // 其次从 shareConfig 读取 const configResult = getPageShareConfig(this.route) if (configResult) { return resolveShareOptions({ ...configResult, path: this.route }) } // 最后走全局默认 return resolveShareOptions({ path: this.route }) } // 统一注入 onShareTimeline pageConfig.onShareTimeline = function () { if (typeof originalShareTimeline === 'function') { const customResult = originalShareTimeline.call(this) if (customResult && typeof customResult === 'object') { return resolveShareOptions(customResult) } } const configResult = getPageShareConfig(this.route) if (configResult) { return resolveShareOptions({ ...configResult, path: this.route }) } return resolveShareOptions({ path: this.route }) } return originalPage(pageConfig) } } module.exports = { installGlobalShare, resolveShareOptions, buildSharePath }这个注入器的关键点在于:它不是简单粗暴地用全局方法覆盖页面方法,而是设计了一个“优先级链”:页面自己的分享方法 > 页面的shareConfig配置 > 全局默认配置。这样既保留了业务侧特殊处理的灵活性,又保证了大多数页面无需写任何分享代码就能获得统一的分享能力。
3.3 全局配置表的使用方式
模块封装好之后,在app.js入口处调用installGlobalShare(),然后在每个页面里按需配置分享参数。
// app.js const { installGlobalShare } = require('./utils/shareInjector') App({ onLaunch() { installGlobalShare() } })页面侧的使用方式有两种,根据需求复杂度自行选择。
第一种:静态配置,适合分享内容不随数据变化的页面。
// pages/article/article.js Page({ data: { article: {} }, shareConfig: { title: '这篇文章写得不错', imageUrl: 'https://cdn.example.com/images/article-share-cover.jpg', queryMap: { channel: 'article_list' } } })第二种:动态配置,适合商品详情、订单页这类分享内容和实时数据强相关的页面。
// pages/goods/detail.js Page({ data: { goods: { id: 10086, name: '山姆同款牛肉干', cover: 'https://cdn.example.com/goods/10086/cover.jpg' } }, shareConfig: function () { return { title: `推荐给你:${this.data.goods.name}`, imageUrl: this.data.goods.cover, queryMap: { goodsId: this.data.goods.id, channel: 'goods_share' } } } })这里有个容易被忽略的细节:如果页面用onLoad里的异步请求来拉取商品数据,那shareConfig函数执行时数据可能还没返回,分享标题就会变成空。解决思路是在数据请求完成之后,通过wx.setStorageSync缓存一份当前商品的分享所需字段,shareConfig函数先读缓存,如果缓存不存在就用默认值。这是我在实际项目中踩过多次的坑,后面在问题排查章节再详细展开。
3.4 分享图片的动态生成方案
配置表解决了分享文案和路径的动态问题,但分享图片本质上仍然是一张静态图。如果业务上有“把商品图+价格+促销信息拼成一张分享卡片”的需求,静态图就满足不了了。
微信官方其实提供了两种动态生成图片的路径:
- 方案一:服务端生成。后端接收到分享图片请求后,用Node.js配合sharp或者Puppeteer生成一张合成图片,返回给前端。优点是图片质量可控、模板调整灵活;缺点是需要增加服务端资源,并且首屏等待时间变长(要等网络往返)。
- 方案二:前端Canvas绘制。在小程序里用canvas把图片和文字绘到画布上,然后通过wx.canvasToTempFilePath导出临时图片,再通过wx.getFileSystemManager().saveFile持久化到本地。这个方案不依赖服务端,但canvas绘制逻辑写起来麻烦,而且不同机型的canvas实现有兼容性差异。
我实际项目里的建议是:优先走服务端生成。原因很简单,前端canvas绘制在性能差的安卓机上经常出现字体渲染错位、图片加载失败、导出图片黑屏的问题,排查起来非常头疼。服务端生成虽然多一些开发成本,但稳定性和效果可控性好太多。
如果团队实在没有服务端资源,前端canvas方案也可以凑合用,但要注意三个关键点:监听图片的onload事件完成后才开始绘制,不能图片没加载完就急着画;导出临时图片时设置destWidth为canvas实际像素宽度的2倍以上,否则图片会模糊;绘制完成导出图片的格式尽量用jpg,不要用png带透明通道,内存会大很多。
// 前端canvas绘制的核心片段,仅供无服务端资源时的备选方案 async function drawShareCard(canvasId, data) { const ctx = wx.createCanvasContext(canvasId) const { goodsName, price, coverUrl } = data // 1. 先加载商品主图,等待onload完成 const coverImage = await new Promise((resolve) => { const img = wx.createImage() img.onload = () => resolve(img) img.onerror = () => resolve(null) img.src = coverUrl }) if (!coverImage) { throw new Error('分享图片加载失败') } // 2. 绘制背景 ctx.setFillStyle('#ffffff') ctx.fillRect(0, 0, 300, 240) // 3. 绘制商品图(保持5:4比例的下部留白区域) ctx.drawImage(coverImage, 20, 20, 260, 180) // 4. 绘制文字 ctx.setFillStyle('#333333') ctx.setFontSize(16) ctx.fillText(goodsName, 20, 215, 260) ctx.setFillStyle('#e64340') ctx.setFontSize(14) ctx.fillText(`¥${price}`, 20, 235, 260) // 5. 结束绘制并导出 return new Promise((resolve, reject) => { ctx.draw(false, () => { wx.canvasToTempFilePath({ canvasId, destWidth: 600, destHeight: 480, success: resolve, fail: reject }) }) }) }4. 复制链接与分享能力的互补设计
4.1 复制链接的实现与应用场景
分享到微信好友和朋友圈是社交传播的主阵地,但并不覆盖所有场景。比如用户想把小程序内容发给QQ上的朋友、发到微博、发到论坛,这时候“复制链接”就成了刚需。
微信小程序本身没有“分享链接”的原生API,但可以通过获取页面路径和参数后,手动拼接出一个可被识别的小程序链接文本,然后调用wx.setClipboardData把这段文字写进剪贴板。
// utils/clipboard.js function copyShareLink(pageRoute, queryMap = {}, extraText = '') { // 拼接小程序页面路径,不带协议头 const queryString = Object.keys(queryMap) .map(key => `${key}=${encodeURIComponent(queryMap[key])}`) .join('&') const path = queryString ? `${pageRoute}?${queryString}` : pageRoute // 这里可以组装一段带说明的文字,让接收方知道这是一个小程序链接 const shareText = `【我正在使用的小程序】${extraText}\n点击打开小程序页面:${path}\n(如无法打开,请在微信中搜索小程序名称)` wx.setClipboardData({ data: shareText, success: () => { wx.showToast({ title: '链接已复制', icon: 'success' }) } }) } module.exports = { copyShareLink }这里需要明确一个重要事实:小程序没有像网页那样完整可访问的URL。微信内部使用的路径格式是pages/index/index?param=xxx这种纯页面路径,接收方需要通过微信的各种入口才能打开。所以复制链接后接收方打开的操作路径是:复制文本 → 打开微信 → 通过搜索或历史记录找到对应小程序 → 手动输入或粘贴路径进入。这个体验和网页链接完全没法比,但它提供了一种“非微信环境下保存和传播小程序入口信息”的兜底方案。
在一些企业微信内部使用的工具类小程序里,复制链接的文本还经常配合二维码一起使用。用户在PC端复制小程序路径文本,然后到微信里通过“添加小程序”或“搜索”入口直达,比反复翻聊天记录找小程序入口要高效得多。
4.2 复制链接与全局分享的整合
在实际项目里,我通常会把复制链接能力整合进分享模块,而不是单独做一套逻辑。具体做法是:在页面配置的shareConfig里增加一个copyText字段,页面调用统一方法时自动带上链接和说明。
// 在注入器里增加一个 copyLink 方法,挂到页面实例上 pageConfig.copyLink = function (customQuery = {}) { const configResult = getPageShareConfig(this.route) || {} const queryMap = { ...globalShareConfig.queryMap, ...(configResult.queryMap || {}), ...customQuery } copyShareLink(this.route, queryMap, configResult.copyText || '') }这样在页面上需要复制链接的位置(比如页面底部的引导按钮、客服消息自动回复里),只需要调用this.copyLink()就能把当前页面信息拼装成完整链接文本写入剪贴板。用户点击后,微信会弹出一条“内容已复制”的提示,配合页面上的说明文案“请复制链接后发给朋友”,引导链路非常顺滑。
4.3 自定义按钮触发分享
纯原生的分享入口(右上角菜单)虽然可靠,但曝光位置太深,很多用户根本不知道右上角还有转发按钮。所以在页面设计里,我通常会在内容底部、商品图片下方、文章结尾等显眼位置加一个分享引导按钮。
<button open-type="share" class="share-btn">分享给好友</button>设置open-type="share"的按钮,点击后会自动触发当前页面的onShareAppMessage,弹起微信的转发面板,和右上角菜单转发的效果完全一致。这是微信原生支持的最标准的主动分享方式。
不过请注意一点:原生share按钮的样式非常固定,只能用button组件自带的样式做调整,如果业务有“点击按钮后先弹出自定义分享面板(比如同时显示分享好友、分享朋友圈、复制链接三个选项)”的需求,原生按钮就不够用了——因为分享朋友圈入口无法通过按钮主动触发,复制链接又需要自定义逻辑。
所以更完整的设计是:先用普通view做一个自定义分享面板,点击“分享给好友”时用open-type=share按钮(或者直接调用wx.showShareMenu并模拟菜单弹起),点击“复制链接”时走copyLink方法。朋友圈分享只能通过右上角菜单进入,所以面板上可以加一行小字引导用户点击右上角分享到朋友圈。
<!-- 自定义分享面板示例结构 --> <view class="share-panel" wx:if="{{showSharePanel}}"> <view class="share-panel-title">分享给好友</view> <button open-type="share" class="share-panel-btn">微信好友</button> <button bindtap="handleCopyLink" class="share-panel-btn">复制链接</button> <view class="share-panel-tip">分享到朋友圈请点击右上角菜单</view> <view class="share-panel-mask" bindtap="closeSharePanel"></view> </view>这种“自定义面板+原生分享”的组合,既保留了微信原生转发的稳定性和后台数据归因能力,又弥补了原生入口曝光不足的问题,是内容型小程序里最常见的分享交互设计方案。
5. 常见问题与排查技巧实录
5.1 分享图片加载失败导致卡片显示空白
这是全局分享方案里出现频率最高的问题。现象是:开发工具里分享预览正常,真机上分享出去的卡片图片区域却是空白或者显示“加载失败”的占位图。
原因通常有三个:
- 图片域名为配置的非法域名。微信小程序在真机上请求网络图片时,会强制校验域名白名单。分享图片的imageUrl如果是非业务域名下的链接(比如存在七牛云、阿里云OSS,但没加到downloadFile合法域名列表里),真机会直接拦截加载。
- 图片超过200KB。这个前面说过,微信会静默失败,不报错,只在最终分享卡片上表现为空白。
- 图片使用了HTTPS以外的协议。微信要求分享图片必须是HTTPS链接,HTTP协议的图片在某些基础库版本上可以加载,但新版本已经全部收紧了。
排查技巧:真机上点开分享面板前,先用wx.downloadFile直接请求这张分享图,看success还是fail,并打印返回的statusCode和errMsg。如果downloadFile能成功,说明网络链路没问题,问题大概率出在图片大小或协议上;如果downloadFile失败,优先检查域名白名单和HTTPS证书。
5.2 分享卡片标题被截断或者出现乱码
onShareAppMessage返回的title如果超过30个字符,微信会在展示时截断,表现形式是标题末尾出现省略号。有人误以为这个限制不存在(因为开发工具里不校验),结果上线后运营反馈分享卡片标题显示不全。
另外踩过的一个坑是:title里如果带了emoji或者特殊符号(比如™、®),某些安卓机型上可能会出现乱码方块。这不是微信的bug,而是系统字体渲染的问题。稳妥的做法是分享标题内容里过滤掉所有非中英文、数字和常规标点之外的字符。
代码层面的过滤可以这么写:
function sanitizeShareTitle(title) { return title .replace(/[^\u4e00-\u9fa5a-zA-Z0-9\s·.,,。!?!?::;;"'()()《》-]/g, '') .slice(0, 30) }5.3 异步数据未加载完成导致分享内容为空
这个坑在前面提过:商品详情页的shareConfig函数执行时,商品数据还没从接口返回,导致分享标题变成“undefined”或者空字符串,分享图片用的是默认图。
典型的场景是:用户快速滑动页面到分享按钮位置,立即点击分享,这时onLoad里发起的请求还在pending状态。虽然分享面板弹起是异步的,但shareConfig函数在弹起前同步执行,所以拿不到异步数据。
解决这个问题的标准方案是:在异步数据回调里写一份“分享数据缓存”到本地,shareConfig函数优先读缓存,如果缓存存在,直接用缓存拼分享内容;如果缓存不存在,用兜底默认文案,同时显示“内容加载中,请稍后再分享”的提示类逻辑。
// pages/goods/detail.js Page({ data: { goods: {} }, onLoad(options) { this.loadGoodsDetail(options.goodsId) }, async loadGoodsDetail(goodsId) { const res = await request(`/api/goods/${goodsId}`) this.setData({ goods: res.data }) // 同步一份分享数据到缓存,键名按页面路由区分,避免互相覆盖 wx.setStorageSync(`share_${this.route}`, { title: `推荐给你:${res.data.name}`, imageUrl: res.data.cover, queryMap: { goodsId: res.data.id } }) }, shareConfig: function () { // 优先使用缓存,保证分享内容不缺失 const cached = wx.getStorageSync(`share_${this.route}`) || {} return { title: cached.title || '好物推荐', imageUrl: cached.imageUrl || DEFAULT_SHARE_IMAGE, queryMap: cached.queryMap || {} } } })这个方案的核心思路是“异步请求的结果不与分享行为强绑定”,请求完成时主动把数据准备好,分享时只读不等待。
5.4 分享到朋友圈菜单不显示
微信从基础库2.4.3开始支持朋友圈分享,但有一个前提:小程序需要在后台“分享设置”里开通朋友圈分享功能,并且类目要符合要求。开发者在代码层无论怎么做,后台开关没打开,右上角菜单里就永远看不到“分享到朋友圈”选项。
排查这个问题的步骤是:先在微信公众平台的“设置-基本设置-分享设置”里确认朋友圈分享功能已开启;再确认小程序的基础库版本不低于2.4.3;最后检查是否在其他地方误调用了wx.hideShareMenu或wx.hideShareMenu({ menus: ['shareTimeline'] })。
还有一个容易被忽视的场景:如果页面是通过web-view组件嵌入的网页,原生分享菜单的转发能力默认是关闭的,需要特别处理才能让分享菜单重新出现。web-view页面的分享是另一个独立的主题,这里不展开。
5.5 分享路径参数丢失导致打开页面不对
分享出去的path如果带query参数,接收方打开小程序时,这些参数会出现在onLoad的options里。但有个细节:如果接收方的小程序已经启动过,微信不会重新触发onLoad,而是触发onShow,然后options参数需要从this.options里取。很多团队只处理了onLoad,导致从分享卡片进入时参数“丢了”。
正确的做法是在onLoad和onShow里都读取options,或者统一走一个initPage(options)方法来处理参数,确保从分享卡片进入和从历史记录重新进入都能正确还原页面状态。
onLoad(options) { this.initPage(options) }, onShow() { // 从分享卡片二次进入时,options从this.options读取 if (this.options && this.options.goodsId) { this.initPage(this.options) } }5.6 分享卡片在部分安卓机型上显示模糊
分享图的清晰度问题主要出在图片源本身的分辨率。如果项目里用的分享图是一张600x480的小尺寸缩略图,分享到高分辨率安卓屏幕上看起来就会发糊。
微信官方没有公开分享卡片的像素规范,但从实际测试来看,建议分享图的分辨率要达到800x640以上(保持5:4比例),并且压缩到200KB以内。这个分辨率能兼顾清晰度和大小限制。如果图片源质量本身不高,宁愿用一张清晰的小图,也不要为了放大而拉伸导致锯齿更明显。
6. 方案落地后的效果与扩展思路
全局自定义分享方案上线后的最直接效果有三个:第一个是分享卡片的打开率明显上升,因为卡片标题和图片都是按业务场景定制的内容,不再是一张模糊的页面截图;第二个是运营侧的分享素材调整不需要再改业务代码,配置文件改完发版即可,发布效率大幅提升;第三个是分享路径统一带上了渠道参数,后端可以基于shareFrom、channel这类query参数做分享来源归因,判断哪个渠道的用户转化质量最高。
这套方案的扩展空间也比较大,我目前已经在几个新项目里尝试了以下方向:
第一个方向是分享数据上报。在注入器的resolveShareOptions方法里埋一个上报点,把分享时的页面路由、分享渠道(好友/朋友圈/复制链接)、query参数上报到数据平台。这样运营可以实时看到哪些页面的分享次数高、哪些渠道带来的用户留存好,为后续的内容运营和投放策略提供数据支撑。
第二个方向是分享卡片AB测试。把同一篇文章、同一个商品配置成多套分享标题和分享图,按用户分桶下发不同版本,通过分享点击率来评估哪套文案效果更优。这个方向和上面的数据上报配合起来效果很好,但要注意一个前提——微信对分享面板弹出的频率有限制,频繁测试分享会让用户体验变差,所以AB测试一般只在小流量用户群里进行。
第三个方向是把分享模块从项目中抽成独立的npm包,供多个小程序项目复用。分享模块本身不依赖具体业务数据,只提供注入器和配置读取的能力,业务数据由页面侧传入。这种设计让团队里同时维护多个小程序时,分享能力保持一致的标准,不必每个项目各写一遍。
回到开头说的问题,微信小程序的分享能力看起来是每个页面写一段配置的事,但真正做到“全局统一、动态可控、渠道可追踪”,必须在架构层面提前设计好。如果没有这套全局分享模块,业务页面越写越多,分享逻辑只会越来越散,最后想统一调整分享样式的时候,要翻遍所有页面代码,那时候改造成本就很高了。
根据我个人的经验,建议所有从零开始的新项目,在上线第一版的时候就顺手把分享注入器加上。这个模块本身量不大,半小时就能写完,但后续收益会持续很久。已经上线的老项目也不用灰心,把页面按分享场景重要程度排个序,先给最重要的内容页和商品页接上全局分享配置,其余的逐步迁移过来就好。分享是小程序传播的核心入口,值得花点心思做扎实。