前端埋点系统之如何用heatmap.js画网页热力图
Hello,大家好。在当今数字化时代,理解用户行为成为了企业成功的关键之一。随着互联网的发展,用户与网站、应用和产品的互动变得愈发复杂而多样化。在这样的背景下,埋点系统成为了洞察用户行为的重要工具之一。而其中的热力图分析,则更加直观的帮助我们分析用户的喜好。
之前我们介绍了什么是热力图,以及它如何成为理解用户行为的有力工具。今天我们从技术的角度来看,如何实现热力图效果呢?
热力图是埋点系统必不可少的一项能力,可以来看看Webfunny一体化埋点系统的效果
一、有哪些免费工具可以实现热力图效果
热力图主要的实现方式,还是利用目前现有的开源工具,如:百度的echarts、阿里系的G2、还有就是我们今天要说的heatmap.js。
当然,你如果有兴趣和精力,可以自己手搓一个,原理也不是特别复杂,关于热力图的实现原理:
一般可大致归纳为以下几个步骤:
-
为每个数据点设置一个从中心向外灰度渐变的圆;
-
利用灰度可以叠加的原理,计算每个像素点数据交叉叠加得到的灰度值;
-
根据每个像素计算得到的灰度值,在一条彩色色带中进行颜色映射,最后对图像进行着色,得到热力图。
百度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.innerWidthconst weTitle = document.titleconst wePath = Utils.b64Code(Utils.getPath())const weFullPath = Utils.b64Code(Utils.getPath('full'))const weScrollWidth = scrollWidth - scrollWidth % 20const weScrollHeigh = (document.body ? document.body.scrollHeight : 0) || window.innerHeightconst weXPath = Utils.getXPath(e)const wePageX = e.pageXconst wePageY = e.pageY// const weScrollX = window.scrollX// const weScrollY = window.scrollYconst 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 timerwindow.addEventListener('mousemove',function(e){if (!e) returnif (timer) {clearTimeout(timer)}/** 检查定时器是否开启 */if (store.timerStatus === 'off') {console.log('定时器已结束,触发鼠标移动,则重新开启')store.timerStatus = 'on';startGlobalTimer()}timer = setTimeout(() => {try {const weTitle = document.titleconst scrollWidth = (document.body ? document.body.scrollWidth : 0) || window.innerWidthconst wePath = Utils.b64Code(Utils.getPath())const weFullPath = Utils.b64Code(Utils.getPath('full'))const weScrollWidth = scrollWidth - scrollWidth % 20const weScrollHeigh = (document.body ? document.body.scrollHeight : 0) || window.innerHeightconst weXPath = Utils.getXPath(e)const wePageX = e.pageXconst wePageY = e.pageY// const weScrollX = window.scrollX// const weScrollY = window.scrollYconst 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 - startTimeconsole.log('鼠标距离上次时间差:', timeDiff, wePageX, wePageY)if (timeDiff > 150) {// 如果停留时间超过上限, 则默认为上限时间timeDiff = timeDiff > COMMON_FIELD.MOUSE_STAY_LIMIT ? COMMON_FIELD.MOUSE_STAY_LIMIT : timeDiffconst 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 = 0heatEle.addEventListener("mousemove", (e) => {const { offsetX, offsetY } = econst 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前端监控和前端埋点系统

相关文章:
前端埋点系统之如何用heatmap.js画网页热力图
Hello,大家好。在当今数字化时代,理解用户行为成为了企业成功的关键之一。随着互联网的发展,用户与网站、应用和产品的互动变得愈发复杂而多样化。在这样的背景下,埋点系统成为了洞察用户行为的重要工具之一。而其中的热力图分析&…...
CentOS 7系统下Redis Cluster集群一键部署脚本发布
引言 在大数据和云计算时代,Redis作为一款高性能的键值存储数据库,广泛应用于各种场景。然而,手动搭建Redis Cluster集群过程繁琐且容易出错。为了简化这一过程,本文提供了一个在CentOS 7系统下Redis Cluster集群的一键部署脚本,帮助开发者快速搭建Redis Cluster集群。 …...
自编以e为底的对数函数ln,性能接近标准库函数
算法描述: (1). 先做自变量x的范围检查,不能出现负数和0. 自己使用时,如果能通过其它途径保证自变量为正,那么可以省略这两个判断,提高速度。 (2). 根据IEEE 754浮点数的格式,,则 ln(x)kln(2)ln…...
Java中的日期时间
JDK8之前常用的日期时间类 System.currentTimeMillis():获取当前毫秒数(long类型) java.util.Date:通用Date类 import java.util.Date;Date date new Date(); // 空参构造器 System.out.println(date.getTime()); // 获取当前时…...
位置编码的表示
位置编码的表示位置编码的表示位置编码的表示位置编码的表示位置编码的表示...
0,国产FPGA(紫光同创)-新建PDS工程
国产FPGA正在蓬勃发展,紫光同创FPGA是大家竞赛时经常遇到的一款国产FPGA,本专栏从IP核开始一直到后续图像处理等。 开发板:盘古50K标准板 1,新建PDS工程 点击File(1),然后是New Projects&#…...
c++联合
结构体与联合体的区别 结构体(struct)中所有变量是“共存”的——优点是“有容乃大”,全面;缺点是struct内存空间的分配是粗放的,不管用不用,全分配。 而联合体(union)中是各变量是“互斥”的——缺点就是不够“包容”ÿ…...
Edit Data. Create Cell Editors. Validate User Input 编辑数据。创建 Cell Editors。验证用户输入
Goto Data Grid 数据网格 Edit Data. Create Cell Editors. Validate User Input 编辑数据。创建 Cell Editors。验证用户输入 Get and Modify Cell Values in Code 在代码中获取和修改单元格值 仅当 Grid 及其列已完全初始化时,才使用以下方法。如果需要在表单仍…...
Java 文件操作与IO流
文件 文件有两个概念,在广义来看就是操作系统上对硬件和软件资源抽象为文件。 在侠义上来看,就是我们保存在硬盘上的文件 在这里我们讨论的是狭义的文件,在外面的硬盘上的文件细分又可以分为二进制文件和文本文件,文本文件可以通…...
探索开源MiniMind项目:让大语言模型不再神秘(1)
简介: 声明:本人非此项目作者,仅仅是探索项目,分享项目。如有不妥,请联系我删除! 原项目地址:GitHub - jingyaogong/minimind: 「大模型」3小时完全从0训练26M的小参数GPT,个人显卡即…...
Android 大疆面经
Android 大疆面经 文章目录 Android 大疆面经一面 一面 自我介绍问项目聊了10分钟View的绘制流程MVC,MVP,MVVM的区别view和viewmodel的通信,除了databing还有其他的方式面向对象和面向过程的区别工厂模式和策略模式,哪些框架使用…...
【2024-10-31-2024-11-03】LeetCode刷题——python语法基础题
📝前言说明: ●本专栏主要记录本人的基础算法学习以及LeetCode刷题记录,主要跟随B站作者灵茶山的视频进行学习,专栏中一篇文章为B站对应的一个视频 题目主要为B站视频内涉及的题目以及B站视频中提到的“课后作业”。 ●文章中的理…...
【算法】二分查找
目录 一、概念 二、思路 三、边界问题 一、概念 在一本书中查找某一页,我们总是倾向于先翻到整本书的中间,然后根据当前页数判断我们想要找的页在当前页的左半本中还是右半本中,接着继续翻到剩下半本书的中间...... 这就是二分查找思想在…...
第十五章 Vue工程化开发及Vue CLI脚手架
目录 一、引言 二、Vue CLI 基本介绍 三、安装Vue CLI 3.1. 安装npm和yarn 3.2. 安装Vue CLI 3.3. 查看 Vue 版本 四、创建启动工程 4.1. 创建项目架子 4.2. 启动工程 五、脚手架目录文件介绍 六、核心文件讲解 6.1. index.html 6.2. main.js 6.3. App.vue 一、…...
【Grafana】Grafana 基础入门
Grafana 简介 什么是Grafana Grafana 是一跨平台的开源的可视化分析工具,是目前网络架构和应用分析中最流行的时序数据展示工具,主要用于大规模指标数据的可视化展示。 它是用Go语言开发,可以做数据监控和数据统计,带有告警功能…...
如何获取页面上所有input框
要获取页面上所有的<input>框,你可以使用JavaScript。这通常可以通过查询DOM(文档对象模型)来实现,有几种方法可以做到这一点,包括使用document.querySelectorAll、document.getElementsByTagName或document.get…...
0-ARM Linux驱动开发-字符设备
一、字符设备概述 Linux 系统中,设备被分为字符设备、块设备和网络设备等。字符设备以字节流的方式进行数据传输,数据的访问是按顺序的,一个字节一个字节地进行读取和写入操作,没有缓冲区。例如,终端(/dev…...
使用 Faster Whisper 和 Gradio 实现实时语音转文字
随着人工智能技术的进步,语音识别已经成为最热门的研究领域之一。如何实现高效、准确的实时语音转文字功能,是许多开发者关注的重点。本文将介绍如何使用 Faster Whisper 和 Gradio 这两个强大工具,快速构建一个实时语音转文字应用。 Faster…...
redis v6.0.16 安装 基于Ubuntu 22.04
redis安装 基于Ubuntu 22.04 本文演示如何在ubuntu22.04下,安装redis v6.0.16,并配置测试远程访问。 Step1 更新环境 sudo apt updateStep2 安装redis sudo apt install redis-server -yStep3 启动 sudo systemctl restart redissudo systemctl sta…...
Milvus - 内存索引类型详解
1. 背景概述 在大规模数据处理和向量相似性搜索场景中,内存索引的使用显著提升了查询速度和效率。Milvus 提供了多种内存索引类型,以满足不同场景下的性能需求。本文将介绍 Milvus 支持的各种内存索引类型及其适用场景、配置参数和使用方法。 2. 为什么…...
深入剖析AI大模型:大模型时代的 Prompt 工程全解析
今天聊的内容,我认为是AI开发里面非常重要的内容。它在AI开发里无处不在,当你对 AI 助手说 "用李白的风格写一首关于人工智能的诗",或者让翻译模型 "将这段合同翻译成商务日语" 时,输入的这句话就是 Prompt。…...
springboot 百货中心供应链管理系统小程序
一、前言 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,百货中心供应链管理系统被用户普遍使用,为方…...
8k长序列建模,蛋白质语言模型Prot42仅利用目标蛋白序列即可生成高亲和力结合剂
蛋白质结合剂(如抗体、抑制肽)在疾病诊断、成像分析及靶向药物递送等关键场景中发挥着不可替代的作用。传统上,高特异性蛋白质结合剂的开发高度依赖噬菌体展示、定向进化等实验技术,但这类方法普遍面临资源消耗巨大、研发周期冗长…...
Cesium1.95中高性能加载1500个点
一、基本方式: 图标使用.png比.svg性能要好 <template><div id"cesiumContainer"></div><div class"toolbar"><button id"resetButton">重新生成点</button><span id"countDisplay&qu…...
相机从app启动流程
一、流程框架图 二、具体流程分析 1、得到cameralist和对应的静态信息 目录如下: 重点代码分析: 启动相机前,先要通过getCameraIdList获取camera的个数以及id,然后可以通过getCameraCharacteristics获取对应id camera的capabilities(静态信息)进行一些openCamera前的…...
NPOI Excel用OLE对象的形式插入文件附件以及插入图片
static void Main(string[] args) {XlsWithObjData();Console.WriteLine("输出完成"); }static void XlsWithObjData() {// 创建工作簿和单元格,只有HSSFWorkbook,XSSFWorkbook不可以HSSFWorkbook workbook new HSSFWorkbook();HSSFSheet sheet (HSSFSheet)workboo…...
MySQL 主从同步异常处理
阅读原文:https://www.xiaozaoshu.top/articles/mysql-m-s-update-pk MySQL 做双主,遇到的这个错误: Could not execute Update_rows event on table ... Error_code: 1032是 MySQL 主从复制时的经典错误之一,通常表示ÿ…...
Spring Boot + MyBatis 集成支付宝支付流程
Spring Boot MyBatis 集成支付宝支付流程 核心流程 商户系统生成订单调用支付宝创建预支付订单用户跳转支付宝完成支付支付宝异步通知支付结果商户处理支付结果更新订单状态支付宝同步跳转回商户页面 代码实现示例(电脑网站支付) 1. 添加依赖 <!…...
React从基础入门到高级实战:React 实战项目 - 项目五:微前端与模块化架构
React 实战项目:微前端与模块化架构 欢迎来到 React 开发教程专栏 的第 30 篇!在前 29 篇文章中,我们从 React 的基础概念逐步深入到高级技巧,涵盖了组件设计、状态管理、路由配置、性能优化和企业级应用等核心内容。这一次&…...
密码学基础——SM4算法
博客主页:christine-rr-CSDN博客 专栏主页:密码学 📌 【今日更新】📌 对称密码算法——SM4 目录 一、国密SM系列算法概述 二、SM4算法 2.1算法背景 2.2算法特点 2.3 基本部件 2.3.1 S盒 2.3.2 非线性变换 编辑…...
