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:

bash 复制代码
import PageScrollMixin from './mixins/page-scroll-mixin.js'

// 注册全局 mixin
Vue.mixin(PageScrollMixin)
  1. 使用导航栏组件
    在任何页面中直接使用 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>

📋 核心文件说明

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

🔧 技术实现

核心原理

  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 项目设计,确保在目标平台上测试兼容性。

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

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

图片都是 网络地址

雀语文章地址

相关推荐
掘金0111 分钟前
Vue3+Element Plus实现动态条件字段联动校验
前端·vue.js·前端框架
蒙面人25 分钟前
拖动组件 vue-draggable-next 跨组件和clone问题
vue.js
蓝爱人42 分钟前
vue3接收SSE流数据进行实时渲染日志
前端·javascript·vue.js
梦鱼1 小时前
🔥 用 Vue2 + PDF.js 手撸一个「PDF 连续预览器」,自适应屏幕、支持缩放,直接拿来用!
前端·javascript·vue.js
極光未晚2 小时前
Vue 项目 webpack 打包体积分析:从 “盲猜优化” 到 “精准瘦身”
前端·vue.js·性能优化
Rudon滨海渔村2 小时前
[失败记录] 使用HBuilderX创建的uniapp vue3项目添加tailwindcss3的完整过程
css·uni-app·tailwindcss
HANK4 小时前
Electron + Vue3 桌面应用开发实战指南
前端·vue.js
極光未晚4 小时前
Vue 前端高效分包指南:从 “卡成 PPT” 到 “丝滑如德芙” 的蜕变
前端·vue.js·性能优化
LIUENG4 小时前
Vue2 中的响应式原理
前端·vue.js