什么是曝光?
商品曝光人数:看到商品在首页、列表页、活动页面,以及在商品详情页下方的更多展现的人数。(不包括商品详情页的访客数量)
商品曝光次数:商品在店铺首页、列表页、活动页面,以及在商品详情页下方的更多展现的次数。(不包括商品详情页的浏览量)
通过商品曝光我们能得出商品在不同营销位的比重, 从而得出用户操作喜好
如何判断元素可视区域?
Element.getBoundingClientRect()
getBoundingClientRect 方法返回一个 对象,该 DOMRect 对象提供有关元素大小及其相对于视口的位置的信息。
如果目标元素 rect 满足 top > 0 && left > 0 && bottom >= 视窗高度 && right <= 视窗宽度
便能得出元素完全在视窗内。 在长列表下 我们可以通过监听滚动条事件, 从而获取目标元素是否暴露在用户视窗内。
在线代码示例 CodePen
这种方法实现起来简单,兼容性相对较好,这个属性频繁计算会引发页面的重绘,当元素过多时,会造成性能问题,出现卡顿,影响使用体验。
1 2 3 4 5 6 7 8 9 10 11
| const isElementInViewport = (el) => { let rect = el.getBoundingClientRect(); return ( rect.top >= 0 && rect.left >= 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && rect.right <= (window.innerWidth || document.documentElement.clientWidth) ); };
|
Intersection Observer API
Intersection Observer API 提供了一种异步检测目标元素与祖先元素或 viewport 相交情况变化的方法。
Intersection Observer API 会注册一个回调函数,每当被监视的元素进入或者退出另外一个元素时 (或者 viewport ),或者两个元素的相交部分大小发生变化时,该回调方法会被触发执行。这样,我们网站的主线程不需要再为了监听元素相交而辛苦劳作,浏览器会自行优化元素相交管理。
浏览器兼容性
在线代码示例 CodePen
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| const intersectionObserverCallBack = (entries) => { for (const entry of entries) { if (entry.isIntersecting) { var container = entry.target; } } };
var observer = new IntersectionObserver(intersectionObserverCallBack);
observer.observe(element);
observer.unobserve(element);
observer.disconnect();
|
总结
两种方案
- getBoundingClientRect 浏览器支持不错, 简单易用但是可能存在性能问题。
- Intersection Observer API 没有性能问题, 但是存在一定兼容性。
好消息是我们可以使用polyfill 解决 Intersection Observer API 兼容性问题
它会在不支持的浏览器 使用 getBoundingClientRect 去重新实现一遍 Intersection Observer API。
polyfill浏览器支持:
1 2
| // 安装 npm install intersection-observer
|
实现
曝光肯定是结合埋点一起使用, 通过采集某个商品是否出现在用户的可视区域内, 进行上报
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 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
| import IntersectionObserver from 'intersection-observer';
const attrName = 'exposure-data'
class Exposure { private observer: IntersectionObserver | undefined;
init() { return new IntersectionObserver((entries, observer) => { entries.forEach((item) => { if (item.isIntersecting) { const el: Element = item.target observer!.unobserve(el); const arrtString = el.getAttribute(attrName) || null; if(!arrtString) return const params = JSON.parse(arrtString); autoSendTracker({ ...params, }); } }); }); } add(entry: { el: Element; binding: any }) { entry.el.setAttribute(attrName, typeof entry.binding.value === 'string' ? entry.binding.value : JSON.stringify(entry.binding.value)) if (this.observer === undefined) { this.observer = this.init(); } this.observer.observe(entry.el); } remove(entry: { el: Element; binding?: any }) { this.observer?.unobserve(entry.el); this.observer?.disconnect(); } }
export default Exposure;
const exposure = new Exposure();
const track = { inserted: function (el: any, binding: { arg: any }) { const { arg } = binding; arg.split("|").forEach((item) => { switch (item) { case "click": cli.add({ el, binding }); break;
case "keyup": keyup.add({ el, binding }); break; case "exposure": exposure.add({ el, binding }); break; } }); }, unbind(el, binding) { const { arg } = binding; arg.split("|").forEach((item) => { switch (item) { case "click": cli.remove({ el, binding }); break;
case "keyup": keyup.remove({ el, binding }); break;
case "exposure": exposure.remove({ el, binding }); break; } }); }, };
<div v-track:exposure="{ object_ids: item.skuoid, event_type: 8, ... }" > ... </div>
|
文中完整代码
github > npm
参考
IntersectionObserver API 使用教程