feat: add smooth animation to mobile FAB drag behavior
Improved the mobile floating action button (FAB) drag experience with smooth, performant animations: - Use requestAnimationFrame to throttle position updates during drag - Add will-change CSS property to optimize rendering performance - Add dragging class to disable transitions during active drag - Change cursor to grab/grabbing for better visual feedback - Remove janky direct CSS updates in favor of RAF-based updates Technical improvements: - Position updates now synced to display refresh rate (~60fps) - Prevents layout thrashing from excessive DOM manipulation - Smooth transition animations when drag ends Result: Dragging the FAB button now feels fluid and responsive on mobile devices instead of laggy and jumpy.
This commit is contained in:
@@ -1032,6 +1032,24 @@ function setupMobileToggle() {
|
||||
let buttonStartY = 0;
|
||||
const LONG_PRESS_DURATION = 200; // ms to hold before enabling drag
|
||||
const MOVE_THRESHOLD = 10; // px to move before enabling drag
|
||||
let rafId = null; // RequestAnimationFrame ID for smooth updates
|
||||
let pendingX = null;
|
||||
let pendingY = null;
|
||||
|
||||
// Update position using requestAnimationFrame for smooth rendering
|
||||
function updateFabPosition() {
|
||||
if (pendingX !== null && pendingY !== null) {
|
||||
$mobileToggle.css({
|
||||
left: pendingX + 'px',
|
||||
top: pendingY + 'px',
|
||||
right: 'auto',
|
||||
bottom: 'auto'
|
||||
});
|
||||
pendingX = null;
|
||||
pendingY = null;
|
||||
}
|
||||
rafId = null;
|
||||
}
|
||||
|
||||
// Touch start - begin tracking
|
||||
$mobileToggle.on('touchstart', function(e) {
|
||||
@@ -1059,7 +1077,7 @@ function setupMobileToggle() {
|
||||
// Start dragging if held long enough OR moved far enough
|
||||
if (!isDragging && (timeSinceStart > LONG_PRESS_DURATION || distance > MOVE_THRESHOLD)) {
|
||||
isDragging = true;
|
||||
$mobileToggle.css('transition', 'none'); // Disable transitions while dragging
|
||||
$mobileToggle.addClass('dragging'); // Disable transitions while dragging
|
||||
}
|
||||
|
||||
if (isDragging) {
|
||||
@@ -1082,13 +1100,12 @@ function setupMobileToggle() {
|
||||
newX = Math.max(minX, Math.min(maxX, newX));
|
||||
newY = Math.max(minY, Math.min(maxY, newY));
|
||||
|
||||
// Apply position
|
||||
$mobileToggle.css({
|
||||
left: newX + 'px',
|
||||
top: newY + 'px',
|
||||
right: 'auto',
|
||||
bottom: 'auto'
|
||||
});
|
||||
// Store pending position and request animation frame for smooth update
|
||||
pendingX = newX;
|
||||
pendingY = newY;
|
||||
if (!rafId) {
|
||||
rafId = requestAnimationFrame(updateFabPosition);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1123,7 +1140,7 @@ function setupMobileToggle() {
|
||||
// Start dragging if held long enough OR moved far enough
|
||||
if (!isDragging && (timeSinceStart > LONG_PRESS_DURATION || distance > MOVE_THRESHOLD)) {
|
||||
isDragging = true;
|
||||
$mobileToggle.css('transition', 'none');
|
||||
$mobileToggle.addClass('dragging'); // Disable transitions while dragging
|
||||
}
|
||||
|
||||
if (isDragging) {
|
||||
@@ -1146,13 +1163,12 @@ function setupMobileToggle() {
|
||||
newX = Math.max(minX, Math.min(maxX, newX));
|
||||
newY = Math.max(minY, Math.min(maxY, newY));
|
||||
|
||||
// Apply position
|
||||
$mobileToggle.css({
|
||||
left: newX + 'px',
|
||||
top: newY + 'px',
|
||||
right: 'auto',
|
||||
bottom: 'auto'
|
||||
});
|
||||
// Store pending position and request animation frame for smooth update
|
||||
pendingX = newX;
|
||||
pendingY = newY;
|
||||
if (!rafId) {
|
||||
rafId = requestAnimationFrame(updateFabPosition);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1178,9 +1194,9 @@ function setupMobileToggle() {
|
||||
// Constrain to viewport bounds (now that position is saved)
|
||||
setTimeout(() => constrainFabToViewport(), 10);
|
||||
|
||||
// Re-enable transitions
|
||||
// Re-enable transitions with smooth animation
|
||||
setTimeout(() => {
|
||||
$mobileToggle.css('transition', '');
|
||||
$mobileToggle.removeClass('dragging');
|
||||
}, 50);
|
||||
|
||||
isDragging = false;
|
||||
@@ -1219,9 +1235,9 @@ function setupMobileToggle() {
|
||||
// Constrain to viewport bounds (now that position is saved)
|
||||
setTimeout(() => constrainFabToViewport(), 10);
|
||||
|
||||
// Re-enable transitions
|
||||
// Re-enable transitions with smooth animation
|
||||
setTimeout(() => {
|
||||
$mobileToggle.css('transition', '');
|
||||
$mobileToggle.removeClass('dragging');
|
||||
}, 50);
|
||||
|
||||
isDragging = false;
|
||||
|
||||
@@ -2942,12 +2942,19 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
|
||||
border: 2px solid var(--SmartThemeBorderColor);
|
||||
color: var(--rpg-text, #ecf0f1);
|
||||
font-size: 1.25rem;
|
||||
cursor: pointer;
|
||||
cursor: grab;
|
||||
z-index: 10002;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
||||
transition: opacity 0.3s ease, transform 0.2s ease;
|
||||
transition: opacity 0.3s ease, transform 0.2s ease, top 0.3s ease, left 0.3s ease, right 0.3s ease, bottom 0.3s ease;
|
||||
user-select: none; /* Prevent text selection while dragging */
|
||||
-webkit-user-select: none;
|
||||
will-change: top, left; /* Optimize for position changes */
|
||||
}
|
||||
|
||||
/* Disable transitions while actively dragging */
|
||||
.rpg-mobile-toggle.dragging {
|
||||
transition: none;
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
.rpg-mobile-toggle:hover {
|
||||
|
||||
Reference in New Issue
Block a user