cbvirtua 1.0.115 → 1.0.122
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/package.json +1 -1
- package/sss.txt +2 -0
- package/v-track-master/v-track-master/.browserslistrc +3 -0
- package/v-track-master/v-track-master/.eslintignore +3 -0
- package/v-track-master/v-track-master/.eslintrc.js +15 -0
- package/v-track-master/v-track-master/.gitattributes +1 -0
- package/v-track-master/v-track-master/.github/FUNDING.yml +12 -0
- package/v-track-master/v-track-master/.github/workflows/node.js.yml +29 -0
- package/v-track-master/v-track-master/.travis.yml +22 -0
- package/v-track-master/v-track-master/.yarnrc +1 -0
- package/v-track-master/v-track-master/LICENSE +21 -0
- package/v-track-master/v-track-master/README.md +131 -0
- package/v-track-master/v-track-master/babel.config.js +25 -0
- package/v-track-master/v-track-master/build/rollup.config.base.js +14 -0
- package/v-track-master/v-track-master/build/rollup.config.browser.js +21 -0
- package/v-track-master/v-track-master/build/rollup.config.es.js +11 -0
- package/v-track-master/v-track-master/docs/.babelrc +5 -0
- package/v-track-master/v-track-master/docs/App.vue +61 -0
- package/v-track-master/v-track-master/docs/assets/app.scss +197 -0
- package/v-track-master/v-track-master/docs/assets/index.scss +10 -0
- package/v-track-master/v-track-master/docs/assets/mixins.scss +64 -0
- package/v-track-master/v-track-master/docs/assets/normalize.scss +349 -0
- package/v-track-master/v-track-master/docs/components/button.vue +15 -0
- package/v-track-master/v-track-master/docs/components/code-snippet.vue +72 -0
- package/v-track-master/v-track-master/docs/dist/css/app.2e14d2d6.css +4 -0
- package/v-track-master/v-track-master/docs/dist/css/chunk-vendors.c687a9b2.css +1 -0
- package/v-track-master/v-track-master/docs/dist/favicon.ico +0 -0
- package/v-track-master/v-track-master/docs/dist/fonts/element-icons.535877f5.woff +0 -0
- package/v-track-master/v-track-master/docs/dist/fonts/element-icons.732389de.ttf +0 -0
- package/v-track-master/v-track-master/docs/dist/js/app.efe84ade.js +2 -0
- package/v-track-master/v-track-master/docs/dist/js/app.efe84ade.js.map +1 -0
- package/v-track-master/v-track-master/docs/dist/js/chunk-vendors.c94e27ba.js +13 -0
- package/v-track-master/v-track-master/docs/dist/js/chunk-vendors.c94e27ba.js.map +1 -0
- package/v-track-master/v-track-master/docs/index.html +1 -0
- package/v-track-master/v-track-master/docs/main.js +78 -0
- package/v-track-master/v-track-master/docs/pages/block-show.vue +177 -0
- package/v-track-master/v-track-master/docs/pages/custom-events.vue +291 -0
- package/v-track-master/v-track-master/docs/pages/home.vue +269 -0
- package/v-track-master/v-track-master/docs/pages/started.vue +151 -0
- package/v-track-master/v-track-master/docs/pages/track-view.vue +234 -0
- package/v-track-master/v-track-master/docs/tracks/action.js +37 -0
- package/v-track-master/v-track-master/docs/tracks/events.js +140 -0
- package/v-track-master/v-track-master/docs/tracks/index.js +13 -0
- package/v-track-master/v-track-master/docs/utils/date.js +219 -0
- package/v-track-master/v-track-master/docs/utils/dom.js +26 -0
- package/v-track-master/v-track-master/jest.config.js +18 -0
- package/v-track-master/v-track-master/package.json +67 -0
- package/v-track-master/v-track-master/postcss.config.js +5 -0
- package/v-track-master/v-track-master/public/favicon.ico +0 -0
- package/v-track-master/v-track-master/public/index.html +18 -0
- package/v-track-master/v-track-master/src/hooks/index.js +217 -0
- package/v-track-master/v-track-master/src/index.js +79 -0
- package/v-track-master/v-track-master/src/utils/debug.js +13 -0
- package/v-track-master/v-track-master/src/utils/dom.js +73 -0
- package/v-track-master/v-track-master/src/utils/helper.js +122 -0
- package/v-track-master/v-track-master/src/utils/vis-monitor.js +183 -0
- package/v-track-master/v-track-master/tests/helper.js +37 -0
- package/v-track-master/v-track-master/tests/unit/.eslintrc.js +5 -0
- package/v-track-master/v-track-master/tests/unit/track-click-async.spec.js +52 -0
- package/v-track-master/v-track-master/tests/unit/track-click-delay.spec.js +46 -0
- package/v-track-master/v-track-master/tests/unit/track-click-native.spec.js +84 -0
- package/v-track-master/v-track-master/tests/unit/track-click-param.spec.js +55 -0
- package/v-track-master/v-track-master/tests/unit/track-click.spec.js +47 -0
- package/v-track-master/v-track-master/tests/unit/track-custom-event-async.spec.js +61 -0
- package/v-track-master/v-track-master/tests/unit/track-custom-event-delay.spec.js +59 -0
- package/v-track-master/v-track-master/tests/unit/track-custom-event-param.spec.js +68 -0
- package/v-track-master/v-track-master/tests/unit/track-custom-event.spec.js +55 -0
- package/v-track-master/v-track-master/tests/unit/track-show-custom-scroll-once.spec.js +62 -0
- package/v-track-master/v-track-master/tests/unit/track-show-custom-scroll.spec.js +62 -0
- package/v-track-master/v-track-master/tests/unit/track-show-once.spec.js +55 -0
- package/v-track-master/v-track-master/tests/unit/track-show-param.spec.js +51 -0
- package/v-track-master/v-track-master/tests/unit/track-show.spec.js +55 -0
- package/v-track-master/v-track-master/tests/unit/track-view-vif.spec.js +54 -0
- package/v-track-master/v-track-master/tests/unit/track-view-watch-delay.spec.js +63 -0
- package/v-track-master/v-track-master/tests/unit/track-view-watch.spec.js +61 -0
- package/v-track-master/v-track-master/tests/unit/track-view.spec.js +32 -0
- package/v-track-master/v-track-master/vue.config.js +45 -0
- package/v-track-master/v-track-master/yarn.lock +10186 -0
- package/vv.txt +10442 -0
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @Author: 宋慧武
|
|
3
|
+
* @Date: 2019-03-06 17:49:29
|
|
4
|
+
* @Last Modified by: 宋慧武
|
|
5
|
+
* @Last Modified time: 2020-06-05 20:23:06
|
|
6
|
+
*/
|
|
7
|
+
import {
|
|
8
|
+
zipArray,
|
|
9
|
+
exactlySameVnode,
|
|
10
|
+
_exactMatch,
|
|
11
|
+
_partialMatch
|
|
12
|
+
} from "../utils/helper";
|
|
13
|
+
import * as debug from "../utils/debug";
|
|
14
|
+
import { isVisible } from "../utils/dom";
|
|
15
|
+
import VisMonitor from "../utils/vis-monitor";
|
|
16
|
+
|
|
17
|
+
const MODIFIERS = ["async", "delay", "watch", "show", "once", "custom"]; // 修饰符
|
|
18
|
+
|
|
19
|
+
/*******************************************************************************
|
|
20
|
+
* @desc 监听数据发生改变时触发埋点,需处理两种情况:
|
|
21
|
+
* ① 初始化时开始监听 v-track:xxxxx.watch="{ common_exp }"
|
|
22
|
+
* ops.immediate 表示初始化时立即开始监听
|
|
23
|
+
*
|
|
24
|
+
* ② 点击事件之后开始监听 v-track:18016.click.async="{ refreshHotSpot, exposureId }"
|
|
25
|
+
* el.contains(this.target) 避免多个“地方”同时监听同一个值出现多次上报的问题
|
|
26
|
+
*******************************************************************************/
|
|
27
|
+
function _watcher(el, exp, cbk, ctt, ops = {}) {
|
|
28
|
+
el.$unwatch = ctt.$watch(
|
|
29
|
+
() => ctt[exp],
|
|
30
|
+
(nv, ov) => {
|
|
31
|
+
nv !== ov && (ops.immediate || el.contains(this.target)) && cbk();
|
|
32
|
+
this.target = null; // 释放当前操作的watcher
|
|
33
|
+
}
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/*************************************************************************
|
|
38
|
+
* @desc 自定义指令 v-track
|
|
39
|
+
*
|
|
40
|
+
* @param {*} el 指令所绑定的元素
|
|
41
|
+
* @param {String} arg 埋点对应event ID
|
|
42
|
+
* @param {Boolean} modifiers.click true: 事件行为埋点; false: 页面级埋点
|
|
43
|
+
* @param {Boolean} modifiers.watch 异步埋点
|
|
44
|
+
* @param {Boolean} modifiers.async 点击事件异步埋点
|
|
45
|
+
* @param {Boolean} modifiers.delay 埋点是否延迟执行,默认先执行埋点再执行cbk
|
|
46
|
+
*
|
|
47
|
+
* @property {Function} tck 对应埋点方法
|
|
48
|
+
*
|
|
49
|
+
* @example v-track:18015
|
|
50
|
+
* @example v-track:18015.watch
|
|
51
|
+
* @example v-track:18015.watch.delay
|
|
52
|
+
* @example v-track:18015.click
|
|
53
|
+
* @example v-track:18015.click.async
|
|
54
|
+
* @example v-track:18015.click.delay
|
|
55
|
+
* @example v-track:18015.[自定义事件名].delay
|
|
56
|
+
* @example v-track:18015.[自定义事件名].async
|
|
57
|
+
*************************************************************************/
|
|
58
|
+
export function bind(
|
|
59
|
+
el,
|
|
60
|
+
{ value, arg: id, modifiers, rawName },
|
|
61
|
+
{ context, componentInstance },
|
|
62
|
+
_,
|
|
63
|
+
__,
|
|
64
|
+
events
|
|
65
|
+
) {
|
|
66
|
+
if (!events[id]) throw new Error("tracking event does not exist");
|
|
67
|
+
|
|
68
|
+
let queue = [];
|
|
69
|
+
let tck = events[id].bind(null, context);
|
|
70
|
+
const watcher = (exp, cbk, ops) =>
|
|
71
|
+
_watcher.call(this, el, exp, cbk, context, ops);
|
|
72
|
+
const exactMatch = (...args) => _exactMatch.call(null, modifiers, args);
|
|
73
|
+
const partialMatch = (...args) => _partialMatch.call(null, modifiers, args);
|
|
74
|
+
|
|
75
|
+
if (!Object.keys(modifiers).length) {
|
|
76
|
+
events[id](context, value);
|
|
77
|
+
}
|
|
78
|
+
// 异步埋点
|
|
79
|
+
else if (exactMatch("watch")) {
|
|
80
|
+
const exp = Object.keys(value).shift();
|
|
81
|
+
|
|
82
|
+
watcher(exp, tck, {
|
|
83
|
+
immediate: true
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
// 指定延长时间埋点
|
|
87
|
+
else if (exactMatch("delay")) {
|
|
88
|
+
el.$timer && clearTimeout(el.$timer);
|
|
89
|
+
el.$timer = setTimeout(() => {
|
|
90
|
+
events[id](context);
|
|
91
|
+
}, value);
|
|
92
|
+
} else if (exactMatch("watch", "delay")) {
|
|
93
|
+
const { delay, ...args } = value;
|
|
94
|
+
const exp = [...Object.keys(args)].pop();
|
|
95
|
+
|
|
96
|
+
tck = () => {
|
|
97
|
+
el.$timer && clearTimeout(el.$timer);
|
|
98
|
+
el.$timer = setTimeout(() => {
|
|
99
|
+
const visible = isVisible(context.$el);
|
|
100
|
+
|
|
101
|
+
visible && events[id](context);
|
|
102
|
+
}, delay);
|
|
103
|
+
};
|
|
104
|
+
watcher(exp, tck, {
|
|
105
|
+
immediate: true
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
// 区域曝光埋点
|
|
109
|
+
else if (partialMatch("show")) {
|
|
110
|
+
const [args] = zipArray(value);
|
|
111
|
+
const tck = events[id].bind(null, context, ...args);
|
|
112
|
+
const once = partialMatch("once");
|
|
113
|
+
const custom = partialMatch("custom");
|
|
114
|
+
|
|
115
|
+
if (!el.$visMonitor) {
|
|
116
|
+
setTimeout(() => {
|
|
117
|
+
const vm = new VisMonitor(
|
|
118
|
+
el,
|
|
119
|
+
custom && context.$refs[value.ref],
|
|
120
|
+
value && context.$refs[value.viewport],
|
|
121
|
+
value && value.percent
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
(once ? vm.$once : vm.$on).call(vm, "fullyvisible", tck);
|
|
125
|
+
el.$visMonitor = vm;
|
|
126
|
+
}, 0);
|
|
127
|
+
}
|
|
128
|
+
} else if (
|
|
129
|
+
(!componentInstance && modifiers.click) ||
|
|
130
|
+
(componentInstance && partialMatch("native"))
|
|
131
|
+
) {
|
|
132
|
+
/**
|
|
133
|
+
* @desc DOM元素事件行为埋点(需区分是否带参数)
|
|
134
|
+
* @var {Function} fn 获取第一个参数作为回调函数
|
|
135
|
+
* @var {String} exp 获取最后一个参数并作为监听对象
|
|
136
|
+
*/
|
|
137
|
+
switch (typeof value) {
|
|
138
|
+
case "object": {
|
|
139
|
+
const [args, keys] = zipArray(value);
|
|
140
|
+
const fn = args.shift();
|
|
141
|
+
const exp = [...keys].pop();
|
|
142
|
+
|
|
143
|
+
debug.checkFun(fn);
|
|
144
|
+
tck = events[id].bind(null, context, ...args);
|
|
145
|
+
queue = [tck, fn.bind(null, ...args)];
|
|
146
|
+
modifiers.delay && queue.reverse();
|
|
147
|
+
modifiers.async && watcher(exp, queue.shift());
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
case "function":
|
|
151
|
+
queue = [tck, value];
|
|
152
|
+
modifiers.delay && queue.reverse();
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
el.$listener = e => {
|
|
156
|
+
this.target = e.target;
|
|
157
|
+
queue.forEach(sub => sub(e));
|
|
158
|
+
};
|
|
159
|
+
el.addEventListener("click", el.$listener);
|
|
160
|
+
} else if (
|
|
161
|
+
/**
|
|
162
|
+
* @desc 组件自定义事件行为埋点(需区分是否带参数)
|
|
163
|
+
* @var {Function} fn 获取第一个参数作为回调函数
|
|
164
|
+
* @var {String} exp 获取最后一个参数并作为监听对象
|
|
165
|
+
*/
|
|
166
|
+
componentInstance &&
|
|
167
|
+
componentInstance.$el === el
|
|
168
|
+
) {
|
|
169
|
+
let args, keys, fn, exp;
|
|
170
|
+
const eventName = Object.keys(modifiers)
|
|
171
|
+
.filter(key => !MODIFIERS.includes(key))
|
|
172
|
+
.pop();
|
|
173
|
+
|
|
174
|
+
if (typeof value === "object") {
|
|
175
|
+
[args, keys] = zipArray(value);
|
|
176
|
+
fn = args.shift();
|
|
177
|
+
exp = [...keys].pop();
|
|
178
|
+
debug.checkFun(fn);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (el[`$on_${eventName}`]) return;
|
|
182
|
+
componentInstance.$on(eventName, (...data) => {
|
|
183
|
+
this.target = el;
|
|
184
|
+
tck = events[id].bind(null, context, ...data);
|
|
185
|
+
queue = [tck, (fn || value).bind(null, ...data)];
|
|
186
|
+
modifiers.delay && queue.reverse();
|
|
187
|
+
modifiers.async && watcher(exp, queue.shift());
|
|
188
|
+
queue.forEach(sub => sub());
|
|
189
|
+
el[`$on_${eventName}`] = true; // 避免重复监听
|
|
190
|
+
});
|
|
191
|
+
} else {
|
|
192
|
+
throw new Error(`${rawName} directive is not supported`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* @desc 由于 DOM 更新采用 diff 算法更新,如果新旧节点相同,则 el 会全等,导致 bind 绑定无法更
|
|
198
|
+
* 新,出现事件绑定诡异的问题,但由于 DOM update 执行频率很高,会导致性能问题,所以这里加
|
|
199
|
+
* 了一层exactlySameVnode过滤,即只有在新旧节点发生变化时才会重新绑定,否则相反
|
|
200
|
+
*
|
|
201
|
+
* @param {*} el 同bind
|
|
202
|
+
* @param {...any} args 同bind
|
|
203
|
+
*/
|
|
204
|
+
export function updated(el, ...args) {
|
|
205
|
+
if (!el.$listener) return;
|
|
206
|
+
if (!exactlySameVnode(args[1], args[2])) {
|
|
207
|
+
unbind.call(this, el);
|
|
208
|
+
bind.call(this, el, ...args);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function unbind(el) {
|
|
213
|
+
el.$listener && el.removeEventListener("click", el.$listener);
|
|
214
|
+
el.$timer && clearTimeout(el.$timer);
|
|
215
|
+
el.$unwatch && el.$unwatch();
|
|
216
|
+
el.$visMonitor && el.$visMonitor.destroy();
|
|
217
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @Author: 宋慧武
|
|
3
|
+
* @Date: 2019-03-06 17:49:29
|
|
4
|
+
* @Last Modified by: 宋慧武
|
|
5
|
+
* @Last Modified time: 2020-01-13 14:38:02
|
|
6
|
+
*/
|
|
7
|
+
import * as hooks from "./hooks";
|
|
8
|
+
|
|
9
|
+
export default class VTrack {
|
|
10
|
+
constructor() {
|
|
11
|
+
this.installed = false;
|
|
12
|
+
}
|
|
13
|
+
// 保存当前点击的元素
|
|
14
|
+
static target = null;
|
|
15
|
+
// Vue.use 将执行此方法
|
|
16
|
+
static install(Vue, { trackEvents, trackEnable = {} }) {
|
|
17
|
+
trackEnable = {
|
|
18
|
+
UVPV: false,
|
|
19
|
+
TONP: false,
|
|
20
|
+
...trackEnable
|
|
21
|
+
};
|
|
22
|
+
const TRACK_TONP = (ctx, et) => {
|
|
23
|
+
if (trackEnable.TONP) {
|
|
24
|
+
trackEvents.TONP(ctx, {
|
|
25
|
+
et,
|
|
26
|
+
dt: Date.now()
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
if (this.installed) return;
|
|
32
|
+
this.installed = true;
|
|
33
|
+
|
|
34
|
+
// 注册v-track全局指令
|
|
35
|
+
Vue.directive("track", {
|
|
36
|
+
bind: (...args) => hooks.bind.call(this, ...args, trackEvents),
|
|
37
|
+
componentUpdated: (...args) =>
|
|
38
|
+
hooks.updated.call(this, ...args, trackEvents),
|
|
39
|
+
unbind: (...args) => hooks.unbind.call(this, ...args)
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// 注册<track-view>全局组件
|
|
43
|
+
Vue.component("TrackView", {
|
|
44
|
+
render: h =>
|
|
45
|
+
h("span", {
|
|
46
|
+
style: "display: none"
|
|
47
|
+
})
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
Vue.mixin({
|
|
51
|
+
data: () => ({
|
|
52
|
+
PAGE_ENTER_TIME: Date.now()
|
|
53
|
+
}),
|
|
54
|
+
created() {
|
|
55
|
+
window.onbeforeunload = () => TRACK_TONP(this, this.PAGE_ENTER_TIME);
|
|
56
|
+
},
|
|
57
|
+
// 统计UV、PV
|
|
58
|
+
beforeRouteEnter(_, __, next) {
|
|
59
|
+
next(vm => {
|
|
60
|
+
trackEnable.UVPV && trackEvents.UVPV(vm);
|
|
61
|
+
});
|
|
62
|
+
},
|
|
63
|
+
beforeRouteUpdate(_, __, next) {
|
|
64
|
+
// 确保导航升级完成
|
|
65
|
+
this.$watch("$route", () => {
|
|
66
|
+
if (trackEnable.UVPV && trackEnable.UVPV === "routeUpdate") {
|
|
67
|
+
trackEvents.UVPV(this);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
next();
|
|
71
|
+
},
|
|
72
|
+
// 页面停留时间
|
|
73
|
+
beforeRouteLeave(_, __, next) {
|
|
74
|
+
TRACK_TONP(this, this.PAGE_ENTER_TIME);
|
|
75
|
+
next();
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @Author: 宋慧武
|
|
3
|
+
* @Date: 2019-04-14 15:55:15
|
|
4
|
+
* @Last Modified by: 宋慧武
|
|
5
|
+
* @Last Modified time: 2019-04-20 18:06:31
|
|
6
|
+
*/
|
|
7
|
+
import { isFun } from "./helper";
|
|
8
|
+
|
|
9
|
+
export const checkFun = fn => {
|
|
10
|
+
if (!isFun(fn)) {
|
|
11
|
+
throw new Error("The first parameter should be Function.");
|
|
12
|
+
}
|
|
13
|
+
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @Author: 宋慧武
|
|
3
|
+
* @Date: 2019-04-08 11:13:34
|
|
4
|
+
* @Last Modified by: 宋慧武
|
|
5
|
+
* @Last Modified time: 2020-06-04 17:08:44
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @desc 是否为元素几点
|
|
10
|
+
*
|
|
11
|
+
* @param {DOMElement} ele 一个 DOM 元素
|
|
12
|
+
* @return {Boolean}
|
|
13
|
+
*/
|
|
14
|
+
export const isElement = ele => ele && ele.nodeType === 1;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @desc 获取 DOM CSS 属性的值
|
|
18
|
+
*
|
|
19
|
+
* @param {DOMElement} ele A DOM 元素
|
|
20
|
+
* @returns {String}
|
|
21
|
+
*/
|
|
22
|
+
export function getStylePropValue(ele, prop) {
|
|
23
|
+
return window.getComputedStyle(ele).getPropertyValue(prop);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @desc 元素是否在可视区域可见
|
|
28
|
+
*
|
|
29
|
+
* @param {Object} rect 元素大小及相对可视区域的位置信息
|
|
30
|
+
* @returns {Boolean} true => 可见 false => 不可见
|
|
31
|
+
*/
|
|
32
|
+
export function isInViewport(rect, viewport) {
|
|
33
|
+
if (!rect || (rect.width <= 0 || rect.height <= 0)) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
rect.bottom > 0 &&
|
|
39
|
+
rect.right > 0 &&
|
|
40
|
+
rect.top < window.innerHeight &&
|
|
41
|
+
rect.left < window.innerWidth &&
|
|
42
|
+
!(
|
|
43
|
+
rect.left > viewport.right ||
|
|
44
|
+
rect.top > viewport.bottom ||
|
|
45
|
+
rect.right < viewport.left ||
|
|
46
|
+
rect.bottom < viewport.top
|
|
47
|
+
)
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @desc 元素是否隐藏
|
|
53
|
+
*
|
|
54
|
+
* @param {DOMElement} ele A DOM 元素
|
|
55
|
+
* @returns {Boolean} true => 未隐藏可见 false => 隐藏不可见
|
|
56
|
+
*/
|
|
57
|
+
export function isVisible(ele) {
|
|
58
|
+
if (ele === window.document) {
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
if (!ele || !ele.parentNode) {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const parent = ele.parentNode;
|
|
66
|
+
const visibility = getStylePropValue(ele, "visibility");
|
|
67
|
+
const display = getStylePropValue(ele, "display");
|
|
68
|
+
|
|
69
|
+
if (visibility === "hidden" || display === "none") {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
return parent ? isVisible(parent) : true;
|
|
73
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @Author: 宋慧武
|
|
3
|
+
* @Date: 2019-04-08 11:13:34
|
|
4
|
+
* @Last Modified by: 宋慧武
|
|
5
|
+
* @Last Modified time: 2019-08-05 15:31:00
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @desc 判断给定变量是否为一个函数
|
|
10
|
+
*
|
|
11
|
+
* @param {*} v
|
|
12
|
+
* @return {Boolean}
|
|
13
|
+
*/
|
|
14
|
+
export const isFun = v => typeof v === "function" || false;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @desc 判断给定变量是否是未定义
|
|
18
|
+
*
|
|
19
|
+
* @param {*} v
|
|
20
|
+
*/
|
|
21
|
+
export const isUndef = v => v === undefined || v === null;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @desc 判断给定变量是否是定义
|
|
25
|
+
*
|
|
26
|
+
* @param {*} v
|
|
27
|
+
*/
|
|
28
|
+
export const isDef = v => v !== undefined && v !== null;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @desc 获取对象的键值
|
|
32
|
+
*
|
|
33
|
+
* @param {Object} value
|
|
34
|
+
* @returns {Array} [keys, values]
|
|
35
|
+
*/
|
|
36
|
+
export function zipArray(value = {}) {
|
|
37
|
+
return [Object.values(value), Object.keys(value)];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @desc 防抖函数,至少间隔200毫秒执行一次
|
|
42
|
+
*
|
|
43
|
+
* @param {Function} fn callback
|
|
44
|
+
* @param {Number} [ms=200] 默认200毫秒
|
|
45
|
+
* @returns {Function}
|
|
46
|
+
*/
|
|
47
|
+
export function debounce(fn, ms = 200) {
|
|
48
|
+
let timeoutId;
|
|
49
|
+
return function(...args) {
|
|
50
|
+
clearTimeout(timeoutId);
|
|
51
|
+
timeoutId = setTimeout(() => fn.apply(this, args), ms);
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* @desc 判断给定变量是否完全匹配目标数组
|
|
57
|
+
*
|
|
58
|
+
* @param {String[]} mdfs 目标数组
|
|
59
|
+
* @param {String} vals
|
|
60
|
+
* @returns {Boolean}
|
|
61
|
+
*/
|
|
62
|
+
export function _exactMatch(mdfs, vals) {
|
|
63
|
+
const keys = Object.keys(mdfs);
|
|
64
|
+
|
|
65
|
+
return keys.length === vals.length && vals.every(v => keys.includes(v));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* @desc 判断给定变量是否匹配目标数组的一部分
|
|
70
|
+
*
|
|
71
|
+
* @param {String[]} mdfs 目标字符串数组
|
|
72
|
+
* @param {String} vals
|
|
73
|
+
* @returns {Boolean}
|
|
74
|
+
*/
|
|
75
|
+
export function _partialMatch(mdfs, vals) {
|
|
76
|
+
const keys = Object.keys(mdfs);
|
|
77
|
+
|
|
78
|
+
return vals.some(v => keys.includes(v));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* @desc 判断两个节点是否为同一个vnode节点
|
|
83
|
+
*
|
|
84
|
+
* @param {VNode} a 虚拟节点
|
|
85
|
+
* @param {VNode} b 虚拟节点
|
|
86
|
+
*/
|
|
87
|
+
export function sameVnode(a, b) {
|
|
88
|
+
return (
|
|
89
|
+
a.key === b.key &&
|
|
90
|
+
a.tag === b.tag &&
|
|
91
|
+
a.isComment === b.isComment &&
|
|
92
|
+
isDef(a.data) === isDef(b.data)
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* @desc 判断两个vnode节点是否全等
|
|
98
|
+
*
|
|
99
|
+
* @param {VNode} a 虚拟节点
|
|
100
|
+
* @param {VNode} b 虚拟节点
|
|
101
|
+
*/
|
|
102
|
+
export function exactlySameVnode(vnode, oldVnode) {
|
|
103
|
+
if (!sameVnode(vnode, oldVnode)) return false;
|
|
104
|
+
|
|
105
|
+
const oldCh = oldVnode.children;
|
|
106
|
+
const ch = vnode.children;
|
|
107
|
+
|
|
108
|
+
// vnode为非文本节点,且新旧节点的子节点都存在但不相同
|
|
109
|
+
if (isUndef(vnode.text) && isDef(oldCh) && isDef(ch)) {
|
|
110
|
+
if (oldCh.length !== ch.length) return false;
|
|
111
|
+
for (let i = 0; i < ch.length; i++) {
|
|
112
|
+
const c = ch[i];
|
|
113
|
+
|
|
114
|
+
if (isDef(c) && isDef(oldCh[i])) {
|
|
115
|
+
return exactlySameVnode(c, oldCh[i]);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// vnode为文本节点,新旧节点内容不相同
|
|
120
|
+
else if (vnode.text !== oldVnode.text) return false;
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @Author: 宋慧武
|
|
3
|
+
* @Date: 2019-04-08 11:13:34
|
|
4
|
+
* @Last Modified by: 宋慧武
|
|
5
|
+
* @Last Modified time: 2020-06-07 10:47:44
|
|
6
|
+
*/
|
|
7
|
+
import { isElement, isVisible, isInViewport } from "./dom";
|
|
8
|
+
import { isFun, debounce } from "./helper";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @class
|
|
12
|
+
* @name VisMonitor
|
|
13
|
+
*
|
|
14
|
+
* @desc 目标元素控制器
|
|
15
|
+
*/
|
|
16
|
+
export default class VisMonitor {
|
|
17
|
+
constructor(ele, ref, refwin = window, percent = 1) {
|
|
18
|
+
if (!isElement(ele)) {
|
|
19
|
+
throw new Error("not an element node");
|
|
20
|
+
}
|
|
21
|
+
if (percent > 1 && percent <= 0) {
|
|
22
|
+
throw new Error("'percent' must be a number between 0 and 1");
|
|
23
|
+
}
|
|
24
|
+
this.ele = ele;
|
|
25
|
+
this.ref = ref;
|
|
26
|
+
this.refWin = refwin;
|
|
27
|
+
this.started = false;
|
|
28
|
+
this.percent = percent;
|
|
29
|
+
this.prevPerc = null; // 保存前一次曝光百分比
|
|
30
|
+
this.listeners = {};
|
|
31
|
+
this.removeScrollLisener = null;
|
|
32
|
+
this.init();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
init() {
|
|
36
|
+
if (!this.started) {
|
|
37
|
+
const listener = debounce(this.visibilitychange.bind(this));
|
|
38
|
+
|
|
39
|
+
listener();
|
|
40
|
+
this.removeScrollLisener = (listener => {
|
|
41
|
+
if (this.ref) {
|
|
42
|
+
return this.ref.$on("scroll", listener);
|
|
43
|
+
} else {
|
|
44
|
+
this.refWin.addEventListener("scroll", listener, true);
|
|
45
|
+
return () =>
|
|
46
|
+
this.refWin.removeEventListener("scroll", listener, true);
|
|
47
|
+
}
|
|
48
|
+
})(listener);
|
|
49
|
+
this.started = true;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
viewport() {
|
|
54
|
+
const win = this.refWin;
|
|
55
|
+
const rect = isElement(win) ? win.getBoundingClientRect() : win;
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
top: isElement(win) ? rect.top : 0,
|
|
59
|
+
right: rect.right || rect.innerWidth,
|
|
60
|
+
bottom: rect.bottom || rect.innerHeight,
|
|
61
|
+
left: rect.left || 0,
|
|
62
|
+
height: win.innerHeight || win.offsetHeight,
|
|
63
|
+
width: win.innerWidth || win.offsetWidth
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* 监听自定义事件
|
|
69
|
+
*/
|
|
70
|
+
$on(evt, cbk) {
|
|
71
|
+
const queue = this.listeners[evt] || (this.listeners[evt] = []);
|
|
72
|
+
|
|
73
|
+
queue.push(cbk);
|
|
74
|
+
return this;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* 移除监听自定义事件
|
|
79
|
+
*/
|
|
80
|
+
$off(evt, cbk) {
|
|
81
|
+
if (!cbk) return;
|
|
82
|
+
|
|
83
|
+
let queue = this.listeners[evt];
|
|
84
|
+
let v;
|
|
85
|
+
let i = queue.length;
|
|
86
|
+
|
|
87
|
+
while (i--) {
|
|
88
|
+
v = queue[i];
|
|
89
|
+
if (v === cbk || v.cbk === cbk) {
|
|
90
|
+
queue.splice(i, 1);
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return this;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* 监听自定义事件,但只触发一次
|
|
99
|
+
*/
|
|
100
|
+
$once(evt, cbk) {
|
|
101
|
+
const on = (...args) => {
|
|
102
|
+
this.$off(evt, on);
|
|
103
|
+
cbk.apply(this, args);
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
on.cbk = cbk;
|
|
107
|
+
this.$on(evt, on);
|
|
108
|
+
return this;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* 触发当前实例的监听回调
|
|
113
|
+
*/
|
|
114
|
+
$emit(evt, ...args) {
|
|
115
|
+
const queue = this.listeners[evt] || [];
|
|
116
|
+
|
|
117
|
+
queue.forEach(sub => sub.apply(this, args));
|
|
118
|
+
return this;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* 计算元素可见比例,如果比例为100%,则触发 fullyvisible 事件
|
|
123
|
+
*/
|
|
124
|
+
visibilitychange() {
|
|
125
|
+
const rect = this.ele.getBoundingClientRect();
|
|
126
|
+
const view = this.viewport();
|
|
127
|
+
|
|
128
|
+
if (!isInViewport(rect, view) || !isVisible(this.ele)) {
|
|
129
|
+
this.prevPerc = 0;
|
|
130
|
+
return 0;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
let vh = 0;
|
|
134
|
+
let vw = 0;
|
|
135
|
+
let perc = 0;
|
|
136
|
+
|
|
137
|
+
if (view.top < 0) {
|
|
138
|
+
view.top = 0;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (view.bottom > window.innerHeight) {
|
|
142
|
+
view.bottom = window.innerHeight;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (view.left < 0) {
|
|
146
|
+
view.left = 0;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (view.right > window.innerWidth) {
|
|
150
|
+
view.right = window.innerWidth;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (rect.top >= view.top && rect.bottom > view.bottom) {
|
|
154
|
+
vh = view.bottom - rect.top;
|
|
155
|
+
} else if (rect.top < view.top && rect.bottom <= view.bottom) {
|
|
156
|
+
vh = rect.bottom - view.top;
|
|
157
|
+
} else {
|
|
158
|
+
vh = rect.height;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (rect.left >= view.left && rect.right > view.right) {
|
|
162
|
+
vw = view.right - rect.left;
|
|
163
|
+
} else if (rect.left < view.left && rect.right <= view.right) {
|
|
164
|
+
vw = rect.right - view.left;
|
|
165
|
+
} else {
|
|
166
|
+
vw = rect.width;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
perc = (vh * vw) / (rect.height * rect.width);
|
|
170
|
+
|
|
171
|
+
if (this.prevPerc < this.percent && perc >= this.percent) {
|
|
172
|
+
this.$emit("fullyvisible");
|
|
173
|
+
this.prevPerc = perc;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* 销毁当前实例的事件
|
|
179
|
+
*/
|
|
180
|
+
destroy() {
|
|
181
|
+
isFun(this.removeScrollLisener) && this.removeScrollLisener();
|
|
182
|
+
}
|
|
183
|
+
}
|