SDK github address github.com/mfaying/web…

No burial site

The non-buried point is actually the full buried point. As long as the SDK is embedded, data can be collected automatically. Since no additional burrow code is required, it can also be called unburrow.

demo

First of all, let’s take a look at the demonstration effect of the SDK experience web site (www.readingblog.cn/track/index)… The parent page (the buried point management page) is embedded with an iframe that points to a child page (the buried point page embedded with the SDK). The SDK automatically calculates the unique identifier (XPath) of the clicked element, as well as information related to the element size and location, and sends the data to the back end. At the same time, the data will also be cross-domain sent to the buried point management page, the management page according to these data to do visualization buried point work. In the diagram, the administrative page gets information about the elements (including size, location, XPath, and so on).

How to use

The way to use the SDK is very simple: first, introduce the SDK code in the head tag

<script src="https://www.readingblog.cn/lib/web-log-sdk-1.1.0.min.js"></script>
Copy the code

Then, initialize the SDK, during which you can pass in some custom parameters. After initialization, the SDK is already working in your page, isn’t it convenient!

new WebLogger.AutoLogger({
  debug: true});Copy the code

Here is a simple demo page that opens in a browser. Click at will, each click can be automatically printed out in the console buried point data.

<! DOCTYPEhtml>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
  <title>web-log-sdk</title>
  <script src="https://www.readingblog.cn/lib/web-log-sdk-1.1.0.min.js"></script>
</head>
<body>
  <div>
    1
    <div id='1'>
      2
      <div id="1">3</div>
      <div>4</div>
    </div>
  </div>
  <div>5</div>
  <script>
    new WebLogger.AutoLogger({
      debug: true});</script>
</body>
</html>
Copy the code

The principle of no buried point

The unburied point actually listens for click events on document.body (the SDK changed to listen for event capture in higher browser versions). So every click on the page sends the buried point data.

_autoClickCollection = () = > {
  event.on(doc.body, 'click'.this._autoClickHandle);
}
Copy the code

This is where the problem arises. Although this click can trigger the transmission of buried data, we must ensure that the data sent is valuable. The key here is to know which element on the page triggers the user to click. Because of automatic burying, we have to think about a way to tag page elements. Although elements have class, nodeName, and so on, this does not uniquely locate an element for the entire page. The id of an element is unique according to the specification, but only individual elements are tagged with an ID attribute. We thought a way, because the HTML dom structure like a tree, for any elements (nodes), we find it’s parent, the parent node then find its parent node, this has been back, will to HTML elements (the root node), thus formed a path, we will this path as a unique identifier element. Of course, if the “XPath” were reversed, the order would be “from parent to child”, such as HTML >body>#app. So we can select the element that’s being clicked only by document.querySelector. The specific implementation is as follows:

const _getLocalNamePath = (elm) = > {
  const domPath = [];
  let preCount = 0;
  for (let sib = elm.previousSibling; sib; sib = sib.previousSibling) {
    if (sib.localName == elm.localName) preCount ++;
  }
  if (preCount === 0) {
    domPath.unshift(elm.localName);
  } else {
    domPath.unshift(`${elm.localName}:nth-of-type(${preCount + 1}) `);
  }
  return domPath;
}

const getDomPath = (elm) = > {
  try {
    const allNodes = document.getElementsByTagName(The '*');
    let domPath = [];
    for (; elm && elm.nodeType == 1; elm = elm.parentNode) {
      if (elm.hasAttribute('id')) {
        let uniqueIdCount = 0
        for (var n = 0; n < allNodes.length; n++) {
          if (allNodes[n].hasAttribute('id') && allNodes[n].id == elm.id) uniqueIdCount++;
          if (uniqueIdCount > 1) break;
        }
        if (uniqueIdCount == 1) {
          domPath.unshift(` #${elm.getAttribute('id')}`);
        } else {
          domPath.unshift(..._getLocalNamePath(elm));
        }
      } else {
        domPath.unshift(..._getLocalNamePath(elm));
      }
    }
    return domPath.length ? domPath.join('>') : null
  } catch (err) {
    console.log(err)
    return null; }}export default getDomPath;
Copy the code

In the code we also do some processing, such as when there are multiple sibling nodes with the same localName, the common example

<ul>
    <li>1</li>
    <li>2</li>
    <li>3</li>
</ul>
Copy the code

This is distinguished by the :nth-of-type selector.

If there is an ID attribute, to ensure that the ID is unique (the specification requires it to be unique, but developers can inadvertently assign duplicate ID attributes), we check to use the ID tag if it is unique to make the selector more efficient.

With the unique identity of the element determined, it’s easy. We just need to get the buried point data we need and send it to the back end.

For example, get element location information

const getBoundingClientRect = (elm) = > {
  const rect = elm.getBoundingClientRect();
  const width = rect.width || rect.right - rect.left;
  const height = rect.height || rect.bottom - rect.top;
  return {
    width,
    height,
    left: rect.left,
    top: rect.top,
  };
}

export default getBoundingClientRect;
Copy the code

Obtain platform information

import { ua } from '.. /common/bom';
import platform from 'platform';

const getPlatform = () = > {
  const platformInfo = {};

  platformInfo.os = `${platform.os.family} ${platform.os.version}` || ' ';
  platformInfo.bn = platform.name || ' ';
  platformInfo.bv = platform.version || ' ';
  platformInfo.bl = platform.layout || ' ';
  platformInfo.bd = platform.description || ' ';

  const wechatInfo = ua.match(/MicroMessenger\/([\d\.]+)/i);
  const wechatNetType = ua.match(/NetType\/([\w\.]+)/i);
  if (wechatInfo) {
    platformInfo.mmv = wechatInfo[1] | |' ';
  }
  if (wechatNetType) {
    platformInfo.net = wechatNetType[1] | |' ';
  }

  return platformInfo;
}

export default getPlatform;
Copy the code

The current URL, the reference URL, the title, the trigger time of the event, and so on can be added. This is a buried point data sent by my SDK

{
	"eventData": {
		"et": "click"."ed": "auto_click"."text": Reference: Elasticsear... Icsearch 2.x version"."nodeName": "p"."domPath": "html>body>#app>section>section>main>div:nth-of-type(5)>div>p>p"."offsetX": "0.768987"."offsetY": "0.333333"."pageX": 263."pageY": 167."scrollX": 0."scrollY": 0."left": 20."top": 153."width": 316."height": 42."rUrl": "http://localhost:8080/"."docTitle": "blog"."cUrl": "http://localhost:8080/#/blog/article/74"."t": 1573987603156
	},
	"optParams": {},
	"platform": {
		"os": "Android 6.0"."bn": "Chrome Mobile"."bv": "77.0.3865.120"."bl": "Blink"."bd": "Chrome Mobile 77.0.3865.120 on Google Nexus 5 (Android 6.0)"
	},
	"appID": ""."sdk": {
		"type": "js"."version": "1.0.0"}}Copy the code

Realize the visualization circle to select the buried point

Visual burials typically use an IFrame to embed the burials page. The child page is the buried page (introduced by the iframe) and the parent page is the administrative page. Since the SRC attribute of the iframe supports cross-domain loading of resources, any buried page can be embedded.

But in order to realize the function of circle selection, the communication between buried point page and management page must be realized, because the management page does not know the buried point information. And because the buried point page is cross-domain, the management page can not operate the buried point page.

Here we need the SDK to implement a communication mechanism, and we use the common cross-domain communication solution postMessage. Add a postMsgOpts field to the SDK configuration item to configure the postMessage parameter. The default value of postMsgOpts is an empty array, which means it allows the buried page to send data to multiple sources. By default, it does not send data through postMessage. The following is an example of the postMsgOpts field:

new AutoLogger({
  debug: true.postMsgOpts: [{
    targetWindow: window.parent,
    targetOrigin,
  }, {
    targetWindow: window.targetOrigin: curOrigin,
  }],
});
Copy the code

The buried data to be sent will also be sent using the postMessage API.

postMsgOpts.forEach((opt) = > {
  const { targetWindow, targetOrigin } = opt;
  targetWindow.postMessage({ logData: JSON.stringify(logData) }, targetOrigin)
});
Copy the code

Let’s go back and analyze how the demonstration implements the visual burial point. First, the iframe of the admin page loads the buried point page. Since the buried point page introduces the SDK, clicking on any element in the page will send a postMessage of the buried point data to the admin page. The data here includes the size and position of the element, XPath, and so on. As long as the admin page listens for the “message” event, it can get the data from the subpage (buried page). To be interactive, the management page can circle selected elements in the iframe based on this information. Of course, as long as the management page gets the buried point data, it can interact with the user who uses the management page on this basis, do some independent configuration and pass the additional information and the information of the selected elements to the back end, so that the back end can do the processing of the selected elements, so as to realize the visualization of buried points.

Configuration items

Finally, to introduce the configuration items of my SDK, first look at the default configuration

import getPlatform from '.. /.. /utils/getPlatform';

const platform = getPlatform();

export default {
  appID: ' '.// Whether to collect click events automatically
  autoClick: true.debug: false.logUrl: ' '.sdk: {
    / / type
    type: 'js'./ / version
    version: SDK_VERSION,
  },
  // Platform parameters
  platform,
  optParams: {},
  postMsgOpts: [],};Copy the code
  1. AppID You can register an appID at the time of initialization, so all relevant buried points will carry this tag, which is equivalent to a layer of app dimension management of buried point data.
  2. AutoClick defaults to true, enabling automatic collection of click events (i.e. click without buried points). Of course, you can implement the buried point function of page login, logout, and browsing time, and you can add the switch control in the configuration, so that users can selectively enable these functions.
  3. Debug is disabled by default. If it is enabled, buried data will be printed to the console for debugging.
  4. LogUrl Indicates the back-end address for receiving logs
  5. SDK Provides information about the SDK itself
  6. By default, platform automatically gets some platform parameters, which you can override by configuring this field
  7. OptParams Custom data