您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
Ferramenta completa para sobrepor imagens no wplace.live com controles avançados
当前为
// ==UserScript== // @name wplace.live Guia Visual Avançado // @namespace ViolentMonkey // @version 3.0 // @description Ferramenta completa para sobrepor imagens no wplace.live com controles avançados // @author Você // @match https://wplace.live/* // @license MIT // // @grant GM_addStyle // @grant GM_getValue // @grant GM_setValue // @require https://code.jquery.com/jquery-3.6.0.min.js // ==/UserScript== (function() { 'use strict'; // ============================================= // CONFIGURAÇÕES E CONSTANTES // ============================================= const DIMENSOES = { PAINEL: { NORMAL: { width: 320, height: 'auto' }, MINIMIZADO: { width: 150, height: 40 } }, MARGENS: { PADRAO: 20, MINIMA: 10 }, OPACIDADE: { MIN: 0.1, MAX: 1, STEP: 0.1 }, TAMANHO: { MIN: 20, MAX: 200, STEP: 10 } }; const config = { urlImagem: '', opacidade: 0.6, deslocamento: { x: 0, y: 0 }, tamanho: 100, janelaMinimizada: false, posicaoJanela: { x: null, y: null } }; // ============================================= // ELEMENTOS DA INTERFACE // ============================================= const elementos = { guia: null, controles: null, urlInput: null, carregarBtn: null, status: null, opacidade: null, tamanhoValor: null, diminuirBtn: null, aumentarBtn: null, tamanhoSlider: null, minimizarBtn: null, dragHandle: null }; // ============================================= // ESTILOS CSS // ============================================= GM_addStyle(` #wplace-guia { position: absolute; pointer-events: none; z-index: 9998; opacity: ${config.opacidade}; image-rendering: pixelated; border: 2px dashed rgba(255,255,255,0.3); display: ${config.urlImagem ? 'block' : 'none'}; transform-origin: top left; will-change: transform; } #wplace-controles { position: fixed; z-index: 9999; background: rgba(0,0,0,0.9); color: white; padding: 15px; padding-top: 35px; border-radius: 8px; font-family: Arial, sans-serif; box-shadow: 0 0 15px rgba(0,0,0,0.7); user-select: none; will-change: transform; transition: all 0.2s ease; width: ${DIMENSOES.PAINEL.NORMAL.width}px; } #wplace-controles.minimizado { height: ${DIMENSOES.PAINEL.MINIMIZADO.height}px; width: ${DIMENSOES.PAINEL.MINIMIZADO.width}px; padding-top: 15px; overflow: hidden; } #wplace-controles.minimizado .conteudo-controles { display: none; } #wplace-drag-handle { position: absolute; top: 0; left: 0; right: 0; height: 30px; cursor: move; touch-action: none; } #wplace-minimizar-btn { position: absolute; top: 35px; right: 5px; background: transparent; border: none; color: white; font-size: 16px; cursor: pointer; padding: 5px; z-index: 10000; } #wplace-controles.minimizado #wplace-minimizar-btn { top: 5px; position: static; margin: 0 auto; display: block; } .grupo-controle { margin-bottom: 12px; } #wplace-url-input { width: 100%; padding: 8px; margin-bottom: 10px; background: rgba(255,255,255,0.1); border: 1px solid #444; color: white; border-radius: 4px; } #wplace-carregar-btn { width: 100%; padding: 8px; background: #4CAF50; border: none; color: white; border-radius: 4px; cursor: pointer; margin-bottom: 10px; transition: background 0.2s; } #wplace-carregar-btn:hover { background: #45a049; } #wplace-carregar-btn.carregando { background: #367c39; } #wplace-status { margin-top: 10px; padding: 8px; background: rgba(255,255,255,0.1); border-radius: 4px; font-size: 13px; } .controle-tamanho { display: flex; align-items: center; gap: 10px; } .botao-tamanho { width: 30px; height: 30px; background: #555; border: none; color: white; border-radius: 4px; cursor: pointer; font-size: 16px; display: flex; align-items: center; justify-content: center; transition: background 0.2s; } .botao-tamanho:hover { background: #666; } #wplace-tamanho-valor { min-width: 40px; text-align: center; } #wplace-controles.dragging { cursor: grabbing; transition: none; } .loader { display: inline-block; width: 12px; height: 12px; border: 2px solid rgba(255,255,255,0.3); border-radius: 50%; border-top-color: white; animation: spin 1s ease-in-out infinite; margin-right: 5px; } @keyframes spin { to { transform: rotate(360deg); } } `); // ============================================= // FUNÇÕES PRINCIPAIS // ============================================= /** * Cria todos os elementos da interface */ function criarInterface() { elementos.controles = $(` <div id="wplace-controles" class="${config.janelaMinimizada ? 'minimizado' : ''}"> <div id="wplace-drag-handle"></div> <button id="wplace-minimizar-btn">${config.janelaMinimizada ? '⬛' : '➖'}</button> <div class="conteudo-controles"> <div class="grupo-controle"> <label>URL da Imagem:</label> <input type="text" id="wplace-url-input" placeholder="https://exemplo.com/imagem.png" value="${config.urlImagem}"> <button id="wplace-carregar-btn">Carregar Imagem</button> <div id="wplace-status">${config.urlImagem ? 'Imagem pronta' : 'Nenhuma imagem carregada'}</div> </div> <div class="grupo-controle"> <label>Opacidade: ${config.opacidade.toFixed(1)}</label> <input type="range" id="wplace-opacidade" min="${DIMENSOES.OPACIDADE.MIN}" max="${DIMENSOES.OPACIDADE.MAX}" step="${DIMENSOES.OPACIDADE.STEP}" value="${config.opacidade}"> </div> <div class="grupo-controle"> <label>Tamanho da Imagem:</label> <div class="controle-tamanho"> <button class="botao-tamanho" id="wplace-diminuir">-</button> <span id="wplace-tamanho-valor">${config.tamanho}%</span> <button class="botao-tamanho" id="wplace-aumentar">+</button> <input type="range" id="wplace-tamanho-slider" min="${DIMENSOES.TAMANHO.MIN}" max="${DIMENSOES.TAMANHO.MAX}" value="${config.tamanho}" style="flex-grow:1;"> </div> </div> <div class="grupo-controle"> <label>Posição Atual:</label> <div>X: ${config.deslocamento.x}px, Y: ${config.deslocamento.y}px</div> </div> </div> </div> `); elementos.guia = $(`<img id="wplace-guia" src="${config.urlImagem}" alt="Guia visual">`); $('body').append(elementos.guia, elementos.controles); // Cache de elementos elementos.urlInput = $('#wplace-url-input'); elementos.carregarBtn = $('#wplace-carregar-btn'); elementos.status = $('#wplace-status'); elementos.opacidade = $('#wplace-opacidade'); elementos.tamanhoValor = $('#wplace-tamanho-valor'); elementos.diminuirBtn = $('#wplace-diminuir'); elementos.aumentarBtn = $('#wplace-aumentar'); elementos.tamanhoSlider = $('#wplace-tamanho-slider'); elementos.minimizarBtn = $('#wplace-minimizar-btn'); elementos.dragHandle = $('#wplace-drag-handle'); } /** * Ajusta a posição da janela para ficar visível * @param {HTMLElement} element - Elemento do painel de controles */ function ajustarPosicaoJanela(element) { if (config.janelaMinimizada) { // Posiciona no canto inferior direito quando minimizado element.style.left = 'auto'; element.style.right = DIMENSOES.MARGENS.PADRAO + 'px'; element.style.top = 'auto'; element.style.bottom = DIMENSOES.MARGENS.PADRAO + 'px'; return; } const width = DIMENSOES.PAINEL.NORMAL.width; const height = element.offsetHeight; // Verifica se a posição está fora da tela if (config.posicaoJanela.x === null || config.posicaoJanela.x > window.innerWidth - DIMENSOES.MARGENS.MINIMA || config.posicaoJanela.x < -width + DIMENSOES.MARGENS.MINIMA) { config.posicaoJanela.x = window.innerWidth - width - DIMENSOES.MARGENS.PADRAO; } if (config.posicaoJanela.y === null || config.posicaoJanela.y > window.innerHeight - DIMENSOES.MARGENS.MINIMA || config.posicaoJanela.y < -height + DIMENSOES.MARGENS.MINIMA) { config.posicaoJanela.y = window.innerHeight - height - DIMENSOES.MARGENS.PADRAO; } // Aplica a posição ajustada element.style.left = `${Math.max(DIMENSOES.MARGENS.MINIMA, Math.min(config.posicaoJanela.x, window.innerWidth - width - DIMENSOES.MARGENS.MINIMA))}px`; element.style.top = `${Math.max(DIMENSOES.MARGENS.MINIMA, Math.min(config.posicaoJanela.y, window.innerHeight - height - DIMENSOES.MARGENS.MINIMA))}px`; element.style.right = 'auto'; element.style.bottom = 'auto'; } /** * Torna a janela arrastável * @param {HTMLElement} element - Elemento do painel de controles */ function makeDraggable(element) { let isDragging = false; let startX, startY, startLeft, startTop; const dragMouseDown = (e) => { if (e.target !== elementos.dragHandle[0] || config.janelaMinimizada) return; e.preventDefault(); isDragging = true; startX = e.clientX; startY = e.clientY; startLeft = element.offsetLeft; startTop = element.offsetTop; element.classList.add('dragging'); document.addEventListener('mousemove', elementDrag); document.addEventListener('mouseup', closeDragElement); }; const elementDrag = (e) => { if (!isDragging) return; e.preventDefault(); const newLeft = startLeft + (e.clientX - startX); const newTop = startTop + (e.clientY - startY); const maxTop = window.innerHeight - (config.janelaMinimizada ? DIMENSOES.PAINEL.MINIMIZADO.height : element.offsetHeight); const maxLeft = window.innerWidth - (config.janelaMinimizada ? DIMENSOES.PAINEL.MINIMIZADO.width : element.offsetWidth); element.style.left = `${Math.min(Math.max(newLeft, DIMENSOES.MARGENS.MINIMA), maxLeft - DIMENSOES.MARGENS.MINIMA)}px`; element.style.top = `${Math.min(Math.max(newTop, DIMENSOES.MARGENS.MINIMA), maxTop - DIMENSOES.MARGENS.MINIMA)}px`; element.style.right = 'auto'; element.style.bottom = 'auto'; config.posicaoJanela = { x: parseInt(element.style.left), y: parseInt(element.style.top) }; }; const closeDragElement = () => { isDragging = false; element.classList.remove('dragging'); salvarConfiguracao(); document.removeEventListener('mousemove', elementDrag); document.removeEventListener('mouseup', closeDragElement); }; elementos.dragHandle.on('mousedown', dragMouseDown); } /** * Carrega a imagem da URL * @param {string} url - URL da imagem a ser carregada * @returns {boolean} Retorna true se o carregamento foi iniciado com sucesso */ function carregarImagem(url) { if (!validarURL(url)) { elementos.status.html('<span style="color:#ff6b6b">URL inválida. Use links que terminem com .png, .jpg, .jpeg, .gif ou .webp</span>'); return false; } mostrarCarregamento(true); elementos.status.text('Carregando imagem...'); const img = new Image(); img.crossOrigin = "Anonymous"; img.onload = function() { config.urlImagem = url; elementos.guia.attr('src', url).show(); elementos.status.text('Imagem carregada com sucesso!'); mostrarCarregamento(false); atualizarTamanho(); atualizarPosicao(); salvarConfiguracao(); }; img.onerror = function() { elementos.status.html(` <span style="color:#ff6b6b">Falha ao carregar imagem</span> <div style="font-size:12px;color:#aaa;margin-top:5px;"> Dica: Use links diretos de serviços como Imgur (ex: https://i.imgur.com/abc123.png) </div> `); mostrarCarregamento(false); }; img.src = url; return true; } /** * Valida a URL da imagem * @param {string} url - URL a ser validada * @returns {boolean} Retorna true se a URL é válida */ function validarURL(url) { return /\.(png|jpe?g|gif|webp)(\?.*)?$/i.test(url); } /** * Mostra/oculta o indicador de carregamento * @param {boolean} mostrar - Se true, mostra o indicador */ function mostrarCarregamento(mostrar) { elementos.carregarBtn.toggleClass('carregando', mostrar) .html(mostrar ? '<span class="loader"></span> Carregando...' : 'Carregar Imagem'); } /** * Atualiza o tamanho da imagem guia */ function atualizarTamanho() { elementos.guia.css({ 'width': `${config.tamanho}%`, 'height': 'auto' }); elementos.tamanhoValor.text(`${config.tamanho}%`); elementos.tamanhoSlider.val(config.tamanho); salvarConfiguracao(); } /** * Atualiza a posição da imagem guia */ function atualizarPosicao() { const canvas = document.querySelector('canvas'); if (!canvas || !config.urlImagem) return; const rect = canvas.getBoundingClientRect(); elementos.guia.css({ left: `${rect.left + config.deslocamento.x}px`, top: `${rect.top + config.deslocamento.y}px` }); } /** * Alterna entre minimizar e maximizar a janela */ function toggleMinimizar() { config.janelaMinimizada = !config.janelaMinimizada; elementos.minimizarBtn.text(config.janelaMinimizada ? '⬛' : '➖'); elementos.controles.toggleClass('minimizado', config.janelaMinimizada); ajustarPosicaoJanela(elementos.controles[0]); salvarConfiguracao(); } /** * Salva a configuração atual */ function salvarConfiguracao() { GM_setValue('wplaceConfig', config); } /** * Configura os event listeners */ function setupEventListeners() { // Carregar imagem elementos.carregarBtn.on('click', () => { const url = elementos.urlInput.val().trim(); if (url) carregarImagem(url); }); // Controle de opacidade elementos.opacidade.on('input', () => { config.opacidade = parseFloat(elementos.opacidade.val()); elementos.guia.css('opacity', config.opacidade); elementos.opacidade.prev('label').text(`Opacidade: ${config.opacidade.toFixed(1)}`); salvarConfiguracao(); }); // Controles de tamanho elementos.diminuirBtn.on('click', () => { config.tamanho = Math.max(config.tamanho - DIMENSOES.TAMANHO.STEP, DIMENSOES.TAMANHO.MIN); atualizarTamanho(); }); elementos.aumentarBtn.on('click', () => { config.tamanho = Math.min(config.tamanho + DIMENSOES.TAMANHO.STEP, DIMENSOES.TAMANHO.MAX); atualizarTamanho(); }); elementos.tamanhoSlider.on('input', () => { config.tamanho = parseInt(elementos.tamanhoSlider.val()); atualizarTamanho(); }); // Botão de minimizar/maximizar elementos.minimizarBtn.on('click', toggleMinimizar); // Atalhos de teclado $(document).on('keydown', (e) => { const passo = e.shiftKey ? DIMENSOES.TAMANHO.STEP * 2 : DIMENSOES.TAMANHO.STEP; let atualizar = false; // Movimento (Ctrl + setas) if (e.ctrlKey && !e.altKey && !e.metaKey) { if (e.key === 'ArrowUp') { config.deslocamento.y -= passo; atualizar = true; } else if (e.key === 'ArrowDown') { config.deslocamento.y += passo; atualizar = true; } else if (e.key === 'ArrowLeft') { config.deslocamento.x -= passo; atualizar = true; } else if (e.key === 'ArrowRight') { config.deslocamento.x += passo; atualizar = true; } } // Tamanho (Alt + setas) else if (e.altKey && !e.ctrlKey && !e.metaKey) { if (e.key === 'ArrowUp' || e.key === 'ArrowRight') { config.tamanho = Math.min(config.tamanho + passo, DIMENSOES.TAMANHO.MAX); atualizar = true; } else if (e.key === 'ArrowDown' || e.key === 'ArrowLeft') { config.tamanho = Math.max(config.tamanho - passo, DIMENSOES.TAMANHO.MIN); atualizar = true; } } // Opacidade (Ctrl + Alt + setas para cima/baixo) else if ((e.ctrlKey && e.altKey) || (e.metaKey && e.altKey)) { if (e.key === 'ArrowUp') { config.opacidade = Math.min(config.opacidade + DIMENSOES.OPACIDADE.STEP, DIMENSOES.OPACIDADE.MAX); atualizar = true; } else if (e.key === 'ArrowDown') { config.opacidade = Math.max(config.opacidade - DIMENSOES.OPACIDADE.STEP, DIMENSOES.OPACIDADE.MIN); atualizar = true; } } if (atualizar) { if (e.altKey && !e.ctrlKey) { atualizarTamanho(); } else if ((e.ctrlKey && e.altKey) || (e.metaKey && e.altKey)) { elementos.guia.css('opacity', config.opacidade); elementos.opacidade.val(config.opacidade); elementos.opacidade.prev('label').text(`Opacidade: ${config.opacidade.toFixed(1)}`); } else { atualizarPosicao(); } salvarConfiguracao(); e.preventDefault(); } }); // Redimensionamento da janela const debouncedResize = debounce(() => { ajustarPosicaoJanela(elementos.controles[0]); atualizarPosicao(); }, 100); $(window).on('resize', debouncedResize); } /** * Debounce para eventos frequentes * @param {Function} fn - Função a ser executada * @param {number} delay - Tempo de espera em ms * @returns {Function} Função debounced */ function debounce(fn, delay) { let timer; return function() { clearTimeout(timer); timer = setTimeout(fn, delay); }; } // ============================================= // INICIALIZAÇÃO // ============================================= function init() { // Carrega configurações salvas const savedConfig = GM_getValue('wplaceConfig'); if (savedConfig) Object.assign(config, savedConfig); // Cria a interface criarInterface(); // Configura a posição inicial ajustarPosicaoJanela(elementos.controles[0]); // Torna a janela arrastável makeDraggable(elementos.controles[0]); // Configura os event listeners setupEventListeners(); // Carrega a imagem se já existir uma URL configurada if (config.urlImagem) carregarImagem(config.urlImagem); } // Inicia o script quando o DOM estiver pronto $(document).ready(init); })();