前言
有需求的地方就有统计,有统计的地方就需要埋点,跟有人的地方就有江湖一样......
产品的埋点文档,发现点击埋点的场景比较多。因为使用的是阿里云sls日志服务去埋点,所以通过使用手动侵入代码式的埋点。定好埋点的形式后,技术实现方法也有很多,哪种比较好呢?
稍加思考...
决定封装个埋点指令,这样使用起来会比较方便,因为指令的颗粒度比较细能够直击要害,挺适合上面所说的业务场景。
基础知识
钩子函数
一个指令定义对象可以提供如下几个钩子函数 (均为可选):
-
bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。 -
inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。 -
update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。 -
componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。 -
unbind:只调用一次,指令与元素解绑时调用。
接下来我们来看一下钩子函数的参数 (即 el、binding、vnode 和 oldVnode)。
钩子函数参数
指令钩子函数会被传入以下参数:
el:指令所绑定的元素,可以用来直接操作 DOM。binding:一个对象,包含以下 property:name:指令名,不包括v-前缀。value:指令的绑定值,例如:v-my-directive="1 + 1"中,绑定值为2。oldValue:指令绑定的前一个值,仅在update和componentUpdated钩子中可用。无论值是否改变都可用。expression:字符串形式的指令表达式。例如v-my-directive="1 + 1"中,表达式为"1 + 1"。arg:传给指令的参数,可选。例如v-my-directive:foo中,参数为"foo"。modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar中,修饰符对象为{ foo: true, bar: true }。
vnode:Vue 编译生成的虚拟节点。移步 VNode API 来了解更多详情。oldVnode:上一个虚拟节点,仅在update和componentUpdated钩子中可用。
除了 el 之外,其它参数都应该是只读的,切勿进行修改。如果需要在钩子之间共享数据,建议通过元素的 dataset 来进行。
这是一个使用了这些 property 的自定义钩子样例:
<div id="hook-arguments-example" v-demo:foo.a.b="message"></div>
Vue.directive('demo', {
bind: function (el, binding, vnode) {
var s = JSON.stringify
el.innerHTML =
'name: ' + s(binding.name) + '<br>' +
'value: ' + s(binding.value) + '<br>' +
'expression: ' + s(binding.expression) + '<br>' +
'argument: ' + s(binding.arg) + '<br>' +
'modifiers: ' + s(binding.modifiers) + '<br>' +
'vnode keys: ' + Object.keys(vnode).join(', ')
}
})
new Vue({
el: '#hook-arguments-example',
data: {
message: 'hello!'
}
})
埋点指令
v-click点击埋点
用户点击事件时上报埋点,并且支持自定义属性等信息
import Vue from 'vue'
// 统计实例
import census from "./census"
/**
* @description 处理json字符转换为上报属性
* @param {string} ele自定义埋点属性
* @return {Object} 转换处理后的属性对象
*/
function jsonAttrsToObj(jsonAttrs: string) {
let params = {}
try {
if (params) {
params = JSON.parse(jsonAttrs)
}
} catch (error) {
params = {}
}
return params
}
// 自定义点击埋点指令
export default (Vue: any) => {
Vue.directive('track', {
inserted: (el: Element, binding: any) => {
const {
arg,
value
} = binding
// 点击埋点
if (arg === 'click') {
el.addEventListener('click', () => {
let params = jsonAttrsToObj(el.getAttribute('data-track'))
census.trackEvent(value, params || {})
})
}
}
})
}
v-exposure曝光埋点
展现埋点不区分是否在可视区只要组件容器渲染就上报的埋点,支持附近属性上报
import Vue from 'vue'
// 统计实例
import census from "./census"
/**
* @description 处理json字符转换为上报属性
* @param {string} ele自定义埋点属性
* @return {Object} 转换处理后的属性对象
*/
function jsonAttrsToObj(jsonAttrs: string) {
let params = {}
try {
if (params) {
params = JSON.parse(jsonAttrs)
}
} catch (error) {
params = {}
}
return params
}
// 自定义点击埋点指令
export default (Vue: any) => {
Vue.directive('track', {
inserted: (el: Element, binding: any) => {
const {
arg,
value
} = binding
// 点击埋点
if (arg === 'click') {
el.addEventListener('click', () => {
let params = jsonAttrsToObj(el.getAttribute('data-track'))
census.trackEvent(value, params || {})
})
// 展示埋点
//展现埋点
} else if (arg === 'exposure') {
let params = jsonAttrsToObj(el.getAttribute('data-track'))
census.trackEvent(value, params || {})
}
}
})
}
v-appear滚动到可视区展现埋点
v-appear是元素只有在可视区内才会上报的埋点,主要为基于 IntersectionObserver实现,为了兼容性,需要引入 intersection-observer
intersection-observer 安装
yarn add intersection-observer
代码实现
import Vue from 'vue'
require('intersection-observer')
// 统计实例
import census from "./census"
/**
* @description 处理json字符转换为上报属性
* @param {string} ele自定义埋点属性
* @return {Object} 转换处理后的属性对象
*/
function jsonAttrsToObj(jsonAttrs: string) {
let params = {}
try {
if (params) {
params = JSON.parse(jsonAttrs)
}
} catch (error) {
params = {}
}
return params
}
// 自定义点击埋点指令
export default (Vue: any) => {
Vue.directive('track', {
inserted: (el: Element, binding: any) => {
const {
arg,
value
} = binding
// 点击埋点
if (arg === 'click') {
el.addEventListener('click', () => {
let params = jsonAttrsToObj(el.getAttribute('data-track'))
census.trackEvent(value, params || {})
})
// 展示埋点
} else if (arg === 'exposure') {
let params = jsonAttrsToObj(el.getAttribute('data-track'))
census.trackEvent(value, params || {})
}
}
})
// 自定义曝光指令: https://www.baidu.com/link?url=PRt_n4u-5bMJKml_BNsClKASYmnZmnmNlbvGjcDhsBvAoWqZZQIWrx_GJ0CGXEcjw3v6nhoZPLgR2PluQJotyTrCmJH_eqNkvLpLDX6z7RW&wd=&eqid=a0bc371d00060d1000000002619ca56f
const options: any = {
root: null, //默认浏览器视窗
threshold: 0.3 //元素完全出现在浏览器视窗内才执行callback函数。
}
// let appearList = [] //记录已经上报过的埋点信息
const callback = (entries: any, observer: any) => {
entries.forEach((entry: any) => {
let eventAttrs = jsonAttrsToObj(entry.target.getAttribute('data-appear'))
// 返回一个布尔值, 如果根与目标元素相交(即从不可视状态变为可视状态),则返回 true。如果返回 false,变换是从可视状态到不可视状态。
if (entry.isIntersecting) {
const {
__eventName = 'fin_default_appear'
} = entry.target
census.trackEvent(__eventName, eventAttrs)
// 上报后清除监听
// appearList.push(__eventName)
observer.unobserve(entry.target)
}
})
}
const observer = new IntersectionObserver(callback, options)
const addListenner = (ele: Element, binding: {
value: any
}) => {
// if (appearList.indexOf(binding.value) !== -1) return
// 缓存曝光事件的名称
(ele as any).__eventName = binding.value
observer.observe(ele)
}
const removeListener = (ele: any) => {
observer.unobserve(ele)
}
//自定义曝光指令
Vue.directive('appear', {
bind: addListenner,
unbind: removeListener
})
}
使用
v-click使用
//引入
import {
vtrack
} from 'track.ts'
// 增加通用指令
Vue.use(vtrack)
模板中使用
<div
:src ="url"
v-click="`xxx_click_event`"
:data-track="`${JSON.stringify({
...trackAttr})}`">
</div>
v-exposure使用
模板中使用
<div
:src ="url"
v-exposure="`xxx_click_event`"
:data-track= "`${JSON.stringify({ ...trackAttr})}`">
</div>
v-appear使用
模板中使用
<div
:src ="url"
v-appear="`xxx_click_event`"
:data-exposure = "`${JSON.stringify({ ...trackAttr})}`">
</div>