全局自动下拉变色解决方案
雀语文章地址
📖 项目简介
这是一个基于 Vue.js 和 uni-app 的全局自动下拉变色解决方案,通过全局 mixin 实现页面滚动时导航栏的自动颜色变化效果。
✨ 核心特性
● 🎯 全局自动生效:无需在每个页面手动导入,自动为所有页面添加滚动监听
● 🎨 智能颜色变化:根据滚动位置自动调整导航栏背景色和文字颜色
● 📱 跨平台兼容:支持微信小程序、H5、App 等多端
● ⚡ 性能优化:使用节流函数优化滚动事件处理
● 🔧 易于配置:支持自定义颜色配置和触发阈值
🏗️ 项目结构
buddhism/
├── mixins/
│ └── page-scroll-mixin.js # 全局滚动监听 mixin
├── components/
│ └── custom-navbar/
│ └── custom-navbar.vue # 自定义导航栏组件
├── main.js # 全局 mixin 注册
└── pages/
└── basePage/
└── basePage.vue # 示例页面
🚀 快速开始
-
安装依赖
-
全局配置
在 main.js 中已经配置了全局 mixin:
bash
import PageScrollMixin from './mixins/page-scroll-mixin.js'
// 注册全局 mixin
Vue.mixin(PageScrollMixin)
- 使用导航栏组件
在任何页面中直接使用 custom-navbar 组件:
bash
<template>
<view class="page">
<!-- 自定义导航栏 -->
<custom-navbar
title="页面标题"
:show-back="true"
@back="goBack"
ref="customNavbar"//必写
/>
<!-- 页面内容 -->
<view class="content">
<!-- 你的页面内容 -->
</view>
</view>
</template>
<script>
export default {
name: 'YourPage',
methods: {
goBack() {
uni.navigateBack()
}
}
}
</script>
📋 核心文件说明
- mixins/page-scroll-mixin.js
全局滚动监听 mixin,为所有页面提供滚动事件处理:
bash
export default {
data() {
return {
scrollTop: 0,
navbarOpacity: 0,
navbarTextColor: '#000000',
navbarBgColor: 'rgba(255, 255, 255, 0)'
}
},
onPageScroll(e) {
this.handlePageScroll(e)
},
methods: {
handlePageScroll(e) {
// 节流处理滚动事件
if (this.scrollTimer) return
this.scrollTimer = setTimeout(() => {
this.scrollTop = e.scrollTop
this.updateNavbarStyle()
this.scrollTimer = null
}, 16) // 约60fps
},
updateNavbarStyle() {
// 根据滚动位置更新导航栏样式
const opacity = Math.min(this.scrollTop / 100, 1)
this.navbarOpacity = opacity
if (opacity > 0.5) {
this.navbarTextColor = '#000000'
this.navbarBgColor = `rgba(255, 255, 255, ${opacity})`
} else {
this.navbarTextColor = '#ffffff'
this.navbarBgColor = `rgba(255, 255, 255, ${opacity})`
}
}
}
}
- components/custom-navbar/custom-navbar.vue
自定义导航栏组件,支持动态样式变化:
bash
<template>
<view
class="custom-navbar"
:style="navbarStyle"
>
<view class="navbar-content">
<view
v-if="showBack"
class="back-btn"
@click="handleBack"
>
<text class="back-icon">‹</text>
</view>
<text
class="navbar-title"
:style="{ color: navbarTextColor }"
>
{{ title }}
</text>
</view>
</view>
</template>
<script>
export default {
name: 'CustomNavbar',
props: {
title: {
type: String,
default: ''
},
showBack: {
type: Boolean,
default: false
}
},
computed: {
navbarStyle() {
return {
backgroundColor: this.navbarBgColor,
color: this.navbarTextColor
}
}
},
methods: {
handleBack() {
this.$emit('back')
}
}
}
</script>
🎨 自定义配置
修改颜色配置
在 mixins/page-scroll-mixin.js 中可以自定义颜色:
bash
updateNavbarStyle() {
const opacity = Math.min(this.scrollTop / 100, 1)
this.navbarOpacity = opacity
// 自定义颜色逻辑
if (opacity > 0.5) {
this.navbarTextColor = '#333333' // 深色文字
this.navbarBgColor = `rgba(255, 255, 255, ${opacity})`
} else {
this.navbarTextColor = '#ffffff' // 白色文字
this.navbarBgColor = `rgba(0, 0, 0, ${opacity * 0.3})`
}
}
修改触发阈值
调整滚动距离阈值:
bash
// 将 100 改为你想要的阈值
const opacity = Math.min(this.scrollTop / 50, 1) // 50px 开始变化
📱 使用示例
基础页面
bash
<template>
<view class="page">
<custom-navbar
title="首页"
:show-back="false"
ref="customNavbar"//必写
/>
<view class="content">
<view class="banner">
<image src="/static/banner.jpg" mode="aspectFill" />
</view>
<view class="list">
<view
v-for="item in 20"
:key="item"
class="list-item"
>
列表项 {{ item }}
</view>
</view>
</view>
</view>
</template>
<script>
export default {
name: 'HomePage'
}
</script>
<style scoped>
.page {
min-height: 100vh;
background: #f5f5f5;
}
.content {
padding-top: 44px; /* 导航栏高度 */
}
.banner {
height: 200px;
background: linear-gradient(45deg, #667eea, #764ba2);
}
.list-item {
padding: 15px;
margin: 10px;
background: white;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
</style>
详情页面
<template>
<view class="page">
<custom-navbar
title="详情页"
:show-back="true"
@back="goBack"
/>
<view class="content">
<view class="hero-image">
<image src="/static/detail.jpg" mode="aspectFill" />
</view>
<view class="detail-content">
<text class="title">详情标题</text>
<text class="description">详情描述内容...</text>
</view>
</view>
</view>
</template>
<script>
export default {
name: 'DetailPage',
methods: {
goBack() {
uni.navigateBack()
}
}
}
</script>
🔧 技术实现
核心原理
- 全局 Mixin:通过 Vue 的全局 mixin 机制,为所有页面自动注入滚动监听
- 节流优化:使用 setTimeout 实现 60fps 的滚动事件节流
- 动态样式:根据滚动位置计算透明度,实现平滑的颜色过渡
- 响应式数据:通过 Vue 的响应式系统,自动更新导航栏样式
性能优化
● ✅ 滚动事件节流(16ms 间隔)
● ✅ 使用 computed 属性缓存样式计算
● ✅ 避免频繁的 DOM 操作
● ✅ 合理的内存管理
🐛 常见问题
Q: 导航栏不显示?
A: 确保页面内容有足够的高度可以滚动,并且设置了正确的 padding-top
Q: 颜色变化不明显?
A: 检查背景图片的对比度,可以调整颜色配置或透明度
Q: 在某些页面不需要效果?
A: 可以在特定页面中覆盖 mixin 的方法:
export default {
onPageScroll() {
// 覆盖全局 mixin,不执行滚动处理
}
}
🤝 贡献
欢迎提交 Issue 和 Pull Request!
注意:此解决方案专为 uni-app 项目设计,确保在目标平台上测试兼容性。
全局导航栏组件,自动实现下拉透明到纯色
bash
import PageScrollMixin from './mixins/page-scroll-mixin.js'
// 注册全局 mixin
Vue.mixin(PageScrollMixin)
/**
* 页面滚动监听 Mixin
* 用于自动处理 custom-navbar 组件的滚动事件传递
*
* 使用方法:
* 1. 在页面中引入此 mixin
* 2. 确保 custom-navbar 组件有 ref="customNavbar"(默认)或自定义 ref
* 3. 自动处理滚动事件传递
*
* 配置选项:
* - scrollRefs: 需要传递滚动事件的组件 ref 数组,默认为 ['customNavbar']
*
* 使用示例:
*
* // 基础用法(使用默认 ref="customNavbar")
* export default {
* mixins: [PageScrollMixin],
* // ... 其他配置
* }
*
* // 自定义 ref 名称
* export default {
* mixins: [PageScrollMixin],
* scrollRefs: ['myNavbar'], // 自定义 ref 名称
* // ... 其他配置
* }
*
* // 多个组件
* export default {
* mixins: [PageScrollMixin],
* scrollRefs: ['customNavbar', 'floatingButton'], // 多个组件
* // ... 其他配置
* }
*/
export default {
data() {
return {
// 默认的滚动组件 ref 列表
scrollRefs: this.$options.scrollRefs || ['customNavbar']
};
},
// 页面生命周期
onPageScroll(e) {
// 自动将页面滚动事件传递给所有配置的组件
this.scrollRefs.forEach(refName => {
if (this.$refs[refName] && typeof this.$refs[refName].handlePageScroll === 'function') {
this.$refs[refName].handlePageScroll(e);
}
});
}
};
<template>
<view>
<!-- 填充区,避免内容被导航栏遮挡 -->
<view class="navbar-placeholder" :style="{ height: navBarHeight + 'px' }"></view>
<!-- 自定义导航栏 -->
<view
class="custom-navbar"
:class="{ 'navbar-scrolled': isScrolled }"
:style="{
paddingTop: statusBarHeight + 'px',
height: navBarHeight + 'px',
backgroundColor: isScrolled ? backgroundColor : 'transparent'
}"
>
<view class="navbar-left" @click="handleBack" :style="{ height: capsuleHeight + 'px', lineHeight: capsuleHeight + 'px' }">
<image :src="backIcon" mode="aspectFit" class="back-icon"></image>
</view>
<view class="navbar-title" :style="{ height: capsuleHeight + 'px', lineHeight: capsuleHeight + 'px' }">{{ title }}</view>
<view class="navbar-right" :style="{ height: capsuleHeight + 'px', lineHeight: capsuleHeight + 'px' }">
<slot name="right"></slot>
</view>
</view>
</view>
</template>
<script>
import CommonEnum from "../../enum/common";
export default {
name: 'CustomNavbar',
props: {
title: {
type: String,
default: ''
},
backIcon: {
type: String,
default: CommonEnum.BACK_BUTTON
},
showBack: {
type: Boolean,
default: true
},
// 新增:滚动相关配置
backgroundColor: {
type: String,
default: CommonEnum.BASE_COLOR // 滚动时的背景色,使用基调颜色
},
scrollThreshold: {
type: Number,
default: 20 // 滚动阈值,超过此值开始变色
},
enableScrollEffect: {
type: Boolean,
default: true // 是否启用滚动效果
}
},
data() {
return {
statusBarHeight: 0,
navBarHeight: 0,
menuButtonInfo: null,
capsuleHeight: 0,
// 新增:滚动状态
isScrolled: false,
scrollTop: 0,
lastScrollTop: 0
}
},
mounted() {
this.getSystemInfo();
},
methods: {
getSystemInfo() {
// 获取系统信息
const systemInfo = uni.getSystemInfoSync();
// 获取状态栏高度
this.statusBarHeight = systemInfo.statusBarHeight;
// 获取胶囊按钮信息
this.menuButtonInfo = uni.getMenuButtonBoundingClientRect();
// 计算胶囊高度
this.capsuleHeight = this.menuButtonInfo.height;
// 计算导航栏高度(胶囊底部到状态栏顶部的距离)
this.navBarHeight = this.menuButtonInfo.bottom + 8;
},
handleBack() {
if (this.showBack) {
// 先触发自定义事件,让父组件有机会处理
this.$emit('back');
// 自动执行返回逻辑
uni.navigateBack({
delta: 1
});
}
},
// 新增:处理页面滚动(供父组件调用)
handlePageScroll(e) {
if (!this.enableScrollEffect) return;
this.scrollTop = e.scrollTop;
// 判断是否超过滚动阈值
if (this.scrollTop > this.scrollThreshold) {
if (!this.isScrolled) {
this.isScrolled = true;
this.$emit('scroll-change', { isScrolled: true, scrollTop: this.scrollTop });
}
} else {
if (this.isScrolled) {
this.isScrolled = false;
this.$emit('scroll-change', { isScrolled: false, scrollTop: this.scrollTop });
}
}
// 触发滚动事件,让父组件可以获取滚动信息
this.$emit('scroll', {
scrollTop: this.scrollTop,
isScrolled: this.isScrolled
});
},
// 新增:手动设置滚动状态(供父组件调用)
setScrollState(scrollTop) {
if (!this.enableScrollEffect) return;
this.scrollTop = scrollTop;
this.isScrolled = scrollTop > this.scrollThreshold;
}
}
}
</script>
<style lang="scss" scoped>
/* 填充区样式 */
.navbar-placeholder {
width: 100%;
background-color: transparent;
}
/* 自定义导航栏样式 */
.custom-navbar {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 999;
padding-top: 0;
background-color: transparent;
display: flex;
align-items: center;
justify-content: space-between;
padding-left: 30rpx;
padding-right: 30rpx;
box-sizing: border-box;
transition: background-color 0.3s ease; /* 添加过渡动画 */
}
/* 滚动状态样式 */
.navbar-scrolled {
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1); /* 滚动时添加阴影 */
}
.navbar-left {
display: flex;
align-items: center;
justify-content: center;
width: 60rpx;
.back-icon {
width: 56rpx;
height: 56rpx;
}
}
.navbar-title {
font-size: 36rpx;
font-weight: bold;
color: #695347;
flex: 1;
text-align: center;
display: flex;
align-items: center;
justify-content: center;
}
.navbar-right {
width: 60rpx;
display: flex;
align-items: center;
justify-content: center;
}
</style>
<template>
<view class="page">
<custom-navbar
ref="customNavbar"
title="基础页面"
/>
<tile-grid/>
<view class="header" :style="{ backgroundColor: CommonEnum.BASE_COLOR }">
<!-- 状态展示 -->
<status-display
v-if="loading"
type="loading"
loading-text="加载中..."
/>
<!-- 页面内容将在这里添加 -->
</view>
</view>
</template>
<script>
import CommonEnum from "../../enum/common";
import StatusDisplay from "../../components/status-display/status-display.vue";
export default {
components: {
StatusDisplay
},
data() {
return {
CommonEnum,
loading: false
}
},
onLoad() {
// 页面加载时获取数据
this.loadPageData()
},
methods: {
// 加载页面数据
async loadPageData() {
this.loading = true
try {
// TODO: 调用页面数据API
// const response = await getPageData()
// 模拟加载
setTimeout(() => {
this.loading = false
}, 1000)
} catch (error) {
console.error('获取页面数据失败:', error)
this.loading = false
}
}
}
}
</script>
<style lang="scss" scoped>
.page {
background: #F5F0E7;
}
.header {
width: 100%;
min-height: 100vh;
display: flex;
align-items: flex-start;
flex-direction: column;
padding: 0 15rpx;
padding-bottom: 40rpx;
}
</style>
颜色是在枚举中是 #FFFFFF
图片都是 网络地址