From ff11337a948aa07d209ca9f0fa748de73bcd5060 Mon Sep 17 00:00:00 2001 From: BKou Date: Sun, 21 Sep 2025 17:41:08 +0800 Subject: [PATCH] feat(FloatingPanel): add prop magnetic (#13608) --- .../vant/src/floating-panel/FloatingPanel.tsx | 9 +++- packages/vant/src/floating-panel/README.md | 16 +++++++ .../vant/src/floating-panel/README.zh-CN.md | 16 +++++++ .../vant/src/floating-panel/demo/index.vue | 12 ++++++ .../test/__snapshots__/demo-ssr.spec.ts.snap | 10 +++++ .../test/__snapshots__/demo.spec.ts.snap | 23 ++++++++++ .../src/floating-panel/test/index.spec.tsx | 42 +++++++++++++++++++ 7 files changed, 127 insertions(+), 1 deletion(-) diff --git a/packages/vant/src/floating-panel/FloatingPanel.tsx b/packages/vant/src/floating-panel/FloatingPanel.tsx index bc1fe2366..ba1129781 100644 --- a/packages/vant/src/floating-panel/FloatingPanel.tsx +++ b/packages/vant/src/floating-panel/FloatingPanel.tsx @@ -28,6 +28,7 @@ export const floatingPanelProps = { height: makeNumericProp(0), anchors: makeArrayProp(), 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 }); diff --git a/packages/vant/src/floating-panel/README.md b/packages/vant/src/floating-panel/README.md index 82b50ee03..a73cdef1e 100644 --- a/packages/vant/src/floating-panel/README.md +++ b/packages/vant/src/floating-panel/README.md @@ -78,6 +78,21 @@ By default, both the header and content areas of FloatingPanel can be dragged, b ``` +### 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 + +
+

Magnetic adsorption disabled

+

Panel can stop at any position within boundaries

+
+
+``` + ## 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` | diff --git a/packages/vant/src/floating-panel/README.zh-CN.md b/packages/vant/src/floating-panel/README.zh-CN.md index 70802cf20..5dcc252b5 100644 --- a/packages/vant/src/floating-panel/README.zh-CN.md +++ b/packages/vant/src/floating-panel/README.zh-CN.md @@ -78,6 +78,21 @@ export default { ``` +### 禁用磁力吸附 + +默认情况下,拖拽结束后面板会自动吸附到最近的锚点。你可以通过 `magnetic` 属性来禁用这种磁力吸附行为。 + +当 `magnetic` 设置为 `false` 时,面板在拖拽结束后不会自动吸附到锚点,但仍然会被约束在锚点定义的最小和最大边界范围内。 + +```html + +
+

已禁用磁力吸附

+

面板可在边界范围内任意位置停留

+
+
+``` + ## 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` | diff --git a/packages/vant/src/floating-panel/demo/index.vue b/packages/vant/src/floating-panel/demo/index.vue index 40b025456..7af9d8d40 100644 --- a/packages/vant/src/floating-panel/demo/index.vue +++ b/packages/vant/src/floating-panel/demo/index.vue @@ -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]); + + + +
+

{{ t('magneticDisabled') }}

+
+
+
diff --git a/packages/vant/src/floating-panel/test/__snapshots__/demo-ssr.spec.ts.snap b/packages/vant/src/floating-panel/test/__snapshots__/demo-ssr.spec.ts.snap index f3aa0e021..9dc5832c1 100644 --- a/packages/vant/src/floating-panel/test/__snapshots__/demo-ssr.spec.ts.snap +++ b/packages/vant/src/floating-panel/test/__snapshots__/demo-ssr.spec.ts.snap @@ -46,6 +46,16 @@ exports[`should render demo and match snapshot 1`] = ` style="display:none;" > + `; diff --git a/packages/vant/src/floating-panel/test/__snapshots__/demo.spec.ts.snap b/packages/vant/src/floating-panel/test/__snapshots__/demo.spec.ts.snap index b4696cc84..25542bfb9 100644 --- a/packages/vant/src/floating-panel/test/__snapshots__/demo.spec.ts.snap +++ b/packages/vant/src/floating-panel/test/__snapshots__/demo.spec.ts.snap @@ -47,6 +47,19 @@ exports[`should render demo and match snapshot 1`] = ` Head Drag Only +
+ `; diff --git a/packages/vant/src/floating-panel/test/index.spec.tsx b/packages/vant/src/floating-panel/test/index.spec.tsx index 570d0cd17..0380b9191 100644 --- a/packages/vant/src/floating-panel/test/index.spec.tsx +++ b/packages/vant/src/floating-panel/test/index.spec.tsx @@ -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 ( + + Content + + ); + }, + }); + + // 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 Content; + }, + }); + + // 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', + ); +});