# Sticky 粘性布局组件

- 来源：[E10 UI 组件库](https://www.e-cology.com.cn/ui/sticky/freepass)
- 平台：PC
- 维护者：王斌

## 代码演示

### 基础用法

展示Sticky组件的基本用法，实现元素在滚动容器中的粘性定位效果

```jsx
import React, { Component } from 'react';
import { Sticky, Switch, RadioDataType, Radio } from '@weapp/ui';
import '../stickyDemo.less';

const optionsData: RadioDataType = [
  {
    id: '0',
    title: '0',
    content: '0',
  },
  {
    id: '20',
    title: '20',
    content: '20',
  },
  {
    id: '50',
    title: '50',
    content: '50',
  },
];

class StickyDemo extends Component<{}, any> {
  constructor(props: {}) {
    super(props);
    this.state = {
      enabled: true,
      top: '0'
    };
  }

  handleToggleSticky = (enabled: boolean) => {
    this.setState({ enabled });
  }

  handleRadioChange = (value: any) => {
    if (!value) return;
    if (value !== this.state.top) {
      this.setState({ top: value  });
    }
  }

  renderLongContent() {
    return Array(10).fill(0).map((_, index) => (
      <p key={index}>长内容区域 - 第 {index + 1} 行</p>
    ));
  }

  render() {
    const { enabled, top } = this.state;

    return (
      <div className="demo-page">
        <div className="control-panel">
          <span style={{ margin: '15px 10px', display: 'flex', alignItems: 'center' }}>
            是否启用Sticky&nbsp;&nbsp;&nbsp;<Switch size={'sm'} value={enabled} onChange={this.handleToggleSticky}/>
          </span>
          <span style={{ margin: '15px 10px', display: 'flex', alignItems: 'center' }}>
            调整顶部距离&nbsp;&nbsp;&nbsp; <Radio data={optionsData} value={top} onChange={this.handleRadioChange}/>
          </span>
        </div>
        <div id={'custom-boundary-container'} style={{ padding: '20px', border: '1px solid #ccc' }}>
          <Sticky
           
            top={Number(top)}
            enabled={enabled}
            scrollContainer={'#demo_content_scroll'}
            // 距离custom-boundary-container元素下边界60px处为粘性下边界
            bottomBoundary={{ target: '#custom-boundary-container', value: 60 }}
            activeClass="sticky-active"
          >
            <div className="sticky-content">
              {`Sticky 内容（距顶 ${top}px）`}
            </div>
          </Sticky>
          {this.renderLongContent()}
        </div>
      </div>
    );
  }
}

export default StickyDemo;
```

### 粘性元素嵌套

实现多层级粘性元素在滚动容器中的粘性定位效果（根据top和bottomBoundary控制）

```jsx
import React, { Component } from 'react';
import { Sticky } from '@weapp/ui';
import '../stickyDemo.less';

class StickyDemo extends Component<{}, any> {
  constructor(props: {}) {
    super(props);
    this.state = {
      enabled: true,
    };
  }

  renderLongContent(num: number) {
    return Array(num).fill(0).map((_, index) => (
      <p key={index}>长内容区域 - 第 {index + 1} 行</p>
    ));
  }

  render() {
    const { enabled } = this.state;

    return (
      <div className="demo-page">
        <div id={'custom2-boundary-container'} style={{ padding: '20px', border: '1px solid #ccc' }}>
          <Sticky
           
            top={0}
            enabled={enabled}
            scrollContainer={'#demo_content_scroll'}
            bottomBoundary={{ target: '#custom2-boundary-container', value: 60 }}
            activeClass="sticky-active"
          >
            <div className="sticky-content">
              {`Sticky - 一级内容`}
            </div>
          </Sticky>
          {this.renderLongContent(5)}
          <Sticky
           
            top={40}
            enabled={enabled}
            scrollContainer={'#demo_content_scroll'}
            bottomBoundary={{ target: '#custom2-boundary-container', value: 60 }}
            activeClass="sticky-active"
          >
            <div className="sticky-content">
              {`Sticky - 二级内容`}
            </div>
          </Sticky>
          {this.renderLongContent(8)}
        </div>
      </div>
    );
  }
}

export default StickyDemo;
```

## 功能介绍

Sticky 组件提供了高级的粘性定位功能，支持在自定义滚动容器中使用，可以设置顶部偏移和底部边界，并提供了丰富的状态控制和样式定制能力。

### 何时使用

- 需要在滚动容器中固定某个元素时
- 需要精确控制元素的粘性行为时
- 需要监听元素粘性状态变化时

## API

### Sticky Props

| 参数 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- |
| scrollContainer | 滚动容器，支持 DOM 元素或选择器 | HTMLElement \| string \| window |  |
| transformContainer | 偏移容器，支持 DOM 元素或选择器 | HTMLElement \| string \| window |  |
| top | 距离顶部偏移量 | number \| string | `0` |
| bottomBoundary | 底部边界限制 | number \| string \| { value?: number; target?: string } | - |
| enabled | 是否启用粘性定位 | `boolean` | `true` |
| enableTransforms | 是否启用 transform 进行位置偏移 | `boolean` | `true` |
| activeClass | 固定状态的类名 | `string` | - |
| releasedClass | 释放状态的类名 | `string` | - |
| innerClass | 内部容器的类名 | `string` | - |
| innerActiveClass | 内部容器固定状态的类名 | `string` | - |
| innerZ | 内部容器的 z-index | `number` | `1000` |
| shouldFreeze | 是否冻结粘性效果的判断函数 | `() => boolean` | - |
| onStateChange | 状态变化回调函数 | `(state: { status: number }) => void` | - |
### 状态说明

组件内部维护了三种状态：

| 状态值 | 说明 | 触发条件 |
| --- | --- | --- |
| 0 (STATUS_ORIGINAL) | 原始状态 | 元素在初始位置 |
| 1 (STATUS_RELEASED) | 释放状态 | 元素到达底部边界 |
| 2 (STATUS_FIXED) | 固定状态 | 元素处于粘性定位中 |
### 什么时候需要设置transformContainer？

- 当父级元素设置了 transform 属性时，会导致其子元素中 position: fixed 的定位基准发生变化，不再相对于视口定位，而是相对于这个包含 transform 的父元素定位。
- 此时，需要设置transformContainer，用于布局计算
- 常用于 `Dialog` 组件中的粘性元素使用场景，需要将对应弹框的 `ui-dialog-content` 作为偏移元素传入

### bottomBoundary 类型说明

`` 支持以下两种形式：

#### 选择器字符串：使用目标元素的位置作为边界

```tsx
<Sticky bottomBoundary="#target-element" />
```

#### 配置对象：提供更精确的控制

```tsx
<Sticky 
  bottomBoundary={{ 
    target: '#target-element',  // 目标元素（必填）
    value: 20                   // 距离目标元素底部的偏移值（可选）
  }} 
/>
```

## 注意事项

1. 使用自定义滚动容器时，容器需要满足：设置 `position` 属性（`relative`/`absolute`/`fixed`）设置明确的高度设置 `overflow: auto` 或 `overflow: scroll`
2. `bottomBoundary` 的计算会自动考虑粘性元素的高度，无需手动减去
3. 启用 `transform` 可以获得更好的性能表现，但在特殊场景下可以通过 `enableTransforms` 禁用
4. 状态变化回调 `onStateChange` 可用于实现更复杂的交互效果
