ECharts Tooltip 配置完全指南:从 trigger 到 formatter 再到样式调优
发布时间:2026/10/2 20:04:15来源:尧图网络
1. 先选对triggertooltip的大方向才不会错很多新手拿到echarts文档第一步就跳去配formatter结果配了半天发现鼠标悬停时要么不弹要么弹出来的内容和预期完全不一样。我一般会先反问一句你的trigger选的是什么trigger是tooltip的触发策略它决定了tooltip在什么情况下出现、一次出现会展示哪些数据也直接决定了后面formatter回调函数拿到的参数到底是对象还是数组。这几样东西一旦选错后面怎么写都是错的。1.1 item触发适合“点对点”查看的图表trigger: item表示当鼠标悬停到具体的图形元素上时才触发tooltip。典型的场景包括饼图的某个扇区、地图上的某个区域、散点图里的某个散点以及折线图上的某个数据点。拿饼图举例鼠标滑到“华东区域”这一块时tooltip只显示“华东区域”对应的数值和百分比不会把旁边“华南区域”的数据也带进来。这其实非常符合人类阅读习惯饼图和地图本身是“看局部”的图表用户关心的是“这一块是什么、占比多少”。如果在这种图表上强行用axis触发效果反而很怪因为你没有坐标轴可供联动。item触发的formatter参数相对简单回调里拿到的params是单个对象直接访问params.name、params.value、params.percent就能拼出想要的内容。1.2 axis触发多系列同屏对比时的首选trigger: axis则是沿着坐标轴方向触发。只要鼠标停留在绘图区某个位置echarts会自动把该位置对应的类目或坐标值下的所有系列数据全部取出来按系列排列显示。最常见的例子就是折线图和柱状图尤其是那种一条图里有“本周销售额”“上周销售额”两条折线x轴是周一到周日的情况。鼠标移到周三这块区域时axis触发的tooltip会同时显示本周三和上周三的销售额用户不需要把鼠标精确移到某条线上对比起来效率高很多。反过来如果在这种多系列对比图中用item触发用户就得手动hover两条折线才能凑齐信息体验差得不是一点半点。所以选择trigger的本质是看你希望用户用tooltip来“看单个点”还是“看一组关联数据”。1.3 trigger直接决定了formatter参数的“形态”这一点我觉得是最值得先了解的。同一个formatter函数在item触发下params是一个普通对象比如{ componentType: series, seriesType: line, seriesName: 本周销售额, name: 周三, value: 12000, color: #5470c6, dataIndex: 3, data: { ... } }但换成axis触发之后params变成了数组数组里每一项对应一个series当前的数据节点。如果你拿写item触发的逻辑去处理axis触发比如直接访问params.seriesName一定会拿到undefined因为数组根本没有seriesName这个属性。所以我平时写formatter的第一步就是做一次统一formatter: function (params) { const list Array.isArray(params) ? params : [params]; // 到这里统一按数组处理 }这样不管配置哪种trigger都能兼容后面写逻辑也不用担心数据类型炸掉。如果团队里有人喜欢把tooltip封装成公共配置这一行判断能免掉大量“为什么我这个图表不显示”的排查时间。2. formatter写不对tooltip等于白弹formatter是tooltip用来生成内容的“渲染函数”也是整个tooltip配置里最能折腾的地方。它的作用就是决定弹窗里最终显示什么字符串、什么HTML结构。echarts官方给了两种方式模板字符串和回调函数两种方式各有适用场景我在项目里基本都用回调因为灵活度更高。2.1 模板字符串里的a、b、c、d怎么用如果你图省事模板字符串是最快的。最常用的几个占位符如下占位符含义典型来源{a}系列名称 seriesName多条折线图的图例名{b}数据名称一般是类目名或扇区名饼图扇区、x轴刻度{c}数据值series里的value{d}百分比饼图里有实际意义饼图扇区占比举个实际例子饼图的tooltip如果要用“名称数值百分比”一行模板就够了tooltip: { trigger: item, formatter: {b}{c} 人{d}% }这个配置的效果就是弹出“华东区域3200 人32%”简洁明了。折线图如果用axis触发也可以靠{a}、{c}把每个系列的数值循环渲染出来。但我要提醒一句模板字符串适合内容格式非常固定的场景一旦涉及条件判断、单位换算、日期格式化模板字符串就不够用了得换回调函数。2.2 回调函数先判断参数再拼HTML回调函数的写法是formatter: function (params) { return 自定义内容; }很多场景下我们需要给tooltip里的每一项加上系列对应的颜色小圆点让用户能通过颜色对应到图例。在回调函数里可以直接使用params.marker它就是带html标签的彩色小圆点。我经常用它拼一个比较“正规”的tooltipformatter: function (params) { const list Array.isArray(params) ? params : [params]; let html div stylefont-weight:600;margin-bottom:6px; (list[0].axisValue || list[0].name) /div; list.forEach(function (item) { html div item.marker item.seriesName item.value 台/div; }); return html; }这段代码在折线图、柱状图里效果很稳第一行显示类目比如“周三”下面每行一个系列带对应颜色圆点数值统一带上单位“台”。不过在做这个拼接的时候有个小细节item触发时params不是数组所以先用Array.isArray判断包成数组list[0].axisValue在item触发时不一定存在所以要加个或条件回退到list[0].name。这种边缘情况在真实项目里非常常见多写一步能少踩一个坑。2.3 项目里我会怎么封装一个通用formatter业务中一般不会只画一个图表所以我习惯提取一个通用函数function tooltipFormatter(prefix , suffix ) { return function (params) { const list Array.isArray(params) ? params : [params]; const rows list.map(function (item) { let value item.value; // value可能是数组比如箱线图、散点图只取最后一个维度 if (Array.isArray(value)) { value value[value.length - 1]; } const num Number(value); const display isNaN(num) ? value : num.toLocaleString(zh-CN); return item.marker item.seriesName prefix display suffix; }); return rows.join(br/); }; }用的时候只要这样挂上去tooltip: { trigger: axis, formatter: tooltipFormatter(¥, 万元) }这类封装最大的好处是不同图表只要传不同的前缀后缀就能复用一套逻辑不用每个option里都重写一遍formatter。等后面客户要求“数值超过一万显示成1.2万”“日期转成YYYY-MM-DD”时也只需要改动这一处方法不用逐个图表去找。还有一点值得养成习惯formatter里尽量不要写太重的DOM操作也不要去修改图表外的全局变量不然鼠标一移动触发频率一高很容易卡顿。3. 把tooltip当成一个小型UI组件来调tooltip的内容搞定了下面就是长相。默认样式真的是“能用就行”放到正式项目里基本都会被吐槽。好在样式相关的配置项都是明明白白摆在那里的照着调就行。3.1 最常用的样式参数配置项说明推荐值backgroundColor背景色rgba(255,255,255,0.96)borderColor边框颜色#409EFFborderWidth边框宽度1padding内边距[12, 16]textStyle.color文字颜色#333textStyle.fontSize文字大小12或14textStyle.fontWeight字重500一套典型的定制化tooltip配置长这样tooltip: { trigger: axis, backgroundColor: rgba(255,255,255,0.96), borderColor: #409EFF, borderWidth: 1, padding: [12, 16], textStyle: { color: #333, fontSize: 14, fontWeight: 500 } }配上前面写的formatter弹窗的观感立刻和页面统一起来。这里我想多说一句backgroundColor很多人喜欢用纯白但在浅色背景的页面上rgba(255,255,255,0.9)这种半透明会更柔和也不会完全挡住底下的图表。如果你做的是深色大屏可以换成rgba(20,30,50,0.9)配合白色的textStyle整体质感会好很多。3.2 extraCssText是“补丁神器”有些效果用常规配置项做不出来比如圆角、阴影、投影、最大宽度。官方留了个后门extraCssText。这个字段会把额外的CSS字符串直接拼接到tooltip容器上。tooltip: { ...其他配置, extraCssText: box-shadow: 0 4px 12px rgba(0,0,0,0.15); border-radius: 8px; max-width: 280px; }我经常在数据大屏里靠它实现“阴影圆角限宽”比去翻源码改样式类方便多了。要注意的是extraCssText里的CSS优先级不一定永远高于其他配置项如果遇到冲突可以再加!important。另外如果设置了appendToBodytooltip容器被挪到body下面此时受全局样式影响的可能性会增加extraCssText这时候也成了兜底方案尽量把该写的都写全。3.3 位置定位position、confine、appendToBodytooltip默认跟手走但有时我们需要让它固定在某一个位置或者防止它在图表边缘被切掉一半。这里面有三个配置最常被问到。先看position它是函数时可以拿到当前鼠标位置然后返回一个坐标数组tooltip: { position: function (point, params, dom, rect, size) { // point就是鼠标位置比如 [120, 300] return [point[0] 10, point[1] 10]; } }再看confine设置true时tooltip会被限制在图表容器内部不会跑到容器外面去。页面边缘的图表很需要它不然弹窗可能一半露在外面观感很差。最后是appendToBody这个配置项在echarts 5.x里支持。当图表被放在overflow: hidden的容器或者某些弹窗、横向滚动容器里时tooltip默认依附于图表容器很可能被裁剪掉。设置appendToBody: true后tooltip的DOM会被挪到body下一般能绕开裁剪问题。代价是它的定位逻辑不再和图表容器百分百绑定在复杂滚动场景里可能出现偏移需要自己再调一调。3.4 enterable让用户能悬停在tooltip上这个配置项知道的人不多但很实用。enterable: true允许鼠标从图形上挪进tooltip内部而不关闭。如果tooltip内部放了可复制的内容、链接或者表单控件就必须把它设成true。默认是false鼠标一离开图形tooltip就消失了想复制一段数值都难。我最早做报表的时候客户想复制tooltip里的一组订单号怎么都复制不了后来查文档才发现是enterable的问题。设成true之后再配合transitionDuration: 0.2这种过渡时间体验会好很多。4. 折线图、饼图、地图tooltip各有各的套路很多人在网上搜“echarts tooltip自定义”搜到的都是一张折线图的例子套到饼图或地图上就失灵了。其实不是代码错而是不同图表形态对tooltip的需求本来就不一样需要针对性调整。4.1 折线图和柱状图轴模式加多系列展示折线图和柱状图的data通常都是数组x轴有类目series有多个。最重要的两个点一是trigger要用axis二是formatter里别只显示一个series否则用户看多系列对比图还得一个个hover体验很割裂。我做一个用户增长报表时折线图的tooltip就处理成多行结构tooltip: { trigger: axis, confine: true, formatter: function (params) { const date params[0].axisValue; let html div stylemargin-bottom:6px;b date /b/div; params.forEach(function (item) { html div styledisplay:flex;align-items:center;gap:6px;line-height:1.8; item.marker item.seriesName item.value /div; }); return html; } }如果series特别多建议在item.marker后面用style控制行间距否则弹窗会非常挤。行数多到超出容器时配合extraCssText里的max-height和overflow-y:auto还能做出可滚动tooltip这个技巧在做监控大盘时尤其好用因为同一时间点上的指标线路可能很多可滚动tooltip能避免弹窗占满整个屏幕。4.2 饼图与环形图数值和占比都不缺饼图的核心信息是占比所以formatter里至少要有名称、数值、百分比三项。模板字符串是{b}{c}{d}%回调函数则用params.percent。要注意percent显示的小数位数不是固定的如果你希望固定到一位小数可以自己在回调里算formatter: function (params) { const total 10000; // 这个值从series.data求和得到 const pct ((params.value / total) * 100).toFixed(1); return params.name br/数值 params.value br/占比 pct %; }环形图还有个常见需求鼠标放到中间空洞上方时不触发tooltip因为那个区域没有任何数据弹一个空白tooltip出来很尴尬。这可以在series里设置silent: true或者用graphic元素把中间区域挡住看具体需求选。我记得有个比较笨的写法是在formatter里判断params.dataIndex undefined就返回空字符串也能达到隐藏效果但不如silent干净。4.3 地图场景空数据地区不能显示undefined地图系列用tooltip也很频繁首先要保证trigger是item其次formatter要处理一个特殊情况某个区域没有数据时value可能是null或undefined。我见过不少页面在hover到无数据地区时tooltip弹出“某地区undefined”瞬间暴露没做兜底。tooltip: { trigger: item, formatter: function (params) { if (params.value null) { return params.name 暂无数据; } return params.name br/数值 params.value; } }另外如果是用geo组件加series.map的组合需要注意geo自带的tooltip和series的tooltip不要同时开启不然可能同时弹出两个弹窗。我的惯例是只在series.map上配tooltipgeo只负责展示地图底色和边框这样逻辑清晰也好排查问题。注册地图数据时如果用的是自己整理的GeoJSON还要检查一下区域名是否和series.data里的name完全一致不然tooltip里的name会显示成undefined之外的异常内容。4.4 散点图和热力图注意value是数组散点图和热力图的数据项常常是[x, y, size]这种数组结构。这时候如果直接在formatter里拿params.value拼字符串会拼出“12, 34, 56”这种难看的样式。我一般把取数逻辑剥离出来function getDisplayValue(value) { if (Array.isArray(value)) { return value[value.length - 1]; } return value; }严格来说箱线图也会遇到类似情况。自己封装formatter的时候统一处理一下能省很多后续麻烦。比如在散点图里数据点的value可能是[15, 20]但用户想看的是最终的数值20而不是整个坐标数组如果不做提取tooltip看起来就是“某系列15,20”非常不专业。5. 实战里绕不开的四个tooltip坑前几节讲的是配置方法紧跟着我把这几年踩过的坑整理一遍基本都是“不遇到会懵遇到后恍然大悟”的类型写出来帮大家少走点弯路。5.1 长文本为什么不换行tooltip默认使用的是HTML渲染其实只要在formatter里拼就能换行。但有时候文本包含空格或者写进了一些样式结果不管加多少都挤在一行里。这种情况多半是extraCssText或全局样式把white-space改成了nowrap。解决办法是在extraCssText里加white-space: normal; word-break: break-all;。如果还是不行检查页面里是不是有全局样式统一设置了div的white-space或word-break因为appendToBody之后tooltip容器不在图表内部更容易被全局样式误伤。5.2 tooltip跑出屏幕外一半看不见位置在页面右侧或底部的图表默认tooltip很可能会超出视口看起来就像被切了一刀。最简单的两个方案一是设置confine: true让tooltip限制在图表区域内二是用position回调自己算位置。我自己更常用confine它不用操心底层计算图表容器本来也就是一块占位div范围完全够用。只有在需要工具提示严格跟随鼠标移动时才会用position回调比如position: function (point) { const x point[0]; const y point[1]; return { left: x 10, top: y 10 }; }要提醒的是position回调返回的坐标是相对图表容器的不是相对页面的。如果页面是滚动型布局位置会跟随滚动发生变化需要结合容器当前所在位置重新计算。这点在大屏页面里尤其突出因为大屏通常有滚动或多层嵌套不仔细算的话tooltip很容易漂移。5.3 大屏缩放时tooltip字号不跟着rem走很多人在大屏项目里用过pxtorem或者transform: scale来做整体缩放图表本身处理得很完美唯独tooltip里的文字字号固死了和整个页面比例不协调。原因是echarts的tooltip DOM虽然由canvas生成但最终样式会被容器或默认样式覆盖而canvas内部字体用的是像素值和rem没有联动关系。解决办法不复杂在tooltip.textStyle.fontSize里写一个固定像素值而不是依赖rem。如果大屏是按设计稿比例缩放的可以直接根据缩放比动态设置fontSize。比如设计稿宽度是1920当前窗口宽度是1440那就乘个0.75。另一个做法是每次resize时把tooltip销毁重建但这种做法会有闪烁效果成本也高我一般不推荐除非项目里暂时没有别的办法。实际项目里我更推荐一个字号换算工具函数统一传给各图表的tooltip比在每个配置里手写要省心。5.4 tooltip刷新不及时或卡顿动态更新数据时我见过有人反复调用chart.setOption(option)传整个option结果tooltip里的数值不更新或者鼠标稍微动一下就卡一下。这通常不是tooltip配置的问题而是setOption方式太粗暴。正确做法是初始化时传完整option后续更新只传变化的部分chart.setOption({ series: [{ data: newData }] });这样tooltip会随着series数据更新自动重新绑定性能也好很多。如果某些场景必须重新设置整个option可以先chart.clear()再重新init避免旧配置残留。这类残留问题很隐蔽表现形式是数据明明变了但tooltip还显示上一轮的旧值。还有一类卡顿出现在数据量特别大的折线图里。tooltip在鼠标移动时会频繁触发formatter如果formatter内部每次都做复杂的字符串拼接或DOM查询可能拖慢帧率。我的习惯是把单位换算、日期格式化提前算好放在data里formatter只做读取和拼接尽量减少计算量。真要面对几十万数据的极端场景还可以考虑开启sampling或减少tooltip的showDelay但那些属于性能调优的进阶话题了。最后分享一个调试技巧调整tooltip样式时就别一遍遍hover触发了直接把alwaysShowContent: true开起来它会让你不移动鼠标也能看到tooltip常驻在页面上改样式秒级见效。等调完确认效果后再关掉调试效率能翻一倍。
网站建设高端定制企业官网