ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

echarts-2.2.7离线内网部署与IE8兼容避坑指南

echarts-2.2.7离线内网部署与IE8兼容避坑指南

简介:echarts-2.2.7.zip 面向Web前端开发者、数据可视化学习者以及需要维护旧版ECharts项目的技术人员,提供ECharts 2.2.7这一经典JavaScript可视化库的源码与配套文档,用于快速搭建图表页面并理解早期版本的配置方式。压缩包约24.92MB;上游未提供有效文件清单,文件总数显示为0、类型明细暂无数据,因此内部构成需以实际解压为准,描述中提及库源码和相关文档。已有250人学习或下载,说明该版本仍有一定参考需求。读者可围绕柱状图、折线图、饼图、散点图、地图和仪表盘等图表类型,结合缩放平移、提示框、点击高亮等交互,以及异步数据加载与动态更新、JSON配置定制、IE8+与移动端兼容、插件扩展、性能优化、响应式设计和多语言支持等知识点进行实践,从而掌握2.2.7版API与常见可视化界面构建方法。

1. 一个还要兼容 IE8 的老后台,让我重新安装 echarts-2.2.7

第一次在离线内网里部署 echarts-2.2.7,是在一个还要兼容 IE8 的老后台项目上。压缩包解压完,里面只有目录和 JS 文件,没有 npm,没有构建脚本,也没有能访问的外网文档,前端同事只留下一句话:“放 static 下面,用 script 引进去就行。”

听起来简单,真正动手才发现,翻车点一个都不少:引错文件路径,页面控制台报echarts is not defined;容器只写了宽度没写高度,init拿到的画布是 0×0;照着新版文档把grid写成left/top,图能出来但布局全乱。这篇笔记按我自己的落地顺序来:先核对包里有什么,再决定用哪种引入方式,然后初始化、写 option,最后给一份能自查的验证页。适合还在维护老系统、做内网离线部署、或者被 3.x/4.x/5.x 文档绕晕后回头找 2.2.7 写法的人。

2. 解压 echarts-2.2.7.zip:先认清 build 与 src 两条路

老包最容易出问题的地方,是拿到手就急着往项目里拷一个echarts.js,拷完发现要么少图表类型,要么引的是源码入口,浏览器直接报模块找不到。echarts-2.2.7 这个包同时带了编译产物和模块源码,两条路对应两种工程形态,选错了后面全是补丁。

2.1 解压后的目录骨架怎么核对

我的习惯是先解压到临时目录,不直接往项目里放。原因很简单:老包没有package-lock.json这类锁定文件,也没有安装脚本帮你校验完整性,先看清结构再决定拷哪几个文件,比事后回滚省事。

# 解压到临时目录,先看结构,不动项目里的 static mkdir -p /tmp/echarts-check && cd /tmp/echarts-check unzip -q ~/Downloads/echarts-2.2.7.zip -d . # 只看两层目录,避免被 test 和 doc 里的海量文件刷屏 find . -maxdepth 2 -type d | sort # 找出体积较大的 js,通常就是完整构建产物 find . -name "*.js" -size +200k -exec ls -lh {} \;

-q是静默解压,不打印每个文件名;-d指定落盘目录;find -maxdepth 2限制层级;-size +200k用来快速定位完整构建文件,因为压缩版和未压缩版体积差得很明显。执行完,你基本能确认包内是不是有build、src、doc、test这几类目录。

按通用结构核对,各目录的用途和是否需要上线大致是这样:

目录/文件作用是否拷进项目
build/编译后的完整包,通常含压缩与未压缩两个版本是,生产只拷压缩版
src/按图表类型拆分的模块源码一般不动,除非工程已有模块加载器
doc/文档与示例页面否,可留本地当参考
test/测试页与临时用例否
package.json包元信息否

提示:不同渠道拿到的 zip 内部层级可能差一层,比如多套一个echarts-2.2.7/目录。判断标准不是路径名,而是build下有没有能直接<script>引入的完整文件。

我一般还会顺手记一下构建产物的校验值,部署到内网多台机器时用来确认拷的是同一份文件:

# 记录校验值,多机部署时比对,避免拷错版本 md5sum build/echarts.min.js

这一步看似多余,但内网机器之间靠 U 盘和跳板机传文件,传一半断掉是常事,比对一次能省掉后面排查“为什么这台机器图表类型少一个”的时间。

2.2 build 版直引与 src 版模块化引入的取舍

这是 echarts-2.2.7 最关键的一次选择。

build下是编译好的完整包,引入后全局挂一个echarts对象,所有图表类型都在里面。适合没有构建链、页面用模板引擎拼、或者干脆是静态 HTML 的老项目。缺点是体积大,即使你只画一个饼图,也会把地图、仪表盘这些全带上。

src下是按模块拆的源码,2.x 的模块粒度能细到图表类型,比如echarts/chart/line只引折线。它要求工程里已经有 RequireJS、SeaJS 这类模块加载器,并且路径映射配好。没有这套基础设施,硬上 src 只会多出一堆 404。

<!-- 路线一:build 直引,老项目首选,顺序不能反 --> <script src="/static/vendor/echarts/2.2.7/echarts.min.js"></script> <script src="/static/js/dashboard.js"></script>

第一行把echarts挂到window,第二行业务代码才能用。dashboard.js里不要写import,2.x 这个包不是 ES Module 产物,写了浏览器也不认。

// 路线二:src 加模块加载器,只在工程本来就有 AMD/CMD 时考虑 require.config({ baseUrl: '/static/vendor/echarts/2.2.7/src' }); // 用到哪个图表引哪个,别整包引,否则失去了拆包的意义 require(['echarts', 'echarts/chart/line', 'echarts/chart/pie'], function (echarts) { var chart = echarts.init(document.getElementById('visit-chart')); // 后续 setOption 与 build 版写法一致 });

两条路线的对比如下,选之前先看工程有没有模块加载器:

对比项build 直引src 模块化
前置条件无已有 AMD/CMD 加载器
体积全量,偏大按需,较小
上手成本低中,要配 baseUrl
升级风险换文件即可模块路径可能变

我的建议很直接:维护老系统就选 build。src 那套在 2.2.7 上不是给普通页面用的,踩进去大概率是路径映射和模块名对不上,收益却只有首屏少几十 KB。

2.3 静态资源写入位置与缓存参数

文件选好之后,放哪里也有讲究。带版本号的目录是我一直坚持的做法:

# 目录带版本号,升级时换路径,不靠刷缓存 mkdir -p /data/www/static/vendor/echarts/2.2.7 cp /tmp/echarts-check/build/echarts.min.js /data/www/static/vendor/echarts/2.2.7/

路径里写死2.2.7,意味着以后升到别的版本是新目录,老页面引用老路径不受影响,回滚也只是把引用改回去。比起在同名文件上覆盖再让运维刷 CDN,这种方式稳得多。

Nginx 侧给它长缓存,减少内网重复请求:

location /static/vendor/echarts/2.2.7/ { # 带版本号的目录内容不会变,可以放心长缓存 expires 180d; add_header Cache-Control "public"; }

expires 180d是让浏览器半年内不再回源;Cache-Control: public允许中间代理缓存。gzip指令实际要写在http或server块里,不要只塞进location,否则不生效。压缩对echarts.min.js这种文本 JS 收益很明显,内网带宽不紧张时也能减少首屏等待。

3. 初始化 echarts-2.2.7:从空 div 到第一张能看的图

文件放对了,只完成了一半。真正让图出来的是三件事:脚本顺序对、容器有尺寸、init拿到的 DOM 不为空。任何一件没做到,页面表现都是“白得干干净净”,但控制台报错各不相同。

3.1 script 引入顺序与全局 echarts 变量

2.2.7 不是模块化产物,它依赖脚本执行顺序。echarts.min.js必须先于业务脚本执行,且不能被async打乱。

<!-- 容器先占位,脚本放 body 末尾,避免 DOM 未就绪 --> <div id="visit-chart" style="width:100%;height:360px;"></div> <script src="/static/vendor/echarts/2.2.7/echarts.min.js"></script> <script> // 此时 window.echarts 已存在,可以直接 init var chart = echarts.init(document.getElementById('visit-chart')); window.__visitChart = chart; // 挂到全局,方便控制台调试 </script>

不要给echarts.min.js加defer或async。async是下载完立即执行,顺序不可控,业务脚本可能先跑,于是echarts是undefined;defer虽然保序,但在一些老浏览器上行为和预期不一致。放 body 末尾、按顺序写,是最不容易出问题的方式。

验证脚本是否真的加载成功,不看代码看网络面板:echarts.min.js必须是 200,响应体开头能看到版本注释。如果返回的是登录页 HTML,说明静态路径被鉴权拦截了,这种问题在后台系统里很常见。

3.2 容器高度是 echarts.init 返回空对象的头号原因

echarts.init在初始化时会读取容器的clientWidth和clientHeight,然后按这个尺寸建画布。容器高度为 0,画布就是 0×0,表现是页面上什么都没有,但 DOM 里确实多了一个 canvas。

/* 宽度可以用百分比,高度必须有确定值 */ #visit-chart { width: 100%; height: 360px; /* 不要指望内容把容器撑开 */ min-height: 240px; /* 小屏兜底,避免被 flex 压扁 */ }

canvas 不参与普通文档流的撑高,容器写height: auto就等于高度 0。父级如果是 flex 或 grid,还要注意align-items: stretch被改成flex-start后,子项高度会塌。

init的完整签名是echarts.init(dom, theme, opts),第二个参数是主题名,第三个参数里可以指定devicePixelRatio。在 retina 屏上图表发虚,多数是这个值没处理好:

// 高分屏下手动指定像素比,图表文字和线条会更清晰 var chart = echarts.init( document.getElementById('visit-chart'), null, { devicePixelRatio: window.devicePixelRatio || 1 } );

2.x 只有 canvas 渲染,没有 3.x 之后的可选渲染器参数,别把新文档里的renderer: 'svg'抄进来,它不认。

3.3 IE8 与低版本浏览器的兼容取舍

这个版本被留在老项目里,多半就是因为还要兼容低版本 IE。2.2.7 在低版本浏览器上需要 ES5 方法补齐,Object.keys、Array.prototype.forEach这些在 IE8 里没有。

<!-- 条件注释只在 IE9 以下生效,现代浏览器当普通注释忽略 --> <!--[if lt IE 9]> <script src="/static/vendor/es5-shim/es5-shim.min.js"></script> <![endif]-->

es5-shim要放在echarts.min.js之前,否则补齐来不及。另外还要确认项目里有没有 canvas 兼容层,IE8 本身不支持 canvas,没有这层兼容,图表区会是一片空白。

我的实际建议是:如果业务方还要求 IE8,先评估成本,再决定要不要接。补齐脚本能解决 API 缺失,但解决不了性能和 tooltip 错位,最后往往是图表能看、交互难用。真到了这一步,和业务方谈一个“低版本降级为表格”的方案,比硬扛更划算。

页面缩放和侧边栏收起时,图表不会自己适应,需要手动调resize:

// 窗口尺寸变化时重算画布,节流避免频繁重绘 var resizeTimer = null; window.addEventListener('resize', function () { clearTimeout(resizeTimer); resizeTimer = setTimeout(function () { window.__visitChart && window.__visitChart.resize(); }, 150); });

resize()会重新读容器尺寸并重绘。150ms 的延迟是经验值,能过滤掉拖拽窗口时的连续触发。图表实例不用时记得dispose(),尤其在单页应用里反复进出同一个页面,不销毁会一直占着 canvas 和事件。

4. 配置 echarts-2.2.7 的 option:坐标轴、series 和主题

2.2.7 的 option 结构和后面几个大版本有明显差异,最典型的是位置参数:这个版本用x、y、x2、y2,不用left、top、right、bottom。照着新文档写,图能出来,但布局怎么调都不对。

4.1 option 的四层结构与书写顺序

我把 option 分成四层来写,改的时候不容易乱:

层级常见键作用
全局层title、legend、toolbox标题、图例、工具栏
坐标系层grid、xAxis、yAxis、dataZoom决定画布内的绘图区域
系列层series数据与图表类型
交互层tooltip、事件回调悬浮提示与点击响应

书写顺序按这个来,先定坐标系再填数据,调布局时只需要动grid,不用在一大坨series里找位置参数。

4.2 折线图与饼图的最小可用配置

先看折线图。这是后台看板里最常见的形态,也是验证环境是否跑通最快的图形:

var option = { title: { text: '日活趋势', x: 'left' }, // 2.x 用 x/y 定位,不是 left/top tooltip: { trigger: 'axis' }, // axis 表示按坐标轴聚合提示 legend: { x: 'right', data: ['PC', '移动'] }, // 图例位置同样用 x grid: { x: 60, y: 60, x2: 40, y2: 50 }, // 四边距,单位像素 xAxis: [{ type: 'category', boundaryGap: false, // false 让折线贴住两端 data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'] }], yAxis: [{ type: 'value', name: '人次' }], series: [ { name: 'PC', type: 'line', smooth: true, data: [820, 932, 901, 934, 1290, 1330, 1320] }, { name: '移动', type: 'line', smooth: true, data: [620, 732, 701, 734, 1090, 1130, 1120] } ] }; window.__visitChart.setOption(option);

grid的四个值分别对应左、上、右、下留白,调大x是给 y 轴文字腾位置;boundaryGap: false只对类目轴有意义,折线从第一个点开始画,不往两边留半格;smooth: true是平滑曲线,数据点少的时候会显得失真,点多于 30 个再开更合适。xAxis和yAxis写成数组,是为了以后加双轴时不用改结构。

饼图要注意radius的类型:

var pieOption = { tooltip: { trigger: 'item', formatter: '{a}<br/>{b}: {c} ({d}%)' }, legend: { orient: 'vertical', // 纵向排列,适合放左侧 x: 'left', data: ['直接访问', '邮件营销', '联盟广告'] }, series: [{ name: '访问来源', type: 'pie', radius: '55%', // 必须带百分号,写 55 会被当成 55 像素 center: ['50%', '60%'], // 圆心位置,同样是百分比字符串 data: [ { value: 335, name: '直接访问' }, { value: 310, name: '邮件营销' }, { value: 234, name: '联盟广告' } ] }] };

radius写成数字是新手最常犯的错,图会缩成硬币大小。center的第二个值给到 60%,是给上方标题和图例留空间,不改也能看,但挤在一起不好读。

4.3 主题、tooltip 与事件绑定的落地写法

主题在init的第二个参数指定,前提是对应主题脚本已经加载。build 包通常只带默认主题,其它主题文件要在包里另找;找不到就别纠结,直接在主色上覆盖:

// 主题脚本没加载时传主题名不会报错,但也不生效,先确认文件再传 var chart = echarts.init(document.getElementById('visit-chart'), 'macarons'); // 更稳的做法:不依赖主题,自己定一套配色 var colors = ['#2f7ed8', '#0d233a', '#8bbc21', '#910000']; chart.setOption({ color: colors, series: [{ type: 'bar', data: [120, 200, 150, 80] }] });

2.x 的color数组会按系列顺序取色,系列多于颜色数量时循环使用。比找主题文件省事,也方便和现有设计规范对齐。

事件绑定用on,回调参数里能拿到系列名、数据名和值:

// 点击柱子时把维度带到下钻查询里 chart.on('click', function (params) { if (params.componentType === 'series') { console.log(params.seriesName, params.name, params.value); // 这里拼查询参数,跳转到明细页 } });

数据刷新时注意setOption的第二个参数。2.x 里它是布尔值,true表示不合并、整体替换;后面的版本改成了对象{ notMerge: true },照抄新写法在 2.2.7 上不生效。

// 切换时间范围后整体替换,避免上一次的 series 残留 chart.setOption(nextOption, true);

工具栏里的保存图片是内置能力,只要toolbox.feature.saveAsImage打开,用户就能把当前图导出成 PNG。内网系统里这个功能很实用,省得业务方截图后失真。

5. echarts-2.2.7 避坑排查:五类高频问题

这一章是我自己踩过的记录,按“现象 → 原因 → 解决”整理。遇到白屏先按顺序过一遍,比在代码里乱改快得多。

5.1 控制台只报 echarts is not defined

现象:页面加载完,控制台第一行就是Uncaught ReferenceError: echarts is not defined,图表区空白。

原因有三种:路径写错导致 404;静态资源被后台鉴权拦截,返回的是登录页 HTML;脚本加了async,业务代码先于库执行。

解决:打开网络面板看echarts.min.js的状态码和响应体。404 就改路径;返回 HTML 就找运维放行静态目录;有async就去掉,把业务脚本放到库后面。路径建议用绝对路径,不要用../这种相对写法,模板层级一深就容易算错。

5.2 图表空白,DOM 里能看到 canvas

现象:审查元素能看到一个 canvas,但尺寸是 0×0,页面上什么都没有。

原因:容器高度没定,或者图表在display: none的 tab 里初始化,clientHeight读到 0。

解决:给容器写死高度;在 tab 里初始化的,改成切换后再init,或者切回来时补一次resize()。

// tab 切换后补一次尺寸重算,隐藏状态下 init 的画布是 0 function onTabShow() { window.__visitChart && window.__visitChart.resize(); }

如果用了懒加载,把init挪到可见之后,比事后补resize更彻底。

5.3 grid 和 legend 写成 left/top 不生效

现象:grid: { left: 60, top: 60 }写完,图表内容还是贴着边,位置参数像没写一样。

原因:2.2.7 的位置参数是x、y、x2、y2,left/top/right/bottom是后续版本才引入的,这个版本不认,也不报错。

解决:全部换回旧写法。

// 2.2.7 正确写法,四边距对应 x/y/x2/y2 grid: { x: 60, y: 60, x2: 40, y2: 50 }, legend: { x: 'right', y: 'top' }

title、legend、grid、dataZoom都有这个差异,迁移代码时搜索一遍left:、top:能捞出大半。

5.4 数字变成字符串,y 轴刻度乱

现象:折线图数据看着对,但 y 轴最大值远大于实际数据,或者堆积柱状图叠成一条长条。

原因:接口返回的数值是字符串,比如"120",2.x 在部分图表类型里会按字符串拼接而不是数值相加。

解决:入库前统一转换,不要指望组件自己纠正。

// 转换时给默认值,避免 NaN 把整条线打穿 var data = raw.map(function (item) { return Number(item.value) || 0; });

Number('')是 0,Number(undefined)是NaN,后者会让整条折线消失,所以用|| 0兜底。数据量大的时候在接口层处理,别每次渲染都遍历一遍。

5.5 反复 setOption 后事件重复触发

现象:切换筛选条件几次后,点一次柱子弹出多个提示,或者下钻逻辑执行了三四次。

原因:每次渲染都重新on('click'),旧监听没解绑,回调叠在一起。图表实例如果也没复用,canvas 会越积越多。

解决:监听只绑一次,或者重建实例前先dispose。

// 渲染前销毁旧实例,事件和画布一起释放 if (window.__visitChart) { window.__visitChart.dispose(); } window.__visitChart = echarts.init(document.getElementById('visit-chart'));

如果实例要复用,就在初始化时绑一次事件,后续只调setOption。用off解绑时注意,2.x 的off不传参数是解全部,传事件名只解该类,别写错。

6. 装完怎么验证 echarts-2.2.7 跑通了:一份离线自检页

部署完成后,我不看业务页面,而是在同目录放一个独立的静态自检页。业务页面里有登录态、有模板变量、有各种前置脚本干扰,出问题分不清是库的问题还是页面的问题。自检页只有一个文件、一个容器、一组假数据,能干净地验证“库本身能不能跑”。

<!doctype html> <html> <head> <meta charset="utf-8"> <title>echarts-2.2.7 自检</title> </head> <body> <!-- 固定宽高,排除容器塌陷的干扰 --> <div id="smoke" style="width:600px;height:300px;"></div> <script src="./echarts.min.js"></script> <script> var chart = echarts.init(document.getElementById('smoke')); chart.setOption({ title: { text: '自检', x: 'center' }, tooltip: { trigger: 'axis' }, xAxis: [{ type: 'category', data: ['A', 'B', 'C'] }], yAxis: [{ type: 'value' }], series: [{ name: 'smoke', type: 'bar', data: [1, 2, 3] }] }); // 版本号和实例挂到全局,方便在控制台核对 console.log('echarts version:', echarts.version); window.__smoke = { version: echarts.version, chart: chart }; </script> </body> </html>

这个页面放在和echarts.min.js同级的目录下,用浏览器直接打开,不经过任何后端。如果柱状图能出来、控制台能打印出版本号,说明文件完整、路径正确、初始化流程通畅。如果echarts.version打印为空,去看文件头部的版本注释,确认拷的是不是同一个构建产物。

我常用的核对项整理成表,出问题按顺序往下看:

检查项预期结果不通过先看哪里
脚本请求状态 200,响应体是 JS路径、鉴权、MIME 类型
全局对象typeof echarts === 'object'脚本顺序、async属性
版本号控制台有输出文件是否为同一次构建产物
画布尺寸canvas 宽高非 0容器高度、父级 display
交互悬浮有提示、点击有回调事件是否重复绑定

从那以后,我每次把 echarts 这类静态库往内网机器上部署,都会强制走一遍这个自检页,确认无误再接业务页面。老项目的环境差异比想象中大,同一份文件在两台机器上表现不同,往往不是代码问题,而是中间多了一层缓存或鉴权。希望这份记录能帮你少绕两圈。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进