您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Zoom hotkeys and a location manager for fast travel!
// ==UserScript== // @name Wplace Zoom Plus & Location Manager // @namespace http://tampermonkey.net/ // @version 2.1 // @description Zoom hotkeys and a location manager for fast travel! // @author Kur0 // @match https://wplace.live/* // @grant unsafeWindow // @grant GM_addStyle // @grant GM_setValue // @grant GM_getValue // @run-at document-start // @license MIT // ==/UserScript== (function() { 'use strict'; let hooked = false; Object.defineProperty(Object.prototype, 'transform', { configurable: true, enumerable: false, set: function(value) { // check if it has setZoom and getCanvas if (!hooked && value && typeof value.setZoom === 'function' && typeof this.getCanvas === 'function') { hooked = true; const mapInstance = this; console.log('%cWplace Zoom Plus: Map instance captured!', 'color: limegreen; font-weight: bold;'); // Expose globally for console debugging stuff unsafeWindow.myMap = mapInstance; console.log("Hotkeys are active: [-] for zoom out, [+] for zoom in, [ ] ] for 1:1 zoom."); // Cleanup: remove the hook to prevent side effects delete Object.prototype.transform; // Set the property on the actual object now that the hook is gone this.transform = value; initializeUI(mapInstance); return; } // For all other objects, set the property normally Object.defineProperty(this, 'transform', { value: value, writable: true, configurable: true, enumerable: true, }); } }); function initializeUI(map) { const STORAGE_KEY = 'wplace_locations'; let locations = GM_getValue(STORAGE_KEY, []); const SETTINGS_KEY = 'wplm_settings'; let settings = GM_getValue(SETTINGS_KEY, {}); console.log(`wplm settings:`, settings) const overlay = document.createElement('div'); overlay.id = 'wplm-overlay'; const dragBar = document.createElement('div'); dragBar.id = 'wplm-bar-drag'; const headerDiv = document.createElement("div"); headerDiv.className = "header-div"; const headerTxt = document.createElement('h1'); headerTxt.textContent = 'Locations'; const animateTxt = document.createElement('p'); animateTxt.textContent = "Animate?"; const animateBtn = document.createElement("input") animateBtn.type = 'checkbox'; if ("animate" in settings) { animateBtn.checked = settings.animate; } const locationsList = document.createElement('div'); locationsList.id = 'wplm-locations-list'; const buttonsContainer = document.createElement('div'); buttonsContainer.id = 'wplm-buttons-container'; const addButton = document.createElement('button'); addButton.textContent = 'Save Current Location'; const clearButton = document.createElement('button'); clearButton.textContent = 'Clear All'; clearButton.style.backgroundColor = '#b91414'; buttonsContainer.appendChild(addButton); buttonsContainer.appendChild(clearButton); overlay.appendChild(dragBar); overlay.appendChild(headerDiv); headerDiv.appendChild(headerTxt); headerDiv.appendChild(animateTxt); headerDiv.appendChild(animateBtn); overlay.appendChild(document.createElement('hr')); overlay.appendChild(locationsList); overlay.appendChild(document.createElement('hr')); overlay.appendChild(buttonsContainer); document.body.appendChild(overlay); animateBtn.onchange = () => { settings.animate = animateBtn.checked; GM_setValue(SETTINGS_KEY, settings); } const saveLocations = () => { GM_setValue(STORAGE_KEY, locations); }; const renderLocations = () => { locationsList.innerHTML = ''; if (locations.length === 0) { locationsList.innerHTML = '<small>No locations saved.</small>'; } locations.forEach((loc, index) => { const item = document.createElement('div'); item.className = 'wplm-location-item'; const label = document.createElement('span'); label.textContent = loc.label; label.className = 'wplm-location-label'; label.onclick = () => { const newLabel = prompt('Enter a new label for this location:', loc.label); if (newLabel && newLabel.trim() !== '') { locations[index].label = newLabel.trim(); saveLocations(); renderLocations(); } }; const teleportButton = document.createElement('button'); teleportButton.textContent = 'Go'; teleportButton.onclick = () => { map.flyTo({ center: loc.center, zoom: loc.zoom, animate: animateBtn.checked}); }; const deleteButton = document.createElement('button'); deleteButton.textContent = 'X'; deleteButton.className = 'wplm-delete-btn'; deleteButton.onclick = () => { if (confirm(`Are you sure you want to delete "${loc.label}"?`)) { locations.splice(index, 1); saveLocations(); renderLocations(); } }; item.appendChild(label); item.appendChild(teleportButton); item.appendChild(deleteButton); locationsList.appendChild(item); }); }; addButton.onclick = () => { const label = prompt('Enter a label for this location:'); if (label && label.trim() !== '') { locations.push({ label: label.trim(), center: map.getCenter(), zoom: map.getZoom() }); saveLocations(); renderLocations(); } }; clearButton.onclick = () => { if (confirm('Are you sure you want to delete ALL saved locations? This cannot be undone.')) { locations = []; saveLocations(); renderLocations(); } }; let isDragging = false; let startX, startY, initialLeft, initialTop; const move = (e) => { if (!isDragging) return; e.preventDefault(); const clientX = e.clientX || e.touches[0].clientX; const clientY = e.clientY || e.touches[0].clientY; const dx = clientX - startX; const dy = clientY - startY; overlay.style.left = `${initialLeft + dx}px`; overlay.style.top = `${initialTop + dy}px`; overlay.style.right = 'auto'; overlay.style.bottom = 'auto'; }; const startDrag = (e) => { isDragging = true; dragBar.classList.add('dragging'); startX = e.clientX || e.touches[0].clientX; startY = e.clientY || e.touches[0].clientY; const rect = overlay.getBoundingClientRect(); initialLeft = rect.left; initialTop = rect.top; document.addEventListener('mousemove', move); document.addEventListener('touchmove', move); document.addEventListener('mouseup', endDrag); document.addEventListener('touchend', endDrag); }; const endDrag = () => { isDragging = false; dragBar.classList.remove('dragging'); document.removeEventListener('mousemove', move); document.removeEventListener('touchmove', move); document.removeEventListener('mouseup', endDrag); document.removeEventListener('touchend', endDrag); }; dragBar.addEventListener('mousedown', startDrag); dragBar.addEventListener('touchstart', startDrag); renderLocations(); } const ONE_TO_ONE_ZOOM = 11.965784285; // log2(4000) document.addEventListener('keydown', (event) => { if (!unsafeWindow.myMap) { return; } // Ignore input fields const activeElement = document.activeElement; if (activeElement && (activeElement.tagName === 'INPUT' || activeElement.tagName === 'TEXTAREA' || activeElement.isContentEditable)) { return; } const getZoom = unsafeWindow.myMap.getZoom.bind(unsafeWindow.myMap); const setZoom = unsafeWindow.myMap.setZoom.bind(unsafeWindow.myMap); const zoomAmount = 0.05; switch (event.key) { case '-': event.preventDefault(); setZoom(getZoom() - zoomAmount); break; case '+': case '=': event.preventDefault(); setZoom(getZoom() + zoomAmount); break; case ']': event.preventDefault(); setZoom(ONE_TO_ONE_ZOOM); break; } }); GM_addStyle(` #wplm-overlay { position: fixed; bottom: 20px; right: 20px; background-color: rgba(21, 48, 99, 0.9); color: white; padding: 10px; border-radius: 8px; z-index: 9999; width: 280px; font-family: 'Roboto Mono', 'Courier New', monospace; letter-spacing: 0.05em; box-shadow: 0 4px 12px rgba(0,0,0,0.4); will-change: transform; backface-visibility: hidden; } #wplm-bar-drag { margin-bottom: 0.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; border-radius: 4px; } #wplm-bar-drag.dragging { cursor: grabbing; } #wplm-overlay h1 { font-size: large; font-weight: bold; text-align: left; padding-left: 2px; flex-grow: 1; } #wplm-overlay .header-div { margin-bottom: 0.5em; display: flex; justify-content: space-around; align-items: center; } #wplm-overlay .header-div p { font-size: 12px; margin-right: 10px; } #wplm-overlay hr { border-color: rgba(255, 255, 255, 0.2); margin: 10px 0; } #wplm-locations-list { max-height: 200px; overflow-y: auto; display: flex; flex-direction: column; gap: 8px; } #wplm-locations-list small { text-align: center; color: lightgray; padding: 10px 0; } .wplm-location-item { display: flex; align-items: center; gap: 8px; } .wplm-location-label { flex-grow: 1; cursor: pointer; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .wplm-location-label:hover { text-decoration: underline; } #wplm-buttons-container { margin-top: 10px; display: flex; justify-content: space-between; gap: 10px; } #wplm-overlay button { background-color: #144eb9; border: none; color: white; border-radius: 1em; padding: 5px 10px; cursor: pointer; font-family: inherit; } #wplm-overlay button:hover { background-color: #1061e5; } .wplm-delete-btn { background-color: #a02c2c !important; flex-grow: 0 !important; padding: 5px 12px; } .wplm-delete-btn:hover { background-color: #c0392b !important; } `); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址