Вместо того, чтобы вычислять количество времени, необходимое для остановки перетаскиваемого элемента (что включает в себя некоторую относительно сложную физическую формулу), мы можем установить его так, чтобы перетаскиваемый элемент останавливался через определенное время.
Чтобы сделать его более реальным, мы можем использовать функцию cubic-bezier
. Используя его, мы можем смоделировать его так, чтобы казалось, что он теряет скорость. Нам не нужно имитировать c способ, которым физика работает точно (используйте иллюзию).
Вот рабочий пример (с некоторыми комментариями о том, как это работает). Я также изменил некоторый код, чтобы начальное событие mousedown
не перемещало элемент.
let draggableEl = document.getElementById('draggable');
let mousePos = {
x: -1,
y: -1,
started: false,
startX: -1,
startY: -1
};
// Used to track the last UI's transform position after the deceleration
// and also after any dragging
let uiTransform = {
x: 0,
y: 0
};
// Used to simulate UI's deceleration
let uiMotion = {
oldX: -1,
oldY: -1,
x: -1,
y: -1
};
draggableEl.addEventListener('mousedown', onMousedown);
function onMousedown(e) {
// Extract the last transform value
// Necessary because deceleration may be stopped by mousedown
// before the UI's natural deceleration is finished
let transformsValue = draggableEl.style.transform.match(/(-?\d*\.?\d+)/g)
draggableEl.style.transition = 'none'
uiTransform.x = (transformsValue && parseFloat(transformsValue[0])) || 0
uiTransform.y = (transformsValue && parseFloat(transformsValue[1])) || 0
uiTransform.offsetTop // Trigger layout reflow so that transition none is applied
draggableEl.style.transform = `translate(${uiTransform.x}px, ${uiTransform.y}px)`
document.documentElement.addEventListener('mouseup', onMouseup);
document.documentElement.addEventListener('mousemove', updateMousePos);
updateMousePos(e);
updateUI();
}
function onMouseup() {
document.documentElement.removeEventListener('mouseup', onMouseup);
document.documentElement.removeEventListener('mousemove', updateMousePos);
uiTransform.x += mousePos.x - mousePos.startX
uiTransform.y += mousePos.y - mousePos.startY
// The throwVal you asked for
// Time to decelerate is 1s
let throwVal = {
x: (uiMotion.x - uiMotion.oldX) * 3,
y: (uiMotion.y - uiMotion.oldY) * 3
}
draggableEl.style.transition = `transform 1s cubic-bezier(.27,1.04,.61,.97)`
draggableEl.style.transform = `translate(${uiTransform.x + throwVal.x}px, ${uiTransform.y + throwVal.y}px)`
mousePos.x = -1;
mousePos.y = -1;
mousePos.startX = -1;
mousePos.startY = -1;
mousePos.started = false;
uiMotion.x = -1
uiMotion.y = -1
uiMotion.oldX = -1
uiMotion.oldY = -1
}
function updateMousePos(e) {
if (!mousePos.started) {
mousePos.startX = e.pageX;
mousePos.startY = e.pageY;
mousePos.started = true;
}
mousePos.x = e.pageX;
mousePos.y = e.pageY;
}
function updateUI() {
if (mousePos.x === -1 && mousePos.y === -1)
return;
// Fixed some code
let xValue = uiTransform.x + mousePos.x - mousePos.startX
let yValue = uiTransform.y + mousePos.y - mousePos.startY
draggableEl.style.transform = `translate(${xValue}px, ${yValue}px)`;
if (uiMotion.oldX === -1 && uiMotion.oldY === -1) {
uiMotion.oldX = xValue
uiMotion.oldY = yValue
} else {
if (uiMotion.x !== -1 && uiMotion.y !== -1) {
uiMotion.oldX = uiMotion.x
uiMotion.oldY = uiMotion.y
}
uiMotion.x = xValue
uiMotion.y = yValue
}
requestAnimationFrame(updateUI);
}
html {
background: #121212;}
#draggable {
position: absolute;
background: #585858;
width: 50px;
height: 50px;
border-radius: 50%;
cursor: grab;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>Drag</title>
</head>
<body>
<div id="draggable"></div>
</body>
</html>