微信小程序组件化埋点实践

文章介绍了微信小程序中如何通过slot和IntersectionObserversAPI实现点击和曝光埋点,同时提及了利用生命周期管理、缓存策略和提供全面的前端学习资源等内容。
摘要由CSDN通过智能技术生成

具体技术实现


点击埋点

由于小程序无法像REACT利用 prop.children,只能使用 slot 绑定实现点击埋点。

// components/common/trackerClick/index.js

import { appendQueue } from ‘…/…/…/utils/report/reportQueue’;

import { formatDate } from ‘…/…/…/utils/formatDate’;

Component({

/**

  • 虚拟化组件节点,防止影响 css 样式以及不增加 DOM 层级

*/

options: {

virtualHost: true

},

/**

  • 组件的属性列表

*/

properties: {

event: {

type: String,

value: ‘Click’,

},

project: {

type: String,

value: ‘’,

},

properties: {

type: Object,

value: null,

},

},

/**

  • 组件的方法列表

*/

methods: {

trackerClick() {

const { event, project, properties } = this.data;

let time = formatDate(new Date(), ‘YYYY-MM-DD hh:mm:ss.S’);

// 后端字段要求时间格式化保留两位小数

time =

time.length >= 19

? time.slice(0, 19)
${time.slice(0, 17)}0${time.slice(17, 18)};

appendQueue(‘click’, {

time,

event,

project,

properties,

});

},

},

});

曝光埋点

曝光埋点是最难实现的埋点,需要满足以下三点:

  • 元素出现在视窗内一定的比例才算一次合法的曝光

  • 元素在视窗内停留的时长达到一定的标准才算曝光

  • 统计元素曝光时长

微信官方文档 – IntersectionObservers

由于微信小程序已支持 IntersectionObservers API,因此我们采用这个 API 实现曝光埋点。

不过由于小程序不具有 DOM 结构,没法像 REACT 传递 DOM 元素实现曝光埋点,并且组件内部的使用有一些需要特别注意的点。

import IntersectionObserver from ‘…/…/…/utils/report/intersection-observer’;

import { appendQueue } from ‘…/…/…/utils/report/reportQueue’;

import { formatDate } from ‘…/…/…/utils/formatDate’;

import CONFIG from ‘…/…/…/utils/report/config’;

Component({

/**

  • 虚拟化组件节点,防止影响 css 样式以及不增加 DOM 层级

*/

options: {

virtualHost: true

},

/**

  • 组件的属性列表

*/

properties: {

/**

  • 上报的参数

*/

event: {

type: String,

value: ‘View’,

},

project: {

type: String,

value: ‘baoyanmp’,

},

properties: {

type: String,

},

/**

  • IntersectionObserver属性

*/

// 监听的对象(组件内部需要使用组件的this属性)

context: {

type: Function,

value: null,

},

// 选择器

/**

selector类似于 CSS 的选择器,但仅支持下列语法。

ID选择器:#the-id

class选择器(可以连续指定多个):.a-class.another-class

子元素选择器:.the-parent > .the-child

后代选择器:.the-ancestor .the-descendant

跨自定义组件的后代选择器:.the-ancestor >>> .the-descendant

多选择器的并集:#a-node, .some-other-nodes

*/

selector: {

type: String,

},

// 相交区域,默认为页面显示区域

relativeTo: {

type: String,

value: null,

},

// 是否同时观测多个目标节点,动态列表不会自动增加

observeAll: {

type: Boolean,

value: false,

},

// 是否只观测一次

once: {

type: Boolean,

value: CONFIG.DEFAULT_EXPOSURE_ONCE,

},

// 曝光时长,满足此时长记为曝光一次,单位ms

exposureTime: {

type: Number,

value: CONFIG.DEFAULT_EXPOSURETIME,

},

// 成功曝光后间隔时长,在此时长内不进行观测,单位ms

interval: {

type: Number,

value: CONFIG.DEFAULT_EXPOSURE_INTERVAL,

},

},

lifetimes: {

ready: function () {

if (!this.data.selector) return;

this.ob = new IntersectionObserver({

context: this.data.context ? this.data.context() : null,

selector: this.data.selector,

relativeTo: this.data.relativeTo,

observeAll: this.data.observeAll,

once: this.data.once,

interval: this.data.interval,

exposureTime: this.data.exposureTime,

onFinal: (startTime, endTime) => {

const { event, project, properties } = this.data;

let time = formatDate(new Date(), ‘YYYY-MM-DD hh:mm:ss.S’);

time =

time.length >= 19

? time.slice(0, 19)
${time.slice(0, 17)}0${time.slice(17, 18)};

appendQueue(‘exposure’, {

time,

event,

project,

properties: {

…properties,

startTime,

endTime,

},

});

},

});

this.ob.connect();

},

detached: function () {

// 在组件实例被从页面节点树移除时执行

this.ob.disconnect();

},

},

pageLifetimes: {

show: function () {

// 所在页面被展示

if (this.ob) this.ob.connect();

},

hide: function () {

// 所在页面被隐藏

if (this.ob) this.ob.disconnect();

},

},

});

import CONFIG from ‘./config’;

export default class IntersectionObserver {

constructor(options) {

this.$options = {

context: null,

selector: null,

relativeTo: null,

observeAll: false,

initialRatio: 0,

// 露出比例

threshold: CONFIG.DEFAULT_THRESHOLD,

once: CONFIG.DEFAULT_EXPOSURE_ONCE,

exposureTime: CONFIG.DEFAULT_EXPOSURETIME,

interval: CONFIG.DEFAULT_EXPOSURE_INTERVAL,

// 满足曝光后回调

onFinal: () => null,

…options,

};

this.$observer = null;

this.startTime = null;

this.isIntervaling = false;

this.stopObserving = false;

this.neverObserving = false;

}

connect() {

this.stopObserving = false;

if (this.$observer || this.isIntervaling || this.neverObserving) return;

this.$observer = this._createObserver();

}

reconnect() {

this.disconnect();

this.connect();

}

disconnect() {

this.stopObserving = true;

if (!this.$observer) return;

this.$observer.disconnect();

this.$observer = null;

// 断开连接,即停止浏览,判断是否上报

if (!this.startTime) return;

this._judgeExposureTime();

}

_createObserver() {

const opt = this.$options;

const observerOptions = {

thresholds: [opt.threshold],

observeAll: opt.observeAll,

initialRatio: opt.initialRatio,

};

// 创建监听器

const ob = opt.context

? opt.context.createIntersectionObserver(observerOptions)
wx.createIntersectionObserver(null, observerOptions);

// 相交区域设置

if (opt.relativeTo) ob.relativeTo(opt.relativeTo);

else ob.relativeToViewport();

// 开始监听

ob.observe(opt.selector, (res) => {

const { intersectionRatio } = res;

const visible = intersectionRatio >= opt.threshold;

if (visible && !this.startTime) {

this.startTime = new Date();

}

if (!visible && this.startTime) {

this._judgeExposureTime();

}

});

return ob;

}

_judgeExposureTime() {

const endTime = new Date();

const lastTime = endTime.getTime() - this.startTime.getTime();

if (lastTime < this.$options.exposureTime) {

this.startTime = null;

console.log(‘曝光时间不足’, lastTime / 1000);

return;

}

console.log(‘曝光时间足够’, lastTime / 1000);

this.$options.onFinal(this.startTime, endTime);

this.startTime = null;

if (this.$options.once) {

this.neverObserving = true;

if (this.$observer) {

this.$observer.disconnect();

this.$observer = null;

}

}

if (this.$options.interval) {

if (this.$observer) {

this.$observer.disconnect();

this.$observer = null;

}

this.isIntervaling = true;

setTimeout(() => {

this.isIntervaling = false;

if (!this.stopObserving) this.connect();

}, this.$options.interval);

}

}

}

页面曝光埋点

由于小程序页面有很多生命周期,因此我们可以借助 onShow,onHide 来实现检测页面的显示和关闭。

appendQueue

一些场景下我们没法绑定事件到 dom 上,比如小程序的分享,针对这种场景我们提供了 appendQueue 方法,把埋点加入到缓冲队列中。

缓存方案


参考网易云方案,我们也采用定时任务上报,点击类上报频率 1000ms,曝光类 3000ms,不过相较于网易云,我们采用了两种方案减少埋点数据丢失的可能性。

  1. 进入队列的同时保存到 localStorage,上报后自动删除,避免在上报间隙用户关闭小程序导致埋点数据丢失。

  2. 小程序 APP 的 onHide 生命周期立即上报,减少由于用户清理小程序缓存导致埋点数据丢失。

import { multiReport } from ‘./report’;

import CONFIG from ‘./config’;

let exposureQueue = [];

let clickQueue = [];

let isCollectingExposure = false;

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)

最后

前15.PNG

前16.PNG

由于文档内容过多,为了避免影响到大家的阅读体验,在此只以截图展示部分内容,详细完整版的JavaScript面试题文档,或更多前端资料可以点此处免费获取

着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!**

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-2oG0BcTd-1713804479721)]

[外链图片转存中…(img-qkhbicGW-1713804479722)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

[外链图片转存中…(img-ewVTlgAv-1713804479722)]

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)

[外链图片转存中…(img-gJDxmV43-1713804479722)]

最后

[外链图片转存中…(img-JDwW9fST-1713804479723)]

[外链图片转存中…(img-SVW4DZdd-1713804479723)]

由于文档内容过多,为了避免影响到大家的阅读体验,在此只以截图展示部分内容,详细完整版的JavaScript面试题文档,或更多前端资料可以点此处免费获取

  • 21
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值