uni-app 滚动视图scroll-view
从入门到精通,这篇就够了!(内含保姆级教程和示例) 🚀
哈喽,各位小伙伴们! 👋
在开发 uni-app 的道路上,你是否也曾被 scroll-view
这个“磨人的小妖精”折磨得抓耳挠腮?明明代码照着文档敲了,可它就是不滚动!或者,你想实现一个炫酷的下拉刷新、一个丝滑的触底加载,却不知从何下手?
别慌!今天,我就带你把 scroll-view
的所有“脾气”都摸透,从基础用法到高阶技巧,一网打尽。看完这篇,保证你也能成为 scroll-view
大师!
黄金法则:开始前必须知道的两件事 ⚠️
在展示任何代码之前,请把下面这两条法则刻在你的DNA里,99% 的 scroll-view
不工作问题都源于此:
- 竖向滚动 (
scroll-y
):你必须给<scroll-view>
组件设置一个明确的、固定的高度。无论是height: 500px
还是height: 100vh
,总之不能让它“随心所欲”。因为它需要知道自己的可视区域有多大,才能判断内容是否超出了、何时该滚动。 - 横向滚动 (
scroll-x
):你必须给<scroll-view>
的内部子元素们设置white-space: nowrap;
样式,告诉它们:“兄弟们,不许换行,给我排成一队!”。通常我们还会让这些子元素display: inline-block;
。
记住了吗?好,让我们开始实战!
场景一:基础竖向滚动与“无限加载” (触底加载) 📱
这是最常见的需求:一个长长的列表,用户滑到底部时,自动加载更多内容。
核心武器:
scroll-y
: 开启竖向滚动。@scrolltolower
: 当滚动条接近底部时触发的“神仙事件”。lower-threshold
: 设置离底部多远时触发@scrolltolower
,避免用户真的要滑到最最最底才加载。
直接上代码:
<template>
<view class="container">
<view class="header">
<text class="title">Demo 1: 基础与触底加载</text>
<text class="scroll-info">当前滚动位置: {{ currentScrollTop.toFixed(0) }}px</text>
<button size="mini" @click="goTop">点我返回顶部</button>
</view>
<!--
scroll-y: 开启纵向滚动
:scroll-top: 动态绑定滚动条位置,用于程序化控制
:scroll-with-animation: 使 scroll-top 的改变产生动画效果
@scroll: 监听滚动事件,实时获取滚动信息
@scrolltolower: 滚动到底部时触发
lower-threshold: 距离底部20px时就触发 scrolltolower
-->
<scroll-view class="list-scroll" scroll-y :scroll-top="scrollTopValue" scroll-with-animation @scroll="onScroll"
@scrolltolower="loadMore" :lower-threshold="20">
<!-- 列表内容 -->
<view v-for="(item, index) in dataList" :key="index" class="list-item">
<text>第 {{ item }} 项数据</text>
</view>
<!-- 加载状态提示 -->l
<view class="loading-status">
<text v-if="loadingStatus === 'loading'">努力加载中...</text>
<text v-if="loadingStatus === 'no-more'">--- 我是有底线的 ---</text>
</view>
</scroll-view>
</view>
</template>
<script>
export default {
data() {
return {
dataList: [], // 列表数据
page: 1, // 当前页码
loadingStatus: 'loading', // 加载状态:'loading', 'no-more'
scrollTopValue: 0, // 控制滚动条位置,0表示顶部
currentScrollTop: 0 // 用于显示当前滚动位置
}
},
onLoad() {
// 页面加载时,获取初始数据
this.fetchData();
},
methods: {
// 模拟从服务器获取数据
fetchData() {
this.loadingStatus = 'loading';
console.log(`正在请求第 ${this.page} 页数据...`);
// 模拟网络请求
setTimeout(() => {
const newItems = [];
for (let i = 0; i < 20; i++) {
newItems.push((this.page - 1) * 20 + i + 1);
}
this.dataList = [...this.dataList, ...newItems];
// 模拟数据加载完毕的情况
if (this.page >= 5) {
this.loadingStatus = 'no-more';
} else {
this.loadingStatus = ''; // 重置状态,准备下一次加载
}
console.log('数据加载完成!');
}, 1000); // 模拟1秒延迟
},
/**
* @scrolltolower 事件触发的方法
* 滚动到底部时调用
*/
loadMore() {
// 如果当前已经是'no-more'状态,或者正在加载中,则不执行任何操作
if (this.loadingStatus === 'no-more' || this.loadingStatus === 'loading') {
console.log('已无更多数据或正在加载,跳过本次请求。');
return;
}
this.page++;
this.fetchData();
},
/**
* @scroll 事件触发的方法
* 滚动过程中实时触发
*/
onScroll(event) {
// event.detail 包含了滚动的所有信息 {scrollLeft, scrollTop, scrollHeight, scrollWidth, deltaX, deltaY}
// console.log(event.detail);
this.currentScrollTop = event.detail.scrollTop;
},
/**
* 返回顶部
*/
goTop() {
// scroll-top 的一个技巧:如果连续设置相同的值,第二次不会生效。
// 所以我们先设置为一个很小但不同的值,再在下一次渲染后设置为0.
this.scrollTopValue = 0.1;
this.$nextTick(() => {
this.scrollTopValue = 0;
});
}
}
}
</script>
<style>
.container {
height: 100vh;
display: flex;
flex-direction: column;
}
.header {
padding: 20rpx;
border-bottom: 1rpx solid #eee;
background-color: #f8f8f8;
}
.title {
font-size: 32rpx;
font-weight: bold;
}
.scroll-info {
font-size: 24rpx;
color: #666;
margin-left: 20rpx;
}
.list-scroll {
/* 这是关键!必须给 scroll-view 一个固定的高度 */
flex: 1;
height: 0;
/* flex:1 和 height:0 是一个经典的组合,让元素撑满剩余空间 */
box-sizing: border-box;
}
.list-item {
padding: 30rpx 20rpx;
border-bottom: 1rpx solid #f0f0f0;
background-color: #fff;
}
.loading-status {
padding: 20rpx;
text-align: center;
color: #999;
font-size: 24rpx;
}
</style>
小贴士:代码里还演示了 @scroll
(实时监听滚动) 和 scroll-top
(JS控制滚动条位置) 的用法,比如做个“返回顶部”的按钮,用户体验UP!
场景二:自定义下拉刷新与横向滚动 🔄
想让你的App看起来更专业?自定义下拉刷新和横向滑动的商品分类绝对是加分项!
核心武器:
refresher-enabled
: 开启下拉刷新功能。:refresher-triggered
: 一个双向绑定的“开关”,true
就显示刷新动画,false
就收起。这是控制的关键!@refresherrefresh
: 当用户下拉到足够距离并松手后,触发这个事件,你就可以在这里请求新数据了。scroll-x
: 开启横向滚动。scroll-into-view
: 可以让scroll-view
自动滚动到指定的子元素ID处。
代码秀:
<template>
<view class="container">
<view class="header">
<text class="title">Demo 2: 下拉刷新与横向滚动</text>
<button size="mini" @click="goToGreenItem">滚动到绿色块</button>
</view>
<!--
refresher-enabled: 开启下拉刷新
:refresher-triggered: 控制刷新动画的显示/隐藏,true为显示
@refresherrefresh: 核心!触发刷新时调用此方法
refresher-default-style="none": 不使用默认的三个点样式,方便完全自定义
-->
<scroll-view class="main-scroll" scroll-y refresher-enabled :refresher-triggered="isRefreshing"
@refresherrefresh="onRefresh">
<!-- 自定义下拉刷新视图 -->
<view v-if="isRefreshing" class="custom-refresher">
<image class="loading-icon" src="/static/loading.gif"></image> <!-- 请准备一个loading.gif图 -->
<text>正在刷新...</text>
</view>
<!-- 横向滚动区域 -->
<view class="section-title">横向滚动区域</view>
<!--
scroll-x: 开启横向滚动
:scroll-into-view: 值应为某子元素id,滚动到该元素
show-scrollbar: 隐藏横向滚动条
-->
<scroll-view class="horizontal-scroll" scroll-x :scroll-into-view="targetItemId" scroll-with-animation
:show-scrollbar="false">
<view class="horizontal-item" v-for="item in horizontalList" :key="item.id" :id="item.id"
@click="handleClick(item.id)" :style="{ backgroundColor: item.color }">
<text>{{ item.text }}</text>
</view>
</scroll-view>
<!-- 纵向列表内容 -->
<view class="section-title">纵向列表</view>
<view v-for="(item, index) in dataList" :key="index" class="list-item">
<text>数据项 {{ item }}</text>
</view>
</scroll-view>
</view>
</template>
<script>
export default {
data() {
return {
isRefreshing: false, // 控制下拉刷新状态
dataList: ["初始数据1", "初始数据2", "初始数据3", "初始数据4", "初始数据5"],
horizontalList: [{
id: 'red-item',
text: '红色块',
color: '#F56C6C'
},
{
id: 'orange-item',
text: '橙色块',
color: '#E6A23C'
},
{
id: 'yellow-item',
text: '黄色块',
color: '#F2D33D'
},
{
id: 'green-item',
text: '绿色块',
color: '#67C23A'
}, // 目标元素
{
id: 'blue-item',
text: '蓝色块',
color: '#409EFF'
},
{
id: 'purple-item',
text: '紫色块',
color: '#9049DE'
},
],
targetItemId: ''
}
},
methods: {
/**
* @refresherrefresh 触发的刷新方法
*/
onRefresh() {
if (this.isRefreshing) return;
console.log('开始刷新...');
this.isRefreshing = true;
// 模拟网络请求
setTimeout(() => {
const newData = `新数据 ${new Date().toLocaleTimeString()}`;
this.dataList.unshift(newData); // 在列表顶部插入新数据
// 关键:刷新完成后,必须手动把 isRefreshing 设置为 false,收起刷新动画
this.isRefreshing = false;
console.log('刷新完成!');
uni.showToast({
title: '刷新成功',
icon: 'success'
});
}, 1500);
},
/**
* 滚动到指定横向元素
*/
goToGreenItem() {
this.targetItemId = 'green-item';
// 小技巧:使用后清空,以便下次还能滚动到同一个id
setTimeout(() => {
this.targetItemId = '';
}, 200);
},
handleClick(id) {
this.targetItemId = id;
}
}
}
</script>
<style>
/* 通用样式,与上例部分重叠 */
page {
height: 100%;
overflow: hidden;
}
.container {
height: 100%;
display: flex;
flex-direction: column;
}
.header {
padding: 20rpx;
border-bottom: 1rpx solid #eee;
background-color: #f8f8f8;
display: flex;
justify-content: space-between;
align-items: center;
}
.title {
font-size: 32rpx;
font-weight: bold;
}
.main-scroll {
flex: 1;
height: 0;
}
.list-item {
padding: 30rpx 20rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.section-title {
padding: 20rpx;
background-color: #f5f5f5;
color: #666;
font-size: 28rpx;
}
/* --- 本示例新增样式 --- */
.custom-refresher {
display: flex;
align-items: center;
justify-content: center;
padding: 20rpx;
color: #666;
}
.loading-icon {
width: 40rpx;
height: 40rpx;
margin-right: 15rpx;
}
.horizontal-scroll {
/* 这是关键!white-space:nowrap 防止子元素换行 */
white-space: nowrap;
width: 100%;
padding: 20rpx 0;
background-color: #fff;
}
.horizontal-item {
/* 这是关键!inline-block 使元素在一行内排列 */
display: inline-block;
width: 250rpx;
height: 180rpx;
margin: 0 15rpx;
border-radius: 10rpx;
display: inline-flex;
align-items: center;
justify-content: center;
color: #fff;
font-weight: bold;
}
</style>
划重点:下拉刷新的逻辑就是:@refresherrefresh
里把 isRefreshing
设为 true
,等数据加载完了再把它设回 false
。就这么简单!
场景三:那些不常用但超有用的“黑魔法”属性 ✨
还有一些属性,平时可能用得少,但在特定场景下能救你一命。
enable-back-to-top
: (仅App和部分小程序) 用户点击手机顶部状态栏,或者双击安卓标题栏时,滚动条能自动回到顶部。开启它,能让你的App更像原生App。enable-flex
: (仅微信小程序) 默认情况下scroll-view
不支持作为 flex 容器。开启它之后,就可以在scroll-view
上使用display: flex
了,布局更自由!
这些属性都是布尔值,直接在标签里写上就行,比如 <scroll-view scroll-y enable-back-to-top>
。
终极属性/事件速查表(建议收藏)
属性/事件 | 功能一句话总结 |
---|---|
scroll-x / scroll-y |
开启横/纵向滚动。 |
@scrolltolower |
滚动到底部,用来加载更多。 |
@scrolltoupper |
滚动到顶部时触发。 |
@scroll |
只要在滚,就一直触发,信息最全。 |
scroll-top / scroll-left |
JS控制滚动条位置,做返回顶部。 |
scroll-into-view |
滚动到指定ID的子元素,做锚点定位。 |
refresher-enabled |
开启自定义下拉刷新。 |
:refresher-triggered |
控制下拉刷新动画的显示和隐藏(true /false )。 |
@refresherrefresh |
执行下拉刷新逻辑的地方。 |
总结
scroll-view
看似复杂,其实只要掌握了核心规则和几个关键的属性事件,就能玩得转。
记住:
- 给高度!给高度!给高度! (竖向滚动)
- 不换行!不换行!不换行! (横向滚动)
- 用
@scrolltolower
做上拉加载。 - 用
refresher
全家桶做下拉刷新,refresher-triggered
是控制动画的开关。
希望这篇保姆级的教程能帮你彻底扫清 scroll-view
的障碍。现在,去你的项目里大展身手吧!
如果觉得有帮助,别忘了点赞、收藏哦!有什么问题,也欢迎在评论区留言讨论!👇