在微信小程序里,系统自带的tabbar只支持纯色或者背景图,没法做角度可变的线性渐变,更别说让渐变角度跟着时间跑形成流光。要实现这种效果,只能走自定义tabbar路线,把原本的导航栏换成自己写的组件,用内联样式去控制background里的linear-gradient角度。核心思路其实很直白:把角度当成一个状态变量,用setData或者更轻量的this.setData局部更新,让它在0到360度之间循环,高光带就会沿着底部栏滑动。

自定义tabbar的基础结构搭建
先要在app.json里声明"tabBar": { "custom": true },同时保留list配置用于页面路径和图标占位。小程序框架在启用custom后会去加载custom-tab-bar目录下的index组件,这个组件就是我们用来自绘导航栏的地方。很多新手容易漏掉list里的pagePath必须和真实页面一致,否则切换时会白屏。
组件内部建议用一个固定定位的容器罩在底部,position: fixed; bottom: 0; left: 0; right: 0;。里面每个tab项用flex均分,点击时通过wx.switchTab跳转。由于自定义tabbar在每个tab页都会实例化一次,所以选中态不能只存在组件内,而要读取getCurrentPages的路由来做高亮,否则从页面A跳到页面B后,导航栏的选中下标错乱。
在结构层面还要处理iPhone的安全区,用padding-bottom: env(safe-area-inset-bottom)撑开底部,不然流光条会和Home Indicator重叠。组件生命周期里建议在attached阶段启动角度动画,在detached阶段清理定时器,防止页面销毁后还在跑setData造成内存泄漏。
动态渐变角度的计算与样式绑定
流光效果的本质是一条窄的高光渐变带绕中心旋转。我们可以用linear-gradient({{angle}}deg, transparent 0%, rgba(255,255,255,0.6) 50%, transparent 100%)作为覆盖层,角度每帧加一。微信小程序的style属性支持字符串拼接,所以在wxml里写style="background: linear-gradient({{angle}}deg, #4facfe, #00f2fe);"就能实时变。
如果只改背景色角度,高光不够明显,可以叠两层:底层是慢速变换的主题色渐变,上层是快速扫过的高光带。角度变量建议放在data里叫angle和lightAngle,用setInterval以30毫秒为步长自增。下面是一段可直接用的逻辑代码:
Component({
data: {
angle: 0,
lightAngle: 45,
list: [
{ pagePath: '/pages/home/home', text: '首页' },
{ pagePath: '/pages/mine/mine', text: '我的' }
],
selected: 0
},
methods: {
startFlow: function () {
var self = this;
this.timer = setInterval(function () {
var a = (self.data.angle + 1) % 360;
var la = (self.data.lightAngle + 3) % 360;
self.setData({
angle: a,
lightAngle: la
});
}, 30);
},
stopFlow: function () {
if (this.timer) clearInterval(this.timer);
}
},
lifetimes: {
attached: function () {
this.startFlow();
},
detached: function () {
this.stopFlow();
}
}
});
上面代码里lightAngle转得比angle快,视觉上就像一道光快速扫过缓慢变色的底栏。要注意setData频率不能太高,30毫秒大约每秒33次,在低端安卓机上可能掉帧,可以降到50毫秒或只用lightAngle做变化来减负。
性能优化与多端兼容要点
频繁setData是小程序流光动画的主要性能瓶颈。一个有效做法是把角度计算移到wxs模块里,在视图层直接算样式字符串,避免逻辑层与视图层通信。比如在wxs中导出函数接收时间戳返回linear-gradient串,然后用{{m.format(angle)}}绑定,这样连setData都省了,只用requestAnimationFrame推时间。
在iOS上,safari内核的渐变渲染比较平滑,但自定义tabbar如果用了box-shadow会加重合成层负担,建议流光层用transform: translateZ(0)单独提层。安卓部分机型对env(safe-area-inset-bottom)支持不好,可以用wx.getSystemInfoSync拿safeArea自己算底部留白,写死padding-bottom更稳。
另外,当小程序切到后台时,setInterval不会被销毁但页面不渲染,回来可能角度跳变。可以在onHide里stopFlow,onShow里重新startFlow并校准角度基准。真机调试时打开渲染性能面板,观察setData耗时,只要单次低于4毫秒,流光就不会卡。以下是wxs优化版的简化示例:
var flow = {
getBg: function (ts) {
var a = (ts / 20) % 360;
var la = (ts / 6) % 360;
return 'linear-gradient(' + a + 'deg, #4facfe, #00f2fe), linear-gradient(' + la + 'deg, transparent 40%, rgba(255,255,255,0.7) 50%, transparent 60%)';
}
};
module.exports = flow;
把这段wxs引入后,wxml里写style="background: {{m.getBg(timestamp)}};",再用一个仅更新timestamp的轻量循环驱动,就能把流光动画的卡顿率压到最低。实际项目中,这种角度动态渐变方案比换图省包体,也比纯CSS动画可控,适合做品牌化的导航交互。