mirror of
https://github.com/SwingTheVine/Wplace-BlueMarble.git
synced 2026-03-11 17:15:38 +00:00
Enhance overlay drag and file input handling
Improved the overlay drag logic for smoother, GPU-accelerated movement using requestAnimationFrame and transform. Updated file input elements to use stronger hiding techniques, preventing browser-native text from appearing during minimize/maximize. Updated version to 0.72.0 and added additional debug logging and minor UI/UX improvements.
This commit is contained in:
parent
da77f2c55a
commit
1d78a140dd
8 changed files with 158 additions and 61 deletions
2
dist/BlueMarble.user.css
vendored
2
dist/BlueMarble.user.css
vendored
|
|
@ -1 +1 @@
|
|||
#bm-l{position:fixed;background-color:#153063e6;color:#fff;padding:10px;border-radius:8px;z-index:9000;transition:all .3s ease;max-width:300px;width:auto}#bm-4,#bm-l hr,#bm-3,#bm-1{transition:opacity .2s ease,height .2s ease}div#bm-l{font-family:Roboto Mono,Courier New,Monaco,DejaVu Sans Mono,monospace,Arial;letter-spacing:.05em}#bm-g{margin-bottom:.5em;background:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="5" height="5"><circle cx="3" cy="3" r="1.5" fill="CornflowerBlue" /></svg>') repeat;cursor:grab;width:100%;height:1em}#bm-g.dragging{cursor:grabbing}#bm-7{margin-bottom:.5em}#bm-7[style*="text-align: center"]{display:flex;flex-direction:column;align-items:center;justify-content:center}#bm-l[style*="padding: 5px"]{width:auto!important;max-width:300px;min-width:200px}#bm-l img{display:inline-block;height:2.5em;margin-right:1ch;vertical-align:middle;transition:opacity .2s ease}#bm-7[style*="text-align: center"] img{display:block;margin:0 auto}#bm-g{transition:margin-bottom .2s ease}#bm-l h1{display:inline-block;font-size:x-large;font-weight:700;vertical-align:middle}#bm-3 input[type=checkbox]{vertical-align:middle;margin-right:.5ch}#bm-3 label{margin-right:.5ch}.bm-p{border:white 1px solid;height:1.5em;width:1.5em;margin-top:2px;text-align:center;line-height:1em;padding:0!important}#bm-c{vertical-align:middle}#bm-c svg{width:50%;margin:0 auto;fill:#111}div:has(>#bm-button-teleport){display:flex;gap:.5ch}#bm-button-favorite svg,#bm-button-template svg{height:1em;margin:2px auto 0;text-align:center;line-height:1em;vertical-align:bottom}#bm-8 input[type=number]{appearance:auto;-moz-appearance:textfield;width:5.5ch;margin-left:1ch;background-color:#0003;padding:0 .5ch;font-size:small}#bm-8 input[type=number]::-webkit-outer-spin-button,#bm-8 input[type=number]::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}#bm-0{display:flex;flex-direction:row;flex-wrap:wrap;align-content:center;justify-content:center;align-items:center;gap:1ch}div:has(>#bm-2)>button{width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}#bm-a{font-size:small;background-color:#0003;padding:0 .5ch;height:3.75em;width:100%}#bm-1{display:flex;justify-content:space-between}#bm-l small{font-size:x-small;color:#d3d3d3}#bm-4,#bm-3,#bm-8,#bm-0,div:has(>#bm-2),#bm-a{margin-top:.5em}#bm-l button{background-color:#144eb9;border-radius:1em;padding:0 .75ch}#bm-l button:hover,#bm-l button:focus-visible{background-color:#1061e5}#bm-l button:active,#bm-l button:disabled{background-color:#2e97ff}#bm-l button:disabled{text-decoration:line-through}
|
||||
#bm-l{position:fixed;background-color:#153063e6;color:#fff;padding:10px;border-radius:8px;z-index:9000;transition:all .3s ease;max-width:300px;width:auto;will-change:transform;backface-visibility:hidden;-webkit-backface-visibility:hidden;transform-style:preserve-3d;-webkit-transform-style:preserve-3d}#bm-4,#bm-l hr,#bm-3,#bm-1{transition:opacity .2s ease,height .2s ease}div#bm-l{font-family:Roboto Mono,Courier New,Monaco,DejaVu Sans Mono,monospace,Arial;letter-spacing:.05em}#bm-g{margin-bottom:.5em;background:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="5" height="5"><circle cx="3" cy="3" r="1.5" fill="CornflowerBlue" /></svg>') repeat;cursor:grab;width:100%;height:1em}#bm-g.dragging{cursor:grabbing}#bm-l:has(#bm-g.dragging){pointer-events:none;user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none}#bm-g.dragging{pointer-events:auto}#bm-7{margin-bottom:.5em}#bm-7[style*="text-align: center"]{display:flex;flex-direction:column;align-items:center;justify-content:center}#bm-l[style*="padding: 5px"]{width:auto!important;max-width:300px;min-width:200px}#bm-l img{display:inline-block;height:2.5em;margin-right:1ch;vertical-align:middle;transition:opacity .2s ease}#bm-7[style*="text-align: center"] img{display:block;margin:0 auto}#bm-g{transition:margin-bottom .2s ease}#bm-l h1{display:inline-block;font-size:x-large;font-weight:700;vertical-align:middle}#bm-3 input[type=checkbox]{vertical-align:middle;margin-right:.5ch}#bm-3 label{margin-right:.5ch}.bm-p{border:white 1px solid;height:1.5em;width:1.5em;margin-top:2px;text-align:center;line-height:1em;padding:0!important}#bm-c{vertical-align:middle}#bm-c svg{width:50%;margin:0 auto;fill:#111}div:has(>#bm-button-teleport){display:flex;gap:.5ch}#bm-button-favorite svg,#bm-button-template svg{height:1em;margin:2px auto 0;text-align:center;line-height:1em;vertical-align:bottom}#bm-8 input[type=number]{appearance:auto;-moz-appearance:textfield;width:5.5ch;margin-left:1ch;background-color:#0003;padding:0 .5ch;font-size:small}#bm-8 input[type=number]::-webkit-outer-spin-button,#bm-8 input[type=number]::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}#bm-0{display:flex;flex-direction:row;flex-wrap:wrap;align-content:center;justify-content:center;align-items:center;gap:1ch}div:has(>#bm-2)>button{width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}#bm-2,input[type=file][id*=template]{display:none!important;visibility:hidden!important;position:absolute!important;left:-9999px!important;top:-9999px!important;width:0!important;height:0!important;opacity:0!important;z-index:-9999!important;pointer-events:none!important}#bm-a{font-size:small;background-color:#0003;padding:0 .5ch;height:3.75em;width:100%}#bm-1{display:flex;justify-content:space-between}#bm-l small{font-size:x-small;color:#d3d3d3}#bm-4,#bm-3,#bm-8,#bm-0,div:has(>#bm-2),#bm-a{margin-top:.5em}#bm-l button{background-color:#144eb9;border-radius:1em;padding:0 .75ch}#bm-l button:hover,#bm-l button:focus-visible{background-color:#1061e5}#bm-l button:active,#bm-l button:disabled{background-color:#2e97ff}#bm-l button:disabled{text-decoration:line-through}
|
||||
|
|
|
|||
6
dist/BlueMarble.user.js
vendored
6
dist/BlueMarble.user.js
vendored
File diff suppressed because one or more lines are too long
4
package-lock.json
generated
4
package-lock.json
generated
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "wplace-bluemarble",
|
||||
"version": "0.69.1",
|
||||
"version": "0.72.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "wplace-bluemarble",
|
||||
"version": "0.69.1",
|
||||
"version": "0.72.0",
|
||||
"devDependencies": {
|
||||
"esbuild": "^0.25.0",
|
||||
"terser": "^5.43.1"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "wplace-bluemarble",
|
||||
"version": "0.70.0",
|
||||
"version": "0.72.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "node build/build.js",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// ==UserScript==
|
||||
// @name Blue Marble
|
||||
// @namespace https://github.com/SwingTheVine/
|
||||
// @version 0.70.0
|
||||
// @version 0.72.0
|
||||
// @description A userscript to automate and/or enhance the user experience on Wplace.live. Make sure to comply with the site's Terms of Service, and rules! This script is not affiliated with Wplace.live in any way, use at your own risk. This script is not affiliated with TamperMonkey. The author of this userscript is not responsible for any damages, issues, loss of data, or punishment that may occur as a result of using this script. This script is provided "as is" under the MPL-2.0 license. The "Blue Marble" icon is licensed under CC0 1.0 Universal (CC0 1.0) Public Domain Dedication. The image is owned by NASA.
|
||||
// @author SwingTheVine
|
||||
// @license MPL-2.0
|
||||
|
|
|
|||
160
src/Overlay.js
160
src/Overlay.js
|
|
@ -430,9 +430,9 @@ export default class Overlay {
|
|||
return this;
|
||||
}
|
||||
|
||||
/** Adds a file input to the overlay. This includes a container and a button.
|
||||
/** Adds a file input to the overlay with enhanced visibility controls.
|
||||
* This input element will have properties shared between all file input elements in the overlay.
|
||||
* You can override the shared properties by using a callback.
|
||||
* Uses multiple hiding methods to prevent browser native text from appearing during minimize/maximize.
|
||||
* @param {Object.<string, any>} [additionalProperties={}] - The DOM properties of the file input that are NOT shared between all overlay file input elements. These should be camelCase.
|
||||
* @param {function(Overlay, HTMLDivElement, HTMLInputElement, HTMLButtonElement):void} [callback=()=>{}] - Additional JS modification to the file input.
|
||||
* @returns {Overlay} Overlay class instance (this)
|
||||
|
|
@ -451,7 +451,10 @@ export default class Overlay {
|
|||
*/
|
||||
addInputFile(additionalProperties = {}, callback = () => {}) {
|
||||
|
||||
const properties = {'type': 'file', 'style': 'display: none;'}; // Shared file input DOM properties
|
||||
const properties = {
|
||||
'type': 'file',
|
||||
'style': 'display: none !important; visibility: hidden !important; position: absolute !important; left: -9999px !important; width: 0 !important; height: 0 !important; opacity: 0 !important;'
|
||||
}; // Complete file input hiding to prevent native browser text interference
|
||||
const text = additionalProperties['textContent'] ?? ''; // Retrieves the text content
|
||||
|
||||
delete additionalProperties['textContent']; // Deletes the text content before applying the additional properties to the file input
|
||||
|
|
@ -463,11 +466,15 @@ export default class Overlay {
|
|||
this.buildElement(); // Signifies that we are done adding children to the button
|
||||
this.buildElement(); // Signifies that we are done adding children to the container
|
||||
|
||||
// Prevent file input from being accessible or visible in any way
|
||||
input.setAttribute('tabindex', '-1');
|
||||
input.setAttribute('aria-hidden', 'true');
|
||||
|
||||
button.addEventListener('click', () => {
|
||||
input.click(); // Clicks the file input
|
||||
});
|
||||
|
||||
// Changes the button text content (and trims the file name)
|
||||
// Update button text when file is selected
|
||||
input.addEventListener('change', () => {
|
||||
button.style.maxWidth = `${button.offsetWidth}px`;
|
||||
if (input.files.length > 0) {
|
||||
|
|
@ -533,14 +540,20 @@ export default class Overlay {
|
|||
}
|
||||
}
|
||||
|
||||
/** Handles dragging of the overlay.
|
||||
/** Handles dragging of the overlay with performance optimizations.
|
||||
* Uses requestAnimationFrame for smooth animations and GPU-accelerated transforms.
|
||||
* @param {string} moveMe - The ID of the element to be moved
|
||||
* @param {string} iMoveThings - The ID of the element to be moved
|
||||
* @param {string} iMoveThings - The ID of the drag handle element
|
||||
* @since 0.8.2
|
||||
*/
|
||||
handleDrag(moveMe, iMoveThings) {
|
||||
let isDragging = false;
|
||||
let offsetX, offsetY = 0;
|
||||
let animationFrame = null;
|
||||
let currentX = 0;
|
||||
let currentY = 0;
|
||||
let targetX = 0;
|
||||
let targetY = 0;
|
||||
|
||||
// Retrieves the elements (allows either '#id' or 'id' to be passed in)
|
||||
moveMe = document.querySelector(moveMe?.[0] == '#' ? moveMe : '#' + moveMe);
|
||||
|
|
@ -552,67 +565,110 @@ export default class Overlay {
|
|||
return; // Kills itself
|
||||
}
|
||||
|
||||
// What to do when the mouse is pressed down on the element that moves things
|
||||
iMoveThings.addEventListener('mousedown', function(event) {
|
||||
// Smooth animation loop using requestAnimationFrame for optimal performance
|
||||
const updatePosition = () => {
|
||||
if (isDragging) {
|
||||
// Only update DOM if position changed significantly (reduce repaints)
|
||||
const deltaX = Math.abs(currentX - targetX);
|
||||
const deltaY = Math.abs(currentY - targetY);
|
||||
|
||||
if (deltaX > 0.5 || deltaY > 0.5) {
|
||||
currentX = targetX;
|
||||
currentY = targetY;
|
||||
|
||||
// Use CSS transform for GPU acceleration instead of left/top
|
||||
moveMe.style.transform = `translate(${currentX}px, ${currentY}px)`;
|
||||
moveMe.style.left = '0px';
|
||||
moveMe.style.top = '0px';
|
||||
moveMe.style.right = '';
|
||||
}
|
||||
|
||||
animationFrame = requestAnimationFrame(updatePosition);
|
||||
}
|
||||
};
|
||||
|
||||
// Cache initial position to avoid expensive getBoundingClientRect calls during drag
|
||||
let initialRect = null;
|
||||
|
||||
const startDrag = (clientX, clientY) => {
|
||||
isDragging = true;
|
||||
offsetX = event.clientX - moveMe.getBoundingClientRect().left;
|
||||
offsetY = event.clientY - moveMe.getBoundingClientRect().top;
|
||||
document.body.style.userSelect = 'none'; // Prevents text selection while dragging
|
||||
iMoveThings.classList.add('dragging'); // Adds a class to indicate a dragging state
|
||||
initialRect = moveMe.getBoundingClientRect();
|
||||
offsetX = clientX - initialRect.left;
|
||||
offsetY = clientY - initialRect.top;
|
||||
|
||||
// Get current position from transform or use element position
|
||||
const computedStyle = window.getComputedStyle(moveMe);
|
||||
const transform = computedStyle.transform;
|
||||
|
||||
if (transform && transform !== 'none') {
|
||||
const matrix = new DOMMatrix(transform);
|
||||
currentX = matrix.m41;
|
||||
currentY = matrix.m42;
|
||||
} else {
|
||||
currentX = initialRect.left;
|
||||
currentY = initialRect.top;
|
||||
}
|
||||
|
||||
targetX = currentX;
|
||||
targetY = currentY;
|
||||
|
||||
document.body.style.userSelect = 'none';
|
||||
iMoveThings.classList.add('dragging');
|
||||
|
||||
// Start animation loop
|
||||
if (animationFrame) {
|
||||
cancelAnimationFrame(animationFrame);
|
||||
}
|
||||
updatePosition();
|
||||
};
|
||||
|
||||
const endDrag = () => {
|
||||
isDragging = false;
|
||||
if (animationFrame) {
|
||||
cancelAnimationFrame(animationFrame);
|
||||
animationFrame = null;
|
||||
}
|
||||
document.body.style.userSelect = '';
|
||||
iMoveThings.classList.remove('dragging');
|
||||
};
|
||||
|
||||
// Mouse down - start dragging
|
||||
iMoveThings.addEventListener('mousedown', function(event) {
|
||||
event.preventDefault();
|
||||
startDrag(event.clientX, event.clientY);
|
||||
});
|
||||
|
||||
// What to do when the touch starts on the element that moves things
|
||||
// Touch start - start dragging
|
||||
iMoveThings.addEventListener('touchstart', function(event) {
|
||||
isDragging = true;
|
||||
const touch = event?.touches?.[0];
|
||||
if (!touch) {return;}
|
||||
offsetX = touch.clientX - moveMe.getBoundingClientRect().left; // Distance between the left edge of the overlay, and the cursor
|
||||
offsetY = touch.clientY - moveMe.getBoundingClientRect().top; // Distance between the top edge of the overlay, and the cursor
|
||||
document.body.style.userSelect = 'none'; // Prevents text selection while dragging
|
||||
iMoveThings.classList.add('dragging'); // Adds a class to indicate a dragging state
|
||||
}, { passive: false }); // Prevents scrolling from being captured
|
||||
startDrag(touch.clientX, touch.clientY);
|
||||
event.preventDefault();
|
||||
}, { passive: false });
|
||||
|
||||
// What to do when the mouse is moved while dragging
|
||||
// Mouse move - update target position
|
||||
document.addEventListener('mousemove', function(event) {
|
||||
if (isDragging) {
|
||||
moveMe.style.left = (event.clientX - offsetX) + 'px'; // Binds the overlay to the left side of the screen, and sets it's position to the cursor
|
||||
moveMe.style.top = (event.clientY - offsetY) + 'px'; // Binds the overlay to the top of the screen, and sets it's position to the cursor
|
||||
moveMe.style.right = ''; // Destroys the right property to unbind the overlay from the right side of the screen
|
||||
if (isDragging && initialRect) {
|
||||
targetX = event.clientX - offsetX;
|
||||
targetY = event.clientY - offsetY;
|
||||
}
|
||||
});
|
||||
}, { passive: true });
|
||||
|
||||
// What to do when the touch moves while dragging
|
||||
// Touch move - update target position
|
||||
document.addEventListener('touchmove', function(event) {
|
||||
if (isDragging) {
|
||||
if (isDragging && initialRect) {
|
||||
const touch = event?.touches?.[0];
|
||||
if (!touch) {return;}
|
||||
moveMe.style.left = (touch.clientX - offsetX) + 'px'; // Binds the overlay to the left side of the screen, and sets it's position to the cursor
|
||||
moveMe.style.top = (touch.clientY - offsetY) + 'px'; // Binds the overlay to the top of the screen, and sets it's position to the cursor
|
||||
moveMe.style.right = ''; // Destroys the right property to unbind the overlay from the right side of the screen
|
||||
event.preventDefault(); // prevent scrolling while dragging
|
||||
targetX = touch.clientX - offsetX;
|
||||
targetY = touch.clientY - offsetY;
|
||||
event.preventDefault();
|
||||
}
|
||||
}, { passive: false }); // Prevents scrolling from being captured
|
||||
}, { passive: false });
|
||||
|
||||
// What to do when the mouse is released
|
||||
document.addEventListener('mouseup', function() {
|
||||
isDragging = false;
|
||||
document.body.style.userSelect = ''; // Restores text selection capability after dragging
|
||||
iMoveThings.classList.remove('dragging'); // Removes the dragging class
|
||||
});
|
||||
|
||||
// What to do when the touch ends
|
||||
document.addEventListener('touchend', function() {
|
||||
isDragging = false;
|
||||
document.body.style.userSelect = ''; // Restores text selection capability after dragging
|
||||
iMoveThings.classList.remove('dragging'); // Removes the dragging class
|
||||
});
|
||||
|
||||
// What to do when the touch is cancelled
|
||||
document.addEventListener('touchcancel', function() {
|
||||
isDragging = false;
|
||||
document.body.style.userSelect = ''; // Restores text selection capability after dragging
|
||||
iMoveThings.classList.remove('dragging'); // Removes the dragging class
|
||||
});
|
||||
// End drag events
|
||||
document.addEventListener('mouseup', endDrag);
|
||||
document.addEventListener('touchend', endDrag);
|
||||
document.addEventListener('touchcancel', endDrag);
|
||||
}
|
||||
|
||||
/** Handles status display.
|
||||
|
|
|
|||
|
|
@ -2,6 +2,12 @@
|
|||
* @since 0.0.0
|
||||
*
|
||||
* VERSION HISTORY:
|
||||
* 0.72.0 - Performance and UI improvements
|
||||
* - Smooth drag performance with requestAnimationFrame and GPU acceleration
|
||||
* - Fixed file upload button text bug during UI state changes
|
||||
* - Added minimize/maximize animations with CSS transitions
|
||||
* - Hardware-accelerated transformations and smart DOM updates
|
||||
*
|
||||
* 0.71.0 - Added minimize/maximize functionality and pixel counting system
|
||||
* Features added:
|
||||
* - Interactive minimize/maximize overlay with click-to-toggle functionality
|
||||
|
|
|
|||
|
|
@ -11,12 +11,18 @@
|
|||
transition: all 0.3s ease;
|
||||
max-width: 300px;
|
||||
width: auto;
|
||||
/* Performance optimizations for smooth dragging */
|
||||
will-change: transform;
|
||||
backface-visibility: hidden;
|
||||
-webkit-backface-visibility: hidden;
|
||||
transform-style: preserve-3d;
|
||||
-webkit-transform-style: preserve-3d;
|
||||
}
|
||||
|
||||
/* Smooth transitions for minimize/maximize functionality */
|
||||
#bm-contain-userinfo,
|
||||
#bm-overlay hr,
|
||||
#bm-contain-automation,
|
||||
#bm-contain-automation,
|
||||
#bm-contain-buttons-action {
|
||||
transition: opacity 0.2s ease, height 0.2s ease;
|
||||
}
|
||||
|
|
@ -49,6 +55,20 @@ div#bm-overlay {
|
|||
cursor: grabbing;
|
||||
}
|
||||
|
||||
/* Disable interactions during drag for better performance */
|
||||
#bm-overlay:has(#bm-bar-drag.dragging) {
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
}
|
||||
|
||||
/* Keep drag bar interactive when dragging */
|
||||
#bm-bar-drag.dragging {
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
/* The container for the overlay header */
|
||||
#bm-contain-header {
|
||||
margin-bottom: 0.5em;
|
||||
|
|
@ -188,6 +208,21 @@ div:has(> #bm-input-file-template) > button {
|
|||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
/* Force complete invisibility of file input to prevent native browser text */
|
||||
#bm-input-file-template,
|
||||
input[type="file"][id*="template"] {
|
||||
display: none !important;
|
||||
visibility: hidden !important;
|
||||
position: absolute !important;
|
||||
left: -9999px !important;
|
||||
top: -9999px !important;
|
||||
width: 0 !important;
|
||||
height: 0 !important;
|
||||
opacity: 0 !important;
|
||||
z-index: -9999 !important;
|
||||
pointer-events: none !important;
|
||||
}
|
||||
|
||||
/* Output status area */
|
||||
#bm-output-status {
|
||||
font-size: small;
|
||||
|
|
|
|||
Loading…
Reference in a new issue