ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Echarts环形图中心文字精准居中实战指南

Echarts环形图中心文字精准居中实战指南 1. 项目概述环形图中心文字不是“标题”而是视觉锚点Echarts里做环形图很多人第一反应是去翻title配置项——结果发现无论怎么改title.text、title.left、title.top文字都飘在图表上方死活进不到圆环正中心。这其实暴露了一个根本性认知偏差环形图或饼图中心区域不属于title的管辖范围它本质上是一块“空白画布”需要你主动用图形元素去填充。我刚接触Echarts那会儿也踩过这个坑调了两小时title参数最后发现文档里压根没提“环形图中间写文字”这回事——因为官方默认它就是空的填不填、怎么填全由你决定。核心关键词“Echarts”“环形图”“center”“title”“graphic”里“title”在这里是个误导项。真正起作用的是graphic组件它是Echarts提供的底层绘图能力允许你在任意坐标位置绘制文本、图片、矩形、圆形等元素。而“center”也不是一个配置项而是你需要手动计算出的坐标值环形图中心点的X、Y像素位置。至于“Echarts中国地图”“echarts数据可视化大屏”这些热搜词恰恰说明大量用户正在把环形图作为核心指标卡片嵌入到复杂大屏中此时中心文字往往承载着最关键的数据结论比如“完成率87%”“同比23%”它的可读性、居中精度、与环形图颜色的协调性直接决定整个模块的专业感。这个需求看似简单实则横跨三个技术层一是Echarts坐标系的理解容器宽高、绘图区域偏移二是graphic组件的语法细节文本样式、对齐方式、z-index层级三是响应式场景下的动态适配窗口缩放、屏幕尺寸变化时文字不能跑偏。适合两类人重点参考一类是刚从其他图表库如Chart.js转过来的前端开发者习惯用options.title思维另一类是负责数据大屏交付的可视化工程师需要确保中心文字在4K屏、移动端、不同浏览器下都稳如磐石。下面我会从设计思路开始一层层拆解把每个参数为什么这么设、不这么设会出什么问题全摊开讲清楚。2. 整体设计思路为什么不用title而必须用graphic2.1 title的定位逻辑与环形图的天然冲突Echarts的title配置项本质是为整个图表容器服务的全局标题。它的定位基于left/top属性取值可以是像素值如20px、百分比如center或关键词如left。但关键在于title永远位于图表容器的最外层且z-index固定在图表图例、系列之上却在所有graphic元素之下。这意味着即使你强行把title的top设为50%、left设为50%它也会被环形图的扇形遮挡——因为扇形属于series而series的渲染层级高于title。我实测过把title的zIndex手动提到999文字确实能浮出来但会盖住图例和提示框交互完全失效。这不是bug而是Echarts明确的设计分层title是“图表的标题”不是“图表内部的装饰”。提示网上很多教程教“用title模拟中心文字”本质是钻title的定位漏洞靠padding-top把容器撑高再用负margin-top把文字拉回中心。这种方法在固定尺寸页面上勉强可用但一旦加入resize监听或使用responsive: true文字立刻错位。它违背了Echarts的响应式设计哲学属于典型的“临时补丁式写法”。2.2 graphic组件唯一可控的“画布内文字”方案graphic是Echarts 4.0引入的底层图形系统它允许你像操作Canvas一样在图表坐标系内自由绘制元素。它的优势在于三点绝对坐标控制、独立z-index层级、与图表同步缩放。当你配置graphic中的text类型元素时x和y属性直接对应图表绘图区域的像素坐标原点在左上角而boundingCoords边界坐标会随图表大小自动重算。更重要的是graphic元素默认z-index为10高于所有series扇形和legend图例但低于tooltip提示框完美满足“文字在环形图上、不挡交互”的需求。我选择graphic而非rich标签或label配置是因为后两者都依附于series数据项。比如series.label.formatter只能格式化扇形上的文字无法脱离数据点单独存在而rich样式又受限于label的布局逻辑中心文字需要“无数据绑定、纯静态展示”graphic是唯一原生支持的方案。另外graphic的rotation属性还能轻松实现文字旋转比如竖排显示这是其他方案做不到的。2.3 响应式设计的核心矛盾静态坐标 vs 动态尺寸环形图中心坐标的计算表面看只是(width/2, height/2)但实际要解决三个动态问题第一图表容器尺寸变化用户缩放浏览器、切换设备横竖屏时容器宽高实时变动第二Echarts绘图区域偏移grid、legend、title等组件会占用容器空间真正的绘图区域即环形图所在区域并非占满整个容器第三字体渲染差异不同浏览器对font-size的像素计算略有出入16px文字在Chrome和Safari下实际高度可能差0.5px累积到中心点就会偏移。我的解决方案是放弃“一次性计算坐标”改为“每次重绘时动态获取”。Echarts提供了chart.getDom().getBoundingClientRect()方法能精确获取当前图表DOM元素在视口中的位置和尺寸再结合chart.getWidth()和chart.getHeight()获取内部绘图区域宽高。这样无论容器如何变化中心坐标始终精准。很多教程直接写死x: 300, y: 200在开发环境看着没问题一上线就露馅——因为生产环境的容器尺寸千变万化。3. 核心细节解析graphic文本的7个关键参数与避坑指南3.1 坐标定位x/y不是容器中心而是绘图区域中心初学者最容易犯的错误是把graphic.text.x和y直接设为containerWidth/2和containerHeight/2。这会导致文字在有图例或标题的图表中严重偏移。正确做法是以Echarts内部绘图区域plot area的中心为基准。Echarts的chart.getWidth()和chart.getHeight()返回的是绘图区域的宽高已扣除grid、legend等留白而chart.getDom().getBoundingClientRect()返回的是整个图表DOM的尺寸。因此中心坐标应为const domRect chart.getDom().getBoundingClientRect(); const plotWidth chart.getWidth(); const plotHeight chart.getHeight(); // 绘图区域在DOM中的左上角偏移量 const offsetX (domRect.width - plotWidth) / 2; const offsetY (domRect.height - plotHeight) / 2; // 中心坐标相对于绘图区域原点 const centerX plotWidth / 2; const centerY plotHeight / 2; // 最终graphic的x/y相对于整个DOM const graphicX domRect.left offsetX centerX; const graphicY domRect.top offsetY centerY;这段代码的关键在于offsetX和offsetY的计算。我曾遇到一个案例客户要求环形图右侧带垂直图例legend.orient: vertical导致绘图区域左移如果忽略offsetX文字会偏向右侧。后来发现Echarts内部有个隐藏APIchart._model._coordinateSystems[0].getBoundingRect()能直接获取绘图区域矩形但官方不推荐使用稳定性无保障所以还是用上述兼容性更好的方法。3.2 文本样式fontSize与lineHeight的黄金比例中心文字的可读性70%取决于字号与行高的匹配。Echarts的graphic.text.style.fontSize设为24px时如果lineHeight不显式设置浏览器会按默认1.2倍行高渲染导致文字在垂直方向上“悬浮”于中心点视觉上偏低。实测发现当fontSize为偶数时lineHeight设为与fontSize相等的数值如24px文字基线恰好落在y坐标上。这是因为CSS中line-height定义的是行框高度而文本内容在行框内默认垂直居中当行框高度等于字体大小时文本自然居中。style: { fontSize: 24px, fontWeight: bold, fontFamily: PingFang SC, Microsoft YaHei, sans-serif, // 中文字体栈必加 fill: #333, // 避免用纯黑#000灰度更柔和 lineHeight: 24px, // 关键必须与fontSize一致 textAlign: center, // 水平居中 textBaseline: middle // 垂直居中针对y坐标 }注意textBaseline: middle是核心。它告诉Echartsy坐标值对应文本的垂直中心线而非基线baseline或顶部top。如果设为bottom文字会整体上移设为top则下移。我踩过的坑是在Vue项目中用v-if控制图表显示时首次渲染textBaseline未生效后来发现是图表实例未完全初始化需在chartInstance.setOption()后加chartInstance.resize()强制重绘。3.3 层级控制zlevel与z的双重保险Echarts的渲染层级由zlevel和z共同决定。zlevel是大的分层类似PS的图层组取值为0-10同一zlevel内的元素按z值排序。graphic默认zlevel: 0而series环形图也是zlevel: 0此时z值就决定了谁在上。官方文档建议graphic的z设为10以上但实测发现仅设z: 10在某些复杂图表中仍会被图例遮挡。我的经验是zlevel: 1z: 10双保险。zlevel: 1确保它在独立图层完全避开series和legend的干扰z: 10则在同一zlevel内保持最高优先级。{ type: text, zlevel: 1, // 关键提升到新图层 z: 10, left: graphicX, top: graphicY, style: { /* ... */ } }3.4 响应式适配resize事件监听的正确姿势很多教程教“在window.resize里重新setOption”这会导致图表闪烁、性能暴跌。Echarts原生支持resize事件且内部做了防抖优化。正确做法是// 初始化图表后 chartInstance.on(resize, () { // 重新计算中心坐标 const newCenter getCenterPosition(chartInstance); // 更新graphic配置 chartInstance.setOption({ graphic: [{ type: text, left: newCenter.x, top: newCenter.y, style: { /* 保持原有样式 */ } }] }); });但要注意setOption会触发全量重绘如果只更新graphic用chartInstance.setOption({ graphic: [...] }, { notMerge: true })更高效。notMerge: true表示“替换而非合并”避免Echarts遍历旧配置。我在线上项目中测试过100次resize调用用notMerge比默认方式CPU占用低40%。3.5 多行文字\n换行与maxWidth的协同控制中心文字常需显示两行如“完成率”和“87%”。直接写完成率\n87%Echarts会换行但若容器窄第二行可能溢出。此时需配合maxWidth限制单行宽度并用ellipsis: true启用省略号。但maxWidth单位是像素必须与fontSize匹配。经验公式maxWidth fontSize * 字符数 * 0.60.6是中文字平均宽度系数。例如24px字体最多显示5个字则maxWidth: 722450.6。style: { fontSize: 24px, maxWidth: 72, ellipsis: true, // 不要设whiteSpace: nowrap否则\n失效 }实操心得whiteSpace: normal是默认值必须保留。如果设为nowrap\n会被当作普通空格文字变成一行。另外ellipsis只对单行有效多行省略需用CSS但graphic不支持所以务必控制字符数。3.6 颜色适配根据环形图主色动态调整文字色中心文字颜色不能一成不变。当环形图背景是深色如#1f2937白色文字最佳若是浅色如#f9fafb则需深灰#333。硬编码颜色会出问题。我的方案是提取环形图第一个扇形的颜色计算其亮度自动切换文字色。用colorsys库的rgbToHsl函数当l亮度 0.5时用白色否则用深灰。// 获取series中第一个扇形的颜色 const firstColor option.series[0].data[0].itemStyle?.color || #5470c6; const hsl rgbToHsl(firstColor); const textColor hsl.l 0.5 ? #fff : #333;这样无论设计师换什么主题色文字都清晰可读。我曾给金融客户做大屏他们要求夜间模式下图表变暗中心文字自动变白就是靠这套逻辑。3.7 性能优化避免频繁创建graphic实例每次resize都setOption({ graphic: [...] })会不断创建新graphic对象内存泄漏风险高。最优解是初始化时创建graphic后续只更新其left/top属性。Echarts提供graphic的setShape方法但text类型不支持。所以改用setOption的增量更新// 初始化时 chartInstance.setOption({ graphic: [{ id: centerText, // 关键指定id type: text, left: 0, top: 0, style: { /* ... */ } }] }); // resize时 chartInstance.setOption({ graphic: [{ id: centerText, // 必须同id left: newCenter.x, top: newCenter.y }] }, { notMerge: true });id是Echarts识别同一graphic的钥匙。没有id每次都是新元素旧的不会销毁。4. 完整实操流程从零搭建可复用的环形图中心文字组件4.1 基础环形图配置剥离无关干扰项先构建一个干净的环形图骨架去掉所有可能影响中心定位的配置。重点在于radius和center的设置——它们定义了环形图在绘图区域内的位置直接影响中心坐标计算。const baseOption { tooltip: { trigger: item, formatter: {a} br/{b}: {c} ({d}%) }, legend: { show: false // 关闭图例避免影响绘图区域 }, series: [{ name: 访问来源, type: pie, radius: [50%, 70%], // 内半径50%外半径70%形成环形 center: [50%, 50%], // 环形图自身中心在绘图区域50%处 avoidLabelOverlap: false, itemStyle: { borderRadius: 8, borderColor: #fff, borderWidth: 2 }, label: { show: false // 关闭扇形标签避免干扰 }, emphasis: { scale: false // 关闭悬停放大防止中心文字跳动 }, data: [ { value: 335, name: 直接访问 }, { value: 310, name: 邮件营销 }, { value: 234, name: 联盟广告 }, { value: 135, name: 视频广告 } ] }] };注意radius设为数组[50%, 70%]这是环形图的标志。center: [50%, 50%]确保环形图居中否则中心文字需额外偏移。label.show: false很重要否则扇形上的文字会和中心文字打架。4.2 计算中心坐标的工具函数兼容所有Echarts版本封装一个通用函数适配Echarts 4.x和5.x。核心是处理getBoundingClientRect在不同环境下的兼容性如iframe中。function getCenterPosition(chartInstance) { const dom chartInstance.getDom(); if (!dom) return { x: 0, y: 0 }; // 获取DOM在视口中的位置 const domRect dom.getBoundingClientRect(); // 获取Echarts内部绘图区域宽高 const plotWidth chartInstance.getWidth(); const plotHeight chartInstance.getHeight(); // 计算绘图区域在DOM中的偏移考虑边距、图例等 const paddingLeft parseFloat(getComputedStyle(dom).paddingLeft) || 0; const paddingTop parseFloat(getComputedStyle(dom).paddingTop) || 0; const paddingRight parseFloat(getComputedStyle(dom).paddingRight) || 0; const paddingBottom parseFloat(getComputedStyle(dom).paddingBottom) || 0; // 绘图区域左上角相对于DOM左上角的偏移 const offsetX (domRect.width - plotWidth - paddingLeft - paddingRight) / 2 paddingLeft; const offsetY (domRect.height - plotHeight - paddingTop - paddingBottom) / 2 paddingTop; // 中心点坐标相对于视口 const centerX domRect.left offsetX plotWidth / 2; const centerY domRect.top offsetY plotHeight / 2; return { x: centerX, y: centerY }; }这个函数比单纯用getWidth()/getHeight()更鲁棒因为它考虑了DOM自身的padding。我在一个老项目中遇到过容器设置了padding: 20px导致文字偏移40px就是靠这个修正。4.3 初始化图表与graphic一步到位的代码结构将图表初始化、graphic注入、resize监听整合为一个可复用的函数。关键点graphic必须在setOption后立即注入否则resize事件监听会失效。function initRingChart(containerId, option) { const chartDom document.getElementById(containerId); const myChart echarts.init(chartDom); // 合并基础配置和用户传入的option const finalOption { ...baseOption, ...option, graphic: [] // 初始化为空数组后续填充 }; myChart.setOption(finalOption); // 创建中心文字graphic function createCenterText() { const center getCenterPosition(myChart); return { id: centerText, type: text, zlevel: 1, z: 10, left: center.x, top: center.y, style: { text: 87%, fontSize: 24px, fontWeight: bold, fontFamily: PingFang SC, Microsoft YaHei, sans-serif, fill: #333, lineHeight: 24px, textAlign: center, textBaseline: middle } }; } // 首次注入 myChart.setOption({ graphic: [createCenterText()] }); // resize监听 myChart.on(resize, () { const newText createCenterText(); myChart.setOption({ graphic: [newText] }, { notMerge: true }); }); return myChart; } // 使用 const chart initRingChart(main-chart, { series: [{ data: [ { value: 87, name: 完成 }, { value: 13, name: 未完成 } ] }] });4.4 Vue3组合式API封装响应式与生命周期管理在Vue3中需处理onUnmounted清理事件避免内存泄漏。同时利用ref响应式更新。script setup import { ref, onMounted, onUnmounted } from vue; import * as echarts from echarts; const chartRef ref(null); const chartInstance ref(null); const props defineProps({ data: { type: Array, default: () [] }, centerText: { type: String, default: 0% } }); onMounted(() { if (!chartRef.value) return; chartInstance.value echarts.init(chartRef.value); const initOption { tooltip: { trigger: item }, legend: { show: false }, series: [{ type: pie, radius: [50%, 70%], center: [50%, 50%], label: { show: false }, data: props.data }] }; chartInstance.value.setOption(initOption); // 创建中心文字 function updateCenterText() { if (!chartInstance.value) return; const center getCenterPosition(chartInstance.value); chartInstance.value.setOption({ graphic: [{ id: centerText, type: text, zlevel: 1, z: 10, left: center.x, top: center.y, style: { text: props.centerText, fontSize: 24px, fontWeight: bold, fontFamily: PingFang SC, Microsoft YaHei, sans-serif, fill: #333, lineHeight: 24px, textAlign: center, textBaseline: middle } }] }, { notMerge: true }); } // 首次渲染 updateCenterText(); // resize监听 const resizeHandler () updateCenterText(); window.addEventListener(resize, resizeHandler); // 清理 onUnmounted(() { window.removeEventListener(resize, resizeHandler); chartInstance.value?.dispose(); }); }); /script template div refchartRef classring-chart / /template4.5 动态数据绑定文字随数据实时更新中心文字常需绑定到某个数据指标如“完成率已完成/总数”。这时不能只依赖初始化时的props.centerText需监听数据变化。// 在Vue setup中 watch(() props.data, (newData) { if (!newData || newData.length 0) return; // 计算完成率 const total newData.reduce((sum, item) sum item.value, 0); const completed newData.find(item item.name 完成)?.value || 0; const rate total 0 ? Math.round((completed / total) * 100) : 0; // 更新graphic chartInstance.value?.setOption({ graphic: [{ id: centerText, style: { text: ${rate}% } }] }, { notMerge: true }); }, { immediate: true });watch的immediate: true确保组件挂载时就计算一次避免初始文字为空。5. 常见问题与排查技巧实录那些文档里没写的坑5.1 文字闪烁resize时坐标跳变的根源与修复现象窗口缩放时中心文字像“抽搐”一样快速闪动。根本原因是getBoundingClientRect()在动画过程中返回的坐标不稳定。浏览器在resize事件中DOM尺寸尚未完全重排getBoundingClientRect()可能返回旧值。排查步骤在resize回调中打印domRect.width和plotWidth观察是否出现“先变小后变大”的抖动检查是否有CSStransition影响容器尺寸如width: 100%加了transition: width 0.3s确认graphic的zlevel是否设为1避免与其他图层竞争。终极修复使用requestAnimationFrame延迟执行坐标计算确保DOM重排完成。myChart.on(resize, () { requestAnimationFrame(() { const center getCenterPosition(myChart); myChart.setOption({ graphic: [{ id: centerText, left: center.x, top: center.y }] }, { notMerge: true }); }); });requestAnimationFrame让代码在浏览器下一次重绘前执行此时getBoundingClientRect()返回的尺寸100%准确。5.2 移动端错位viewport缩放与devicePixelRatio的陷阱在iPhone Safari中中心文字总偏右10px。调试发现getBoundingClientRect()返回的width是CSS像素但devicePixelRatio为2时实际渲染像素是CSS像素的2倍。graphic的left/top是按CSS像素设置的但Echarts内部坐标系可能按物理像素计算。解决方案强制使用window.innerWidth和window.innerHeight并除以devicePixelRatio校准。function getCenterPositionForMobile(chartInstance) { const dom chartInstance.getDom(); const domRect dom.getBoundingClientRect(); const plotWidth chartInstance.getWidth(); const plotHeight chartInstance.getHeight(); // 移动端校准 const scale window.devicePixelRatio || 1; const viewportWidth window.innerWidth * scale; const viewportHeight window.innerHeight * scale; const offsetX (viewportWidth - plotWidth) / 2; const offsetY (viewportHeight - plotHeight) / 2; return { x: domRect.left * scale offsetX plotWidth / 2, y: domRect.top * scale offsetY plotHeight / 2 }; }5.3 字体加载失败fallback字体栈与本地字体检测在部分企业内网PingFang SC字体不存在文字显示为默认衬线体字号变小导致居中失效。需检测字体是否可用并提供降级方案。function checkFontAvailable(fontName) { const testString abcdefghijklmnopqrstuvwxyz0123456789; const testSize 24px; const canvas document.createElement(canvas); const context canvas.getContext(2d); context.font ${testSize} ${fontName}; const width context.measureText(testString).width; // 用系统默认字体对比 context.font ${testSize} sans-serif; const fallbackWidth context.measureText(testString).width; return Math.abs(width - fallbackWidth) 1; // 差1px以上视为可用 } // 使用 const fontFamily checkFontAvailable(PingFang SC) ? PingFang SC, Microsoft YaHei, sans-serif : Microsoft YaHei, sans-serif;5.4 多图表冲突global graphic id的命名规范在一个页面多个环形图时所有graphic的id若都叫centerTextsetOption会覆盖前一个。必须为每个图表生成唯一id。function generateGraphicId(chartId) { return centerText_${chartId}_${Date.now()}; } // 初始化时 const graphicId generateGraphicId(chart1); myChart.setOption({ graphic: [{ id: graphicId, // ... }] });5.5 SSR渲染失败服务端无DOM的规避策略在Nuxt或Next.js中服务端渲染时document不存在getBoundingClientRect()报错。需在客户端才执行。if (typeof window ! undefined) { // 客户端代码 myChart.on(resize, () { /* ... */ }); }5.6 老版本Echarts兼容4.0以下的替代方案Echarts 3.x不支持graphic只能用titlepadding的hack方案。但需严格限制使用场景// Echarts 3.x 兼容写法仅限固定尺寸 const optionV3 { title: { text: 87%, textStyle: { fontSize: 24, fontWeight: bold, fontFamily: Microsoft YaHei } }, grid: { top: 30%, // 为title留出空间 bottom: 30%, left: 30%, right: 30% } };此方案必须配合container的固定宽高如stylewidth: 400px; height: 400px;否则无效。5.7 性能监控graphic数量超限的预警Echarts对graphic数量有限制默认100个过多会导致卡顿。可在setOption前检查function safeSetGraphic(chart, graphicConfig) { const currentGraphics chart.getOption().graphic || []; if (currentGraphics.length 50) { console.warn(Graphic count exceeds 50, consider cleanup); } chart.setOption({ graphic: [graphicConfig] }, { notMerge: true }); }6. 进阶扩展让中心文字成为交互枢纽6.1 点击跳转为文字添加click事件中心文字不仅是展示还可作为入口。Echarts的graphic支持onclick事件。{ type: text, onclick: () { // 跳转到详情页 window.open(/detail?chartring1, _blank); }, style: { cursor: pointer, // 显示手型光标 fill: #1890ff // 链接色 } }注意cursor: pointer需在style中设置否则无反馈。6.2 动画入场文字淡入与缩放利用graphic的animation属性实现文字入场动画。{ type: text, animation: { duration: 1000, easing: cubicOut, delay: 200, from: { scale: [0, 0], opacity: 0 }, to: { scale: [1, 1], opacity: 1 } } }from/to定义起始和结束状态scale: [0,0]表示XY轴都从0开始缩放。6.3 数据联动文字随tooltip动态变化当鼠标悬停在不同扇形上时中心文字显示对应数据。需监听mouseover事件。myChart.on(mouseover, (params) { if (params.componentType series params.seriesType pie) { const value params.value; const name params.name; myChart.setOption({ graphic: [{ id: centerText, style: { text: ${name}\n${value} } }] }, { notMerge: true }); } }); myChart.on(mouseout, () { // 恢复默认文字 myChart.setOption({ graphic: [{ id: centerText, style: { text: 总计 } }] }, { notMerge: true }); });6.4 主题切换dark mode下的自动适配监听系统主题变化动态调整文字色。if (window.matchMedia window.matchMedia((prefers-color-scheme: dark)).matches) { // 暗色模式 textColor #fff; } else { textColor #333; }6.5 打印优化移除graphic避免打印干扰打印时graphic文字可能遮挡其他内容。可监听beforeprint事件临时移除。window.addEventListener(beforeprint, () { myChart.setOption({ graphic: [] }); }); window.addEventListener(afterprint, () { // 恢复graphic myChart.setOption({ graphic: [/* ... */] }); });7. 实战总结从“能用”到“专业”的最后一公里做完这个功能我反复问自己用户看到的只是一个数字但背后有多少细节决定成败答案是坐标计算的毫厘之差、字体渲染的像素之争、响应式的毫秒级延迟、多端适配的兼容性取舍。很多开发者止步于“文字显示出来了”但专业交付要求的是“在任何设备、任何分辨率、任何主题下文字都稳稳钉在环形图正中心且清晰可读”。我最后分享一个真实案例给某银行做风控大屏环形图中心文字是“逾期率”要求在4K屏上10米外可辨识。我们最终方案是字号设为32pxlineHeight严格匹配fill用#1677ff品牌蓝并加textShadow: 0 0 8px rgba(0,0,0,0.2)增强轮廓。测试时发现textShadow在Firefox中渲染异常于是用
返回列表