---
name: "scroll-view"
mode: "component"
versionRequirements:
  - { name: "基础库", version: "2.10.0" }
title: "scroll-view - 可滚动视图区域，可实现横向滚动和竖向滚动"
---

## scroll-view

> [VERSION] 基础库 >= 2.10.0

### 描述

可滚动视图区域，支持横向或纵向滚动，可配置下拉刷新、滚动事件监听等功能。

### 属性

| 属性 | 类型 | 必填 | 默认值 | 描述 |
| --- | --- | --- | --- | --- |
| `refresher-enabled` | `boolean` | 否 | `false` | 开启自定义下拉刷新 |
| `refresher-threshold` | `number` | 否 | `45` | 设置自定义下拉刷新阈值，单位 px |
| `refresher-default-style` | `string` | 否 | `"black"` | 设置自定义下拉刷新默认样式，none 表示不使用默认样式 |
| `refresher-background` | `string` | 否 | `"#fff"` | 设置自定义下拉刷新区域背景颜色 |
| `refresher-triggered` | `boolean` | 否 | `false` | 设置当前下拉刷新状态，true 表示已触发，false 表示未触发 |
| `scroll-x` | `boolean` | 否 | `false` | 允许横向滚动 |
| `scroll-y` | `boolean` | 否 | `false` | 允许纵向滚动 |
| `upper-threshold` | `number` | 否 | `50` | 距顶部/左边多远时触发 scrolltoupper 事件，单位 px |
| `lower-threshold` | `number` | 否 | `50` | 距底部/右边多远时触发 scrolltolower 事件，单位 px |
| `scroll-top` | `number` | 否 | `0` | 设置竖向滚动条位置，单位 px |
| `scroll-left` | `number` | 否 | `0` | 设置横向滚动条位置，单位 px |
| `scroll-into-view` | `string` | 否 | - | 值应为某子元素 id（id 不能以数字开头），设置哪个方向可滚动则在哪个方向滚动到该元素 |
| `scroll-into-view-offset` | `number` | 否 | `0` | 跳转到 scroll-into-view 目标节点时的额外偏移，单位 px |
| `scroll-with-animation` | `boolean` | 否 | `false` | 在设置滚动条位置时使用动画过渡 |
| `hide-scrollbar` | `boolean` | 否 | `true` | 是否隐藏滚动条 |
| `bounces` | `boolean` | 否 | `true` | 是否启用 iOS 滚动回弹效果（iOS 16.0+ 完全支持） |

#### 事件

| 事件名 | 类型 | 描述 |
| --- | --- | --- |
| `scroll` | `(event: ScrollEvent) => void` | 滚动时触发 |
| `scrolltoupper` | `(event: ScrolltoupperEvent) => void` | 滚动到顶部/左边时触发 |
| `scrolltolower` | `(event: ScrolltolowerEvent) => void` | 滚动到底部/右边时触发 |
| `refresherpulling` | `(event: Object) => void` | 自定义下拉刷新控件被下拉时触发 |
| `refresherrefresh` | `(event: Object) => void` | 自定义下拉刷新被触发时触发 |
| `refresherrestore` | `(event: Object) => void` | 自定义下拉刷新被复位时触发 |
| `refresherabort` | `(event: Object) => void` | 自定义下拉刷新被中止时触发 |

**ScrollEvent**

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `type` | `"scroll"` | 事件类型 |
| `detail` | `ScrollDetail` | 滚动事件详情数据 |
| `scrollLeft` | `number` | 横向滚动位置 |
| `scrollTop` | `number` | 纵向滚动位置 |
| `scrollHeight` | `number` | 滚动内容高度 |
| `scrollWidth` | `number` | 滚动内容宽度 |
| `deltaX` | `number` | 横向滚动变化量 |
| `deltaY` | `number` | 纵向滚动变化量 |

**ScrolltoupperEvent**

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `type` | `"scrolltoupper"` | 事件类型 |
| `detail` | `ScrollDirectionDetail` | 滚动事件详情数据 |

**ScrolltolowerEvent**

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `type` | `"scrolltolower"` | 事件类型 |
| `detail` | `ScrollDirectionDetail` | 滚动事件详情数据 |

**refresherpulling 的回调参数对象**

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `type` | `"refresherpulling"` |  |

**refresherrefresh 的回调参数对象**

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `type` | `"refresherrefresh"` |  |

**refresherrestore 的回调参数对象**

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `type` | `"refresherrestore"` |  |

**refresherabort 的回调参数对象**

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `type` | `"refresherabort"` |  |

### 示例代码

#### 基础用法

*index.tyml*

```xml
<scroll-view
  scroll-y="{{true}}"
  style="height: 400rpx;"
  class="scroll-container"
  upper-threshold="{{50}}"
  lower-threshold="{{50}}"
  bind:scrolltoupper="onScrollToUpper"
  bind:scrolltolower="onScrollToLower"
  bind:scroll="onScroll"
>
  <view ty:for="{{list}}" ty:key="*this" class="scroll-item">
    <text>{{item}}</text>
  </view>
</scroll-view>
```

*index.tyss*

```css
.scroll-container {
  background-color: #f5f5f5;
}
.scroll-item {
  height: 100rpx;
  margin: 10rpx;
  background-color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
}
```

*index.js*

```javascript
Page({
  data: {
    list: Array.from({ length: 10 }, (_, i) => '列表项 ' + (i + 1)),
  },
  onScrollToUpper() {
    console.log('已滚动到顶部');
  },
  onScrollToLower() {
    console.log('已滚动到底部');
  },
  onScroll(e) {
    console.log('scrollTop:', e.detail.scrollTop);
  },
});
```

#### 下拉刷新

*index.tyml*

```xml
<scroll-view
  scroll-y="{{true}}"
  style="height: 600rpx;"
  class="scroll-container"
  refresher-enabled="{{true}}"
  refresher-threshold="{{50}}"
  refresher-triggered="{{refreshing}}"
  refresher-background="#f5f5f5"
  bind:refresherrefresh="onRefresh"
  bind:scrolltolower="onLoadMore"
  lower-threshold="{{100}}"
>
  <view ty:for="{{list}}" ty:key="*this" class="scroll-item">
    <text>{{item}}</text>
  </view>
  <view ty:if="{{loading}}" class="loading-tip">
    <text>加载中...</text>
  </view>
</scroll-view>
```

*index.tyss*

```css
.scroll-container {
  background-color: #f5f5f5;
}
.scroll-item {
  height: 100rpx;
  margin: 10rpx 20rpx;
  background-color: #fff;
  border-radius: 8rpx;
  display: flex;
  align-items: center;
  justify-content: center;
}
.loading-tip {
  height: 80rpx;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #999;
  font-size: 28rpx;
}
```

*index.js*

```javascript
Page({
  data: {
    list: Array.from({ length: 10 }, (_, i) => '初始数据 ' + (i + 1)),
    refreshing: false,
    loading: false,
  },
  onRefresh() {
    this.setData({ refreshing: true });
    setTimeout(() => {
      var newList = Array.from({ length: 5 }, (_, i) => '刷新数据 ' + (i + 1));
      this.setData({ list: newList.concat(this.data.list), refreshing: false });
    }, 1000);
  },
  onLoadMore() {
    if (this.data.loading) return;
    this.setData({ loading: true });
    var len = this.data.list.length;
    setTimeout(() => {
      var more = Array.from({ length: 5 }, (_, i) => '数据 ' + (len + i + 1));
      this.setData({ list: this.data.list.concat(more), loading: false });
    }, 1000);
  },
});
```

#### 横向滚动

*index.tyml*

```xml
<scroll-view
  scroll-x="{{true}}"
  class="horizontal-scroll"
  bind:scroll="onScroll"
>
  <view class="card-list">
    <view ty:for="{{cards}}" ty:key="*this" class="card">
      <text class="card-text">{{item}}</text>
    </view>
  </view>
</scroll-view>
```

*index.tyss*

```css
.horizontal-scroll {
  width: 100%;
  height: 200rpx;
  white-space: nowrap;
}
.card-list {
  display: inline-flex;
}
.card {
  width: 300rpx;
  height: 180rpx;
  margin-right: 20rpx;
  background-color: #1890ff;
  border-radius: 12rpx;
  display: flex;
  align-items: center;
  justify-content: center;
}
.card-text {
  color: #fff;
  font-size: 32rpx;
}
```

*index.js*

```javascript
Page({
  data: {
    cards: ['卡片1', '卡片2', '卡片3', '卡片4', '卡片5'],
  },
  onScroll(e) {
    console.log('横向滚动位置:', e.detail.scrollLeft);
  },
});
```

#### 滚动到指定元素

*index.tyml*

```xml
<view class="btn-row">
  <button
    ty:for="{{anchors}}"
    ty:key="*this"
    size="mini"
    bindtap="scrollTo"
    data-id="{{item}}"
  >到 {{item}}</button>
</view>
<scroll-view
  scroll-y="{{true}}"
  scroll-into-view="{{targetId}}"
  scroll-with-animation="{{true}}"
  hide-scrollbar="{{false}}"
  style="height: 400rpx;"
  class="scroll-container"
>
  <view
    ty:for="{{anchors}}"
    ty:key="*this"
    id="{{item}}"
    class="section"
  >
    <text>{{item}}</text>
  </view>
</scroll-view>
```

*index.tyss*

```css
.btn-row {
  display: flex;
  gap: 10rpx;
  padding: 20rpx;
}
.scroll-container {
  background-color: #f5f5f5;
}
.section {
  height: 300rpx;
  margin: 10rpx 20rpx;
  background-color: #fff;
  border-radius: 8rpx;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 32rpx;
}
```

*index.js*

```javascript
Page({
  data: {
    anchors: ['sec-a', 'sec-b', 'sec-c', 'sec-d'],
    targetId: '',
  },
  scrollTo(e) {
    this.setData({ targetId: e.currentTarget.dataset.id });
  },
});
```


### 常见问题

#### 为何 scroll-view 在 popup 扩展组件中无法滑动？

popup 组件上加上 `disableScroll` 属性并将值设为 `false` 才能滑动。

#### 如何监听 scroll-view 滚动到底部？

可以直接在 `bind:scroll` 方法中进行处理，使用 `bind:scrolltolower` 监听 `scroll-view` 的滚动高度来进行判断是否滑动到了底部。
`scrollHeight` 是 `scroll-view` 里面所有 `view` 的高度和，`scrollTop` 是滚动的值；

#### 自定义页面蒙层的时候，当滚动蒙层里面的内容，蒙层底下页面也能跟着滑动？

可以给蒙层内部 `scroll-view` 或 `view` 添加 `catch:touchmove`事件,阻止事件冒泡。
