当前位置: 首页 > news >正文

前端埋点系统之如何用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.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)中是各变量是“互斥”的——缺点就是不够“包容”&#xff…...

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>框&#xff0c;你可以使用JavaScript。这通常可以通过查询DOM&#xff08;文档对象模型&#xff09;来实现&#xff0c;有几种方法可以做到这一点&#xff0c;包括使用document.querySelectorAll、document.getElementsByTagName或document.get…...

0-ARM Linux驱动开发-字符设备

一、字符设备概述 Linux 系统中&#xff0c;设备被分为字符设备、块设备和网络设备等。字符设备以字节流的方式进行数据传输&#xff0c;数据的访问是按顺序的&#xff0c;一个字节一个字节地进行读取和写入操作&#xff0c;没有缓冲区。例如&#xff0c;终端&#xff08;/dev…...

使用 Faster Whisper 和 Gradio 实现实时语音转文字

随着人工智能技术的进步&#xff0c;语音识别已经成为最热门的研究领域之一。如何实现高效、准确的实时语音转文字功能&#xff0c;是许多开发者关注的重点。本文将介绍如何使用 Faster Whisper 和 Gradio 这两个强大工具&#xff0c;快速构建一个实时语音转文字应用。 Faster…...

redis v6.0.16 安装 基于Ubuntu 22.04

redis安装 基于Ubuntu 22.04 本文演示如何在ubuntu22.04下&#xff0c;安装redis v6.0.16&#xff0c;并配置测试远程访问。 Step1 更新环境 sudo apt updateStep2 安装redis sudo apt install redis-server -yStep3 启动 sudo systemctl restart redissudo systemctl sta…...

Milvus - 内存索引类型详解

1. 背景概述 在大规模数据处理和向量相似性搜索场景中&#xff0c;内存索引的使用显著提升了查询速度和效率。Milvus 提供了多种内存索引类型&#xff0c;以满足不同场景下的性能需求。本文将介绍 Milvus 支持的各种内存索引类型及其适用场景、配置参数和使用方法。 2. 为什么…...

日语AI面试高效通关秘籍:专业解读与青柚面试智能助攻

在如今就业市场竞争日益激烈的背景下&#xff0c;越来越多的求职者将目光投向了日本及中日双语岗位。但是&#xff0c;一场日语面试往往让许多人感到步履维艰。你是否也曾因为面试官抛出的“刁钻问题”而心生畏惧&#xff1f;面对生疏的日语交流环境&#xff0c;即便提前恶补了…...

STM32标准库-DMA直接存储器存取

文章目录 一、DMA1.1简介1.2存储器映像1.3DMA框图1.4DMA基本结构1.5DMA请求1.6数据宽度与对齐1.7数据转运DMA1.8ADC扫描模式DMA 二、数据转运DMA2.1接线图2.2代码2.3相关API 一、DMA 1.1简介 DMA&#xff08;Direct Memory Access&#xff09;直接存储器存取 DMA可以提供外设…...

【快手拥抱开源】通过快手团队开源的 KwaiCoder-AutoThink-preview 解锁大语言模型的潜力

引言&#xff1a; 在人工智能快速发展的浪潮中&#xff0c;快手Kwaipilot团队推出的 KwaiCoder-AutoThink-preview 具有里程碑意义——这是首个公开的AutoThink大语言模型&#xff08;LLM&#xff09;。该模型代表着该领域的重大突破&#xff0c;通过独特方式融合思考与非思考…...

自然语言处理——Transformer

自然语言处理——Transformer 自注意力机制多头注意力机制Transformer 虽然循环神经网络可以对具有序列特性的数据非常有效&#xff0c;它能挖掘数据中的时序信息以及语义信息&#xff0c;但是它有一个很大的缺陷——很难并行化。 我们可以考虑用CNN来替代RNN&#xff0c;但是…...

Spring是如何解决Bean的循环依赖:三级缓存机制

1、什么是 Bean 的循环依赖 在 Spring框架中,Bean 的循环依赖是指多个 Bean 之间‌互相持有对方引用‌,形成闭环依赖关系的现象。 多个 Bean 的依赖关系构成环形链路,例如: 双向依赖:Bean A 依赖 Bean B,同时 Bean B 也依赖 Bean A(A↔B)。链条循环: Bean A → Bean…...

Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习)

Aspose.PDF 限制绕过方案&#xff1a;Java 字节码技术实战分享&#xff08;仅供学习&#xff09; 一、Aspose.PDF 简介二、说明&#xff08;⚠️仅供学习与研究使用&#xff09;三、技术流程总览四、准备工作1. 下载 Jar 包2. Maven 项目依赖配置 五、字节码修改实现代码&#…...

招商蛇口 | 执笔CID,启幕低密生活新境

作为中国城市生长的力量&#xff0c;招商蛇口以“美好生活承载者”为使命&#xff0c;深耕全球111座城市&#xff0c;以央企担当匠造时代理想人居。从深圳湾的开拓基因到西安高新CID的战略落子&#xff0c;招商蛇口始终与城市发展同频共振&#xff0c;以建筑诠释对土地与生活的…...

scikit-learn机器学习

# 同时添加如下代码, 这样每次环境(kernel)启动的时候只要运行下方代码即可: # Also add the following code, # so that every time the environment (kernel) starts, # just run the following code: import sys sys.path.append(/home/aistudio/external-libraries)机…...

【学习笔记】erase 删除顺序迭代器后迭代器失效的解决方案

目录 使用 erase 返回值继续迭代使用索引进行遍历 我们知道类似 vector 的顺序迭代器被删除后&#xff0c;迭代器会失效&#xff0c;因为顺序迭代器在内存中是连续存储的&#xff0c;元素删除后&#xff0c;后续元素会前移。 但一些场景中&#xff0c;我们又需要在执行删除操作…...

Python竞赛环境搭建全攻略

Python环境搭建竞赛技术文章大纲 竞赛背景与意义 竞赛的目的与价值Python在竞赛中的应用场景环境搭建对竞赛效率的影响 竞赛环境需求分析 常见竞赛类型&#xff08;算法、数据分析、机器学习等&#xff09;不同竞赛对Python版本及库的要求硬件与操作系统的兼容性问题 Pyth…...