[English](./README.md) | 简体中文

# @ray-js/circle-progress

[![latest](https://img.shields.io/npm/v/@ray-js/circle-progress/latest.svg)](https://www.npmjs.com/package/@ray-js/circle-progress) [![download](https://img.shields.io/npm/dt/@ray-js/circle-progress.svg)](https://www.npmjs.com/package/@ray-js/circle-progress)

> 通用圆环

## 安装

```sh
$ npm install @ray-js/circle-progress
// 或者
$ yarn add @ray-js/circle-progress
```

## 使用

### 注意：如果机型不支持 ctx.createConicGradient 属性，则会降级渲染，也即没有两端的圆角效果

### 基础使用

```tsx
import RayCircleProgress from '@ray-js/circle-progress';

const [value, setValue] = useState(0);

const handleMove = (v: number) => {
  console.warn('handleMove', v);
  setValue(v);
};

const handleEnd = (v: number) => {
  console.warn('handleEnd', v);
  setValue(v);
};

<RayCircleProgress
  value={value}
  startDegree={125}
  offsetDegree={290}
  onTouchMove={handleMove}
  onTouchEnd={handleEnd}
/>;
```

### 高级用法 1: 自定义颜色 + 自定义圆环半径

```tsx
import RayCircleProgress from '@ray-js/circle-progress';

const [value, setValue] = useState(0);

const handleMove = (v: number) => {
  console.warn('handleMove', v);
  setValue(v);
};

const handleEnd = (v: number) => {
  console.warn('handleEnd', v);
  setValue(v);
};

<RayCircleProgress
  value={value}
  ringRadius={100}
  innerRingRadius={84}
  colorList={[
    { offset: 0, color: '#fbebaf' },
    { offset: 0.25, color: '#efb4a3' },
    { offset: 0.5, color: '#ee7a79' },
    { offset: 1, color: '#ec80a7' },
  ]}
  startDegree={180}
  offsetDegree={180}
  onTouchMove={handleMove}
  onTouchEnd={handleEnd}
/>;
```

### 高级用法 2: 自定义颜色 + 向下圆环

```tsx
import RayCircleProgress from '@ray-js/circle-progress';

const [value, setValue] = useState(0);

const handleMove = (v: number) => {
  console.warn('handleMove', v);
  setValue(v);
};

const handleEnd = (v: number) => {
  console.warn('handleEnd', v);
  setValue(v);
};

<RayCircleProgress
  value={value}
  innerRingRadius={84}
  ringRadius={100}
  startDegree={300}
  offsetDegree={300}
  colorList={[
    { offset: 0, color: '#eced77' },
    { offset: 0.5, color: '#ef865b' },
    { offset: 1, color: '#7be0f8' },
  ]}
  onTouchMove={handleMove}
  onTouchEnd={handleEnd}
/>;
```

### 高级用法 3: 自定义颜色 + 向下水平圆环

```tsx
import RayCircleProgress from '@ray-js/circle-progress';

const [value, setValue] = useState(0);

const handleMove = (v: number) => {
  console.warn('handleMove', v);
  setValue(v);
};

const handleEnd = (v: number) => {
  console.warn('handleEnd', v);
  setValue(v);
};
<RayCircleProgress
  value={value3}
  innerRingRadius={84}
  ringRadius={100}
  startDegree={0}
  colorList={[
    { offset: 0, color: '#e8a989' },
    { offset: 0.5, color: '#efce85' },
    { offset: 1, color: '#d66e6b' },
  ]}
  offsetDegree={180}
  onTouchMove={handleMove3}
  onTouchEnd={handleEnd3}
/>;
```

### 高级用法 4: 自定义颜色 + 整圆环

```tsx
import RayCircleProgress from '@ray-js/circle-progress';

const [value, setValue] = useState(0);

const handleMove = (v: number) => {
  console.warn('handleMove', v);
  setValue(v);
};

const handleEnd = (v: number) => {
  console.warn('handleEnd', v);
  setValue(v);
};

<RayCircleProgress
  value={value}
  startDegree={90}
  offsetDegree={360}
  colorList={[
    { offset: 0, color: '#e8a989' },
    { offset: 0.5, color: '#efce85' },
    { offset: 1, color: '#d66e6b' },
  ]}
  onTouchMove={handleMove}
  onTouchEnd={handleEnd}
/>;
```

### 高级用法 5: 自定义内部元素

```tsx
import RayCircleProgress from '@ray-js/circle-progress';

const [value, setValue] = useState(0);

const handleMove = (v: number) => {
  console.warn('handleMove', v);
  setValue(v);
};

const handleEnd = (v: number) => {
  console.warn('handleEnd', v);
  setValue(v);
};

<RayCircleProgress
  value={value4}
  startDegree={45}
  offsetDegree={315}
  colorList={[
    { offset: 0, color: '#e8a989' },
    { offset: 0.5, color: '#efce85' },
    { offset: 1, color: '#d66e6b' },
  ]}
  renderInnerCircle={() => (
    <View
      style={{
        width: 160,
        height: 160,
        backgroundColor: '#d66e6b',
        borderRadius: 100,
        display: 'flex',
        justifyContent: 'center',
        alignItems: 'center',
      }}
    >
      {/* 自定义内容: */}
      <Text>自定义:{value}</Text>
    </View>
  )}
  onTouchMove={handleMove}
  onTouchEnd={handleEnd}
/>;
```

### 高级用法 6: 支持 thumb 半径和 thumb border 自定义

```tsx
<RayCircleProgress
  value={value}
  ringRadius={135}
  innerRingRadius={130}
  colorList={[
    { offset: 0, color: '#295bdd' },
    { offset: 0.5, color: '#6A53D1' },
    { offset: 1, color: '#f65028' },
  ]}
  thumbRadius={30}
  thumbOffset={20}
  thumbBorderWidth={0}
  startDegree={135}
  offsetDegree={270}
  touchCircleStrokeStyle="rgba(0, 0, 0, 0.4)"
  onTouchStart={handleTouchStart}
  onTouchMove={handleMove}
  onTouchEnd={handleEnd}
/>
```

### 高级用法 7: 支持滑动区域背景色功能

```tsx
<RayCircleProgress
  value={value}
  trackColor="#ef7e85"
  colorList={[
    { offset: 0, color: '#e8a989' },
    { offset: 1, color: '#e8a989' },
  ]}
  startDegree={135}
  offsetDegree={270}
  touchCircleStrokeStyle="rgba(0, 0, 0, 0.4)"
  thumbBorderWidth={0}
  onTouchStart={handleTouchStart}
  onTouchMove={handleMove}
  onTouchEnd={handleEnd}
/>
```

### 高级用法 8: autoFitHeight 弧形自适应占位高度

默认情况下组件占位高度恒为整圆直径（`ringRadius * 2`），半圆或小弧配置下下方会留出一大块空白。开启 `autoFitHeight` 后，组件会按 `startDegree` / `offsetDegree` 计算弧形（含 thumb 半径、描边和 `thumbOffset` 出界余量）的垂直包围盒，把占位高度收缩到弧形实际范围。

```tsx
<RayCircleProgress
  autoFitHeight
  value={value}
  startDegree={180}
  offsetDegree={180} // 上半弧：占位高度约为整圆的 55%~60%（视 thumb 尺寸而定）
  ringRadius={110}
  innerRingRadius={100}
  colorList={[
    { offset: 0, color: '#e0e0e0' },
    { offset: 1, color: '#e0e0e0' },
  ]}
  trackColor="#4a90e2"
  onTouchMove={handleMove}
  onTouchEnd={handleEnd}
/>
```

注意事项：

- 需要同时设置 `startDegree` 与 `offsetDegree`，否则不生效并输出警告。
- `renderInnerCircle` 内容仍以圆心为锚点定位（与不开启时位置完全一致），且不会被裁剪；内容超出弧形范围时会探出占位盒子，请把内容控制在弧形区域内，或改用默认占位。
- 接近整圆的弧形开启后高度可能略大于 `ringRadius * 2`，因为收缩后的盒子会把原本溢出的 thumb 出界余量也包含进来。
- 宽度不收缩，仍为整圆直径。

## 更新日志

### Unreleased

- feat: 新增 `autoFitHeight` 属性（默认关闭）：按弧形（含 thumb 出界余量）的垂直包围盒收缩组件占位高度，半圆/小弧配置下不再占用整圆直径的高度；`renderInnerCircle` 内容仍以圆心为锚点。
- docs: example 面板所有示例统一用 DemoBlock 加标题说明，并新增 8 个示例：半圆环（endDegree = 360）、autoFitHeight 自适应高度、弧形 endDegree < 360（缺口跨 0°）、整圆环（360° 无缺口）、向下半圆环、宽环 + ringBorderColor、自定义 thumb + 触摸事件回调、禁用状态。
- fix: 弧形配置下拖动 thumb 滑入缺口（无效区域）时，改为按拖动连续性滞回吸附到上次位置更近的端点，修复拖到终点附近 value 从 100 瞬间跳回 0 的问题；首次点按缺口仍按角距离就近吸附。同时修复 `endDegree <= 360` 时缺口就近判定跨 0°/360° 反号的计算错误，并为 percent 计算增加 0-100 兜底钳制。
- fix: 修复 iOS 部分版本（如 iPhone 14 / iOS 18.6.2）滑动时偶现进度弧/滑块脱离滑轨的残影问题。原因是滑动过程中每帧重建 canvas 底层缓冲区，改为仅在尺寸变化时重建。

### 0.1.6-beta.1

- fix: 修复 iOS 个别版本渲染问题（引入设备像素对齐）。

### 0.1.5-beta-1

- feat: 新增 `trackColorList`，支持滑过区域使用渐变色。
- fix: 调整角度范围与颜色索引处理逻辑。