uniapp-vue2导航栏全局自动下拉变色 - 详解

全局自动下拉变色解决方案
雀语文章地址
项目简介
这是一个基于 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 # 示例页面
快速开始

  1. 安装依赖

  2. 全局配置
    在 main.js 中已经配置了全局 mixin:

import PageScrollMixin from './mixins/page-scroll-mixin.js'
// 注册全局 mixin
Vue.mixin(PageScrollMixin)
  1. 使用导航栏组件
    在任何页面中直接使用 custom-navbar 组件:
<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>

核心文件说明

  1. mixins/page-scroll-mixin.js
    全局滚动监听 mixin,为所有页面提供滚动事件处理:
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
})`
}
}
}
}
  1. components/custom-navbar/custom-navbar.vue
    自定义导航栏组件,支持动态样式变化:
<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 中可以自定义颜色:

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
})`
}
}

修改触发阈值
调整滚动距离阈值:

// 将 100 改为你想要的阈值
const opacity = Math.min(this.scrollTop / 50, 1) // 50px 开始变化

使用示例
基础页面

<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>

技术实现
核心原理

  1. 全局 Mixin:通过 Vue 的全局 mixin 机制,为所有页面自动注入滚动监听
  2. 节流优化:使用 setTimeout 实现 60fps 的滚动事件节流
  3. 动态样式:根据滚动位置计算透明度,实现平滑的颜色过渡
  4. 响应式数据:通过 Vue 的响应式系统,自动更新导航栏样式
    性能优化
    ● ✅ 滚动事件节流(16ms 间隔)
    ● ✅ 使用 computed 属性缓存样式计算
    ● ✅ 避免频繁的 DOM 操作
    ● ✅ 合理的内存管理
    常见问题
    Q: 导航栏不显示?
    A: 确保页面内容有足够的高度可以滚动,并且设置了正确的 padding-top
    Q: 颜色变化不明显?
    A: 检查背景图片的对比度,可以调整颜色配置或透明度
    Q: 在某些页面不需要效果?
    A: 可以在特定页面中覆盖 mixin 的方法:
    export default {
    onPageScroll() {
    // 覆盖全局 mixin,不执行滚动处理
    }
    }

贡献
欢迎提交 Issue 和 Pull Request!

注意:此解决方案专为 uni-app 项目设计,确保在目标平台上测试兼容性。

全局导航栏组件,自动实现下拉透明到纯色

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
图片都是 网络地址

雀语文章地址

posted on 2025-08-10 14:58  ljbguanli  阅读(26)  评论(0)    收藏  举报