做小程序的这些年,我最怕的不是接口联调,也不是样式适配,而是上线前一天晚上,真机预览一打开,底部 TabBar 上本该是购物车的那一格,赫然显示成一个白色的方块。这套「含首页/分类/购物车/我的等多状态PNG图标、透明背景、规范命名」的图标包,就是被这类事故反复教育之后,我给自己攒下来的一套标准件。它解决的事情很具体:把底部导航需要的每一个状态图,用统一的尺寸、干净的透明背景、一眼能看懂的命名整理好,让app.json里那几行配置一次写对,不用再靠猜路径和试错。如果你正在从零搭一个小程序、或者接手了一个图标风格五花八门的旧项目,这篇东西基本能覆盖你从拿到图标包到真机跑通的全部环节——包括那些文档里不会写、只有踩过才知道的细节。
1. TabBar图标这套"小图"为什么最容易在最后一刻出问题
1.1 一次真机预览里的白色方块
那次事故的现场我记得很清楚。设计给的图标是一套挺漂亮的线性图标,我在开发者工具里看了一遍,四个格子都好端端的,颜色、间距、文字对齐都没毛病。真机预览一开,购物车那一格变成了纯白方块,其余三个正常。第一反应是图片没打包进去,翻了一遍目录,文件在;第二反应是路径写错了,逐字对了一遍app.json,也没错。折腾了快四十分钟才发现,那张购物车图标的源文件是从一张带白色背景的 JPG 里抠出来的,导出时存成了不带 alpha 通道的 PNG,开发者工具的渲染层在某些情况下会做近似处理,真机就老老实实把白底画了出来。
这件事给我留下的教训不是"以后注意点",而是:TabBar 图标是整个小程序里最不值钱、却最容易翻车的资源。它小到没人愿意为它单独走一遍设计评审,又重要到一旦出问题,用户第一眼就看见。而且它的约束条件特别集中——尺寸、格式、大小、路径、背景,五件事里错一件,表现都是"显示不出来"或者"显示得不对劲",错误现象高度相似,排查时几乎没有指向性。
更麻烦的是,这类问题往往在开发后期才暴露。因为大部分人在开发者工具里调样式的时候,根本不会去点底部那几个 tab,等到功能都做完了、准备提审了,才发现图标有问题。这时候改图、重新导出、重新走一遍资源替换流程,时间成本比一开始就规范化高出好几倍。
所以我后来给自己定了个规矩:不管项目多小,TabBar 图标必须在项目初始化阶段就落地,且必须真机验证一次。这套图标包就是为这个规矩服务的——它把首页、分类、购物车、我的这几个几乎每个项目都会用到的高频图标,按统一规格做好,选中态和未选中态成对出现,透明背景处理干净,文件名直接对应配置字段,拿来就能用。
1.2 原生tabBar对图标的四道硬约束
很多人对 TabBar 图标的认知停留在"放张图上去就行",实际上原生 TabBar 对图片有四道非常明确的约束,任何一条不满足,轻则显示异常,重则整个 tabBar 配置报错。
第一道是格式。原生 TabBar 只认位图,PNG、JPG、JPEG 这三种,SVG 不支持,WebP 不支持,字体图标更不支持。这一点劝退了不少想用 iconfont 统一管理图标的同学——在原生 TabBar 里这条路是走不通的,要么老老实实用 PNG,要么就得开自定义 tabBar,那是另一套成本。
第二道是体积。单张图标的文件大小有上限,官方给的建议是控制在 40KB 以内。这个数字看着宽松,实际很容易超——尤其是那种带渐变、带阴影、尺寸做到 200px 以上的图标,一张就能干到 60KB 往上。超了不会报错,但有可能在某些机型上加载失败,表现就是空白。
第三道是路径。iconPath写的是相对路径,相对于app.json所在的目录。写绝对路径、写网络地址都不行。而且路径是大小写敏感的——开发者工具在部分系统上不敏感,真机敏感,这个差异坑过无数人。
第四道是数量与配对。list数组最少 2 项、最多 5 项,少于 2 项或多于 5 项都会直接报配置错误。同时每个 tab 的iconPath和selectedIconPath最好成对给,只给一个的话,切到选中态时图标会消失或者保持不变,视觉上很怪。
把这四条约束记住,其实已经能避开 TabBar 图标 70% 的问题。剩下的 30%,集中在"图本身画得对不对"上——那就不是配置问题,是设计规范问题。
2. 81×81画布上的取舍:尺寸、留白与像素对齐
2.1 81这个数字是怎么定下来的
刚接触的人常问:为什么图标要做 81×81?做个 48×48 不是更省体积吗?
这个数字的推导逻辑其实很朴素。原生 TabBar 上,单个图标实际渲染出来的视觉尺寸大约在 25 到 27 个逻辑像素之间(不同平台略有差异)。而主流的移动设备屏幕像素比,常见的是 2 倍和 3 倍。为了让图标在 3 倍屏上也能保持清晰,你至少需要准备 27×3 = 81 像素的图。这就是 81 的来源——它是为了覆盖最高密度屏幕而准备的三倍图。
理解了这一点,尺寸选择就有依据了:
| 画布尺寸 | 适用情况 | 3倍屏表现 | 建议 |
|---|---|---|---|
| 48×48 | 只考虑低密度屏 | 明显发虚 | 不建议 |
| 64×64 | 折中方案 | 轻微发虚 | 勉强可用 |
| 81×81 | 覆盖 3 倍屏 | 清晰 | 推荐 |
| 96×96 及以上 | 有余量 | 清晰 | 可用,但注意体积 |
| 120×120 | 冗余 | 清晰 | 体积浪费,慎用 |
我个人的习惯是固定 81×81。理由有两个:一是它刚好是 27 的整数倍,缩放比例是干净的 1/3,像素对齐好处理;二是这个尺寸下,一张纯色线性图标的 PNG 通常只有 2 到 5KB,一套五个 tab 十个状态图全加上也就四五十 KB,对主包压力可以忽略。
如果项目里有大尺寸需求(比如某些安卓平板),我会额外准备一套 96×96 的,但不会默认塞进主包,而是按需加载。大部分项目 81×81 足够用,别为了"预留余量"把体积翻倍。
2.2 三分法留白:图形该画多大
拿到画布之后,第二个问题就是:图形在 81×81 的格子里画多大?
新手最容易犯的错是把图形撑满画布。看起来"更清楚",实际显示在 TabBar 上会显得又大又挤,和旁边的文字挤在一起,整个底部导航看起来非常压迫。而且撑满之后,不同图标之间的视觉重量会严重失衡——圆形的购物车和方形的首页图标,同样撑满画布,圆形的视觉面积只有方形的 78% 左右,看起来就是一大一小。
我的经验值是:图形的有效外接框控制在 60 到 66 像素之间,也就是画布的 74% 到 81%。四周留出大约 8 到 10 像素的空白。这个比例下,图标既不会显得小气,又能和上下元素拉开呼吸感。
具体到几个高频图标,可以这样把握:
- 首页:房子的屋顶是最宽处,宽度控制在 62px 左右,屋檐到底部的总高度约 58px,整体略微偏上,因为房子的视觉重心在屋顶。
- 分类:四宫格或者三横线,最外框 60×60,格子之间的间距用 6px,避免缩小后糊成一块。
- 购物车:车身宽度约 56px,把手往左上延伸,整体高度约 54px。购物车的视觉重心偏下,画的时候整体上移 2px 左右,看起来才不会"往下掉"。
- 我的:人像的肩宽是主体,宽度约 58px,头部的圆直径约 20px,头部中心大概在画布纵向 30% 的位置。
这些数字不是玄学,是反复对比之后的结果。判断标准只有一个:把五个图标并排放在一起,眯起眼睛看,它们的视觉重量应该差不多。如果有一个明显更重或更轻,就回去调它的外接框。
还有一个容易被忽略的点:图标和文字之间的垂直关系。原生 TabBar 里文字大小是系统定的,你改不了。所以图标的视觉重心必须自己控制好。如果图形重心偏下,图标会显得贴着文字;偏上,又会显得离文字太远。稳妥的做法是把图形在画布内整体上移 1 到 2 像素,给底部的文字留出视觉间隙。
2.3 线条取3的倍数,避免半像素灰边
这一条是从像素对齐里来的,属于那种"不知道也能做,知道了就再也回不去"的细节。
前面说了,81 是 27 的 3 倍,缩放比正好是 1/3。这意味着设计稿上一根 3 像素宽的线,显示出来正好是 1 个逻辑像素,边缘干净。如果画成 4 像素,缩放后是 1.33 个像素,渲染引擎会做抗锯齿处理,结果就是一根边缘发灰、看起来有点糊的线。
所以我在 81px 的画布上画线性图标时,线宽一律取 3 的倍数:主干线用 6px(显示 2px),次级细节用 3px(显示 1px)。圆角半径也尽量取 3 的倍数,比如拐角用 9px 或 12px 的圆角。这样导出后无论在哪档屏幕上,边缘都是锐利的。
同样的逻辑适用于图形的位置。图形的起始坐标、宽度、间距,尽量都落在 3 的整数倍的网格上。听起来有点强迫症,但你会发现,按这个网格画出来的图标,五个摆在一起的时候,间距天然就是均匀的——因为大家用的是同一套网格。
提示:如果设计稿是按 162×162 画的(二倍稿),那线宽就用 6 和 12,网格步长变成 6,逻辑是一样的。关键是设计稿尺寸和最终导出尺寸保持整数倍关系,中间不要出现 0.5 这样的缩放。
这三件事——尺寸、留白、对齐——决定了图标"看起来正不正规"。而下面要说的透明背景和状态配对,决定的是它"能不能用"。
3. 透明背景与状态对:两件比画图更重要的手艺
3.1 alpha通道踩坑:白底图、描边白边、PNG-8索引色
回到开头那个白色方块。它的成因,值得单独拆开说清楚,因为这一类的坑反复出现。
第一种:从 JPG 或截图里抠图,导出时没有保留 alpha 通道。很多人在做图标时图省事,从网上找一张图、或者从设计稿里截一块,抠掉背景之后直接"另存为 PNG"。但 PNG 有两种常见形态:带 alpha 通道的 32 位 PNG,和不带 alpha 的 24 位 PNG。后者虽然有"PNG"的扩展名,背景其实是实打实的白色像素。这种图在浅色背景下看不出来,一旦 TabBar 背景色是深色或者带渐变,白底立刻现形。
第二种:抠图不干净留下的白边。抠图工具处理边缘时,往往会在图形轮廓外侧留下半透明的白色像素。在白色背景上看不出来,放到深色背景上,图标周围会有一圈淡淡的白晕,看起来像描了个不干净的边。这个在真机上特别明显,因为真机的 PPI 高。
第三种:导出时选了 PNG-8 索引色模式。PNG-8 最多支持 256 色,虽然也支持"单一透明色",但边缘只有"透明"和"不透明"两种状态,没有半透明过渡。结果就是图标的圆弧边缘出现锯齿状的硬边,看起来像上世纪的图标。正确做法是导出 PNG-24 或者 PNG-32,保留完整的 alpha 通道。
我的自检流程是这样的,三步,很快:
- 在图片查看器里把背景切成纯黑和纯白各看一遍。纯黑背景上看有没有白边,纯白背景上看有没有黑边或者灰底。
- 放大到 400% 看图形边缘。正常应该有一圈半透明的过渡像素(颜色是图形色到透明的渐变),如果边缘是硬切或者有杂色,说明抠图不干净。
- 看一眼文件大小。81×81 的纯色线性图标,正常在 1.5 到 5KB 之间。如果只有 800 字节,很可能是索引色或者色深被压过头了;如果超过 15KB,大概率是带了多余的通道或者元数据。
注意:这套图标包里所有图标的背景都是完全透明的,也就是说,它们在 TabBar 上显示的颜色,完全由图标自身的填充色决定。因此图标的颜色必须在导出前就定好,不要指望用 CSS 或者配置项去染——原生 TabBar 的
color和selectedColor字段只作用于文字,对图标无效,这一点是新手最常见的误解之一。
3.2 选中态与未选中态:靠明度和填充,不靠换形状
一套 TabBar 图标真正专业的地方,不在单个图标画得多好,而在两个状态之间的关系处理得对不对。
我见过两种典型错误。一种是两个状态用了完全不同的图形——未选中是线框房子,选中变成实心圆点,用户点完之后会有一种"图标换了个人"的割裂感。另一种是两个状态只差颜色,形状完全一样,结果在低亮度屏幕或者强光下,用户根本分不清当前在哪一页。
正确的做法是:轮廓保持一致,靠填充方式和明度建立差异。
具体拆开讲:
| 维度 | 未选中态 | 选中态 | 说明 |
|---|---|---|---|
| 描边方式 | 线性描边 | 实心填充或加粗描边 | 最直观的差异来源 |
| 图形颜色 | 中性灰(明度约 55%) | 深色或品牌主色(明度约 15%) | 明度差拉开到 40% 以上 |
| 线条粗细 | 基础线宽 | 可加粗一档 | 视觉重量增加 |
| 图形尺寸 | 基准尺寸 | 可略微放大 1-2px | 强化"被点亮"的感觉 |
| 内部细节 | 保留 | 可适当简化 | 实心后细节容易糊 |
这里的关键是明度差。很多设计稿只在色相上做区分(比如灰色和蓝色),但灰色和蓝色如果明度接近,在小尺寸下几乎看不出区别。判断标准很简单:把图转成灰度看一眼,如果两个状态还能明显分出深浅,说明明度差够了。
关于灰色取什么值,我的经验是未选中态用#8A8A8A到#999999这个区间,选中态用#1A1A1A到#262626,或者直接用品牌主色但把明度压到 20% 以下。避免用纯黑#000000,在某些屏幕上会显得过于锐利;也避免用纯灰#CCCCCC,在浅色背景上对比度不够。
还有一个细节:选中态的实心填充要留出"呼吸孔"。比如购物车的实心版本,如果能从填充块里透出车身和轮子之间的间隙,辨识度会高很多。全部糊成一坨黑,缩到 27px 之后就是一块黑色多边形,用户得盯着看两秒才认出来是什么。我的做法是填充区域之间至少留 3px(在 81px 画布上)的间隙,保证缩小后还能看见结构。
4. 命名规范不是洁癖,是给三个月后的自己留后路
4.1 一套可执行的命名公式
先说一个真实场景:项目做了半年,产品突然要把"分类"改成"发现",图标内容不变,只是文案变了。这时候你需要找到所有相关的资源并改掉引用。如果文件名是1.png、2.png、3.png,你只能靠肉眼比对;如果是tab_category_normal.png,改起来就是一次搜索替换的事。
命名这件事的价值,只有在需要维护的时候才体现出来。所以我给这套图标包定的命名公式是:
tab_功能名_状态.png四个部分,每一段都有明确含义:
tab_前缀:标明这是底部导航专用图标。项目里往往还有大量业务图标(列表页的箭头、空状态的插图、按钮里的小图标),统一加tab_前缀,一眼就能筛出来,也避免了业务图标和 tab 图标重名的可能。- 功能名:用英文小写,多个单词用下划线连接。首页用
home,分类用category,购物车用cart,我的用profile。不要用拼音(shouye这种),也不要缩写到看不懂(ct、pf)。 - 状态:只有两个值,
normal和active。不要用off/on,不要用default/selected,也不要出现hover这种从 Web 时代带过来的词。全项目统一一套词,比用哪个词更重要。 - 扩展名:全部小写
.png。
按这个公式,一套五个 tab 的图标包,文件清单是这样的:
tab_home_normal.png tab_home_active.png tab_category_normal.png tab_category_active.png tab_cart_normal.png tab_cart_active.png tab_profile_normal.png tab_profile_active.png如果要放第五个 tab(比如"订单"),就是tab_order_normal.png和tab_order_active.png。规律很直白,新人接手也能照着往下加。
有几个细节需要提醒:
- 不要用中文文件名。开发者工具里可能没问题,但打包上传、跨平台构建的时候,中文名是经典的事故源。
- 不要用空格和特殊字符。空格在某些构建流程里会被转义成
%20,路径就匹配不上了。 - 不要用版本号后缀(如
_v2、_final)。要迭代就在 Git 里管,文件系统里永远保持干净的名字。真要区分版本,用目录分。 - 大小写统一小写。前面提过,部分平台对文件名大小写敏感,
Tab_Home_Normal.png和tab_home_normal.png在开发者工具里可能都能读,真机上就是找不到。
4.2 目录分层与app.json路径的对应
文件放哪,也是一个需要一开始就定好的事。我习惯在项目根目录建这样的结构:
project/ ├── app.json ├── app.js ├── app.wxss ├── assets/ │ └── tabbar/ │ ├── tab_home_normal.png │ ├── tab_home_active.png │ ├── tab_category_normal.png │ ├── tab_category_active.png │ ├── tab_cart_normal.png │ ├── tab_cart_active.png │ ├── tab_profile_normal.png │ └── tab_profile_active.png └── pages/关键是assets/tabbar/这个分层。assets放所有静态资源,下面再按用途分子目录,tabbar专门放底部导航图标。这样做的好处是:图标包可以整目录替换,不会和业务图标混在一起;同时也方便你算体积——直接看这个目录多大,就知道静态资源占了多少主包空间。
对应的app.json路径写法是:
"iconPath": "assets/tabbar/tab_home_normal.png", "selectedIconPath": "assets/tabbar/tab_home_active.png"注意路径是相对于app.json所在目录的,前面不要加./,也不要加/。加了/在某些版本里会被解析成项目根目录的绝对路径,虽然有时候也能用,但属于不规范写法,跨端构建工具处理时容易出问题。
如果项目页面层级比较深,导入了子包的,app.json在主包根目录,路径依然按主包根目录算,这一点别忘了。
4.3 图标包跨项目复用时的冲突处理
这套图标包我用了好几个项目,中途遇到过复用带来的问题:项目 A 用的是圆角风格,项目 B 要用更锐利的直线风格,但两边的文件名一模一样。如果直接复制过去,很容易在某个项目里改错了文件,改完才发现改的是另一个项目的副本。
解决办法是在目录层级上做风格区分:
assets/tabbar/ ├── line/ # 线性风格 │ ├── tab_home_normal.png │ └── ... ├── filled/ # 实心风格 │ ├── tab_home_normal.png │ └── ...文件名保持不变,靠父目录区分风格。这样app.json里只要换一个目录名,整体风格就切换了,文件名规则依然是统一的。比在文件名里加_line后缀要干净得多。
还有一种情况是同一个项目里要做主题切换(浅色主题/深色主题两套图标)。原生 TabBar 是支持wx.setTabBarItem动态改图标的,但频繁调用有性能开销,而且切换时会闪一下。我的做法是把两套图标的目录分好,主题切换时统一调一次setTabBarItem批量替换,而不是每张图单独调。
5. 从图标包到跑通:app.json配置与替换全流程
5.1 目录落位与文件清单核对
拿到一套图标包,第一步不是急着改配置,而是先把文件放好、核对清单。这一步花三分钟,能省掉后面半小时的排查。
具体动作:
- 建目录。在项目根目录下建
assets/tabbar/,把八个(或十个)PNG 全部拷进去。不要散放在根目录,也不要放进pages里。 - 数文件。
ls assets/tabbar/ | wc -l,或者直接在文件管理器里看。数量必须等于 tab 数乘以 2。四个 tab 就是八张,少一张后面配置就写不完整。 - 查命名。逐张核对文件名,确认状态后缀是
normal还是active,别出现actived这种拼写变体。命名不统一的话,配置里手写路径的时候极容易写错一个字母。 - 看大小。每张图应该在 2 到 8KB 之间。如果有超过 20KB 的,单独看看是不是尺寸做大了或者带了多余的通道。
- 验透明。随手抽两张,在深色背景的查看器里打开,确认没有白底。
这五步做完,再动app.json。顺序反过来的人很多——先写配置,路径报错了再回头找文件,越找越乱。
5.2 tabBar字段逐个说清楚
配置本身不复杂,但每个字段的边界得弄明白。一个完整的配置长这样:
{ "pages": [ "pages/home/home", "pages/category/category", "pages/cart/cart", "pages/profile/profile" ], "tabBar": { "color": "#8A8A8A", "selectedColor": "#1A1A1A", "backgroundColor": "#FFFFFF", "borderStyle": "black", "list": [ { "pagePath": "pages/home/home", "text": "首页", "iconPath": "assets/tabbar/tab_home_normal.png", "selectedIconPath": "assets/tabbar/tab_home_active.png" }, { "pagePath": "pages/category/category", "text": "分类", "iconPath": "assets/tabbar/tab_category_normal.png", "selectedIconPath": "assets/tabbar/tab_category_active.png" }, { "pagePath": "pages/cart/cart", "text": "购物车", "iconPath": "assets/tabbar/tab_cart_normal.png", "selectedIconPath": "assets/tabbar/tab_cart_active.png" }, { "pagePath": "pages/profile/profile", "text": "我的", "iconPath": "assets/tabbar/tab_profile_normal.png", "selectedIconPath": "assets/tabbar/tab_profile_active.png" } ] } }几个字段的实际含义,和容易搞混的地方:
| 字段 | 作用对象 | 常见误解 |
|---|---|---|
color | 未选中 tab 的文字颜色 | 误以为能改图标颜色 |
selectedColor | 选中 tab 的文字颜色 | 同上 |
backgroundColor | tabBar 背景色 | 误以为能改文字背景 |
borderStyle | tabBar 上边框 | 只有black和white两个值 |
iconPath | 未选中状态图标 | 忘记写或写了网络地址 |
selectedIconPath | 选中状态图标 | 写成了和iconPath一样的路径 |
pagePath | 对应页面路径 | 与pages数组里的路径不一致 |
再说几个实操中的补充点:
pagePath必须和pages数组里的路径完全一致,包括大小写。这是最容易犯的错之一,因为手写路径的时候人眼会自动忽略细微差别。对不上不会在开发者工具里报错,但真机上点了没反应。
list的顺序就是 tab 的显示顺序,从左到右。先想清楚哪个放在第一位(通常是首页),再写配置。
text是必填的,即使你想做纯图标无文字的 tabBar,原生也不支持隐藏文字。想隐藏,只能走自定义 tabBar。
iconPath和selectedIconPath都要给。只给一个的话,切换状态时会出现图标消失或者两个状态看起来一样的情况。
图标颜色和文字颜色要匹配。如果图标是深灰的(#666666),文字是浅灰的(#8A8A8A),底部看起来会很不协调。既然图标颜色在导出时已经定死,那color和selectedColor就应该按图标颜色来填,保持视觉一致。
5.3 换成自定义tabBar之后要注意的三件事
原生 TabBar 有个硬限制:图标只能用位图,位置、大小、动效都改不了。当产品要求"图标上加角标动画""选中时图标弹一下""tabBar 做成悬浮胶囊样式"这类效果时,就必须开自定义 tabBar("custom": true)。
开了之后,这套图标包依然能用,但用法变了——不再是app.json里配路径,而是自己写一个组件,用<image>标签贴图标。这里有三件事要特别注意:
第一,尺寸单位换成 rpx。图标在自定义组件里可以用任意尺寸,但建议用rpx而不是px,这样能自动适配不同屏宽。81px 的图在 375 屏宽下,显示成54rpx左右比较合适(27 逻辑像素 ≈ 54rpx)。
第二,底部安全区必须自己处理。原生 TabBar 会自动避开 iPhone 底部那条横条,自定义的不会。不加处理的话,图标会被裁掉一截。做法是在 tabBar 容器的padding-bottom里加上env(safe-area-inset-bottom),这是标准写法。
第三,selectedIconPath的切换逻辑得自己写。组件里通常用一个currentIndex变量控制,点击时切换。要注意的是,自定义 tabBar 每次切换页面都会重新渲染,如果状态管理没做好,会出现"点了图标高亮不跟着变"的问题。稳妥的做法是把当前索引存在全局数据里,onShow时同步一次。
自定义 TabBar 的成本比原生高不少,我的建议是:除非有明确的产品需求,否则一律用原生。图标包本身就是为原生场景设计的,直接配置就能跑,省下来的时间可以花在更值得的地方。
6. 真机异常排查:图标不显示、发虚、被裁切
6.1 图标完全不出现的排查链路
图标不显示是最常见的症状,但成因有五六种,靠猜很浪费时间。我的排查顺序是固定的,从最可能到最不可能,一条条过:
第一步,看控制台有没有配置报错。list项数不对、pagePath缺失、字段名拼错,这些都会在编译阶段报出来,控制台直接能看到。如果控制台是干净的,说明配置结构没问题,往下走。
第二步,在开发者工具的调试器里看图片请求。打开 Network 面板,切到图片资源,看那几张 tabBar 图标有没有被成功加载。如果显示 404,说明路径不对;如果显示 200 但是不显示,说明是图片本身的问题。
第三步,核对路径大小写。这是 404 的头号原因。assets/tabbar/tab_home_normal.png和Assets/Tabbar/tab_home_normal.png在开发者工具里可能都能读到(取决于系统),真机上一定失败。把路径复制出来,和文件名逐字符对一遍。
第四步,检查图片格式和体积。把图片拖进浏览器直接打开,能正常显示说明文件没坏。然后看文件大小,超过 40KB 的单独压一下。再看扩展名,是不是.webp改了后缀伪装的 PNG——这种情况开发者工具能显示,真机解析失败。
第五步,确认图片有没有真的被打进包里。有些项目的构建配置会忽略assets目录下的某些文件(比如配了ignore规则),或者资源被 CDN 化了。打开打包产物,直接在包里搜文件名,确认它在。
第六步,看tabBar的层级有没有被遮挡。有些页面用了全屏的fixed定位容器,z-index设得比 tabBar 高,把 tabBar 整个盖住了。这种情况下图标其实加载了,只是看不见。临时把容器背景改成半透明,如果能看到图标轮廓,就是层级问题。
这六步走完,基本没有定位不到的情况。我把它写成了一个小清单贴在工位边上,每次遇到直接照着过,比凭经验瞎猜快得多。
6.2 图标发虚与位置偏移的定位
图标能显示,但看起来糊,或者位置偏了,这类问题更隐蔽,因为不报错,只是"感觉不对"。
发虚通常有三个原因。一是图标本身尺寸不够,用了 48×48 的图在 3 倍屏上放大到 81,必然糊。二是图标导出时被压缩过头,色深掉了或者用了有损压缩。三是图形里的细节太多,比如一根头发丝粗细的线条(在 81px 画布上只有 1px),缩小到 27px 之后直接消失了,视觉上就是一片糊。
前两种换图就行,第三种得改设计。我的原则是:在 81px 画布上,任何线条宽度不低于 3px,任何间隙不小于 3px。低于这个值的细节,在 TabBar 这个尺寸下都是无效信息,只会增加视觉噪音。
位置偏移分为横向和纵向。横向偏移通常是图形在画布内没有居中——比如画购物车的时候,把手往左上伸,导致整体视觉重心偏左。解决办法不是挪文件,是回设计稿里把图形往右补一点,让视觉重心对齐画布中心(注意是视觉重心,不是几何外接框的中心)。
纵向偏移更常见,表现是图标离文字太近或太远。前面提过,原生 TabBar 的文字位置是固定的,所以只能调图标。我的一般做法是让图形在画布内整体略偏上——因为视觉上,图形的下沿和文字的上沿之间需要留出间隙,如果图形居中,实际显示时会显得贴着文字。
还有一个小概率情况:某些安卓机型上,tabBar 的高度和 iOS 有差异,导致图标看起来整体偏下。这种情况不用改图,改了反而在 iOS 上不对。统一按主流机型的表现调,个别机型的微小差异可以接受。
6.3 包体积与上传限制的取舍
图标本身不大,但架不住项目里图标多。一个中型小程序,各种状态的图标加起来上百张是很正常的。主包体积有上限(2MB),超了就得拆子包,而assets目录下的静态资源是不能拆到子包里的——tabBar 图标必须在主包。
算一笔账:一套五个 tab 的图标包,十个文件,按平均 4KB 算,总共 40KB,占主包 2%。听起来不多,但如果项目里还有十几个业务图标集、几张大图、几套字体,加起来就危险了。
我的做法是:
- TabBar 图标严格控制体积。线性图标压到 3KB 以内,实心图标不超过 6KB。方法是导出时用无损压缩工具过一遍,通常能再瘦 20% 到 30%。
- 不要在图标里放渐变和阴影。这两样东西会让文件体积翻好几倍,而且在 27px 的显示尺寸下根本看不出来。
- 一个 tab 只配一套图。不要为了"以后可能换风格"把两套都塞进主包。要换风格就在发版时替换。
- 定期清理废弃图标。项目改版之后,老的图标文件经常留在目录里没人删。每隔一两个月搜一遍引用,把没人用的清掉。
有个细节值得单独说:图标文件的体积和它的像素内容强相关。一张纯色、线条简单的 PNG,81×81 可能只有 1.8KB;同样的尺寸,加一圈渐变或者一点噪点,就能到 15KB。所以"控制体积"这件事,本质是在设计阶段就要做的决策,而不是导出之后再压。
7. 图标包的长期维护与交付说明
7.1 角标、禁用态这些"多状态"要不要做成图
标题里说的是"多状态",实际项目里最容易让人纠结的是:除了选中和未选中,还要不要做禁用态、警告态、带角标的状态?
我的答案是:大部分不要做图,用 API 解决。
角标(购物车上显示"3"这种小红点)绝对不要做成图。原生提供了wx.setTabBarBadge和wx.showTabBarRedDot两个接口,动态设置,位置和样式都是系统算好的,各机型一致。自己做图的话,数字一变就得换图,完全不现实。
禁用态在 TabBar 上几乎用不到。用户能不能点某个 tab,通常是通过页面内的权限控制实现的,而不是把 tab 灰掉——把 tab 灰掉是个很差的体验,用户会以为程序坏了。
警告态(比如"我的"上面有个红点表示有待处理事项)同样用 API 解决,showTabBarRedDot就够。
所以这套图标包只做两个状态,normal和active,是刻意的取舍。状态越多,维护成本指数上升,而实际用得到的场景非常有限。真要做特殊状态,在具体项目里单独加一两张图,比在通用包里预置一堆用不上的文件要合理。
如果确实需要自定义角标样式(比如数字背景是品牌色、带描边),那就只能走自定义 tabBar 了,那是另一套方案。
7.2 给图标包配一份能看懂的使用说明
最后说一件容易被跳过、但价值很高的事:给图标包写一份 README。
这套图标包我前后给过好几个项目组用,一开始只是丢一个文件夹过去,结果每次都有人来问"这个图是选中还是未选中""为什么我放进去显示不出来""能不能给我加个订单的图标"。问的次数多了,我就写了一页说明,放在目录里,问题少了八成。
说明里包含这几块内容:
基本信息:画布尺寸 81×81,格式 PNG-24 带 alpha 通道,单文件建议不超过 8KB,图形有效区域 60 到 66px。这几个数字写清楚,别人拿到就能判断自己的新图标符不符合规范。
命名规则:tab_功能名_状态.png,状态只有normal和active。附一张现有文件清单,方便对照。
颜色约定:未选中#8A8A8A,选中#1A1A1A。如果项目要用品牌色,说明里注明"选中态可整体替换为品牌色,但明度需压到 20% 以下"。
新增图标的操作步骤:从哪个模板文件复制、怎么改、导出参数是什么、导出后怎么自检(就是我前面说的深色背景看一遍那三步)。
常见问题:把我踩过的那些坑列成问答,比如"图标显示成白块怎么办""真机上图标模糊怎么办""路径应该怎么写"。每条给一句话的解法。
这份说明看着琐碎,实际省下的是团队里最贵的东西——沟通成本。而且写说明的过程本身也是一次梳理,很多自己没想清楚的细节,写着写着自己就明白了。
提示:说明文档里别写"请参考官方文档"就完事。官方文档讲的是通用规则,你这套图标包的具体约定(尺寸、颜色、命名)是项目私有的,必须自己写清楚,否则下一个人拿到手还是得重新问一遍。
用久了会发现,一套好的图标包,价值不在那八张图本身,而在它背后那套可复制的规范。图会过时,风格会变,但"尺寸统一、背景干净、命名清晰、状态成对"这四条原则,换多少个项目都成立。
我在实际使用中最大的体会是:图标这种看起来最不起眼的资源,反而是最考验工程习惯的地方。设计稿画得再漂亮,导出时忘了留 alpha 通道,上线前就得多熬一个晚上;命名随手写成1.png,三个月后改需求就得靠肉眼找文件。把这几件小事在项目开头做对,后面能省下的时间,远超你想象。