bezier-slider 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +673 -0
- package/bezier-slider-1.0.0.tgz +0 -0
- package/demo/index.html +89 -0
- package/demo/native-main.js +309 -0
- package/demo/shared/clipboard.js +59 -0
- package/demo/shared/constants.js +49 -0
- package/demo/shared/container-size.js +77 -0
- package/demo/shared/demo-native.css +172 -0
- package/demo/shared/demo.css +400 -0
- package/demo/shared/format-code.js +177 -0
- package/demo/shared/geometry-preset-bar.js +25 -0
- package/demo/shared/geometry-presets.js +99 -0
- package/demo/shared/param-utils.js +76 -0
- package/demo/shared/params-panel.js +71 -0
- package/demo/shared/track-helpers.js +94 -0
- package/package.json +66 -0
- package/src/bezier-slider.native.js +999 -0
- package/src/components/BezierSlider.jsx +84 -0
- package/src/components/BezierSlider.vue +119 -0
- package/src/index.js +11 -0
- package/src/native-bridge.js +86 -0
- package/src/react-component.js +5 -0
- package/src/track-renderer.js +108 -0
- package/src/vue-component.js +5 -0
- package/vite.config.js +35 -0
package/README.md
ADDED
|
@@ -0,0 +1,673 @@
|
|
|
1
|
+
# BezierSlider — 贝塞尔弧形图标滑块
|
|
2
|
+
|
|
3
|
+
基于**二次贝塞尔曲线**的弧形图标滑块组件。图标沿平滑弧线排列,支持拖动、吸附、边界回弹,以及近大远小的视觉反馈。
|
|
4
|
+
|
|
5
|
+
> **Arc** 指弧形滑轨(一段曲线,非整圆);**Bezier** 指轨迹由二次贝塞尔(`Q` 命令)描述。
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 功能介绍
|
|
10
|
+
|
|
11
|
+
| 功能 | 说明 |
|
|
12
|
+
|------|------|
|
|
13
|
+
| 弧形滑轨 | 图标沿二次贝塞尔曲线分布,滑轨由 SVG 绘制 |
|
|
14
|
+
| 近大远小 | 越靠近中心点(`centerT`)的图标越大、越亮 |
|
|
15
|
+
| 渐隐效果 | 距离中心越远越透明,可通过 `fadeEnabled` 控制 |
|
|
16
|
+
| 拖动滑动 | 支持鼠标拖动与触摸滑动,方向与手势一致 |
|
|
17
|
+
| 自动吸附 | 松手后平滑吸附到最近的图标 |
|
|
18
|
+
| 边界回弹 | 滑到首/末图标后继续越界拖动,松手带回弹效果 |
|
|
19
|
+
| 可见数量控制 | 拖动区域内最多同时显示 2 个图标(可配置) |
|
|
20
|
+
| 动态布局 | 初始化时自动测量容器尺寸,窗口变化时重新布局 |
|
|
21
|
+
| 弧度可配 | 支持整体平滑度、左/右端高度、局部下弯角度等 |
|
|
22
|
+
| 多种图标 | 支持 emoji、图片、SVG 路径、SVG 文件、font-icon |
|
|
23
|
+
| 滑动防选中 | 拖动过程中禁用页面文字框选 |
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 文件结构
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
slider-master/
|
|
31
|
+
├── demo/
|
|
32
|
+
│ ├── index.html # 演示页(滑轨预览 + 参数调节 + 代码输出)
|
|
33
|
+
│ ├── native-main.js # 演示入口
|
|
34
|
+
│ └── shared/ # 演示公共样式与工具
|
|
35
|
+
├── src/
|
|
36
|
+
│ ├── bezier-slider.native.js # 核心:拖动交互、图标布局、贝塞尔轨迹(不含滑轨绘制)
|
|
37
|
+
│ ├── track-renderer.js # 可选:默认 SVG 滑轨渲染(ensure / update / renderDefaultTrack)
|
|
38
|
+
│ ├── index.js # 包入口 → export BezierSlider + 滑轨工具
|
|
39
|
+
│ ├── native-bridge.js # 框架封装共享:pickNativeOptions / mountNativeSlider
|
|
40
|
+
│ ├── vue-component.js # Vue 包入口 → export BezierSlider 组件 + 滑轨工具
|
|
41
|
+
│ ├── react-component.js # React 包入口 → export BezierSlider 组件 + 滑轨工具
|
|
42
|
+
│ └── components/
|
|
43
|
+
│ ├── BezierSlider.vue # Vue 3 薄封装(内部使用 native 核心)
|
|
44
|
+
│ └── BezierSlider.jsx # React 薄封装(内部使用 native 核心)
|
|
45
|
+
├── dist/ # npm run build 生成(.gitignore,使用前需先 build)
|
|
46
|
+
│ ├── bezier-slider.mjs # import 'bezier-slider'
|
|
47
|
+
│ ├── bezier-slider.vue.mjs # import 'bezier-slider/vue'
|
|
48
|
+
│ ├── bezier-slider.react.mjs # import 'bezier-slider/react'
|
|
49
|
+
│ └── style.css # Vue 组件样式
|
|
50
|
+
├── package.json
|
|
51
|
+
├── package-lock.json
|
|
52
|
+
├── vite.config.js
|
|
53
|
+
├── .gitignore
|
|
54
|
+
└── README.md
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## 快速开始
|
|
60
|
+
|
|
61
|
+
### 方式一:原生 JavaScript
|
|
62
|
+
|
|
63
|
+
先执行 `npm run build` 生成 `dist/`,或通过 `npm install bezier-slider` 安装包。
|
|
64
|
+
|
|
65
|
+
```html
|
|
66
|
+
<div class="carousel-container" id="carouselContainer"></div>
|
|
67
|
+
|
|
68
|
+
<script type="module">
|
|
69
|
+
// npm 安装后:
|
|
70
|
+
// import BezierSlider, { renderDefaultTrack } from 'bezier-slider';
|
|
71
|
+
|
|
72
|
+
// 本仓库本地 dist:
|
|
73
|
+
import BezierSlider, { renderDefaultTrack } from './dist/bezier-slider.mjs';
|
|
74
|
+
|
|
75
|
+
const container = document.querySelector('#carouselContainer');
|
|
76
|
+
|
|
77
|
+
const slider = new BezierSlider({
|
|
78
|
+
container,
|
|
79
|
+
icons: [
|
|
80
|
+
{ name: '首页', emoji: '🏠', color: '#8b5cf6' },
|
|
81
|
+
{ name: '搜索', emoji: '🔍', color: '#ec4899' },
|
|
82
|
+
{ name: '消息', emoji: '💬', color: '#06b6d4' },
|
|
83
|
+
{ name: '设置', emoji: '⚙️', color: '#22c55e' }
|
|
84
|
+
],
|
|
85
|
+
onLayout: (layout) => renderDefaultTrack(container, layout),
|
|
86
|
+
onSlideEnd: (index) => {
|
|
87
|
+
console.log('当前停留图标下标:', index);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
</script>
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
要点:
|
|
94
|
+
|
|
95
|
+
- 必须使用 **单个** `<script type="module">`;普通 `<script>` 里不能写 `import`
|
|
96
|
+
- 不要同时写 `<script type="module" src="...">` 和另一个带 `import` 的脚本——模块作用域隔离,后者拿不到前者里的变量
|
|
97
|
+
- 滑轨需调用方绘制;上面用 `renderDefaultTrack` 渲染默认样式,也可改用背景图等方式(见 §6.1)
|
|
98
|
+
|
|
99
|
+
**注意**:若用 `file://` 直接打开 HTML,浏览器可能拦截 ES 模块;请用 `npm run dev` 或任意静态服务器访问。
|
|
100
|
+
|
|
101
|
+
### 方式二:Vue 3
|
|
102
|
+
|
|
103
|
+
```vue
|
|
104
|
+
<template>
|
|
105
|
+
<BezierSlider
|
|
106
|
+
class="carousel-container"
|
|
107
|
+
:icons="icons"
|
|
108
|
+
:initial-index="0"
|
|
109
|
+
@select="onSelect"
|
|
110
|
+
@slide-end="onSlideEnd"
|
|
111
|
+
/>
|
|
112
|
+
</template>
|
|
113
|
+
|
|
114
|
+
<script setup>
|
|
115
|
+
import { BezierSlider } from 'bezier-slider/vue';
|
|
116
|
+
|
|
117
|
+
const icons = [
|
|
118
|
+
{ name: '首页', emoji: '🏠', color: '#8b5cf6' },
|
|
119
|
+
{ name: '搜索', emoji: '🔍', color: '#ec4899' },
|
|
120
|
+
{ name: '消息', emoji: '💬', color: '#06b6d4' },
|
|
121
|
+
{ name: '设置', emoji: '⚙️', color: '#22c55e' }
|
|
122
|
+
];
|
|
123
|
+
|
|
124
|
+
function onSelect(icon, index) {
|
|
125
|
+
console.log('选中:', icon.name, index);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function onSlideEnd(index) {
|
|
129
|
+
console.log('滑动结束:', index);
|
|
130
|
+
}
|
|
131
|
+
</script>
|
|
132
|
+
|
|
133
|
+
<style scoped>
|
|
134
|
+
.carousel-container {
|
|
135
|
+
width: min(100vw - 32px, 480px);
|
|
136
|
+
height: min(62vw, 300px);
|
|
137
|
+
}
|
|
138
|
+
</style>
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
默认使用 `renderDefaultTrack` 绘制滑轨;传 `:render-track="null"` 可关闭,或传入自定义函数。演示页右侧代码面板可切换 Vue 代码输出。
|
|
142
|
+
|
|
143
|
+
Vue 封装为**薄包装**,内部复用 native `BezierSlider` 核心,与 React 版一致。
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
### 方式三:React
|
|
148
|
+
|
|
149
|
+
需安装 peer 依赖:`npm install react react-dom`
|
|
150
|
+
|
|
151
|
+
```jsx
|
|
152
|
+
import { useRef } from 'react';
|
|
153
|
+
import BezierSlider, { renderDefaultTrack } from 'bezier-slider/react';
|
|
154
|
+
|
|
155
|
+
function App() {
|
|
156
|
+
const sliderRef = useRef(null);
|
|
157
|
+
|
|
158
|
+
return (
|
|
159
|
+
<BezierSlider
|
|
160
|
+
ref={sliderRef}
|
|
161
|
+
className="carousel-container"
|
|
162
|
+
icons={[
|
|
163
|
+
{ name: '首页', emoji: '🏠', color: '#8b5cf6' },
|
|
164
|
+
{ name: '搜索', emoji: '🔍', color: '#ec4899' },
|
|
165
|
+
{ name: '消息', emoji: '💬', color: '#06b6d4' },
|
|
166
|
+
{ name: '设置', emoji: '⚙️', color: '#22c55e' }
|
|
167
|
+
]}
|
|
168
|
+
initialIndex={0}
|
|
169
|
+
renderTrack={renderDefaultTrack}
|
|
170
|
+
onSelect={(icon, index) => console.log('选中:', icon.name, index)}
|
|
171
|
+
onSlideEnd={(index) => console.log('滑动结束:', index)}
|
|
172
|
+
/>
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
通过 `ref` 调用实例方法:
|
|
178
|
+
|
|
179
|
+
```jsx
|
|
180
|
+
sliderRef.current?.slideTo(2);
|
|
181
|
+
sliderRef.current?.getCurrentIndex();
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
演示页右侧代码面板可切换 React 代码输出。
|
|
185
|
+
|
|
186
|
+
React 封装为**薄包装**,内部复用 native `BezierSlider` 核心,逻辑与原生版一致。
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
### 开发
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
npm install
|
|
194
|
+
npm run dev
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
启动后访问 **http://127.0.0.1:5173/** 打开演示页(Vite 以 `demo/` 为 dev 根目录)。右侧代码面板支持切换 **原生 JS / React / Vue** 三种代码输出。
|
|
198
|
+
|
|
199
|
+
### 构建
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
npm run build
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### 本地测试(npm link)
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
# 在当前项目创建链接
|
|
209
|
+
npm link
|
|
210
|
+
|
|
211
|
+
# 在测试项目中使用
|
|
212
|
+
npm link bezier-slider
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## 使用样例
|
|
218
|
+
|
|
219
|
+
### 1. 自定义 icons(数量不限)
|
|
220
|
+
|
|
221
|
+
#### 文本 / Emoji
|
|
222
|
+
|
|
223
|
+
```javascript
|
|
224
|
+
new BezierSlider({
|
|
225
|
+
container: '#carouselContainer',
|
|
226
|
+
icons: [
|
|
227
|
+
{ name: '首页', emoji: '🏠', color: '#8b5cf6' },
|
|
228
|
+
{ name: '搜索', emoji: '🔍', color: '#ec4899' }
|
|
229
|
+
]
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
// 简写:字符串或数字
|
|
233
|
+
icons: ['A', 'B', 'C']
|
|
234
|
+
icons: [1, 2, 3, 4, 5, 6]
|
|
235
|
+
|
|
236
|
+
// 不传 icons:默认 4 个,显示数字 1、2、3、4
|
|
237
|
+
new BezierSlider({
|
|
238
|
+
container: '#carouselContainer',
|
|
239
|
+
defaultIconCount: 6
|
|
240
|
+
});
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
#### 图片(base64 或资源路径)
|
|
244
|
+
|
|
245
|
+
```javascript
|
|
246
|
+
icons: [
|
|
247
|
+
{ name: '头像', image: 'data:image/png;base64,iVBORw0KG...' },
|
|
248
|
+
{ name: '封面', src: './assets/cover.png' },
|
|
249
|
+
{ name: '远程', img: 'https://example.com/icon.jpg' }
|
|
250
|
+
]
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
#### SVG 资源路径
|
|
254
|
+
|
|
255
|
+
```javascript
|
|
256
|
+
icons: [
|
|
257
|
+
{ name: '设置', svgUrl: './icons/gear.svg' },
|
|
258
|
+
{ name: '星标', svg: '/assets/star.svg' } // 字符串且为 .svg 路径
|
|
259
|
+
]
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
#### Font Icon(unicode)
|
|
263
|
+
|
|
264
|
+
```javascript
|
|
265
|
+
icons: [
|
|
266
|
+
{
|
|
267
|
+
name: '消息',
|
|
268
|
+
fontIcon: '\\ue001', // 也支持 '0xe001'、'U+E001'、59001、''
|
|
269
|
+
fontFamily: 'iconfont',
|
|
270
|
+
fontClass: 'iconfont icon-msg' // 可选,iconfont 类名
|
|
271
|
+
}
|
|
272
|
+
]
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
#### SVG Path(内联路径)
|
|
276
|
+
|
|
277
|
+
```javascript
|
|
278
|
+
icons: [
|
|
279
|
+
{
|
|
280
|
+
name: '搜索',
|
|
281
|
+
svgPath: 'M15.5 14h-.79l-.28-.27A6.471 6.471 0 0 0 16 9.5...',
|
|
282
|
+
viewBox: '0 0 24 24',
|
|
283
|
+
fill: 'currentColor',
|
|
284
|
+
stroke: '#fff',
|
|
285
|
+
strokeWidth: 1.5
|
|
286
|
+
}
|
|
287
|
+
]
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
**字段识别优先级:**
|
|
291
|
+
|
|
292
|
+
`image / src / img` → `svgUrl / svgSrc` → `svgPath / path` → `fontIcon / unicode` → `emoji / text`
|
|
293
|
+
|
|
294
|
+
**通用字段:**
|
|
295
|
+
|
|
296
|
+
| 字段 | 类型 | 必填 | 说明 |
|
|
297
|
+
|------|------|------|------|
|
|
298
|
+
| `name` | string | 否 | 名称,默认取序号 |
|
|
299
|
+
| `color` | string | 否 | 背景色,默认自动分配 |
|
|
300
|
+
| `emoji` | string | 否 | 文本/emoji 内容 |
|
|
301
|
+
| `label` / `text` | string | 否 | 文本别名 |
|
|
302
|
+
| `image` / `src` / `img` | string | 否 | 图片 base64 或路径 |
|
|
303
|
+
| `svgUrl` / `svgSrc` | string | 否 | SVG 文件路径 |
|
|
304
|
+
| `svgPath` / `path` | string | 否 | SVG path 的 `d` 属性 |
|
|
305
|
+
| `viewBox` | string | 否 | 内联 SVG 视口,默认 `0 0 24 24` |
|
|
306
|
+
| `fill` / `iconFill` | string | 否 | SVG 填充色 |
|
|
307
|
+
| `stroke` / `iconStroke` | string | 否 | SVG 描边色 |
|
|
308
|
+
| `fontIcon` / `unicode` / `iconCode` | string \| number | 否 | 字体图标 unicode |
|
|
309
|
+
| `fontFamily` | string | 否 | 字体图标 font-family |
|
|
310
|
+
| `fontClass` / `iconClass` | string | 否 | 字体图标 CSS 类名 |
|
|
311
|
+
|
|
312
|
+
---
|
|
313
|
+
|
|
314
|
+
### 2. 控制渐隐效果
|
|
315
|
+
|
|
316
|
+
```javascript
|
|
317
|
+
new BezierSlider({
|
|
318
|
+
container: '#carouselContainer',
|
|
319
|
+
icons: [...],
|
|
320
|
+
fadeEnabled: false // 禁用渐隐效果,所有图标保持相同透明度
|
|
321
|
+
});
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
**Vue 版本:**
|
|
325
|
+
```vue
|
|
326
|
+
<BezierSlider :icons="icons" :fade-enabled="false" />
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
| 参数 | 默认值 | 说明 |
|
|
330
|
+
|------|--------|------|
|
|
331
|
+
| `fadeEnabled` | `true` | 是否启用渐隐效果(距离中心越远越透明) |
|
|
332
|
+
|
|
333
|
+
---
|
|
334
|
+
|
|
335
|
+
### 3. 控制贝塞尔弧度(二次贝塞尔)
|
|
336
|
+
|
|
337
|
+
不传 `bezier` 时使用组件内置默认值(与 demo 当前效果一致)。
|
|
338
|
+
|
|
339
|
+
```javascript
|
|
340
|
+
new BezierSlider({
|
|
341
|
+
container: '#carouselContainer',
|
|
342
|
+
bezier: {
|
|
343
|
+
fitted: {
|
|
344
|
+
p0: { x: 0.003, y: 0.438 },
|
|
345
|
+
p1: { x: 0.490, y: 1.090 },
|
|
346
|
+
p2: { x: 0.994, y: 0.000 }
|
|
347
|
+
},
|
|
348
|
+
curveSmooth: 0.7,
|
|
349
|
+
rightTilt: 1,
|
|
350
|
+
rightEndOffset: 0.04,
|
|
351
|
+
localBend: { t: 0.36, degrees: 40 },
|
|
352
|
+
leftEndBend: { degrees: 10 }
|
|
353
|
+
},
|
|
354
|
+
centerT: 0.72
|
|
355
|
+
});
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
| 参数 | 默认值 | 说明 |
|
|
359
|
+
|------|--------|------|
|
|
360
|
+
| `bezier.fitted` | 见源码 | 二次贝塞尔基础三控制点(归一化 0~1) |
|
|
361
|
+
| `bezier.curveSmooth` | `0.7` | 整体平滑度,越大越接近直线 |
|
|
362
|
+
| `bezier.rightTilt` | `1` | 右端上扬强度 |
|
|
363
|
+
| `bezier.rightEndOffset` | `0.04` | 右端相对左端的高度差 |
|
|
364
|
+
| `bezier.localBend.t` | `0.36` | 中间下弯位置 |
|
|
365
|
+
| `bezier.localBend.degrees` | `40` | 中间下弯角度 |
|
|
366
|
+
| `bezier.leftEndBend.degrees` | `10` | 左端下沉角度 |
|
|
367
|
+
| `centerT` | `0.72` | 选中中心在曲线上的参数位置(固定不变) |
|
|
368
|
+
|
|
369
|
+
---
|
|
370
|
+
|
|
371
|
+
### 4. 控制图标间距
|
|
372
|
+
|
|
373
|
+
```javascript
|
|
374
|
+
new BezierSlider({
|
|
375
|
+
container: '#carouselContainer',
|
|
376
|
+
tStep: 0.33,
|
|
377
|
+
visibleIconCount: 2
|
|
378
|
+
});
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
| 参数 | 默认值 | 说明 |
|
|
382
|
+
|------|--------|------|
|
|
383
|
+
| `tStep` | `0.33` | 相邻图标的参数 `t` 间距,越大间隔越远 |
|
|
384
|
+
| `visibleIconCount` | `2` | 拖动区域内最多显示几个图标 |
|
|
385
|
+
|
|
386
|
+
---
|
|
387
|
+
|
|
388
|
+
### 5. 边界拉扯回弹
|
|
389
|
+
|
|
390
|
+
滑到第一个或最后一个图标后,继续向边界外拖动会出现阻尼拉扯;松手后带回弹动画。
|
|
391
|
+
|
|
392
|
+
```javascript
|
|
393
|
+
new BezierSlider({
|
|
394
|
+
container: '#carouselContainer',
|
|
395
|
+
rubberBandLimit: 0.42, // 最大越界量(offset 单位)
|
|
396
|
+
rubberBandDuration: 420 // 回弹动画时长(ms)
|
|
397
|
+
});
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
---
|
|
401
|
+
|
|
402
|
+
### 6. 回调
|
|
403
|
+
|
|
404
|
+
```javascript
|
|
405
|
+
const slider = new BezierSlider({
|
|
406
|
+
container: '#carouselContainer',
|
|
407
|
+
|
|
408
|
+
// 图标滑到中心选中时触发(拖动过程中也会触发)
|
|
409
|
+
onSelect: (icon, index) => {
|
|
410
|
+
document.getElementById('selectedIcon').textContent = icon.displayLabel;
|
|
411
|
+
document.getElementById('selectedName').textContent = icon.name;
|
|
412
|
+
},
|
|
413
|
+
|
|
414
|
+
// 滑动吸附或边界回弹完成后触发
|
|
415
|
+
onSlideEnd: (index) => {
|
|
416
|
+
console.log('停留图标下标:', index);
|
|
417
|
+
},
|
|
418
|
+
|
|
419
|
+
// 布局更新时触发,由调用方自行绘制滑轨(初始化 / resize 时也会触发)
|
|
420
|
+
onLayout: (layout) => {
|
|
421
|
+
// layout 含 bezier、width、height、centerPoint、fillBottom 等几何数据
|
|
422
|
+
// 可使用内置 renderDefaultTrack(container, layout) 快速渲染默认样式
|
|
423
|
+
}
|
|
424
|
+
});
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
---
|
|
428
|
+
|
|
429
|
+
### 6.1 滑轨绘制(由调用方负责)
|
|
430
|
+
|
|
431
|
+
核心 `BezierSlider` **不包含**滑轨 SVG / 中心高亮的 DOM 创建与绘制。布局变化时通过 `onLayout` 传出几何数据,调用方自行渲染:
|
|
432
|
+
|
|
433
|
+
```javascript
|
|
434
|
+
import BezierSlider, { renderDefaultTrack } from 'bezier-slider';
|
|
435
|
+
|
|
436
|
+
const container = document.querySelector('#carouselContainer');
|
|
437
|
+
|
|
438
|
+
new BezierSlider({
|
|
439
|
+
container,
|
|
440
|
+
icons: [...],
|
|
441
|
+
onLayout: (layout) => renderDefaultTrack(container, layout)
|
|
442
|
+
});
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
`layout` 对象字段:
|
|
446
|
+
|
|
447
|
+
| 字段 | 说明 |
|
|
448
|
+
|------|------|
|
|
449
|
+
| `container` | 容器元素 |
|
|
450
|
+
| `width` / `height` | 容器尺寸 |
|
|
451
|
+
| `bezier` | 像素坐标 `{ p0, p1, p2 }` |
|
|
452
|
+
| `bezierNorm` | 归一化控制点 |
|
|
453
|
+
| `trackBounds` / `dragArea` | 滑轨与拖动区域 |
|
|
454
|
+
| `centerT` / `centerPoint` | 中心参数 t 及坐标 |
|
|
455
|
+
| `fillBottom` | 填充区域底部 y 坐标 |
|
|
456
|
+
|
|
457
|
+
也可按需使用 `ensureDefaultTrackDom` / `updateDefaultTrack`,或完全自定义绘制。静态工具:`BezierSlider.bezierPath(curve, fillToY?)`、`BezierSlider.pointOnCurve(t, curve)`。
|
|
458
|
+
|
|
459
|
+
**背景图滑轨**:容器用 CSS `background-image` 放设计稿,不传 `renderDefaultTrack`,通过 `bezier` 参数让图标轨迹与背景凹槽对齐。Demo 顶部可切换「背景图滑轨」模式查看,并支持勾选调试线对比贴合度。
|
|
460
|
+
|
|
461
|
+
---
|
|
462
|
+
|
|
463
|
+
### 7. 完整示例
|
|
464
|
+
|
|
465
|
+
```javascript
|
|
466
|
+
import BezierSlider, { renderDefaultTrack } from 'bezier-slider';
|
|
467
|
+
|
|
468
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
469
|
+
const container = document.querySelector('#carouselContainer');
|
|
470
|
+
|
|
471
|
+
const slider = new BezierSlider({
|
|
472
|
+
container,
|
|
473
|
+
icons: [
|
|
474
|
+
{ name: '首页', emoji: '🏠', color: '#8b5cf6' },
|
|
475
|
+
{ name: '头像', src: './avatar.png', color: '#ec4899' },
|
|
476
|
+
{ name: '消息', fontIcon: '\\ue001', fontFamily: 'iconfont', color: '#06b6d4' },
|
|
477
|
+
{ name: '设置', svgUrl: './icons/gear.svg', color: '#22c55e' }
|
|
478
|
+
],
|
|
479
|
+
tStep: 0.33,
|
|
480
|
+
fadeEnabled: true,
|
|
481
|
+
rubberBandLimit: 0.42,
|
|
482
|
+
bezier: {
|
|
483
|
+
curveSmooth: 0.7,
|
|
484
|
+
localBend: { t: 0.36, degrees: 40 },
|
|
485
|
+
leftEndBend: { degrees: 10 }
|
|
486
|
+
},
|
|
487
|
+
onSelect: (icon, index) => console.log('选中:', index, icon.name),
|
|
488
|
+
onSlideEnd: (index) => console.log('停留:', index),
|
|
489
|
+
onLayout: (layout) => renderDefaultTrack(container, layout)
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
setTimeout(() => slider.slideTo(1), 3000);
|
|
493
|
+
});
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
---
|
|
497
|
+
|
|
498
|
+
## 实例方法
|
|
499
|
+
|
|
500
|
+
| 方法 | 说明 |
|
|
501
|
+
|------|------|
|
|
502
|
+
| `getCurrentIndex()` | 获取当前选中图标的下标 |
|
|
503
|
+
| `getOffsetForIndex(index)` | 指定下标处于中心时的 offset 值 |
|
|
504
|
+
| `getLayoutState()` | 获取当前布局几何(与 `onLayout` 参数相同) |
|
|
505
|
+
| `slideTo(index, animate?)` | 滑动到指定下标,`animate` 默认 `true` |
|
|
506
|
+
| `initLayout()` | 重新测量布局(一般无需手动调用) |
|
|
507
|
+
| `destroy()` | 销毁实例,移除事件与图标 DOM |
|
|
508
|
+
|
|
509
|
+
---
|
|
510
|
+
|
|
511
|
+
## 静态工具
|
|
512
|
+
|
|
513
|
+
| 属性 / 方法 | 说明 |
|
|
514
|
+
|-------------|------|
|
|
515
|
+
| `BezierSlider.DEFAULTS` | 查看全部默认配置 |
|
|
516
|
+
| `BezierSlider.resolveIconContent(icon, index)` | 解析单个 icon 的展示类型 |
|
|
517
|
+
| `BezierSlider.renderIconContent(iconData)` | 将标准化 icon 渲染为 DOM 节点 |
|
|
518
|
+
| `BezierSlider.bezierPath(curve, fillToY?)` | 生成 SVG path 字符串 |
|
|
519
|
+
| `BezierSlider.pointOnCurve(t, curve)` | 求曲线上参数 t 处的坐标 |
|
|
520
|
+
|
|
521
|
+
### 可选滑轨渲染(从包中单独导入)
|
|
522
|
+
|
|
523
|
+
| 导出 | 说明 |
|
|
524
|
+
|------|------|
|
|
525
|
+
| `renderDefaultTrack(container, layout)` | 插入默认 DOM 并绘制滑轨 |
|
|
526
|
+
| `ensureDefaultTrackDom(container)` | 仅插入默认 SVG / 中心高亮 DOM |
|
|
527
|
+
| `updateDefaultTrack(container, layout)` | 仅更新已有滑轨 path |
|
|
528
|
+
|
|
529
|
+
---
|
|
530
|
+
|
|
531
|
+
## 配置项一览
|
|
532
|
+
|
|
533
|
+
```javascript
|
|
534
|
+
console.log(BezierSlider.DEFAULTS);
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
| 选项 | 类型 | 默认值 | 说明 |
|
|
538
|
+
|------|------|--------|------|
|
|
539
|
+
| `container` | string \| Element | — | **必填**,容器选择器或 DOM 元素 |
|
|
540
|
+
| `icons` | Array | `null` | 图标列表,不传则显示数字 |
|
|
541
|
+
| `defaultIconCount` | number | `4` | 未传 icons 时的默认数量 |
|
|
542
|
+
| `tStep` | number | `0.33` | 图标间距 |
|
|
543
|
+
| `visibleIconCount` | number | `2` | 可见图标上限 |
|
|
544
|
+
| `centerT` | number | `0.72` | 滑轨中心点 |
|
|
545
|
+
| `initialIndex` | number | `0` | 初始化时停在中心的图标下标 |
|
|
546
|
+
| `sensitivity` | number | `0.008` | 拖动灵敏度 |
|
|
547
|
+
| `snapDuration` | number | `300` | 吸附动画时长(ms) |
|
|
548
|
+
| `rubberBandLimit` | number | `0.42` | 边界拉扯最大越界量 |
|
|
549
|
+
| `rubberBandDuration` | number | `420` | 边界回弹动画时长(ms) |
|
|
550
|
+
| `fadeEnabled` | boolean | `true` | 是否启用渐隐效果 |
|
|
551
|
+
| `bezier` | object | 见源码 | 二次贝塞尔弧度配置 |
|
|
552
|
+
| `onSelect` | function | `null` | 选中回调 `(icon, index)` |
|
|
553
|
+
| `onSlideEnd` | function | `null` | 滑动完成回调 `(index)` |
|
|
554
|
+
| `onLayout` | function | `null` | 布局更新回调 `(layout)`,供调用方绘制滑轨 |
|
|
555
|
+
|
|
556
|
+
---
|
|
557
|
+
|
|
558
|
+
## Vue 组件 Props
|
|
559
|
+
|
|
560
|
+
与 native 配置项一致;默认调用 `renderDefaultTrack` 绘制滑轨。
|
|
561
|
+
|
|
562
|
+
| Prop | 类型 | 默认值 | 说明 |
|
|
563
|
+
|------|------|--------|------|
|
|
564
|
+
| `icons` | Array | `[...]` | 图标列表 |
|
|
565
|
+
| `initialIndex` | Number | `0` | 初始化停在中心的图标下标 |
|
|
566
|
+
| `tStep` / `visibleIconCount` / `centerT` / `bezier` 等 | — | 同 native | 见配置项一览 |
|
|
567
|
+
| `renderTrack` | Function \| null | `renderDefaultTrack` | 滑轨绘制;`null` 关闭 |
|
|
568
|
+
| `rootClass` / `rootStyle` | — | — | 外层容器样式 |
|
|
569
|
+
|
|
570
|
+
### Vue 组件 Events
|
|
571
|
+
|
|
572
|
+
| Event | 参数 | 说明 |
|
|
573
|
+
|-------|------|------|
|
|
574
|
+
| `select` | `(icon, index)` | 图标进入中心选中时触发 |
|
|
575
|
+
| `slide-end` | `(index)` | 滑动吸附完成时触发 |
|
|
576
|
+
|
|
577
|
+
### Vue 组件 Methods(ref)
|
|
578
|
+
|
|
579
|
+
| 方法 | 说明 |
|
|
580
|
+
|------|------|
|
|
581
|
+
| `slideTo(index, animate?)` | 滑动到指定下标 |
|
|
582
|
+
| `getCurrentIndex()` | 当前选中下标 |
|
|
583
|
+
| `getLayoutState()` | 当前布局几何 |
|
|
584
|
+
| `getInstance()` | native `BezierSlider` 实例 |
|
|
585
|
+
|
|
586
|
+
---
|
|
587
|
+
|
|
588
|
+
## React 组件 Props
|
|
589
|
+
|
|
590
|
+
与 native 配置项一致,通过 props 传入;滑轨使用 `renderTrack`(等价于 native 的 `onLayout` + `renderDefaultTrack`)。
|
|
591
|
+
|
|
592
|
+
| Prop | 类型 | 说明 |
|
|
593
|
+
|------|------|------|
|
|
594
|
+
| `icons` | Array | 图标列表 |
|
|
595
|
+
| `initialIndex` | number | 初始化停在中心的图标下标,默认 `0` |
|
|
596
|
+
| `tStep` / `visibleIconCount` / `centerT` / `bezier` 等 | — | 同 native 配置项 |
|
|
597
|
+
| `renderTrack` | `(container, layout) => void` | 绘制滑轨,如 `renderDefaultTrack` |
|
|
598
|
+
| `onLayout` | `(layout) => void` | 布局更新(在 `renderTrack` 之后触发) |
|
|
599
|
+
| `onSelect` | `(icon, index) => void` | 选中回调 |
|
|
600
|
+
| `onSlideEnd` | `(index) => void` | 滑动完成回调 |
|
|
601
|
+
| `className` / `style` | — | 容器 div 样式 |
|
|
602
|
+
|
|
603
|
+
### React ref 方法
|
|
604
|
+
|
|
605
|
+
| 方法 | 说明 |
|
|
606
|
+
|------|------|
|
|
607
|
+
| `slideTo(index, animate?)` | 滑动到指定下标 |
|
|
608
|
+
| `getCurrentIndex()` | 当前选中下标 |
|
|
609
|
+
| `getLayoutState()` | 当前布局几何 |
|
|
610
|
+
| `getInstance()` | 获取 native `BezierSlider` 实例 |
|
|
611
|
+
|
|
612
|
+
`icons`、`bezier` 等配置变更时会自动 destroy 并按新配置重建实例。
|
|
613
|
+
|
|
614
|
+
---
|
|
615
|
+
|
|
616
|
+
## 样式说明
|
|
617
|
+
|
|
618
|
+
核心会在首次初始化时自动注入图标、滑轨 SVG、中心高亮等**基础样式**(`abs-slider-base-styles`)。调用方只需为容器定义尺寸,例如:
|
|
619
|
+
|
|
620
|
+
```css
|
|
621
|
+
.carousel-container {
|
|
622
|
+
position: relative;
|
|
623
|
+
width: min(100vw - 32px, 480px);
|
|
624
|
+
height: min(62vw, 300px);
|
|
625
|
+
overflow: hidden;
|
|
626
|
+
}
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
如需自定义外观,可在页面 CSS 中覆盖以下类名:
|
|
630
|
+
|
|
631
|
+
| 类名 | 说明 |
|
|
632
|
+
|------|------|
|
|
633
|
+
| `.carousel-container` | 外层容器(需自行定义尺寸) |
|
|
634
|
+
| `.track-fill` / `.track-glow` / `.track-line` | 滑轨 SVG 路径 |
|
|
635
|
+
| `.center-highlight` | 中心高亮光晕 |
|
|
636
|
+
| `.carousel-icon` | 图标外层圆形容器 |
|
|
637
|
+
| `.abs-icon-image` | 图片 / SVG 文件图标 |
|
|
638
|
+
| `.abs-icon-font` | 字体图标 |
|
|
639
|
+
| `.abs-icon-svg` | 内联 SVG path 图标 |
|
|
640
|
+
| `.abs-icon-text` | 文本 / emoji 图标 |
|
|
641
|
+
|
|
642
|
+
以上 `.carousel-icon`、`.track-*` 等默认样式已由核心注入,通常无需重复编写。
|
|
643
|
+
|
|
644
|
+
使用 font-icon 时,记得在页面中引入对应字体:
|
|
645
|
+
|
|
646
|
+
```css
|
|
647
|
+
@font-face {
|
|
648
|
+
font-family: 'iconfont';
|
|
649
|
+
src: url('./fonts/iconfont.woff2') format('woff2');
|
|
650
|
+
}
|
|
651
|
+
```
|
|
652
|
+
|
|
653
|
+
---
|
|
654
|
+
|
|
655
|
+
## 技术说明
|
|
656
|
+
|
|
657
|
+
- 滑轨类型:**二次贝塞尔**(`M … Q …`),非三次贝塞尔,非折线近似
|
|
658
|
+
- 图标位置:沿曲线参数 `t` 均匀偏移,中心固定在 `centerT`
|
|
659
|
+
- 拖动区域:初始化时通过 `container.clientWidth / clientHeight` 动态测量
|
|
660
|
+
- 边界回弹:越界拖动时阻尼递增,松手后以 `easeOutBack` 回弹至合法边界
|
|
661
|
+
- 无第三方依赖,纯原生 JavaScript + Vue 3
|
|
662
|
+
|
|
663
|
+
---
|
|
664
|
+
|
|
665
|
+
## 版本记录
|
|
666
|
+
|
|
667
|
+
| 版本 | 日期 | 说明 |
|
|
668
|
+
|------|------|------|
|
|
669
|
+
| v3.0 | 2026-06-10 | 支持 Vue 3;添加 `fadeEnabled` 参数;项目结构重构 |
|
|
670
|
+
| v2.2 | 2026-06-09 | 支持图片/SVG/font-icon 多种图标;边界拉扯回弹 |
|
|
671
|
+
| v2.1 | 2026-06-09 | 抽象公共组件;中文注释;动态布局 |
|
|
672
|
+
| v2.0 | 2026-06-09 | 二次贝塞尔滑轨;弧度/间距/回调可配置 |
|
|
673
|
+
| v1.0 | 2026-06-09 | 初始圆弧版本(已废弃) |
|
|
Binary file
|