三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

可拖拽按钮吸附效果实现

可拖拽按钮吸附效果实现

可拖拽按钮吸附效果实现教程

效果描述

页面右下角有一个可拖拽的呼叫按钮,用户可以:

  1. 按住按钮自由拖拽(水平 + 垂直)
  2. 松开后按钮自动吸附到最近的屏幕边缘(left: 0right: 0
  3. 吸附状态下只保留垂直位置(top),水平方向使用left: 0right: 0

核心思路

  • 吸附状态:使用left: 0right: 0定位,按钮紧贴屏幕一侧
  • 拖拽状态:使用left: Npx定位,按钮跟随手指自由移动
  • 松手判定:根据按钮中心点位于屏幕左半还是右半,决定吸附到哪一侧

通过computed动态生成 style 对象,根据dragging状态切换定位方式。


代码分解

1. 定义状态

constcallBtnSnapLeft=ref(false)// 吸附方向:true=靠左, false=靠右constcallBtnTop=ref(0)// 垂直位置(px)constdragging=ref(false)// 是否正在拖拽conststartX=ref(0)// 拖拽起始手指 XconststartY=ref(0)// 拖拽起始手指 YconststartTop=ref(0)// 拖拽起始 top 值conststartSnapLeft=ref(false)// 拖拽起始吸附方向constdragLeft=ref(0)// 拖拽过程中的临时水平位置(px)constcallBtnRef=ref<HTMLElement|null>(null)// 按钮 DOM 引用

2. 计算动态样式

constcallBtnStyle=computed(()=>{if(dragging.value){// 拖拽中:使用 left + top 自由定位return{left:dragLeft.value+'px',top:callBtnTop.value+'px'}}// 吸附状态:使用 left: 0 或 right: 0return{[callBtnSnapLeft.value?'left':'right']:'0',top:callBtnTop.value+'px',}})

关键点:[callBtnSnapLeft.value ? 'left' : 'right']是 ES6 计算属性名语法,动态决定用left还是right作为 CSS 属性。

3. 手指按下(开始拖拽)

functiononCallPointerDown(e:PointerEvent){dragging.value=truestartX.value=e.clientX startY.value=e.clientY startTop.value=callBtnTop.value startSnapLeft.value=callBtnSnapLeft.valueconstel=callBtnRef.valueif(!el)returnel.setPointerCapture(e.pointerId)// 捕获指针,防止拖拽过程中丢失事件// 根据当前吸附方向计算等效的 left 值constpw=el.parentElement!.clientWidth dragLeft.value=startSnapLeft.value?0:pw-el.offsetWidth}

setPointerCapture确保即使手指滑出按钮范围,仍然能接收到指针事件。

4. 手指移动(拖拽中)

functiononCallPointerMove(e:PointerEvent){if(!dragging.value)returnconstdx=e.clientX-startX.value// 水平偏移量constdy=e.clientY-startY.value// 垂直偏移量constel=callBtnRef.valueif(!el)returnconstpw=el.parentElement!.clientWidthconstbaseLeft=startSnapLeft.value?0:pw-el.offsetWidth// 限制范围,不让按钮拖出屏幕dragLeft.value=Math.max(0,Math.min(pw-el.offsetWidth,baseLeft+dx))callBtnTop.value=Math.max(0,startTop.value+dy)}

Math.max(0, Math.min(pw - el.offsetWidth, ...))确保按钮不会超出父容器左右边界。

5. 手指松开(吸附判定)

functiononCallPointerUp(e:PointerEvent){if(!dragging.value)returndragging.value=falseconstel=callBtnRef.valueif(el)el.releasePointerCapture(e.pointerId)if(!el)returnconstparent=el.parentElement!constpw=parent.clientWidth// 按钮中心在屏幕左半 → 靠左,否则靠右callBtnSnapLeft.value=dragLeft.value+el.offsetWidth/2<pw/2}

判定逻辑:dragLeft + 按钮宽度/2 < 父容器宽度/2→ 按钮中心在左半侧 → 吸附到左侧。


模板绑定

<divref="callBtnRef"class="call-btn":class="{ dragging }":style="callBtnStyle"@pointerdown="onCallPointerDown"@pointermove="onCallPointerMove"@pointerup="onCallPointerUp"@pointercancel="onCallPointerUp"><imgsrc="/home/call.png"class="call-icon"alt=""/></div>

使用 Pointer Events API,兼容触摸和鼠标。


注意事项

  1. Pointer Events vs Touch/Mouse Events:Pointer Events 统一了触摸和鼠标输入,推荐在现代浏览器中使用。setPointerCapture是核心 API。

  2. 为什么不直接用left: Npx吸附?:用left: 0/right: 0可以自适应容器宽度变化(如屏幕旋转),而left: 8px这样的像素值在屏幕翻转后会错位。

  3. 边界限制Math.max(0, ...)防止按钮拖出顶部/左侧,Math.min(pw - bw, ...)防止拖出右侧/底部。

  4. 吸附动画:当前实现是瞬间切换,如果需要平滑过渡,可以在.call-btn上添加transition: left 0.2s, right 0.2s。但要注意拖拽时不能有过渡(否则按钮会延迟跟随手指),所以只在非拖拽状态添加。

← 返回列表