前端埋点实现

Intersection Observer API: https://developer.mozilla.org/zh-CN/docs/Web/API/Intersection_Observer_API

2023.2.20 星期一

小程序,h5,taro等埋点:属性埋点,自动/全/零埋点,区别于手动上报。

曝光埋点

IntersectionObserver 方式

1
2
3
4
5
6
7
8
9
10
11
var intersectionObserver = new IntersectionObserver(entries => {
entries.forEach(entry => {
if( entry.intersectionRatio > 0 ) {
report(entry.target)
}
})
})
var nodes = document.querySelectorAll(".item")
nodes.forEach(node => {
intersectionObserver.observe(node)
})

主要用到的API
IntersectionObserver 主要用来检测被监听的目标元素可见部分与root元素的交叉状况,比如获取相交区域的比例值,后面做曝光埋点的判断需要用到。

requestIdleCallback 方法,浏览器会在空闲时执行传入的函数。后面埋点我们使用这个方法,避免埋点影响主业务。

react 两种方式

实现的方式主要有两种:

  • 函数的方式;
  • 高阶组件的方式;
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // # 调用方式
    // ## 函数方式
    // 重新设置IntersectionObserver的配置
    exposeListener(document.querySelector('.dom3'), {
    observerOptions: {
    threshold: [0, 0.2, 1],
    },
    onExpose() {
    console.log('dom1 expose', Date.now());
    },
    });

    // ## 类的方式
    // ## useEffect()
    <ComExpose always onExpose={() => console.log('expose')} onHide={() => console.log('hide')}>
    <div className="dom dom1">dom1 always</div>
    </ComExpose>

PS: 可以用自定义useExpo。
<!– PS:
app.jsx中使用新的hooks;不用每个组件都调用。可以用高阶组件, 或者函数试:useEffect(() => {useExpo()}, [])
高阶组件,不用通过class获取目标元素。而是const ref = useRef<any>(null);

–>

Taro

taro或者小程序项目有api:Taro.createIntersectionObserver
const observer = Taro.createIntersectionObserver(this, { thresholds: [0], observeAll: true })

创建并返回一个 IntersectionObserver 对象实例。
监听文档/dom的变化以他用

refactor(runtime/shared/api): 优化获取节点的逻辑,增加其成功率

  1. 小程序和 H5 的 createIntersectionObserver 和 createSelectorQuery API 自动在一个 Taro.nextTick 后执行,减少开发者手动使用 Taro.nextTick 或 setTimeout 的需要。
  2. Taro.nextTick 增加等待执行逻辑,增加其在开发框架完成渲染后再执行的成功率

SMTC

兼容h5语法,使用原生js写法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
// useExposure。
useEffect(() => {
const observeCb = (e) => {
// const dataset = document.getElementById(res.id)?.dataset;
let target;
let dataset;
// let intersectionRatio:number;
if (process.env.TARO_ENV === 'h5') {
target = e.target;
dataset = target.dataset;
} else {
dataset = document.getElementById(e.id)?.dataset
}
if (!dataset) return;
// do sth
}

if (process.env.TARO_ENV === "weapp") {
timerRef.current = setTimeout(() => {
obRef.current = Taro.createIntersectionObserver(this as unknown as TaroGeneral.IAnyObject, {
observeAll: true
});
obRef.current
.relativeToViewport({ top: 10 })
.observe(".expo", observeCb);
}, 0)
} else {
const options = {
root: null,
// rootMargin: '0px 0px 0px 0px',
// threshold: [0, 0.3, 1],
};
const observer = new IntersectionObserver((entries/* , observer */) => {
entries.forEach(requestIdle(observeCb));
// entries.forEach(entry => {
// Each entry describes an intersection change for one observed
// target element:
// entry.boundingClientRect
// entry.intersectionRatio
// entry.intersectionRect
// entry.isIntersecting
// entry.rootBounds
// entry.target
// entry.time
// const target = entry.target;
// observeCb(entry)
// });
}, options);
console.log("[Expo]doms:", document.querySelectorAll('.expo'))
// eslint-disable-next-line semi-style
;[].slice.call(document.querySelectorAll('.expo')).forEach(el => {
observer.observe(el);
});

// window['myObserver'] = observer;
obRef.current = observer;
}
return (() => {
obRef.current?.disconnect()
timerRef.current && clearTimeout(timerRef.current);
})
// eslint-disable-next-line react-hooks/exhaustive-deps
}, deps);

滚动监听

点击埋点

自动埋点

web页面

document.addEventListener

taro 小程序

运行时注入事件监听modifyDispatchEvent

Taro小程序-生命周期

思路
​ 我们要想要实现无侵入或低侵入的监控页面声明周期函数,对于原生的微信小程序,我们可以参考网上的一个现有解决方案:小程序从手动埋点到自动埋点

​ 其实现原理主要是:通过代理微信小程序的Page方法,在用户传递进来的生命周期钩子函数外层包装一层wrapper函数,并在wrapper函数中实现统一数据上报的逻辑,然后再调用用户定义的声明周期钩子函数,这样,使用者便可以在无感知的情况下进行编码,所有的数据收集与上报操作都可以在这个wrapper函数中执行。

​ 然而,上述方案仅适用于原生微信小程序,在基于Taro开发的微信小程序项目中,由于在Taro中所有单元都是组件Component,而非Page,经过本人的反复试验,Taro在运行的过程中,并没有调用过Page方法,因此,通过代理微信原生Page方法这条路是行不通了。

​ 那么,既然在Taro中一切皆组件,我们能不能通过代理Component实现类似的逻辑呢?经过试验,这个想法是可行的,不过由于Component的生命周期钩子跟Page的生命周期钩子不一样,所以我们需要对其做一定的转化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 重写微信原生Page
* @param newPage
*/
export function overrideWxPage(newPage: any):void {
Page = newPage;
}

// 需要代理的生命周期钩子,包含Page和Component的钩子
const proxyMethods = [
"onShow",
"onHide",
"onReady",
"onLoad",
"onUnload",
"created",
"attached",
"ready",
"moved",
"detached",
];

Github: taro-track

注:由于taro是第三方框架,版本升级不可控,因此,我们只是对特定个版本,如2.1.5进行兼容,其他版本尚未做兼容处理

<!–
了解微信小程序 Taro 的自动埋点
POST: 2020-09-10 17:12:43

而在小程序中因为其和浏览器不同的架构,导致了监听页面变的更加困难,通常我们都会通过重写 Page 方法来达到对小程序原生生命周期的拦截代理,从而进行业务埋点,但是在 Taro 中这一切变得不同了。

想在小程序中进行自动的埋点,其实要做的就是在小程序指定的生命周期里做一些固定的处理,所以我们自动埋点的问题实际上是如何劫持小程序的生命周期,而要劫持小程序的生命周期,我们需要做的就是去重写options。
–>
weapp-lifecycle-hook-plugin: npm插件。

PS: 以上两篇基于taro2.x。

Taro小程序-事件

# 30-a Taro3 无埋点的探索与实践
这点在 Taro2 时期已经是实现完美适配的,但在 Taro3 之后,由于 Taro 团队对其整体架构的调整,使得之前的方式已经无法实现准确的无埋点,促使了本次探索。
<!–

GrowingIO 小程序 SDK 无埋点功能的实现有两个核心问题:

  • 如何拦截到用户事件的触发方法
  • 如何为节点生成一个唯一且稳定的标识符

如果想处理掉已定义无埋点事件失效问题,那就必须能提供一个稳定的标识符。类比与在 Taro2 上的实现,如果也能在拦截到事件触发的时候获取到用户方法名,那就可以了。也就是说只要能把以下两个问题处理掉,便能实现这个目标了。

  • 运行时 SDK 能拦截用户方法
  • 能在生产环境将用户方法名保留下来
    结语
    –>
    在 Taro3 无埋点功能的实现上,GrowingIO 小程序 SDK 从运行期和编译期同时下手,在运行期实现事件拦截,在编译期实现用户方法名的保留,以此实现较稳定的无埋点功能。具体的使用方式可见:Taro3中集成GrowingIO小程序SDK。通过这次 Taro3 无埋点的支持,GrowingIO 小程序无埋点实现也从仅运行期的操作扩展到了编译期,这也是一种新的方式,未来也可能会在这个方向上继续优化,提供更稳定的无埋点功能。相关 Babel 插件以开源,仓库可见:
    growing-babel-plugin-setname

npm install --dev babel-plugin-setname

为匿名函数设置函数名

平台

小程序零开发埋点,就是这么简单!

Taro 引入了腾讯有数的微信小程序无痕埋点能力,为 Taro 的开发者提供真·零开发的 8 大无痕埋点能力以及自定义埋点能力,包含小程序启动、显示、隐藏、页面浏览、页面离开、分享、下拉刷新、上拉触底等八大自动化埋点能力以及搜索、商品归因等定制化埋点,以及经营分析、直播分析、导购分析等能力,让你的小程序可以基于微信生态,串联全场景多触点,实现全域经营洞察。

requestIdleCallback

knowledge is no pay,reward is kindness
0%