上周有做数据看板的朋友发来一段录屏:柱状图鼠标一移上去,旁边几根柱子就像被人拿橡皮擦抹了一样迅速变淡,鼠标移开又恢复。他反复确认数据没问题、series 也没写错,最后在配置里翻了半天才发现,问题出在 emphasis 里的一个 focus 字段。这个场景在 ECharts 柱状图里太常见了——"hover 时柱子不隐藏"听起来是个小需求,往下挖却牵着 ECharts 5 引入的图形状态系统、坐标轴指示器的绘制方式,以及不同版本之间配置项的合并规则。网上搜"echarts柱状图hover 柱子不隐藏",大多只给一句focus: 'none',看完还是不知道该往哪改、为什么改、改完会不会引出新问题。我打算把这件事从头捋一遍:先讲清楚 hover 时柱子到底被谁动了手脚,再逐项拆开控制视觉的配置项,最后给一套可以直接抄的完整方案和排查表。无论你是刚接触 ECharts 的新手,还是天天调图表的老手,这篇应该都能帮你少走几趟弯路。
1. 先定位问题:hover 时柱子究竟"消失"在哪一步
很多开发者一上来就改颜色、改透明度,方向就错了。柱子不会凭空消失,它只会在某个状态下被赋予了另一套样式。ECharts 的图形渲染本质上是"数据映射成图元,图元再按状态切换样式",搞清楚状态切换的规则,问题就解决了一半。
1.1 ECharts 5 的图形状态机:normal、emphasis、blur、select
ECharts 5 之后,每一个图形元素(柱子、扇区、散点、折线节点)都维护着自己的状态。最常打交道的四个状态是:
- normal:常态,没有交互时的样子。
- emphasis:强调态,鼠标 hover 或调用
dispatchAction({ type: 'highlight' })时进入。 - blur:模糊态,被其他元素的聚焦行为"连累"时进入,视觉上通常表现为透明度降低、颜色变灰。
- select:选中态,点击后进入,需要配合
selectedMode使用。
关键在于,emphasis 和 blur 是一对联动状态。当你 hover 某个柱子让它进入 emphasis 时,ECharts 会根据配置决定要不要让"别的元素"进入 blur。这个"要不要"和"别的是谁",正是focus与blurScope两个字段在控制。很多人以为 hover 时柱子"消失"是透明度被改成了 0,实际上它只是被推进了 blur 状态,拿到了一套默认更淡的样式。理解这一点,后面所有配置的意义就都通了。
1.2 四类"柱子不见了"的真实成因
我把实际项目里遇到过的情形归了归类,大致是四种,外观相似但根因完全不同:
| 现象 | 真实成因 | 高发配置位置 |
|---|---|---|
| hover 一个柱子,其余柱子整体变淡、发灰 | 其他元素被推入 blur 状态 | series.emphasis.focus、series.blurScope |
| 柱子没变淡,但被一层半透明灰色矩形盖住 | 坐标轴指示器用了 shadow 类型 | tooltip.axisPointer.type、axisPointer.type |
| 柱子颜色突然变成接近背景色的浅色,视觉上"融合"了 | emphasis 默认高亮色与主题撞色 | series.emphasis.itemStyle.color |
| 堆叠柱里某一层整条变色,像是被抽走 | focus 设置为 series 级别,整条系列被强调 | series.emphasis.focus |
第一种和第四种是同一套机制的不同表现,第二种完全是另一个维度的东西——它根本不是状态切换,而是 ECharts 在类目轴上画了一个矩形指示器。第三种则常出现在暗色大屏里,默认的高亮色是白色或浅色,柱子在深色背景上看起来就"没了"。分清这四类,你才知道该改哪一行代码。
1.3 默认配置到底做了什么
很多人误以为 ECharts 默认就会隐藏其他柱子,其实不完全是。在 ECharts 5 的默认配置下,emphasis.focus的值是'none',意思是 hover 某个柱子时,其他柱子不会进入 blur 状态,视觉上只是当前柱子略微高亮。真正让柱子"消失"的,往往是下面几种情况:
- 项目模板、二次封装的图表组件里,有人为了"好看"统一加了
emphasis: { focus: 'series' },或者更狠的focus: 'self'。 - 用了带主题的图表库或设计系统,主题文件里预设了 focus 配置。
- 从旧版本迁移过来,ECharts 4 的 emphasis 写法和 5 的状态系统不完全兼容,配置合并后产生了意料之外的效果。
- tooltip 的 axisPointer 默认就是
'line',但有人为了"更醒目"改成'shadow',于是出现了遮盖感。
所以排查的第一步永远是:把当前生效的 option 打印出来看,而不是凭记忆猜。chart.getOption()拿到的是合并后的完整配置,比人眼扫源码靠谱得多。
2. 核心配置拆解:控制 hover 视觉的五个开关
定位清楚之后,改造就变成了配置层面的精调。下面这五个配置项基本覆盖了 90% 的"柱子不隐藏"需求,我按重要性排了个序,逐个说清楚它管什么、可选值有哪些、改了会有什么副作用。
2.1 emphasis.focus:决定谁被聚焦、谁被冷落
focus是整个状态联动的总开关,它定义的是"hover 时聚焦范围有多大"。取值和效果如下:
| 取值 | 含义 | 典型表现 |
|---|---|---|
'none' | 不聚焦任何其他元素 | 只有当前柱子高亮,其他柱子原样不动 |
'self' | 只聚焦当前图形元素 | 当前柱子高亮,其余柱子进入 blur、变淡 |
'series' | 聚焦当前系列的所有图形元素 | 同系列柱子整体保持,其他系列进入 blur |
'ancestor' | 聚焦所有祖先节点 | 常用于树图、旭日图 |
'descendant' | 聚焦所有后代节点 | 同上 |
对于"hover 时柱子不隐藏"这个诉求,正确取值就是'none'。这是最直接、副作用最小的解法。如果你的图表是单系列柱状图,把focus设成'self'或'series',hover 时的表现其实差不多——都是其他柱子被推进 blur、整体变淡,用户看到的就是"柱子隐藏了"。
需要提醒的是,focus的影响范围不止于同系列内部。多系列柱状图里,focus: 'series'会让非当前系列的所有柱子一起变淡,这种"整组消失"的观感比单系列更强烈,也更容易被误判为渲染 bug。
注意:
focus属于emphasis的子配置,写在series层级里对当前系列生效,写在全局emphasis里则对所有系列生效。写之前想清楚作用域,别在全局写了一刀切又在系列里覆盖,最后自己也搞不清生效的是哪一层。
2.2 blurScope:blur 状态能蔓延多远
如果说focus决定"要不要拉别人下水",blurScope决定的就是"水能漫到哪"。它只在 focus 不为'none'时才有意义,取值有三个:
'coordinateSystem':默认值。同一个坐标系内的其他系列都会受影响。'series':只有同一个系列内的元素受影响,跨系列不受牵连。'global':全局生效,图表里所有同类型图形都可能被波及。
实际项目里有个典型场景:一个图表里既有柱状图又有折线图,共用同一个类目轴。如果 focus 设成了 series 且 blurScope 保持默认的'coordinateSystem',hover 柱子时折线可能也会变淡,整个图看起来"缺了一块"。这时候把 blurScope 改成'series',影响范围就收窄了。
但反过来说,如果你的诉求就是"完全别动其他元素",那根本不用调 blurScope,直接把 focus 设为'none'更干脆。blurScope 是给那些"确实需要聚焦效果,但希望控制蔓延范围"的场景准备的,别把它当成解决隐藏问题的主力。
2.3 blur.itemStyle:给淡出效果兜底
有时候你没法改 focus——比如图表组件是第三方封装的,focus 被锁死在配置深处,或者业务上确实需要聚焦效果但不想让柱子"消失"。这时候可以从 blur 状态样式入手,给被淡化的柱子做兜底。
ECharts 允许在 series 级别或者全局级别配置 blur 状态下的样式:
series: [{ type: 'bar', data: [120, 200, 150, 80, 70, 110], blur: { itemStyle: { opacity: 1, // 不透明,保住柱子本体 color: '#93c5fd' // 只换颜色,不降低可见度 }, label: { opacity: 1 // 标签也保持可读 } } }]这段配置的意思是:就算某个柱子被推进了 blur 状态,它也保持完全不透明,颜色略作区分。视觉上用户仍然能看出"哪根被聚焦了",但不会产生"柱子消失"的错觉。这招特别适合大屏和汇报场景,因为观众往往来不及反应,柱子一淡反而以为数据缺了。
实操心得:
opacity和color一起调效果最好。只把 opacity 调回 1 的话,blur 状态和 normal 状态几乎没区别,用户反而感知不到 hover 反馈;加上颜色区分,既有交互感又不破坏整体可读性。
2.4 emphasis.disabled 与 selectedMode:直接关掉或换一种交互
如果你的图表压根不需要 hover 高亮——比如纯静态展示、截图导出场景,或者鼠标交互会干扰读数——可以直接把 emphasis 状态整个关掉。ECharts 5.3 之后支持:
series: [{ type: 'bar', emphasis: { disabled: true } }]设为 true 之后,hover 完全不会触发高亮,柱子保持原样,tooltip 不受影响。这是最彻底的方案,代价是失去 hover 的视觉反馈,用户可能不知道图表是可交互的,需要配合 tooltip 的样式补偿。
另一条路是改用selectedMode,把交互从"hover 高亮"迁移到"点击选中"。用户点击某根柱子,它进入 selected 状态,其他柱子不受影响:
series: [{ type: 'bar', selectedMode: 'single', // 或 'multiple' select: { itemStyle: { borderWidth: 2, borderColor: '#1d4ed8' } } }]点击选中这套交互在数据看板里其实更稳,因为鼠标划过时不会产生视觉抖动,读数体验更专注。
2.5 axisPointer.type:别让阴影盖住柱子
前面说过,有一类"柱子消失"跟状态系统毫无关系,纯粹是坐标轴指示器在作怪。ECharts 的axisPointer有三种类型:'line'、'shadow'、'none'。'shadow'会在当前类目位置画一块半透明矩形,覆盖在柱子上面。柱子本身没变,但被盖了一层灰,视觉上就像"消失"了。
触发这个配置有两个入口,容易搞混:
// 入口一:tooltip 内部的 axisPointer tooltip: { trigger: 'axis', axisPointer: { type: 'line' // 改成 'line' 或 'none' } } // 入口二:顶层 axisPointer,全局坐标轴指示器 axisPointer: { type: 'line' }两个入口的优先级不同:tooltip.axisPointer只影响 tooltip 触发时的指示器,顶层axisPointer影响的是所有场景下的指示器。如果只改了一个而另一个还是 shadow,阴影依然会出现,这也是很多人在排查时反复修改却不生效的原因。
如果你希望 hover 时完全没有任何视觉遮盖,把两处都设为'none'最保险。当然,'none'也会让用户失去"当前在哪个类目"的定位提示,这时候可以在柱子自身加 hover 边框来补偿,后面实操部分会给出具体写法。
3. 实操:一套"hover 不隐藏柱子"的完整配置
理论讲完,下面直接给可跑的方案。我按单系列、多系列、堆叠、自定义交互四个场景分别配置,每一份都能直接贴进 ECharts 官方的在线编辑器验证效果。
3.1 单系列基础柱状图
这是最基础的形态,目标是 hover 时只让当前柱子轻微高亮,其余柱子纹丝不动。完整配置如下:
option = { tooltip: { trigger: 'axis', axisPointer: { type: 'line' // 不用 shadow,避免遮盖 } }, grid: { left: 48, right: 24, top: 40, bottom: 40 }, xAxis: { type: 'category', data: ['一月', '二月', '三月', '四月', '五月', '六月'], axisTick: { alignWithLabel: true } }, yAxis: { type: 'value' }, series: [{ name: '订单量', type: 'bar', barWidth: '46%', data: [120, 200, 150, 80, 70, 110], itemStyle: { color: '#3b82f6', borderRadius: [4, 4, 0, 0] }, emphasis: { focus: 'none', // 关键:不让其他柱子进入 blur itemStyle: { color: '#2563eb', // 只加深当前柱子的颜色 borderWidth: 1, borderColor: '#1e40af' } }, blur: { itemStyle: { opacity: 1 // 双保险:即便进入 blur 也不透明 } } }] };这段配置里有三个关键点。focus: 'none'是主开关,blur.itemStyle.opacity: 1是兜底,axisPointer.type: 'line'是切断遮盖来源。三者组合,hover 时只有当前柱子颜色变深、描边出现,其他柱子保持原样,用户既知道鼠标在哪,又不会误以为数据缺失。
barWidth设置成百分比而不是固定像素,是为了让图表在不同宽度下自适应,这一点在做响应式看板时很重要。固定像素在宽屏下会显得柱子细长、间距过大,在窄屏下又会挤在一起。
3.2 多系列与堆叠柱状图的差异化处理
多系列柱状图的坑在于系列之间会互相影响。默认的 blurScope 是'coordinateSystem',hover 一个系列的柱子时,其他系列的柱子也会被推入 blur。这在对比类图表里非常常见,也是"柱子隐藏"反馈最多的地方。
解决办法有两种。第一种是简单粗暴地设focus: 'none',全局都不聚焦,各系列互不影响。第二种是保留聚焦效果但收窄范围,适合"确实想让用户关注某个系列"的场景:
series: [ { name: '线上', type: 'bar', data: [120, 200, 150, 80, 70, 110], stack: 'total', emphasis: { focus: 'self', // 只聚焦当前柱子 itemStyle: { color: '#2563eb' } }, blur: { itemStyle: { opacity: 1 } } }, { name: '线下', type: 'bar', data: [60, 80, 90, 50, 40, 70], stack: 'total', emphasis: { focus: 'self', itemStyle: { color: '#f97316' } }, blur: { itemStyle: { opacity: 1 } } } ]注意这里每个系列都得写一遍blur兜底。series 级别的配置不会自动继承到其他系列,把兜底写在全局emphasis里也未必生效,因为 blur 状态的样式匹配是按元素来的。与其纠结继承规则,不如显式写两遍,虽然啰嗦但可控。
堆叠柱状图还有一个特殊现象:hover 某一层时,如果 focus 设成了'series',同一层的所有柱子都会保持高亮,其他层则整体变淡,看起来像"整条柱子被抽掉一层"。所以堆叠场景强烈建议使用'self'或'none',不要用'series'。
3.3 用边框与阴影替代变色,hover 更克制
有些业务场景对颜色很敏感,比如品牌色不能变、配色有严格的视觉规范。这时候可以让 hover 效果完全走边框和阴影,颜色保持不变:
series: [{ type: 'bar', data: [120, 200, 150, 80, 70, 110], itemStyle: { color: '#3b82f6', borderRadius: 6 }, emphasis: { focus: 'none', itemStyle: { color: '#3b82f6', // 颜色保持原值 borderWidth: 2, borderColor: '#1e40af', shadowBlur: 12, shadowColor: 'rgba(37, 99, 235, 0.45)' } } }]边框加阴影这套组合在暗色主题下尤其好用。因为深色背景里颜色变化不容易被察觉,但光晕和描边非常醒目。配置时注意shadowColor用带透明度的 rgba,直接用 hex 会显得很生硬,阴影边缘会发黑。
还有一个细节:borderRadius要写在itemStyle的普通配置里,不必在 emphasis 里重复。ECharts 的状态样式是叠加的,emphasis 里只写需要变化的部分即可,没写的属性会沿用 normal 状态的设置。
3.4 框架封装与实例销毁的注意点
在 Vue 或 React 项目里,图表通常会被封装成组件。封装时有两个地方和"hover 柱子不隐藏"直接相关。
第一个是配置合并。很多团队会写一个基础配置对象,再用Object.assign或展开运算符和业务配置合并。这种浅合并会把嵌套的emphasis整个覆盖掉,如果业务配置里只写了emphasis: { itemStyle: { color: 'xx' } },基础配置里的focus: 'none'就丢了,hover 行为又回到默认。稳妥的做法是用深合并,或者把状态相关的配置单独抽成一个对象,在合并后显式赋值。
第二个是实例复用。图表容器在路由切换、组件卸载时如果没有调用chart.dispose(),旧实例的状态可能残留,再次挂载时 hover 表现异常。我见过一个案例,页面来回切换两次之后,hover 突然开始隐藏其他柱子,查了半天是上一个实例的 emphasis 状态没清干净。
// React 中的典型写法 useEffect(() => { const chart = echarts.init(ref.current); chart.setOption(option, { notMerge: true }); // 关键:不合并旧配置 return () => { chart.dispose(); // 关键:销毁实例 }; }, [option]);notMerge: true这个参数很多人不知道。默认情况下setOption是合并模式,新的 option 会和老配置逐层合并。如果上一份配置里有focus: 'series',新配置没写这个字段,它就留着了。加notMerge: true可以让每次设置都是全量替换,避免这类幽灵配置。
4. 常见问题速查与排查手法
配置写对不代表一劳永逸,真实项目里还有一堆边角情况。下面这张表是我这几年攒下来的高频问题清单,基本覆盖了"柱子不隐藏"这个诉求下的绝大多数分支。
4.1 现象-原因-处理对照表
| 问题现象 | 可能的根因 | 处理方式 |
|---|---|---|
| hover 时其他柱子变淡 | focus为'self'或'series' | 改为'none',或补blur.itemStyle.opacity: 1 |
| hover 时其他系列整体消失 | blurScope为'coordinateSystem' | 改focus: 'none',或把 blurScope 收窄到'series' |
| 柱子上像蒙了一层灰 | axisPointer.type为'shadow' | 两处入口都改为'line'或'none' |
| 只有某个系列有异常 | 该系列单独覆盖了 emphasis 配置 | 用getOption()检查合并结果 |
| 切换页面后行为变了 | 实例未销毁,旧状态残留 | 卸载时dispose(),设置时加notMerge |
| 导出图片后柱子变淡 | 导出时保留了 hover 状态 | 导出前dispatchAction({ type: 'downplay' }) |
| 暗色主题下看不见高亮 | 默认高亮色与背景撞色 | 显式指定emphasis.itemStyle.color |
这张表建议收藏,下次遇到类似症状可以直接对照排查,比在配置里大海捞针快得多。尤其要注意最后两条,它们的触发条件比较隐蔽,很多人会误以为是渲染引擎的问题。
4.2 用 dispatchAction 快速复现与验证
排查状态类问题,最快的手段是用 API 手动触发,不依赖鼠标操作。ECharts 提供了highlight和downplay两个 action:
// 让第 3 根柱子进入 emphasis 状态 chart.dispatchAction({ type: 'highlight', seriesIndex: 0, dataIndex: 2 }); // 取消所有强调 chart.dispatchAction({ type: 'downplay', seriesIndex: 0 });这两行代码的价值在于,它们不受鼠标位置影响,可以在控制台反复执行,稳定复现问题。你可以先highlight某个柱子,截图对比其他柱子的颜色;再改一个配置项,重新highlight,看变化是否如预期。整个调试过程不碰鼠标,效率高很多。
还有一个更土但有效的办法:临时把blur.itemStyle.opacity设成 0.1,让被淡化的柱子变得极其明显。这样一眼就能看出到底有没有柱子进入 blur 状态,从而判断问题出在状态系统还是指示器遮盖。确认之后再把值改回来。
4.3 版本与配置合并带来的坑
ECharts 4 到 5 的状态系统改动不小。4.x 里 emphasis 的配置项比较零散,没有 blur 和 select,focus 的概念也不完整。如果项目里有旧代码,或者用了某个年代久远的图表模板,配置在新版本里可能表现异常。
另一个高频坑是 CDN 引入和 npm 引入的版本不一致。页面里同时加载了两个版本的 ECharts,后者覆盖前者,你以为改的是 A 版本的配置,实际生效的是 B 版本,怎么调都没反应。排查时打开控制台执行echarts.version,确认版本号和你预期的一致。
至于配置合并,除了前面提到的setOption合并模式,还有主题文件的干扰。ECharts 的主题可以预设emphasis相关样式,业务配置如果没显式覆盖,主题里的设定就会生效。判断方法很简单:把主题临时去掉,看问题是否消失。如果消失了,说明主题里有预设,在业务配置里显式覆盖对应字段即可。
5. 进阶:自定义 hover 交互的三种思路
标准配置能解决大部分问题,但如果业务对交互有更细腻的要求——比如 hover 时显示额外的标记、联动其他图表、或者根据数据动态改变高亮样式——就需要往事件层面走。
5.1 事件监听手动控制高亮
ECharts 提供了mouseover和mouseout事件,参数里带着当前元素的系列索引、数据索引和数据值。你可以在事件里做任意逻辑:
chart.on('mouseover', { seriesType: 'bar' }, function (params) { // params 里能拿到 dataIndex、seriesIndex、value 等 if (params.value > 200) { // 超过阈值的柱子,hover 时加红色描边 chart.setOption({ series: [{ emphasis: { focus: 'none', itemStyle: { borderWidth: 2, borderColor: '#dc2626' } } }] }); } });这种写法适合"数据本身有语义"的场景,比如超标的柱子和正常的柱子应该有不同的 hover 反馈。注意事件绑定要在setOption之后,且只在初始化时绑定一次,不要在每次数据更新时重复chart.on,否则会绑定多个监听器,一次 hover 触发多次回调,性能直接崩掉。
另一个细节是事件过滤。chart.on的第二个参数是过滤条件,写成{ seriesType: 'bar' }可以只监听柱状图元素,避开坐标轴、图例等区域的误触发。不写过滤条件的话,鼠标划过空白区域也会触发事件,逻辑里就得额外判断params.componentType。
5.2 tooltip formatter 与坐标轴指示器的配合
关闭阴影之后,用户失去了一部分定位反馈,这时候可以靠 tooltip 的内容来补偿。tooltip.formatter支持返回 HTML 字符串,可以把多个系列的信息整理成一张小表格:
tooltip: { trigger: 'axis', axisPointer: { type: 'line' }, formatter: function (params) { const title = params[0].axisValue; let html = `<div style="font-weight:600;margin-bottom:4px">${title}</div>`; params.forEach(item => { html += `<div style="display:flex;justify-content:space-between;gap:16px"> <span>${item.marker}${item.seriesName}</span> <span style="font-weight:600">${item.value}</span> </div>`; }); return html; } }item.marker是小圆点,颜色自动跟随系列色,省得自己配。这种 tooltip 在多系列对比时特别好用,信息密度高,且不受坐标轴指示器类型影响。
需要提醒的是,formatter 里返回的 HTML 要控制好宽度,内容太长会撑出容器。可以给外层 div 加个max-width并配合tooltip.extraCssText设置内边距和圆角,整体观感会更协调。
5.3 性能与可访问性的取舍
做 hover 交互时,性能是个容易被忽略的维度。数据量大的柱状图(几百根柱子),每次 hover 都触发样式重算和重绘,如果 emphasis 里写了阴影、模糊这类开销大的效果,帧率会明显下降。优化方向有三个:把shadowBlur控制在 8 以内、避免在 emphasis 里用linearGradient、数据超过 300 条时考虑用large: true模式。
可访问性方面,纯靠颜色变化传达 hover 状态对色盲用户不友好。更稳妥的做法是颜色变化加边框变化双重提示,或者配合 tooltip 的文字说明。图表如果要支持键盘操作,还得给 canvas 容器加tabindex并处理焦点事件,这部分内容比较多,这里先不展开。
最后提一个容易踩的坑:emphasis里配置的动画效果和animationDuration有关,如果全局动画时长设得很长(比如 1000ms),hover 高亮的过渡会显得拖沓,用户鼠标都移开了颜色还没到位。把emphasis.itemStyle相关的过渡控制在 150 到 250 毫秒之间,体感最舒服。
我个人在实际项目里的做法是:默认全部用focus: 'none'加边框高亮,只有在明确需要"聚焦对比"的场景才开启 focus,并且一定配 blur 兜底。这套组合用了两年多,没有再收到过"柱子怎么没了"的反馈。另外建议把 emphasis 和 blur 的配置抽成公共变量,多图表复用,改一处全局生效,比每个图表单独调省心太多。