@rk911/tracker-sdk 1.6.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/core/compat.js +344 -0
- package/core/config.js +132 -0
- package/core/queue.js +461 -0
- package/core/session.js +128 -0
- package/core/tracker.js +287 -0
- package/customize/index.js +92 -0
- package/customize/modules/common.js +43 -0
- package/customize/modules/store-manager.js +56 -0
- package/index.js +403 -0
- package/package.json +21 -0
- package/platforms/app.js +36 -0
- package/platforms/weapp.js +218 -0
- package/platforms/web.js +483 -0
- package/plugins/api-watch.js +240 -0
- package/plugins/app-lifecycle.js +90 -0
- package/plugins/click.js +557 -0
- package/plugins/exposure.js +376 -0
- package/plugins/js-error.js +232 -0
- package/plugins/page-stay.js +44 -0
- package/plugins/page-view.js +172 -0
- package/plugins/performance.js +157 -0
- package/plugins/scroll-depth.js +256 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
export class PageViewPlugin {
|
|
2
|
+
constructor() {
|
|
3
|
+
this.name = 'page-view'
|
|
4
|
+
this.tracker = null
|
|
5
|
+
this._lastPage = ''
|
|
6
|
+
this._pendingTimer = null
|
|
7
|
+
this._lastFiredRoute = ''
|
|
8
|
+
this._lastFiredTime = 0
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
install(tracker) {
|
|
12
|
+
this.tracker = tracker
|
|
13
|
+
var adapter = tracker.getAdapter()
|
|
14
|
+
var platform = adapter.getPlatformName()
|
|
15
|
+
var self = this
|
|
16
|
+
|
|
17
|
+
if (platform === 'web' || platform === 'app') {
|
|
18
|
+
adapter.onRouteChange(function () {
|
|
19
|
+
// 取消上一次未完成的检测(防止 pushState+hashchange 双触发)
|
|
20
|
+
if (self._pendingTimer) {
|
|
21
|
+
clearTimeout(self._pendingTimer)
|
|
22
|
+
self._pendingTimer = null
|
|
23
|
+
}
|
|
24
|
+
self._waitForNewRoute(function () {
|
|
25
|
+
self._reportPageView()
|
|
26
|
+
})
|
|
27
|
+
})
|
|
28
|
+
// 首次 PV
|
|
29
|
+
self._waitForRoute(function () {
|
|
30
|
+
self._reportPageView()
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
// 会话过期重建时补报当前页 page_view(保证每个 session 都有入口页)
|
|
34
|
+
tracker.getSession().onRenew(function () {
|
|
35
|
+
var currentPage = adapter.getCurrentPagePath()
|
|
36
|
+
if (currentPage && currentPage !== '/') {
|
|
37
|
+
self._lastPage = ''
|
|
38
|
+
self._reportPageView()
|
|
39
|
+
}
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// 首次加载:等路径有效即可
|
|
45
|
+
_waitForRoute(callback) {
|
|
46
|
+
var adapter = this.tracker.getAdapter()
|
|
47
|
+
var attempts = 0
|
|
48
|
+
var maxAttempts = 10
|
|
49
|
+
|
|
50
|
+
function check() {
|
|
51
|
+
var path = adapter.getCurrentPagePath()
|
|
52
|
+
if (path && path !== '/') {
|
|
53
|
+
callback()
|
|
54
|
+
} else if (attempts < maxAttempts) {
|
|
55
|
+
attempts++
|
|
56
|
+
setTimeout(check, 20)
|
|
57
|
+
} else {
|
|
58
|
+
callback()
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
check()
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// 后续导航:等路径真正变化后再上报 page_view
|
|
65
|
+
_waitForNewRoute(callback) {
|
|
66
|
+
var adapter = this.tracker.getAdapter()
|
|
67
|
+
var self = this
|
|
68
|
+
var attempts = 0
|
|
69
|
+
var maxAttempts = 25
|
|
70
|
+
|
|
71
|
+
function check() {
|
|
72
|
+
var path = adapter.getCurrentPagePath()
|
|
73
|
+
if (path && path !== '/' && path !== self._lastPage) {
|
|
74
|
+
self._pendingTimer = null
|
|
75
|
+
callback()
|
|
76
|
+
} else if (attempts < maxAttempts) {
|
|
77
|
+
attempts++
|
|
78
|
+
self._pendingTimer = setTimeout(check, 20)
|
|
79
|
+
} else {
|
|
80
|
+
self._pendingTimer = null
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
check()
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
_reportPageView() {
|
|
87
|
+
var adapter = this.tracker.getAdapter()
|
|
88
|
+
var currentPage = adapter.getCurrentPagePath()
|
|
89
|
+
|
|
90
|
+
// 去重:同一个页面不重复上报
|
|
91
|
+
if (currentPage === this._lastPage) return
|
|
92
|
+
|
|
93
|
+
// 防抖:同路由 500ms 内不重复上报(防框架路由重入导致的竞态)
|
|
94
|
+
var now = Date.now()
|
|
95
|
+
if (this._lastFiredRoute === currentPage && now - this._lastFiredTime < 500) return
|
|
96
|
+
|
|
97
|
+
this._lastPage = currentPage
|
|
98
|
+
this._lastFiredRoute = currentPage
|
|
99
|
+
this._lastFiredTime = now
|
|
100
|
+
this.tracker.updatePageRoute(currentPage)
|
|
101
|
+
|
|
102
|
+
var self = this
|
|
103
|
+
// 代次令牌:每次进入生成新代次,旧的 title 重试链检测到代次变化即中止
|
|
104
|
+
// 防止导航交叠时,未完成的重试链把过期路由的 page_view 发出去
|
|
105
|
+
var myGen = (this._pvGen = (this._pvGen || 0) + 1)
|
|
106
|
+
var titleAttempts = 0
|
|
107
|
+
var prevTitle = ''
|
|
108
|
+
function reportWithTitle() {
|
|
109
|
+
if (myGen !== self._pvGen) return
|
|
110
|
+
var title = typeof document !== 'undefined' ? document.title : ''
|
|
111
|
+
// 第一轮:无论 title 有无值,都等一个 tick 让框架有机会更新
|
|
112
|
+
if (titleAttempts === 0) {
|
|
113
|
+
prevTitle = title
|
|
114
|
+
titleAttempts++
|
|
115
|
+
setTimeout(reportWithTitle, 50)
|
|
116
|
+
return
|
|
117
|
+
}
|
|
118
|
+
// title 为空:继续等
|
|
119
|
+
if (!title && titleAttempts < 10) {
|
|
120
|
+
titleAttempts++
|
|
121
|
+
setTimeout(reportWithTitle, 50)
|
|
122
|
+
return
|
|
123
|
+
}
|
|
124
|
+
// title 在两次检查间发生变化:说明框架刚更新了,再等一轮确认稳定
|
|
125
|
+
if (title !== prevTitle && titleAttempts < 10) {
|
|
126
|
+
prevTitle = title
|
|
127
|
+
titleAttempts++
|
|
128
|
+
setTimeout(reportWithTitle, 50)
|
|
129
|
+
return
|
|
130
|
+
}
|
|
131
|
+
// 兜底:如果 title 在所有重试后仍未变化(框架未更新 document.title),
|
|
132
|
+
// 尝试从 uni-app 页面栈读取 navigationBarTitle
|
|
133
|
+
if (titleAttempts >= 10 && title === prevTitle) {
|
|
134
|
+
try {
|
|
135
|
+
var pages = getCurrentPages()
|
|
136
|
+
var cur = pages[pages.length - 1]
|
|
137
|
+
var stackTitle = (cur && cur.$page && cur.$page.meta && cur.$page.meta.navigationBar
|
|
138
|
+
? cur.$page.meta.navigationBar.titleText : '') || ''
|
|
139
|
+
if (stackTitle) title = stackTitle
|
|
140
|
+
} catch (e) {}
|
|
141
|
+
}
|
|
142
|
+
self._lastTitle = title
|
|
143
|
+
self.tracker.track('page_view', {
|
|
144
|
+
pageRoute: currentPage,
|
|
145
|
+
page: currentPage,
|
|
146
|
+
pageTitle: title,
|
|
147
|
+
})
|
|
148
|
+
}
|
|
149
|
+
reportWithTitle()
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// 供 mixin 调用(小程序端)
|
|
153
|
+
trackPageShow() {
|
|
154
|
+
var adapter = this.tracker.getAdapter()
|
|
155
|
+
var currentPage = adapter.getCurrentPagePath()
|
|
156
|
+
this._lastPage = currentPage
|
|
157
|
+
this.tracker.updatePageRoute(currentPage)
|
|
158
|
+
|
|
159
|
+
this.tracker.track('page_view', {
|
|
160
|
+
pageRoute: currentPage,
|
|
161
|
+
page: currentPage,
|
|
162
|
+
})
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
destroy() {
|
|
166
|
+
if (this._pendingTimer) {
|
|
167
|
+
clearTimeout(this._pendingTimer)
|
|
168
|
+
this._pendingTimer = null
|
|
169
|
+
}
|
|
170
|
+
this.tracker = null
|
|
171
|
+
}
|
|
172
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 页面性能监控插件
|
|
3
|
+
*
|
|
4
|
+
* 采集指标:
|
|
5
|
+
* dns — DNS 解析耗时
|
|
6
|
+
* tcp — TCP 连接耗时
|
|
7
|
+
* ttfb — 首字节时间(Time To First Byte)
|
|
8
|
+
* domReady — DOM 加载完成时间
|
|
9
|
+
* load — 页面完全加载时间
|
|
10
|
+
* fcp — 首次内容绘制(First Contentful Paint)
|
|
11
|
+
* lcp — 最大内容绘制(Largest Contentful Paint)
|
|
12
|
+
*
|
|
13
|
+
* 兼容策略:
|
|
14
|
+
* 优先使用 PerformanceObserver(现代浏览器)
|
|
15
|
+
* 降级使用 performance.timing(旧浏览器,已弃用但兼容性好)
|
|
16
|
+
*
|
|
17
|
+
* 移植自 smart-tracker 的 _initPerformanceMonitor,并增加了现代 API 支持
|
|
18
|
+
*/
|
|
19
|
+
export class PerformancePlugin {
|
|
20
|
+
constructor() {
|
|
21
|
+
this.name = 'performance'
|
|
22
|
+
this.tracker = null
|
|
23
|
+
this._reported = false
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
install(tracker) {
|
|
27
|
+
this.tracker = tracker
|
|
28
|
+
var adapter = tracker.getAdapter()
|
|
29
|
+
|
|
30
|
+
// 仅 Web 和 APP(WebView) 环境支持
|
|
31
|
+
if (adapter.getPlatformName() === 'weapp') return
|
|
32
|
+
if (typeof window === 'undefined' || typeof performance === 'undefined') return
|
|
33
|
+
|
|
34
|
+
this._collectMetrics()
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
_collectMetrics() {
|
|
38
|
+
var self = this
|
|
39
|
+
|
|
40
|
+
// 等待页面加载完成后采集
|
|
41
|
+
if (document.readyState === 'complete') {
|
|
42
|
+
setTimeout(function () {
|
|
43
|
+
self._report()
|
|
44
|
+
}, 0)
|
|
45
|
+
} else {
|
|
46
|
+
window.addEventListener('load', function () {
|
|
47
|
+
// 延迟一点确保 timing 数据完整
|
|
48
|
+
setTimeout(function () {
|
|
49
|
+
self._report()
|
|
50
|
+
}, 100)
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// 使用 PerformanceObserver 采集 FCP 和 LCP
|
|
55
|
+
this._observePaintMetrics()
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
_report() {
|
|
59
|
+
if (this._reported) return
|
|
60
|
+
this._reported = true
|
|
61
|
+
|
|
62
|
+
var data = {}
|
|
63
|
+
|
|
64
|
+
// 优先用 Navigation Timing Level 2
|
|
65
|
+
if (performance.getEntriesByType) {
|
|
66
|
+
var navEntries = performance.getEntriesByType('navigation')
|
|
67
|
+
if (navEntries && navEntries.length > 0) {
|
|
68
|
+
var nav = navEntries[0]
|
|
69
|
+
data.dns = Math.round(nav.domainLookupEnd - nav.domainLookupStart)
|
|
70
|
+
data.tcp = Math.round(nav.connectEnd - nav.connectStart)
|
|
71
|
+
data.ttfb = Math.round(nav.responseStart - nav.requestStart)
|
|
72
|
+
data.domReady = Math.round(nav.domContentLoadedEventEnd - nav.startTime)
|
|
73
|
+
data.load = Math.round(nav.loadEventEnd - nav.startTime)
|
|
74
|
+
data.redirect = Math.round(nav.redirectEnd - nav.redirectStart)
|
|
75
|
+
data.domParse = Math.round(nav.domInteractive - nav.responseEnd)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// 降级到 performance.timing(旧浏览器)
|
|
80
|
+
if (!data.dns && performance.timing) {
|
|
81
|
+
var timing = performance.timing
|
|
82
|
+
data.dns = timing.domainLookupEnd - timing.domainLookupStart
|
|
83
|
+
data.tcp = timing.connectEnd - timing.connectStart
|
|
84
|
+
data.ttfb = timing.responseStart - timing.requestStart
|
|
85
|
+
data.domReady = timing.domContentLoadedEventEnd - timing.navigationStart
|
|
86
|
+
data.load = timing.loadEventEnd - timing.navigationStart
|
|
87
|
+
data.redirect = timing.redirectEnd - timing.redirectStart
|
|
88
|
+
data.domParse = timing.domInteractive - timing.responseEnd
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// FCP(如果 PerformanceObserver 已采集到)
|
|
92
|
+
if (this._fcp) data.fcp = this._fcp
|
|
93
|
+
if (this._lcp) data.lcp = this._lcp
|
|
94
|
+
|
|
95
|
+
// 过滤无效值(0 或负数)
|
|
96
|
+
for (var key in data) {
|
|
97
|
+
if (data[key] <= 0) delete data[key]
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (Object.keys(data).length > 0) {
|
|
101
|
+
this.tracker.track('performance', data)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
_observePaintMetrics() {
|
|
106
|
+
if (typeof PerformanceObserver === 'undefined') return
|
|
107
|
+
|
|
108
|
+
var self = this
|
|
109
|
+
|
|
110
|
+
// FCP
|
|
111
|
+
try {
|
|
112
|
+
var fcpObserver = new PerformanceObserver(function (list) {
|
|
113
|
+
var entries = list.getEntries()
|
|
114
|
+
for (var i = 0; i < entries.length; i++) {
|
|
115
|
+
if (entries[i].name === 'first-contentful-paint') {
|
|
116
|
+
self._fcp = Math.round(entries[i].startTime)
|
|
117
|
+
fcpObserver.disconnect()
|
|
118
|
+
break
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
})
|
|
122
|
+
fcpObserver.observe({ type: 'paint', buffered: true })
|
|
123
|
+
} catch (e) {}
|
|
124
|
+
|
|
125
|
+
// LCP
|
|
126
|
+
try {
|
|
127
|
+
var lcpObserver = new PerformanceObserver(function (list) {
|
|
128
|
+
var entries = list.getEntries()
|
|
129
|
+
if (entries.length > 0) {
|
|
130
|
+
self._lcp = Math.round(entries[entries.length - 1].startTime)
|
|
131
|
+
}
|
|
132
|
+
})
|
|
133
|
+
lcpObserver.observe({ type: 'largest-contentful-paint', buffered: true })
|
|
134
|
+
|
|
135
|
+
// LCP 在用户交互后不再更新,页面可见性变化时停止观察
|
|
136
|
+
if (typeof document !== 'undefined') {
|
|
137
|
+
document.addEventListener(
|
|
138
|
+
'visibilitychange',
|
|
139
|
+
function () {
|
|
140
|
+
if (document.visibilityState === 'hidden') {
|
|
141
|
+
lcpObserver.disconnect()
|
|
142
|
+
// 如果此时已有 LCP 数据但还没上报过主性能数据
|
|
143
|
+
if (self._lcp && !self._reported) {
|
|
144
|
+
self._report()
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
{ once: true }
|
|
149
|
+
)
|
|
150
|
+
}
|
|
151
|
+
} catch (e) {}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
destroy() {
|
|
155
|
+
this.tracker = null
|
|
156
|
+
}
|
|
157
|
+
}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 页面滚动深度插件
|
|
3
|
+
*
|
|
4
|
+
* scrollDepth 字段上报实际滚动距离(像素值)
|
|
5
|
+
* 每滚动一屏距离上报一次 scroll_depth 事件
|
|
6
|
+
*/
|
|
7
|
+
export class ScrollDepthPlugin {
|
|
8
|
+
constructor() {
|
|
9
|
+
this.name = 'scroll-depth'
|
|
10
|
+
this.tracker = null
|
|
11
|
+
this._maxScrollTop = 0
|
|
12
|
+
this._lastReportedDistance = 0
|
|
13
|
+
this._config = null
|
|
14
|
+
this._cachedContainers = null
|
|
15
|
+
this._rafId = null
|
|
16
|
+
this._debounceTimer = null
|
|
17
|
+
this._touchEndHandler = null
|
|
18
|
+
this._touchMoveHandler = null
|
|
19
|
+
this._wheelHandler = null
|
|
20
|
+
this._unloadHandler = null
|
|
21
|
+
this._hideHandler = null
|
|
22
|
+
this._isScrolling = false
|
|
23
|
+
this._screenHeight = 0
|
|
24
|
+
this._trackingPage = null
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
install(tracker) {
|
|
28
|
+
this.tracker = tracker
|
|
29
|
+
var adapter = tracker.getAdapter()
|
|
30
|
+
|
|
31
|
+
if (!adapter.supportsDOM()) return
|
|
32
|
+
if (typeof window === 'undefined') return
|
|
33
|
+
|
|
34
|
+
this._screenHeight = window.innerHeight || 800
|
|
35
|
+
|
|
36
|
+
var trackerConfig = tracker.getConfig()
|
|
37
|
+
this._config = {
|
|
38
|
+
throttle: 2000,
|
|
39
|
+
...(trackerConfig.scrollDepth || {}),
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
this._trackingPage = tracker._currentPage || (adapter.getCurrentPagePath ? adapter.getCurrentPagePath() : null)
|
|
43
|
+
|
|
44
|
+
this._initTracking()
|
|
45
|
+
this._setupFinalOnHide()
|
|
46
|
+
|
|
47
|
+
this._unloadHandler = function () {
|
|
48
|
+
if (tracker._destroyed) return
|
|
49
|
+
}
|
|
50
|
+
window.addEventListener('beforeunload', this._unloadHandler)
|
|
51
|
+
|
|
52
|
+
var self = this
|
|
53
|
+
adapter.onRouteChange(function () {
|
|
54
|
+
if (self._maxScrollTop > 0) {
|
|
55
|
+
self._reportDepth(self._maxScrollTop, true, self._trackingPage)
|
|
56
|
+
}
|
|
57
|
+
self._maxScrollTop = 0
|
|
58
|
+
self._lastReportedDistance = 0
|
|
59
|
+
self._cachedContainers = null
|
|
60
|
+
if (self.tracker) {
|
|
61
|
+
self.tracker._currentScrollDepth = null
|
|
62
|
+
}
|
|
63
|
+
self._trackingPage = self.tracker ? self.tracker._currentPage : null
|
|
64
|
+
})
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
getMaxDepth() {
|
|
68
|
+
return this._maxScrollTop
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
_setupFinalOnHide() {
|
|
72
|
+
if (typeof document === 'undefined') return
|
|
73
|
+
var self = this
|
|
74
|
+
this._hideHandler = function () {
|
|
75
|
+
if (document.visibilityState === 'hidden') {
|
|
76
|
+
if (self._maxScrollTop > self._lastReportedDistance) {
|
|
77
|
+
self._reportDepth(self._maxScrollTop, true)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
document.addEventListener('visibilitychange', this._hideHandler)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
_initTracking() {
|
|
85
|
+
var self = this
|
|
86
|
+
|
|
87
|
+
this._touchEndHandler = function () {
|
|
88
|
+
self._scheduleCheck()
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
this._touchMoveHandler = function () {
|
|
92
|
+
// 立即更新 _currentScrollDepth(不触发上报),确保其他事件能实时拿到值
|
|
93
|
+
self._quickUpdate()
|
|
94
|
+
if (self._isScrolling) return
|
|
95
|
+
self._isScrolling = true
|
|
96
|
+
clearTimeout(self._debounceTimer)
|
|
97
|
+
self._debounceTimer = setTimeout(function () {
|
|
98
|
+
self._isScrolling = false
|
|
99
|
+
self._doCheck()
|
|
100
|
+
}, self._config.throttle)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
this._wheelHandler = function () {
|
|
104
|
+
self._quickUpdate()
|
|
105
|
+
self._scheduleCheck()
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
document.addEventListener('touchend', this._touchEndHandler, { capture: true, passive: true })
|
|
109
|
+
document.addEventListener('touchmove', this._touchMoveHandler, { capture: true, passive: true })
|
|
110
|
+
document.addEventListener('wheel', this._wheelHandler, { capture: true, passive: true })
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
_scheduleCheck() {
|
|
114
|
+
var self = this
|
|
115
|
+
clearTimeout(this._debounceTimer)
|
|
116
|
+
this._debounceTimer = setTimeout(function () {
|
|
117
|
+
self._isScrolling = false
|
|
118
|
+
if (self._rafId) return
|
|
119
|
+
self._rafId = requestAnimationFrame(function () {
|
|
120
|
+
self._rafId = null
|
|
121
|
+
self._doCheck()
|
|
122
|
+
})
|
|
123
|
+
}, 300)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
_quickUpdate() {
|
|
127
|
+
var containers = this._getContainers()
|
|
128
|
+
var maxSt = 0
|
|
129
|
+
for (var i = 0; i < containers.length; i++) {
|
|
130
|
+
var c = containers[i]
|
|
131
|
+
var st = 0
|
|
132
|
+
if (c.type === 'window') {
|
|
133
|
+
st = window.pageYOffset || document.documentElement.scrollTop || 0
|
|
134
|
+
} else {
|
|
135
|
+
st = c.el.scrollTop || 0
|
|
136
|
+
}
|
|
137
|
+
if (st > maxSt) maxSt = st
|
|
138
|
+
}
|
|
139
|
+
if (maxSt > this._maxScrollTop) {
|
|
140
|
+
this._maxScrollTop = maxSt
|
|
141
|
+
if (this.tracker) {
|
|
142
|
+
this.tracker._currentScrollDepth = maxSt
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
_doCheck() {
|
|
148
|
+
var containers = this._getContainers()
|
|
149
|
+
var maxSt = 0
|
|
150
|
+
|
|
151
|
+
for (var i = 0; i < containers.length; i++) {
|
|
152
|
+
var c = containers[i]
|
|
153
|
+
var st = 0
|
|
154
|
+
if (c.type === 'window') {
|
|
155
|
+
st = window.pageYOffset || document.documentElement.scrollTop || 0
|
|
156
|
+
} else {
|
|
157
|
+
st = c.el.scrollTop || 0
|
|
158
|
+
}
|
|
159
|
+
if (st > maxSt) maxSt = st
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (maxSt <= 0) return
|
|
163
|
+
|
|
164
|
+
if (maxSt > this._maxScrollTop) {
|
|
165
|
+
this._maxScrollTop = maxSt
|
|
166
|
+
if (this.tracker) {
|
|
167
|
+
this.tracker._currentScrollDepth = maxSt
|
|
168
|
+
}
|
|
169
|
+
// 每滚动一屏上报一次
|
|
170
|
+
var nextMilestone = this._lastReportedDistance + this._screenHeight
|
|
171
|
+
if (maxSt >= nextMilestone) {
|
|
172
|
+
this._reportDepth(maxSt, false)
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
_getContainers() {
|
|
178
|
+
if (this._cachedContainers) return this._cachedContainers
|
|
179
|
+
|
|
180
|
+
var containers = [{ type: 'window' }]
|
|
181
|
+
|
|
182
|
+
var scrollViews = document.querySelectorAll('uni-scroll-view')
|
|
183
|
+
for (var i = 0; i < scrollViews.length; i++) {
|
|
184
|
+
var found = this._findScrollEl(scrollViews[i])
|
|
185
|
+
if (found) {
|
|
186
|
+
containers.push({ type: 'element', el: found })
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
var overflowEls = document.querySelectorAll('.uni-scroll-view')
|
|
191
|
+
for (var i = 0; i < overflowEls.length; i++) {
|
|
192
|
+
var el = overflowEls[i]
|
|
193
|
+
if (el.scrollHeight > el.clientHeight && el.clientHeight >= 200) {
|
|
194
|
+
containers.push({ type: 'element', el: el })
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (containers.length > 1) {
|
|
199
|
+
this._cachedContainers = containers
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return containers
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
_findScrollEl(el) {
|
|
206
|
+
if (this._isScrollContainer(el)) return el
|
|
207
|
+
var children = el.children
|
|
208
|
+
for (var i = 0; i < children.length; i++) {
|
|
209
|
+
if (this._isScrollContainer(children[i])) return children[i]
|
|
210
|
+
var grandchildren = children[i].children
|
|
211
|
+
for (var j = 0; j < grandchildren.length; j++) {
|
|
212
|
+
if (this._isScrollContainer(grandchildren[j])) return grandchildren[j]
|
|
213
|
+
var great = grandchildren[j].children
|
|
214
|
+
for (var k = 0; k < great.length; k++) {
|
|
215
|
+
if (this._isScrollContainer(great[k])) return great[k]
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return null
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
_isScrollContainer(el) {
|
|
223
|
+
return el && el.clientHeight >= 200 && el.scrollHeight > el.clientHeight + 10
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
_reportDepth(distance, isFinal, pageRoute) {
|
|
227
|
+
this._lastReportedDistance = distance
|
|
228
|
+
|
|
229
|
+
var data = {
|
|
230
|
+
scrollDepth: distance,
|
|
231
|
+
extraData: {
|
|
232
|
+
isFinal: isFinal,
|
|
233
|
+
},
|
|
234
|
+
}
|
|
235
|
+
if (pageRoute) {
|
|
236
|
+
data.pageRoute = pageRoute
|
|
237
|
+
}
|
|
238
|
+
this.tracker.track('scroll_depth', data)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
destroy() {
|
|
242
|
+
if (typeof document !== 'undefined') {
|
|
243
|
+
if (this._touchEndHandler) document.removeEventListener('touchend', this._touchEndHandler, { capture: true })
|
|
244
|
+
if (this._touchMoveHandler) document.removeEventListener('touchmove', this._touchMoveHandler, { capture: true })
|
|
245
|
+
if (this._wheelHandler) document.removeEventListener('wheel', this._wheelHandler, { capture: true })
|
|
246
|
+
if (this._hideHandler) document.removeEventListener('visibilitychange', this._hideHandler)
|
|
247
|
+
}
|
|
248
|
+
if (typeof window !== 'undefined' && this._unloadHandler) {
|
|
249
|
+
window.removeEventListener('beforeunload', this._unloadHandler)
|
|
250
|
+
}
|
|
251
|
+
if (this._rafId) cancelAnimationFrame(this._rafId)
|
|
252
|
+
if (this._debounceTimer) clearTimeout(this._debounceTimer)
|
|
253
|
+
this._cachedContainers = null
|
|
254
|
+
this.tracker = null
|
|
255
|
+
}
|
|
256
|
+
}
|