前端埋点系统之如何用heatmap.js画网页热力图

时间:2024-11-04 16:59:57

Hello,大家好。在当今数字化时代,理解用户行为成为了企业成功的关键之一。随着互联网的发展,用户与网站、应用和产品的互动变得愈发复杂而多样化。在这样的背景下,埋点系统成为了洞察用户行为的重要工具之一。而其中的热力图分析,则更加直观的帮助我们分析用户的喜好。

之前我们介绍了什么是热力图,以及它如何成为理解用户行为的有力工具。今天我们从技术的角度来看,如何实现热力图效果呢?

热力图是埋点系统必不可少的一项能力,可以来看看Webfunny一体化埋点系统的效果

一、有哪些免费工具可以实现热力图效果

热力图主要的实现方式,还是利用目前现有的开源工具,如:百度的echarts、阿里系的G2、还有就是我们今天要说的heatmap.js。

当然,你如果有兴趣和精力,可以自己手搓一个,原理也不是特别复杂,关于热力图的实现原理:

一般可大致归纳为以下几个步骤:

  1. 为每个数据点设置一个从中心向外灰度渐变的圆;

  2. 利用灰度可以叠加的原理,计算每个像素点数据交叉叠加得到的灰度值;

  3. 根据每个像素计算得到的灰度值,在一条彩色色带中进行颜色映射,最后对图像进行着色,得到热力图。

百度echarts的热力图效果

在这里插入图片描述

阿里系的G2热力图效果

在这里插入图片描述

heatmap.js热力图效果

在这里插入图片描述

根据效果来看,其中G2和heatmap.js的热力图效果都比较符合我们的使用场景,heatmap.js已经处理好颜色效果了,所以最后选择了heatmap.js。

二、如何采集网页上的热力数据

热力图在我们的印象中,主要的使用场景是在地图上,比如哪个地方温度,就会呈现红色,哪个地方的温度低就会呈现蓝色。而我们今天要做的是,采集网页上的热力图,主要包含三个指标的数据。

网页上又没有温度,哪来的热力值呢,他们分别是:鼠标点击量、鼠标停留时长、页面元素曝光时长

1. 点击热力数据
点击量热力图和好理解,点击的越多,热力值则越高。采集方式是通过监听全局点击事件,需要采集的几个主要的指标有:

页面地址:这个是用来确定是哪个页面的;

页面宽度/高度:这个是用来确定页面尺寸的;

鼠标点击位置(x坐标,y坐标):这个是用来锁定页面坐标,计算热力值的;

/**
 * 启动点击事件监听
 */
export function startClickRecord() {
  window.addEventListener('click',function(e){
    console.log('触发点击事件!', e)
    if (!e) return

    /** 检查定时器是否开启 */
    if (store.timerStatus === 'off') {
      console.log('定时器已结束,触发鼠标点击,则重新开启')
      store.timerStatus = 'on';
      startGlobalTimer()
    }

    try {
      const scrollWidth = (document.body ? document.body.scrollWidth : 0) || window.innerWidth
      const weTitle = document.title
      const wePath = Utils.b64Code(Utils.getPath())
      const weFullPath = Utils.b64Code(Utils.getPath('full'))
      const weScrollWidth = scrollWidth - scrollWidth % 20
      const weScrollHeigh = (document.body ? document.body.scrollHeight : 0) || window.innerHeight
      const weXPath = Utils.getXPath(e)
      const wePageX = e.pageX
      const wePageY = e.pageY
      // const weScrollX = window.scrollX
      // const weScrollY = window.scrollY
      const weRatio = parseInt(window.devicePixelRatio)

      // 上报点击数据
      const data = {weTitle, wePath, weScrollWidth, weScrollHeigh, weXPath, weFullPath, wePageX, wePageY, weRatio}
      webfunnyGlobal.webfunnyEvent('Webfunny-Replace-HeatMapClickPointId').trackEvent(data);
    } catch(e) {
      console.error('click error', e)
    }
    
  }, true);
}

2. 鼠标停留时长热力图
点击量是直接反映用户达成的目标,鼠标停留时间则是反应了用户的兴趣之所在,也很重要。停留和点击的热力图很相似,只是停留时长的热力图数据会更密集一些。

鼠标停留时间的采集方式跟点击类似,通过监听mousemove事件进行采集,需要采集的几个主要的指标有:

页面地址:这个是用来确定是哪个页面的;

页面宽度/高度:这个是用来确定页面尺寸的;

鼠标点击位置(x坐标,y坐标):这个是用来锁定页面坐标,计算热力值的;

停留时间:这是一个关键性指标,这个值的上限是一个不确定的,但是它的上限对热力值的影响很大。

例如:如何设置很高,就会影响热力的准确性,用户鼠标放在哪里不动,那个点的热力值就会很高,其实他只有一个用户,并不能反馈出有价值的数据;如果设置很低,又无法反馈用户真是的停留时长了,所以这里的做成动态配置的最好。

建议:用户量小的应用,这个上限值设置低一些, 如:1000ms,因为个别用户会造成较大影响;用户量大的应用,设置稍微偏高些,2000ms,大量的用户会让拉平整体的数据,让数据趋于准确。

/**
 * 启动鼠标停留事件监听
 */
export function startMousemoveRecord() {
  console.log('全埋点,鼠标停留时长,启动')
  let timer
  window.addEventListener('mousemove',function(e){
    if (!e) return
    if (timer) {
      clearTimeout(timer)
    }
    /** 检查定时器是否开启 */
    if (store.timerStatus === 'off') {
      console.log('定时器已结束,触发鼠标移动,则重新开启')
      store.timerStatus = 'on';
      startGlobalTimer()
    }
    timer = setTimeout(() => {
      try {

        const weTitle = document.title
        const scrollWidth = (document.body ? document.body.scrollWidth : 0) || window.innerWidth
        const wePath = Utils.b64Code(Utils.getPath())
        const weFullPath = Utils.b64Code(Utils.getPath('full'))
        const weScrollWidth = scrollWidth - scrollWidth % 20
        const weScrollHeigh = (document.body ? document.body.scrollHeight : 0) || window.innerHeight
        const weXPath = Utils.getXPath(e)
        const wePageX = e.pageX
        const wePageY = e.pageY
        // const weScrollX = window.scrollX
        // const weScrollY = window.scrollY
        const weRatio = parseInt(window.devicePixelRatio)
        
        const mousemoveInfo = {weTitle, wePath, weScrollWidth, weScrollHeigh, weXPath, weFullPath, wePageX, wePageY, weRatio}
        // 鼠标移动停留生效
        console.log('鼠标移动停留生效:', store.mouseStayInfo)
        if (!store.mouseStayInfo) {
          // 如果没有记录信息,直接存入内存中
          store.mouseStayInfo = { ...mousemoveInfo, startTime: new Date().getTime() }
        } else {
          // 如果有记录信息,就需要将之前的停留信息放进任务队列,并重新记录当前有效的停留
          const nowTime = new Date().getTime()
          const { startTime = 0 } = store.mouseStayInfo || {}
          let timeDiff = nowTime - startTime
          console.log('鼠标距离上次时间差:', timeDiff, wePageX, wePageY)
          if (timeDiff > 150) {
            // 如果停留时间超过上限, 则默认为上限时间
            timeDiff = timeDiff > COMMON_FIELD.MOUSE_STAY_LIMIT ? COMMON_FIELD.MOUSE_STAY_LIMIT : timeDiff
            const tempMousemoveInfo = { ...mousemoveInfo, stayTime: timeDiff }
            // TaskQueue.addTask(config.trackUrl, tempMousemoveInfo)
            // 上报鼠标移动数据
            console.log('即将执行上报', tempMousemoveInfo)
            webfunnyGlobal.webfunnyEvent('Webfunny-Replace-HeatMapStopPointId').trackEvent(tempMousemoveInfo);
            store.mouseStayInfo = { ...mousemoveInfo, startTime: nowTime }
          }
        }
      } catch(e) {
        console.error('mousemove error: ', e)
      }
    }, 200)
  }, true);

  // 鼠标离开浏览器后,需要清理历史数据,延迟1s,防止
  window.addEventListener('mouseout', function(e){
    var tagName = e.target.tagName ? e.target.tagName.toLowerCase() : ''
    if (tagName === 'html') {
      setTimeout(function() {
        store.mouseStayInfo = ''
        console.log('鼠标移出了浏览器, 清理鼠标停留数据', store.mouseStayInfo)
      }, 1000)
    }
  }, true)
}

三、如何将热力图覆盖到网页上呢

热力数据采集到了,怎么才能将它们正确的放到网页上呢。

其实也简单,上层是heatmap.js生成的热力效果图;下层是iframe,显示的是网页内容,这样热力图效果就呈现出来了。

在这里插入图片描述

需要注意的是:网页会滚动,宽度也不同,鼠标停留和点击的位置需要取相对位置,而不是绝对位置

四、heatmap.js生成热力图代码

生成heatmap对象,并将热力值数据一个个填充进去就可以了。

heatmap的配置项有很多,下方是我试验出来比较简单的配置项。

安装依赖:npm install heatmap.js --save

import h337 from “heatmap.js”

// 查找元素
const heatEle = document.getElementById(this.state.heatId)
heatmapInstance = h337.create({
container: heatEle,
// radius: 30,
// maxOpacity: 0.7

      radius: 20, // 点的半径
      // maxOpacity: 0.8, // 最大不透明度
      // minOpacity: 0.2, // 最小不透明度
      blur: 0.75, // 模糊半径
      useLocalExtrema: true, // 是否使用局部极值
    });

    this.props.dataList.forEach((item) => {
      heatmapInstance.addData({
        x: item.x,
        y: item.y,
        value: item.value * 100
      });
    })

五、热力图细节优化

heatmap的热力效果虽然不错,但是我并未在文档中找到能提示热力值的API,这就有点尴尬了,因为热力图效果虽然好,但是没有热力值提示,总会让人觉得缺点什么

没办法,只有手动加一个了;虽然heatmap没有提供tip提示,但是却提供了获取热力值的API,这下就简单多了,添加鼠标移动事件,在鼠标位置上方添加div显示热力值,不要忘记延时显示和添加防抖哦

在这里插入图片描述

代码如下:

let mousemoveTimer = 0
        heatEle.addEventListener("mousemove", (e) => {
          const { offsetX, offsetY } = e
          const heatTipCon = heatmapInstance.getValueAt({ x: offsetX, y: offsetY })

          if (mousemoveTimer) {
            clearTimeout(mousemoveTimer)
            mousemoveTimer = setTimeout(() => {
              this.setState({heatTipCon: heatTipCon / 100, heatTipX: offsetX, heatTipY: offsetY - 20})
            }, 300)
          } else {
            mousemoveTimer = setTimeout(() => {
              this.setState({heatTipCon: heatTipCon / 100, heatTipX: offsetX, heatTipY: offsetY - 20})
            }, 300)
          }
        })

以上是如何利用heatmap实现热力图效果介绍,感兴趣的同学可以直接访问webfunny前端监控和前端埋点系统
在这里插入图片描述