v-scroll-mask 滚动遮罩指令
v-scroll-mask 是一个 Vue 3 自定义指令,用于为滚动容器添加渐隐遮罩效果和阴影,提升用户体验。
特性
- 🎨 智能遮罩:根据滚动位置自动显示/隐藏渐隐效果
- 🎯 多方向支持:支持垂直、水平和双向滚动
- 🎨 自定义颜色:支持自定义遮罩颜色和背景色
- ⚡ 过渡动画:可配置的平滑过渡效果
- 📱 响应式:自动适应容器尺寸变化
- 🔧 高度可配置:丰富的配置选项
安装
bash
npm install @pt/common-ui基础用法
注册指令
typescript
// main.ts
import { createApp } from "vue";
import { scrollMask } from "@pt/common-ui";
const app = createApp(App);
app.directive("scroll-mask", scrollMask);简单示例
vue
<template>
<!-- 基础用法 -->
<div class="scroll-container" v-scroll-mask>
<div class="content">
<p v-for="i in 20" :key="i">内容行 {{ i }}</p>
</div>
</div>
</template>
<style>
.scroll-container {
height: 300px;
overflow: auto;
border: 1px solid #ccc;
border-radius: 8px;
}
</style>配置选项
ScrollMaskOptions 接口
typescript
interface ScrollMaskOptions {
fadePercent?: number; // 渐隐区域百分比,默认 15
shadowSize?: number; // 阴影大小,默认 8px
shadowColor?: string; // 阴影颜色,默认 rgba(0,0,0,0.3)
hideScrollbar?: boolean; // 是否隐藏滚动条,默认 true
direction?: "auto" | "vertical" | "horizontal" | "both"; // 滚动方向,默认 auto
maskColor?: string; // 遮罩颜色,默认 black
backgroundColor?: string; // 背景颜色,默认 transparent
enableTransition?: boolean; // 是否启用过渡动画,默认 false
transitionDuration?: string; // 过渡动画持续时间,默认 0.3s
transitionEasing?: string; // 过渡动画缓动函数,默认 ease
// 🎯 细粒度方向控制选项
enableTop?: boolean; // 是否启用顶部遮罩,默认 true
enableBottom?: boolean; // 是否启用底部遮罩,默认 true
enableLeft?: boolean; // 是否启用左侧遮罩,默认 true
enableRight?: boolean; // 是否启用右侧遮罩,默认 true
}使用示例
1. 自定义颜色
vue
<template>
<!-- 蓝色主题 -->
<div
class="scroll-container"
v-scroll-mask="{
maskColor: '#1e40af',
backgroundColor: 'rgba(59, 130, 246, 0.1)',
}"
>
<div class="content">
<p v-for="i in 15" :key="i">蓝色主题内容 {{ i }}</p>
</div>
</div>
<!-- 红色主题 -->
<div
class="scroll-container"
v-scroll-mask="{
maskColor: '#dc2626',
backgroundColor: 'rgba(239, 68, 68, 0.1)',
}"
>
<div class="content">
<p v-for="i in 15" :key="i">红色主题内容 {{ i }}</p>
</div>
</div>
</template>2. 过渡动画配置
vue
<template>
<!-- 启用过渡动画 -->
<div
class="scroll-container"
v-scroll-mask="{
enableTransition: true,
transitionDuration: '0.2s',
transitionEasing: 'ease-out',
}"
>
<div class="content">
<p v-for="i in 12" :key="i">带过渡动画的内容 {{ i }}</p>
</div>
</div>
</template>3. 水平滚动
vue
<template>
<div
class="horizontal-scroll"
v-scroll-mask="{
direction: 'horizontal',
maskColor: '#7c3aed',
enableTransition: true,
}"
>
<div class="horizontal-content">
<div
v-for="i in 10"
:key="i"
class="card"
:style="{ backgroundColor: `hsl(${i * 36}, 70%, 80%)` }"
>
卡片 {{ i }}
</div>
</div>
</div>
</template>
<style>
.horizontal-scroll {
height: 150px;
overflow-x: auto;
overflow-y: hidden;
}
.horizontal-content {
display: flex;
gap: 16px;
padding: 16px;
width: max-content;
}
.card {
min-width: 150px;
height: 100px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 8px;
color: white;
font-weight: 500;
}
</style>4. 双向滚动
vue
<template>
<div
class="both-scroll"
v-scroll-mask="{
direction: 'both',
maskColor: '#0891b2',
enableTransition: true,
fadePercent: 20,
}"
>
<div class="grid-content">
<div v-for="row in 15" :key="row" class="grid-row">
<span v-for="col in 20" :key="col" class="grid-cell">
{{ row }}-{{ col }}
</span>
</div>
</div>
</div>
</template>
<style>
.both-scroll {
height: 200px;
overflow: auto;
}
.grid-content {
width: max-content;
padding: 16px;
}
.grid-row {
display: flex;
margin-bottom: 8px;
}
.grid-cell {
min-width: 80px;
padding: 8px;
margin-right: 8px;
background: #e0e7ff;
border-radius: 4px;
text-align: center;
font-size: 0.875rem;
}
</style>5. 自定义参数
vue
<template>
<!-- 大渐隐区域 -->
<div
class="scroll-container"
v-scroll-mask="{
fadePercent: 30,
shadowSize: 15,
shadowColor: 'rgba(139, 69, 19, 0.5)',
}"
>
<div class="content">
<p v-for="i in 12" :key="i">大渐隐区域 (30%) - 内容 {{ i }}</p>
</div>
</div>
<!-- 显示滚动条 -->
<div
class="scroll-container"
v-scroll-mask="{
hideScrollbar: false,
maskColor: '#f59e0b',
}"
>
<div class="content">
<p v-for="i in 12" :key="i">显示滚动条 - 内容 {{ i }}</p>
</div>
</div>
</template>6. 动态配置
vue
<template>
<div class="controls">
<label>
遮罩颜色:
<input type="color" v-model="config.maskColor" />
</label>
<label>
渐隐百分比:
<input
type="range"
min="5"
max="50"
v-model.number="config.fadePercent"
/>
{{ config.fadePercent }}%
</label>
<label>
<input type="checkbox" v-model="config.enableTransition" />
启用过渡动画
</label>
</div>
<div class="scroll-container" v-scroll-mask="config">
<div class="content">
<p v-for="i in 15" :key="i">动态配置内容 {{ i }}</p>
</div>
</div>
</template>
<script setup>
import { reactive } from "vue";
const config = reactive({
maskColor: "#6366f1",
fadePercent: 15,
enableTransition: true,
transitionDuration: "0.3s",
transitionEasing: "ease",
});
</script>滚动状态
指令会自动为元素添加相应的 CSS 类名,用于标识当前滚动状态:
| 状态类名 | 描述 |
|---|---|
scroll-mask-top | 滚动到顶部 |
scroll-mask-bottom | 滚动到底部 |
scroll-mask-left | 滚动到左侧 |
scroll-mask-right | 滚动到右侧 |
scroll-mask-middle-v | 垂直方向中间位置 |
scroll-mask-middle-h | 水平方向中间位置 |
scroll-mask-middle | 双向滚动中间位置 |
工作原理
1. 遮罩实现
指令使用 CSS mask-image 属性创建渐隐效果:
css
/* 顶部遮罩 */
mask-image: linear-gradient(to bottom, black 85%, transparent 100%);
/* 底部遮罩 */
mask-image: linear-gradient(to top, black 85%, transparent 100%);
/* 中间遮罩 */
mask-image: linear-gradient(
to bottom,
transparent 0%,
black 15%,
black 85%,
transparent 100%
);2. 阴影效果
使用 box-shadow 的 inset 属性创建内阴影:
css
/* 底部阴影 */
box-shadow: inset 0 -8px 8px -8px rgba(0, 0, 0, 0.3);
/* 顶部阴影 */
box-shadow: inset 0 8px 8px -8px rgba(0, 0, 0, 0.3);3. 滚动条隐藏
通过多种方式确保跨浏览器兼容:
css
/* Firefox */
scrollbar-width: none;
/* IE/Edge */
-ms-overflow-style: none;
/* Webkit */
.hide-scrollbar::-webkit-scrollbar {
display: none;
}最佳实践
1. 容器设置
确保滚动容器有明确的高度和 overflow 属性:
css
.scroll-container {
height: 300px;
overflow: auto; /* 或 overflow-y: auto */
}2. 性能优化
- 指令使用
passive: true监听滚动事件 - 使用
ResizeObserver监听尺寸变化 - 避免在大量元素上同时使用
3. 颜色搭配
选择与内容背景相匹配的遮罩颜色:
javascript
// 浅色背景
{ maskColor: '#ffffff', backgroundColor: 'transparent' }
// 深色背景
{ maskColor: '#1f2937', backgroundColor: 'transparent' }
// 彩色背景
{ maskColor: '#3b82f6', backgroundColor: 'rgba(59, 130, 246, 0.1)' }4. 响应式设计
在移动设备上考虑调整参数:
javascript
const isMobile = window.innerWidth < 768;
const config = {
fadePercent: isMobile ? 20 : 15,
shadowSize: isMobile ? 6 : 8,
enableTransition: !isMobile, // 移动设备上禁用动画以提升性能
};注意事项
1. 浏览器兼容性
- CSS
mask属性在较老的浏览器中可能不支持 - IE 浏览器需要使用前缀
-webkit-mask
2. 性能考虑
- 避免在滚动过程中频繁更新配置
- 大量元素使用时注意内存占用
3. 样式冲突
- 指令会修改元素的
mask-image和box-shadow属性 - 避免在 CSS 中覆盖这些属性
故障排除
问题:遮罩效果不显示
解决方案:
- 检查容器是否设置了正确的
overflow属性 - 确保容器有固定高度
- 验证内容是否超出容器高度
问题:滚动条仍然显示
解决方案:
- 确保
hideScrollbar: true(默认值) - 检查 CSS 是否有
!important覆盖 - 验证浏览器是否支持相关属性
问题:过渡动画不流畅
解决方案:
- 调整
transitionDuration和transitionEasing - 在移动设备上禁用过渡动画
- 检查是否有其他 CSS 动画冲突
API 参考
指令使用
vue
<div v-scroll-mask="options">
<!-- 内容 -->
</div>完整配置示例
javascript
const options = {
fadePercent: 15, // 渐隐区域百分比 (5-50)
shadowSize: 8, // 阴影大小 (px)
shadowColor: "rgba(0,0,0,0.3)", // 阴影颜色
hideScrollbar: true, // 隐藏滚动条
direction: "auto", // 滚动方向: auto | vertical | horizontal | both
maskColor: "black", // 遮罩颜色
backgroundColor: "transparent", // 背景颜色
enableTransition: false, // 启用过渡动画
transitionDuration: "0.3s", // 过渡持续时间
transitionEasing: "ease", // 过渡缓动函数
// 🎯 细粒度方向控制选项
enableTop: true, // 是否启用顶部遮罩
enableBottom: true, // 是否启用底部遮罩
enableLeft: true, // 是否启用左侧遮罩
enableRight: true, // 是否启用右侧遮罩
};📚 效果预览
v-scroll-mask 指令
请打开代码查看
<template>
<div class="scroll-mask-test-page">
<h1 class="page-title">ScrollMask 指令测试页面</h1>
<p class="page-description">测试 v-scroll-mask 指令的各种配置选项和使用场景</p>
<!-- 基础用法 -->
<div class="test-section">
<h2 class="section-title">1. 基础用法(默认配置)</h2>
<div class="test-container">
<div class="scroll-box basic-test" v-scroll-mask>
<div class="content">
<p v-for="i in 20" :key="i">
这是第 {{ i }} 行内容,用于测试垂直滚动遮罩效果。滚动时会在顶部和底部显示渐隐效果。
</p>
</div>
</div>
</div>
</div>
<!-- 自定义颜色 -->
<div class="test-section">
<h2 class="section-title">2. 自定义颜色配置</h2>
<div class="test-grid">
<div class="test-item">
<h3>蓝色遮罩</h3>
<div class="scroll-box" v-scroll-mask="{ maskColor: '#1e40af', backgroundColor: 'rgba(59, 130, 246, 0.1)' }">
<div class="content">
<p v-for="i in 15" :key="i">蓝色主题遮罩效果 - 第 {{ i }} 行</p>
</div>
</div>
</div>
<div class="test-item">
<h3>红色遮罩</h3>
<div class="scroll-box" v-scroll-mask="{ maskColor: '#dc2626', backgroundColor: 'rgba(239, 68, 68, 0.1)' }">
<div class="content">
<p v-for="i in 15" :key="i">红色主题遮罩效果 - 第 {{ i }} 行</p>
</div>
</div>
</div>
</div>
</div>
<!-- 过渡动画配置 -->
<div class="test-section">
<h2 class="section-title">3. 过渡动画配置</h2>
<div class="test-grid">
<div class="test-item">
<h3>无过渡(默认)</h3>
<div class="scroll-box" v-scroll-mask="{ enableTransition: false }">
<div class="content">
<p v-for="i in 12" :key="i">无过渡动画 - 第 {{ i }} 行</p>
</div>
</div>
</div>
<div class="test-item">
<h3>快速过渡</h3>
<div class="scroll-box" v-scroll-mask="{ enableTransition: true, transitionDuration: '0.2s', transitionEasing: 'ease-out' }">
<div class="content">
<p v-for="i in 12" :key="i">快速过渡动画 - 第 {{ i }} 行</p>
</div>
</div>
</div>
</div>
</div>
<!-- 水平滚动 -->
<div class="test-section">
<h2 class="section-title">4. 水平滚动测试</h2>
<div class="test-container">
<div class="scroll-box horizontal-scroll" v-scroll-mask="{ direction: 'horizontal', maskColor: '#7c3aed', enableTransition: true }">
<div class="horizontal-content">
<div v-for="i in 10" :key="i" class="horizontal-item" :style="{ backgroundColor: `hsl(${i * 36}, 70%, 80%)` }">
卡片 {{ i }}
</div>
</div>
</div>
</div>
</div>
<!-- 双向滚动 -->
<div class="test-section">
<h2 class="section-title">5. 双向滚动测试</h2>
<div class="test-container">
<div class="scroll-box both-scroll" v-scroll-mask="{ direction: 'both', maskColor: '#0891b2', enableTransition: true, fadePercent: 20 }">
<div class="both-content">
<div v-for="row in 15" :key="row" class="both-row">
<span v-for="col in 20" :key="col" class="both-cell">{{ row }}-{{ col }}</span>
</div>
</div>
</div>
</div>
</div>
<!-- 自定义参数测试 -->
<div class="test-section">
<h2 class="section-title">6. 自定义参数测试</h2>
<div class="test-grid">
<div class="test-item">
<h3>大渐隐区域</h3>
<div class="scroll-box" v-scroll-mask="{ fadePercent: 30, shadowSize: 15, shadowColor: 'rgba(139, 69, 19, 0.5)' }">
<div class="content">
<p v-for="i in 12" :key="i">大渐隐区域 (30%) - 第 {{ i }} 行</p>
</div>
</div>
</div>
<div class="test-item">
<h3>显示滚动条</h3>
<div class="scroll-box" v-scroll-mask="{ hideScrollbar: false, maskColor: '#f59e0b' }">
<div class="content">
<p v-for="i in 12" :key="i">显示滚动条 - 第 {{ i }} 行</p>
</div>
</div>
</div>
</div>
</div>
<!-- 动态配置测试 -->
<div class="test-section">
<h2 class="section-title">7. 动态配置测试</h2>
<div class="controls">
<label>
遮罩颜色:
<input type="color" v-model="dynamicConfig.maskColor">
</label>
<label>
渐隐百分比:
<input type="range" min="5" max="50" v-model.number="dynamicConfig.fadePercent">
{{ dynamicConfig.fadePercent }}%
</label>
<label>
<input type="checkbox" v-model="dynamicConfig.enableTransition">
启用过渡动画
</label>
</div>
<div class="test-container">
<div class="scroll-box" v-scroll-mask="dynamicConfig">
<div class="content">
<p v-for="i in 15" :key="i">动态配置测试 - 第 {{ i }} 行内容</p>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { reactive } from 'vue'
// 动态配置
const dynamicConfig = reactive({
maskColor: '#6366f1',
fadePercent: 15,
enableTransition: true,
transitionDuration: '0.3s',
transitionEasing: 'ease'
})
</script>
<style scoped>
.scroll-mask-test-page {
padding: 20px;
max-width: 1200px;
margin: 0 auto;
}
.page-title {
font-size: 2rem;
font-weight: bold;
margin-bottom: 0.5rem;
color: #1f2937;
}
.page-description {
color: #6b7280;
margin-bottom: 2rem;
}
.test-section {
margin-bottom: 3rem;
}
.section-title {
font-size: 1.5rem;
font-weight: 600;
margin-bottom: 1rem;
color: #374151;
}
.test-container {
display: flex;
justify-content: center;
}
.test-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 1.5rem;
}
.test-item h3 {
font-size: 1.1rem;
font-weight: 500;
margin-bottom: 0.5rem;
color: #4b5563;
}
.scroll-box {
width: 100%;
height: 200px;
border: 2px solid #e5e7eb;
border-radius: 8px;
background: #f9fafb;
position: relative;
overflow: auto;
}
.content {
padding: 1rem;
}
.content p {
margin-bottom: 0.5rem;
line-height: 1.5;
color: #374151;
}
/* 水平滚动样式 */
.horizontal-scroll {
overflow-x: auto;
overflow-y: hidden;
}
.horizontal-content {
display: flex;
gap: 1rem;
padding: 1rem;
width: max-content;
}
.horizontal-item {
min-width: 150px;
height: 100px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 8px;
color: white;
font-weight: 500;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
/* 双向滚动样式 */
.both-scroll {
overflow: auto;
}
.both-content {
width: max-content;
padding: 1rem;
}
.both-row {
display: flex;
margin-bottom: 0.5rem;
}
.both-cell {
display: inline-block;
min-width: 80px;
padding: 0.5rem;
margin-right: 0.5rem;
background: #e0e7ff;
border-radius: 4px;
text-align: center;
font-size: 0.875rem;
}
/* 控制面板样式 */
.controls {
display: flex;
gap: 1.5rem;
margin-bottom: 1rem;
padding: 1rem;
background: #f3f4f6;
border-radius: 8px;
flex-wrap: wrap;
}
.controls label {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.875rem;
font-weight: 500;
color: #374151;
}
.controls input[type="color"] {
width: 40px;
height: 30px;
border: none;
border-radius: 4px;
cursor: pointer;
}
.controls input[type="range"] {
width: 100px;
}
.controls input[type="checkbox"] {
width: 16px;
height: 16px;
}
/* 主题变体 */
.blue-theme {
background: linear-gradient(135deg, #dbeafe, #bfdbfe);
}
.red-theme {
background: linear-gradient(135deg, #fee2e2, #fecaca);
}
.green-theme {
background: linear-gradient(135deg, #d1fae5, #a7f3d0);
}
/* 响应式设计 */
@media (max-width: 768px) {
.test-grid {
grid-template-columns: 1fr;
}
.controls {
flex-direction: column;
gap: 1rem;
}
.scroll-box {
height: 150px;
}
}
</style>相关链接
- useScrollMask Hook 文档 - 组合式函数版本
- 在线演示 - 查看实际效果
- GitHub 仓库 - 源码地址
更新日志
v1.3.0
- ✨ 新增细粒度方向控制功能
- ✨ 支持独立控制各方向遮罩显示
- ✨ 新增方向控制相关 CSS 类名
- 🔧 优化双向滚动组合逻辑
v1.2.0
- ✨ 新增自定义颜色支持
- ✨ 新增过渡动画配置
- 🐛 修复双向滚动遮罩计算问题
v1.1.0
- ✨ 新增双向滚动支持
- ✨ 新增响应式尺寸监听
- 🔧 优化性能
v1.0.0
- 🎉 首次发布
- ✨ 基础滚动遮罩功能
- ✨ 垂直和水平滚动支持