分类: 技术

前端热力图系统实现

1. 背景

今年6月开发了一个前端热力图系统,目前已经应用在公司的主要业务中,一直没时间做个总结,现在梳理一下实现思路。

先说下为什么要做这样一个系统,电商网站的一种常用营销手段,就是配置眼花缭乱的活动页展现给用户,有的用来展示各种商品,有的供用户领取优惠券。但屏幕的展示空间有限,如何配置不同的模块才能最大化利用页面空间,一种比较好的方式就是采集用户点击数据,绘制出热力图,供产品、运营和设计同学参考,不断优化模块配置,有效提升点击转化。

2. 系统架构


热力图架构

先将热力图系统进行子功能拆分,可以得到以下几个部分:

  1. 用户点击数据采集:包括页面埋点、数据入库;
  2. 热力图绘制:包括:数据读取、数据加工、热力图绘制;
  3. 数据查询平台:主要是按日期和活动ID查询自定义区域的点击数。

2.1 数据采集

数据采集部分,主要通过事件代理在body上绑定click事件,采集数据主要包括:

  1. x:点击事件触发相对于 document 的横坐标,主要取自于event.pageX
  2. y:点击事件触发相对于 document 的纵坐标,主要取自于event.pageY
  3. screenWidth:点击事件触发时屏幕的宽度;
  4. screenHeight:点击事件触发时屏幕的高度;
  5. moduleType:表示当前元素或其父元素是否是一个fixed元素,如是则取值screen,否则为floor
  6. url:当前活动页面的 URL;
  7. date:当前日期。

此处代码不再赘述。获取到数据后,直接通过前端埋点方案进行数据上报,由数据组对数据进行入库。

2.2 热力图绘制

2.2.1 数据读取

现在要绘制给定 URL (通过活动ID拼接而成)活动页的热力图,需要先从数据库中获取该页面所有点击数据:

这里的VIEW_NUM表示热力图截取的屏数,因为有些页面非常长会超过一屏,所以这里增加一个配置。SAMPLE_NUM表示样本数量,因为有的页面每天点击数量会达到几十甚至上百万,如果都绘制在热力图上会造成数据过多,且内存消耗过大。而热力图主要反映一个点击趋势,一个合理的样本数量即可达到效果。

2.2.2 数据加工 & 热力图绘制

这里将数据加工和热力图绘制放在一起介绍,主要因为加工是在绘制过程中的一个步骤中实现的,所以先介绍热力图绘制过程。

这里采用 Nightmare(一个浏览器模拟器)渲染页面,并且在页面加载前,注入两个脚本:

  1. heatmap.js:一个绘制热力图的前端 JS 库,提供热力图绘制工具;
  2. ${date}-${activity_id}.js:加工上一步中采集到的数据,并用数据初始化热力图工具示例。

heatmap.js 是一个非常好用的前端热力图绘制工具,有丰富的参数进行配置,有兴趣可以看下源码。绘制工具需要提供 point 数据,这就是第2个脚本做的事:

其中viewWidthviewHeight分别表示 Nightmare 的宽高,list是原始数据,item数组项含义见注释。这里需要注意的是第10、11行代码,对坐标数据进行了加工。因为用户的屏幕尺寸多种多样,而 Nightmare 的尺寸是固定的,所以需要对原始坐标进行一次加工,转换为 Nightmare 中的坐标。对于原始坐标 X、Y,根据以下简单公式:

得到 Nightmare 中的转换坐标 x、y,脚本中的其它配置可以参考 heapmap 的 API 文档。同时,这里需要将转换过的数据按照日期保存到一个 JSON 文件中,供后续热力图平台查数使用,数据格式如下:

然后就是使用 Nightmare 绘制热力图的主要代码:

所有的数据截图和每个截图对应的 JSON 数据文件都保存在SCREENSHOT_PATH目录下。比如我们的活动页面 URL 有固定的格式,只有 path 的最后一部分不同,表示一个活动 ID。所以在该目录下,每个活动页面对应 3 个文件。比如对应7月4日的 ID 为 6098 的活动页面,有以下几个文件:

  1. 2017-07-04-6098.js
  2. 2017-07-04-6098.json
  3. 2017-07-04-6098.png

第一个 JS 文件就是数据加工后的那段注入脚本,也对其进行了保存,PNG 图片就是最终的热力图,如下:


热力图示例

这里需要注意一点,在普通服务器上,因为普遍没有安装 X Server,导致无法直接执行热力图脚本来通过 Nightmare 跑热力图。可以通过安装 XVFB,执行xvfb -a命令来执行脚本。

2.2.3 热力图平台

最后,为了让产品等同学快速按天查看对应活动的热力图,自己搭建了一个热力图平台,筛选界面如下:

输入活动 ID,选择对应的日期,即可展示对应热力图。还可以在热力图上任意框选一个范围,系统会弹窗显示框选区域内的实际点击数。主要的处理流程为:

  1. 用户点击“查询”按钮后,根据活动 ID 和日期参数请求 Node 接口;
  2. 接口根据参数判断该日期对应的热力图是否已经截取,如果已绘制,则拼接出SCREENSHOT_PATH目录下 PNG 截图的访问链接(通过 Nginx 配置),并且读取对应热力图的加工数据文件,一并返回给平台;如果未绘制,则限制性热力图绘制脚本,绘制对应热力图,然后执行上述逻辑;
  3. 平台获取图片链接后进行替换展示,并在图片上方覆盖一个透明遮罩元素,监听该元素上的mousedown / mousemove / mouseup3个事件,根据用户框选用 Canvas 绘制半透明区域,并得到该区域的的起始和结束横纵坐标;
  4. 筛选出接口返回坐标数据中,位于框选区域的点,并计算出总数;
  5. 弹窗提示。

最终弹窗展示效果如下:

浏览器端主要代码如下:

Node 层接口的主要代码如下:

其中picasso.js就是热力图脚本,这里要注意的是在 Node 层,对于同一个热力图,应避免重复跑热力图绘制脚本,造成系统资源浪费和图片覆盖。

3. 结论

目前通过热力图系统,已经可以了解到一些用户行为,来帮助产品运营和设计做决策:

  1. 资源位在屏幕上的位置和资源位内容,哪个更容易影响用户点击;
  2. 是否有展示型元素,因为样式的原因,导致用户误以为是按钮频繁进行点击;
  3. 是否“更多”文案的按钮被用户频繁点击,能否将隐藏的内容进行展开,减少用户操作;
  4. 页面多少屏以后的内容就已经没有(或很少)点击,从而缩减页面长度,提升页面性能。

以上就是一个简单热力图系统完整的开发思路,后续也会持续优化。

注:转载注明出处并联系作者,本文链接:https://nodefe.com/heatmap-system/

发表评论

评论

To create code blocks or other preformatted text, indent by four spaces:

    This will be displayed in a monospaced font. The first four 
    spaces will be stripped off, but all other whitespace
    will be preserved.
    
    Markdown is turned off in code blocks:
     [This is not a link](http://example.com)

To create not a block, but an inline code span, use backticks:

Here is some inline `code`.

For more help see http://daringfireball.net/projects/markdown/syntax