前端埋点系统之如何用heatmap.js画网页热力图(一)
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 }); })
五、热力图细节优化
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) } })

浙公网安备 33010602011771号