feat(FloatingPanel): add prop magnetic (#13608)

This commit is contained in:
BKou
2025-09-21 17:41:08 +08:00
committed by GitHub
parent 330a013b10
commit ff11337a94
7 changed files with 127 additions and 1 deletions

View File

@@ -28,6 +28,7 @@ export const floatingPanelProps = {
height: makeNumericProp(0),
anchors: makeArrayProp<number>(),
duration: makeNumericProp(0.3),
magnetic: truthProp,
contentDraggable: truthProp,
lockScroll: Boolean,
safeAreaInsetBottom: truthProp,
@@ -130,7 +131,13 @@ export default defineComponent({
const onTouchend = () => {
maxScroll = -1;
dragging.value = false;
height.value = closest(anchors.value, height.value);
if (props.magnetic) {
height.value = closest(anchors.value, height.value);
} else {
const { min, max } = boundary.value;
height.value = Math.max(min, Math.min(max, height.value));
}
if (height.value !== -startY) {
emit('heightChange', { height: height.value });

View File

@@ -78,6 +78,21 @@ By default, both the header and content areas of FloatingPanel can be dragged, b
</van-floating-panel>
```
### Disable Magnetic Adsorption
By default, when dragging ends, the panel will automatically snap to the nearest anchor point. You can disable this magnetic adsorption behavior through the `magnetic` attribute.
When `magnetic` is set to `false`, the panel will not automatically snap to anchor points after dragging, but it will still be constrained within the minimum and maximum boundaries defined by the anchors.
```html
<van-floating-panel :anchors="[100, 200, 300]" :magnetic="false">
<div style="text-align: center; padding: 15px">
<p>Magnetic adsorption disabled</p>
<p>Panel can stop at any position within boundaries</p>
</div>
</van-floating-panel>
```
## API
### Props
@@ -87,6 +102,7 @@ By default, both the header and content areas of FloatingPanel can be dragged, b
| v-model:height | The current display height of the panel | _number \| string_ | `0` |
| anchors | Setting custom anchors, unit `px` | _number[]_ | `[100, window.innerHeight * 0.6]` |
| duration | Transition duration, unit second | _number \| string_ | `0.3` |
| magnetic | Whether to enable magnetic adsorption to anchors. When disabled, panel can stop at any position within the anchor boundaries | _boolean_ | `true` |
| content-draggable | Allow dragging content | _boolean_ | `true` |
| lock-scroll `v4.6.4` | When not dragging, Whether to lock background scroll | _boolean_ | `false` |
| safe-area-inset-bottom | Whether to enable bottom safe area adaptation | _boolean_ | `true` |

View File

@@ -78,6 +78,21 @@ export default {
</van-floating-panel>
```
### 禁用磁力吸附
默认情况下,拖拽结束后面板会自动吸附到最近的锚点。你可以通过 `magnetic` 属性来禁用这种磁力吸附行为。
`magnetic` 设置为 `false` 时,面板在拖拽结束后不会自动吸附到锚点,但仍然会被约束在锚点定义的最小和最大边界范围内。
```html
<van-floating-panel :anchors="[100, 200, 300]" :magnetic="false">
<div style="text-align: center; padding: 15px">
<p>已禁用磁力吸附</p>
<p>面板可在边界范围内任意位置停留</p>
</div>
</van-floating-panel>
```
## API
### Props
@@ -87,6 +102,7 @@ export default {
| v-model:height | 当前面板的显示高度 | _number \| string_ | `0` |
| anchors | 设置自定义锚点, 单位 `px` | _number[]_ | `[100, window.innerHeight * 0.6]` |
| duration | 动画时长,单位秒,设置为 0 可以禁用动画 | _number \| string_ | `0.3` |
| magnetic | 是否启用磁力吸附到锚点。禁用后面板可在锚点边界范围内任意位置停留 | _boolean_ | `true` |
| content-draggable | 允许拖拽内容容器 | _boolean_ | `true` |
| lock-scroll `v4.6.4` | 当不拖拽时,是否锁定背景滚动 | _boolean_ | `false` |
| safe-area-inset-bottom | 是否开启[底部安全区适配](#/zh-CN/advanced-usage#di-bu-an-quan-qu-gua-pei) | _boolean_ | `true` |

View File

@@ -14,14 +14,18 @@ const t = useTranslate({
'zh-CN': {
customAnchors: '自定义锚点',
headDragOnly: '仅头部拖拽',
disableMagnetic: '禁用吸附',
panelShowHeight: '面板显示高度',
contentUnDrag: '内容不可拖拽',
magneticDisabled: '已禁用磁力吸附,可在边界内任意停留',
},
'en-US': {
customAnchors: 'Custom Anchors',
headDragOnly: 'Head Drag Only',
disableMagnetic: 'Disable Magnetic',
panelShowHeight: 'Panel Show Height',
contentUnDrag: 'Content cannot be dragged',
magneticDisabled: 'Magnetic disabled, free positioning within boundaries',
},
});
@@ -64,5 +68,13 @@ const height = ref(anchors[0]);
</div>
</van-floating-panel>
</van-tab>
<van-tab :title="t('disableMagnetic')">
<van-floating-panel :magnetic="false">
<div style="text-align: center; padding: 15px">
<p>{{ t('magneticDisabled') }}</p>
</div>
</van-floating-panel>
</van-tab>
</van-tabs>
</template>

View File

@@ -46,6 +46,16 @@ exports[`should render demo and match snapshot 1`] = `
style="display:none;"
>
</div>
<div
id="van-tab"
role="tabpanel"
class="van-tab__panel"
tabindex="-1"
aria-labelledby="van-tabs-3"
data-allow-mismatch="attribute"
style="display:none;"
>
</div>
</div>
</div>
`;

View File

@@ -47,6 +47,19 @@ exports[`should render demo and match snapshot 1`] = `
Head Drag Only
</span>
</div>
<div
id="van-tabs-3"
role="tab"
class="van-tab van-tab--line"
tabindex="-1"
aria-selected="false"
aria-controls="van-tab"
data-allow-mismatch="attribute"
>
<span class="van-tab__text van-tab__text--ellipsis">
Disable Magnetic
</span>
</div>
<div
class="van-tabs__line"
style="transform: translateX(50px) translateX(-50%);"
@@ -280,6 +293,16 @@ exports[`should render demo and match snapshot 1`] = `
style="display: none;"
>
</div>
<div
id="van-tab"
role="tabpanel"
class="van-tab__panel"
tabindex="-1"
aria-labelledby="van-tabs-3"
data-allow-mismatch="attribute"
style="display: none;"
>
</div>
</div>
</div>
`;

View File

@@ -128,3 +128,45 @@ test('should render header slot correctly', () => {
wrapper.unmount();
});
test('should not snap to anchors when magnetic is false', async () => {
const wrapper = mount({
render() {
return (
<FloatingPanel anchors={[100, 200, 400]} magnetic={false}>
Content
</FloatingPanel>
);
},
});
// Initial height should be 400 (highest anchor)
expect((wrapper.element as HTMLDivElement).style.height).toBe('400px');
// Drag to a position between anchors (down from 400 to around 250)
await triggerDrag(wrapper.find('.van-floating-panel__header'), 0, -150);
await later();
// Should stay at dragged position (around 250px), not snap to nearest anchor (200px)
const {transform} = (wrapper.element as HTMLDivElement).style;
expect(transform).not.toContain('-200px');
expect(transform).not.toContain('-400px');
expect(transform).toContain('-250px');
});
test('should snap to nearest anchor when magnetic is true (default)', async () => {
const wrapper = mount({
render() {
return <FloatingPanel anchors={[100, 200, 400]}>Content</FloatingPanel>;
},
});
// Drag to trigger height change and snapping
await triggerDrag(wrapper.find('.van-floating-panel__header'), 0, 10);
await later();
// Should snap to nearest anchor (100px)
expect((wrapper.element as HTMLDivElement).style.transform).toContain(
'-100px',
);
});