移动端的拖拽交互,在项目中常常使用。javascript
实现拖拽也不复杂,经过绑定 touchstart
、touchmove
、和 touchend
事件,就能够完成 自定义滚轮 、游戏人物拖拽 等各类交互。java
若是想让体验更好一些,添加一些缓动效果,是必不可少的。git
以上图片是没有添加缓动的,能够看到拖拽起来画面至关灵活,可是随着手指离开屏幕,画面也就戛然而止了。github
以上图片是添加了缓动的,手指快速的滑动后离开,画面没有快速中止,而是通过一段惯性动做后中止。typescript
添加缓动以后的效果,更符合现实中的惯性习惯。npm
以前制做缓动,会在 touchmove
事件中经过多个参数来记录实时的拖拽速度,在 touchend
的时候,在经过必定的加速度来让速度逐步减到 0 ,完整代码较长,放在了 文末。markdown
这样实现的缺点不少,好比参数不少、逻辑复杂,不能知足快速开发的需求。oop
在 touchmove
阶段,不直接赋值,而是记录下来,经过 requestAnimationFrame
计算差值,逐步赋值给运动的元素,便可实现。ui
这样实现的优势就很明确了,参数少、逻辑清晰。this
接下来详细介绍一下思路二:
首先不考虑缓动,先完成基本的拖拽事件:
let initX = 0;
let initY = 0;
let changeX = 0;
let changeY = 0;
let element = window.document;
element.addEventListener('touchstart',(e)=>{
initX = e.touches[0].clientX;
initY = e.touches[0].clientY;
});
element.addEventListener('touchmove',(e)=>{
const { clientX: moveX, clientY: moveY } = e.touches[0];
changeX += (moveX - initX);
changeY += (moveY - initY);
initX = moveX;
initY = moveY;
});
复制代码
经过以上简单的代码,咱们就拿到了实时拖拽的 changeX
、changeY
,而后赋值给元素便可。
按照以前的思路,咱们在 touchmove
中不直接赋值,而是只记录值:
let targetX = 0;
let targetY = 0;
element.addEventListener('touchmove',(e)=>{
const { clientX: moveX, clientY: moveY } = e.touches[0];
targetX += (moveX - initX);
targetY += (moveY - initY);
initX = moveX;
initY = moveY;
});
复制代码
拿到在 touchmove
中记录的 targetX
和 targetY
以后,只须要思考如何缓慢的把 target
的值赋值给 change
,在这里只须要给 changeX
、changeY
赋值的时候,添加一个减速倍率便可:
function touchAnimate(){
requestAnimationFrame(touchAnimate);
changeX += (targetX - changeX) * 0.1; // 此处减速倍率设置为 0.1
changeY += (targetY - changeY) * 0.1;
}
复制代码
至此,就完成了拖拽的缓动操做,完整代码查看 文末附录。
上述的简单代码就实现了拖拽的缓动,同时也是这个逻辑的核心代码。
实际开发中还要考虑添加 touchFlag
、手指移出规定的区域怎么处理等状况。所以我把该拖拽缓动功能制做成了一个组件,方便后续各类相似的项目使用。
npm i drag-easing --save
复制代码
基础示例
import DragEasing from 'drag-easing';
// 初始化
const de = new DragEasing({
onDragging: (e)=>{
// e.changeX 即为添加了缓动的拖拽 X 坐标
// e.changeY 即为添加了缓动的拖拽 Y 坐标
},
});
复制代码
同时 组件 还提供了指定绑定元素、x
y
方向的区间限制、以及手指移出交互区域的操做等,更多 API 能够在 点此 查看。
(完)
tip: 此处使用到了 ts
语法,代码未处理,但只要阅读思路便可:
private timer:number = null;
private easeTimer:number = null;
private rotateNum:number = 359;
private startX:number = -1;
private moveX:number = -1;
private scaleNum:number = 750 / window.innerWidth;
private easeTime:number = -1;
private tempUnit:number = null;
private easeUnit:number = -1;
private easeDirection:string = '';
private rotateToTimer:number = null;
private rotateToNum:number = null;
private rotateToSpeed:number = null;
private touchStartCtrl(e){
if(this.rotateToSpeed !== null) return;
this.easeStop();
this.startX = e.touches[0].clientX * this.scaleNum;
this.animate();
}
private touchMoveCtrl(e){
this.moveX = e.touches[0].clientX * this.scaleNum;
if(this.moveX < 0 || this.moveX > 750){
this.touchFinish(false);
}
}
private touchEndCtrl(e){
this.touchFinish(this.moveX !== -1);
}
private touchFinish(easeFlag:boolean = true){
if(easeFlag) this.easing();
this.startX = -1;
this.moveX = -1;
window.cancelAnimationFrame(this.timer);
}
private animate(){
if(this.startX>-1&&this.moveX>-1){
const changeNum:number = (this.moveX - this.startX) / 30;
this.rotateNum = this.rotateNum += changeNum;
this.tempUnit = 0.05 * (this.moveX-this.startX);
this.startX = this.moveX;
}
this.timer = requestAnimationFrame(()=>{
this.animate();
})
}
private easing(){
if(this.easeTime === -1){
this.easeTime = 100;
if(this.tempUnit === 0){
this.easeStop();
return;
}
this.easeDirection = this.tempUnit > 0 ? 'right' : 'left';
const maxUnit = 1;
this.easeUnit = this.tempUnit > 0 ? Math.max(maxUnit, this.tempUnit) : Math.min(maxUnit*-1, this.tempUnit);
}
if(this.easeTime > 0){
this.easeTime = this.easeTime - 1;
this.easeUnit -= this.easeUnit*0.1;
this.rotateNum += this.easeUnit;
this.easeTimer = requestAnimationFrame(()=>{
this.easing();
})
}else{
this.easeStop();
}
}
private easeStop(){
window.cancelAnimationFrame(this.easeTimer);
this.easeTime = -1;
this.easeUnit = -1;
}
复制代码
let initX = 0;
let initY = 0;
let changeX = 0;
let changeY = 0;
let targetX = 0;
let targetY = 0;
let element = window.document;
element.addEventListener('touchstart',(e)=>{
initX = e.touches[0].clientX;
initY = e.touches[0].clientY;
});
element.addEventListener('touchmove',(e)=>{
const { clientX: moveX, clientY: moveY } = e.touches[0];
targetX += (moveX - initX);
targetY += (moveY - initY);
initX = moveX;
initY = moveY;
});
function touchAnimate(){
requestAnimationFrame(touchAnimate);
changeX += (targetX - changeX) * 0.1;
changeY += (targetY - changeY) * 0.1;
}
touchAnimate();
复制代码