[date: 2018-03-10 14:00] [visits: 219]

小程序分享功能开发

接触小程序不久,在第一次开发分享功能的过程中遇到了些许波折,此文介绍在小程序中,如何应对不同业务场景下的分享功能开发需求。

功能介绍

小程序转发分享功能,跟开发相关的内容并不多,主要通过onShareAppMessage定制页面分享内容,但有两个重要约束:

这两个约束,对实际业务的开发带来了不小的影响,如果需要用户在一个操作内保存数据并分享,因为上述两个限制,该功能就能很难实现,因为保存,涉及网络请求,必然是异步的。

异步指定分享内容的解决方案

这里提供两种解决方案,都是曲线救国,多少有些瑕疵。

方案一

将用户行为拆分为两个步骤,先保存数据,成功后再由用户主动分享给他人。这种方案在技术上相比第二种更为安全,但在某些业务场景下,用户体验并不完美,比如“给好友发送xxx”这种场景模式。

方案二

在用户分享成功后再保存数据,采用这个方案需要结合具体业务思考,标准是:“数据如果没有保存成功,会造成多大的影响与后果?是不是可接受的?”。

常规POST/CREATE操作,一般数据ID都是后端负责生成,操作成功后接口返回ID,但为了面对目前这种情况,在接口设计上需要做出部分调整:

其他的就不过多废话了,一切尽在代码中:

Page({
    data: {
        id: null
    },

    onLoad: function () {
        return this.fetchId();
    },

    onShow: function () {
        if (!this.loading && !this.data.id) {
            return this.fetchId();
        }
    },

    onShareAppMessage: function () {
        if (!this.data.id) {
            // todo 返回默认分享信息,比如小程序首页
        }

        return {
            title: 'xxx',
            path: 'xxx?id=' + this.data.id,
            success: function (res) {
                if (this.data.savedId === this.data.id) {
                    return;
                }

                this.saveData().then(() => {
                    this.setData({
                        savedId: this.data.id
                    });
                    // todo 如果跳转到其他页面,删除this.data.id
                });
            }
        };
    },

    fetchId: function () {
        // fetch id and set to data
    },

    saveData: function () {
        // save data to server
    }
});

canvas绘制分享图片

针对上一部分,如果分享内容是异步生成的,往往分享图片也是动态的(非固定的本地资源或者网络资源)。

针对动态分享图片,通常有两种解决方案:

依赖服务器的图片处理功能,灵活合成所需图片,缺点很明显,图片处理会消耗过多的服务器资源,影响服务器整体吞吐量

在客户端本地使用canvas绘制内容,最后通过wx.canvasToTempFilePath保存图片临时路径用于分享,这种方式的缺点:兼容性不够好,可能遇到各种BUG

在第一次开发过程中,选择了canvas绘制分享图片,主要步骤如下:

此处有一个主意事项:canvas区块应该不影响布局且不可见,但经过实践发现不能使用display: none;opacity: 0;这种方式。最终摸索出一种可行方式:使用view容器包裹canvas,然后设置view的style为height: 0; overflow: hidden;

这种方式的优点是不依赖服务器,节省了服务器性能开销,但是也有一个致命缺点:图片生成是异步的,与onShareAppMessage需要同步返回数据有冲突。

为解决该问题,采取的方案是在影响分享图片因素变化时,立即重新生成,且需要考虑失败时的替代图片。这种做法,因为频繁的使用canvas绘制与保存图片,可能会影响小程序的运行流畅度。

最后贴上针对输入框内容动态生成分享图片的核心代码:

App({
    drawLock: false,        // 避免同时对一个canvas进行操作
    shareImageCache: {},    // 尽可能优化

    drawShareImage: function (canvasId, ctx, text) {
        if (this.drawLock) {
            return;
        }
        this.drawLock = true;
        
        let that = this;
        let cacheKey = util.hash(text);
        if (this.shareImageCache[cacheKey]) {
            return Promise.resolve(this.shareImageCache[cacheKey]);
        }

        ctx.clearRect(0, 0, 500, 400);

        return new Promise(resolve, reject => {
            ctx.setFillStyle('#333333');
            ctx.setFontSize(40);
            ctx.fillText(text, 20, 60);

            ctx.draw(true, () => {
                promisify(wx.canvasToTempFilePath)({
                    canvasId: canvasId
                }).then(res => {
                    this.drawLock = false;

                    that.shareImageCache[cacheKey] = res.tempFilePath;
                    resolve(res.tempFilePath);
                }).catch(err => {
                    this.drawLock = false;

                    reject(err);
                });
            });
        });
    }
});

Page({
    data: {
        canvasId: 'share-canvas',
    },
    shareImagePath: null,

    onLoad: function () {
        this.ctx = wx.createCanvasContext(this.data.canvasId);

        this.drawShare();
    },
    
    onInput: function () {
        if (this.preInputTimer) {
            clearTimeout(this.preInputTimer);
        }
        this.preInputTimer = setTimeout(() => {
            this.drawShare();
        }, 1000);       // 这个数字最好是比一般人连续输入时间间隔大一丢丢
    },
    
    drawShare: function () {
        return app.drawShareImage(this.data.canvasId, this.ctx, e.detail.value).then(path => {
            this.shareImagePath = path;
        }).catch(err => {
            logger.error(err);
        });
    }
});

以上代码是从自己小程序代码中提炼的部分内容,把绘制行为放在app中,是为了与其他页面共享。

上述是一个极端例子,因为用户的输入实时反馈到分享图片上,如果在产品层面考虑变通,可能会有更好的思路与解决方案。