UNPKG

pixel-perfect-overlay

Version:

A customizable pixel-perfect overlay with adjustable offset and opacity

250 lines (231 loc) 7.63 kB
;(function () { const overlayId = 'pixel-perfect-overlay' const panelId = 'pixel-perfect-panel' const toggleBtnId = 'toggle-settings-panel' if (document.getElementById(overlayId)) return // === Load saved state === const saved = JSON.parse(localStorage.getItem('pixelPerfectSettings') || '{}') let offsetX = saved.offsetX || 0 let offsetY = saved.offsetY || 0 let urlImage = saved.urlImage || '' let dragImage = saved.dragImage || '' let opacity = saved.opacity ?? 0.5 let display = saved.display ?? 'block' let panelVisible = saved.panelVisible ?? true // === Overlay === const overlay = document.createElement('div') overlay.id = overlayId Object.assign(overlay.style, { position: 'fixed', top: offsetY + 'px', left: offsetX + 'px', width: '100vw', height: '100vh', pointerEvents: 'none', backgroundRepeat: 'no-repeat', backgroundSize: 'contain', backgroundPosition: 'top left', opacity, zIndex: 9998, display, transition: 'opacity 0.2s ease', }) document.body.appendChild(overlay) updateOverlayBackground() // === Panel === const panel = document.createElement('div') panel.id = panelId panel.className = panelVisible ? '' : 'collapsed' panel.innerHTML = ` <button id="${toggleBtnId}" title="Открыть настройки">⚙️</button> <div class="panel-inner"> <input type="text" id="mock-url" placeholder="URL изображения" value="${urlImage}" /> <label>Прозрачность: <input type="range" id="mock-opacity" min="0" max="1" step="0.01" value="${opacity}" /> </label> <div style="display:flex;gap:8px;justify-content:space-between;"> <label style="flex:1;">X: <input type="number" id="offset-x" value="${offsetX}" /></label> <label style="flex:1;">Y: <input type="number" id="offset-y" value="${offsetY}" /></label> </div> <div id="coordinate-grid" style="text-align:center;font-size:12px;color:#555;margin-top:4px;"> Смещение: X: ${offsetX}px, Y: ${offsetY}px </div> <button id="toggle-overlay">👁 Показать / Скрыть</button> </div> ` Object.assign(panel.style, { position: 'fixed', bottom: '10px', left: '10px', padding: '10px', background: '#fff', borderRadius: '12px', fontSize: '14px', fontFamily: 'sans-serif', boxShadow: '0 4px 16px rgba(0,0,0,0.1)', zIndex: 10000, width: '240px', height: '200px', transition: 'all 0.3s ease', }) document.body.appendChild(panel) // === Panel Styles === const style = document.createElement('style') style.textContent = ` #${panelId} input[type="text"], #${panelId} input[type="number"] { width: 100%; padding: 4px 6px; font-size: 13px; margin: 4px 0; border: 1px solid #ccc; border-radius: 6px; box-sizing: border-box; } #${panelId} input[type="range"] { width: 100%; } #${panelId} button { padding: 6px; border: none; border-radius: 6px; background: #eee; cursor: pointer; } #${panelId} #toggle-overlay { width: 100%; } #${toggleBtnId} { position: absolute; top: -40px; left: 0; width: 36px; height: 36px; background: #555; color: #fff; border-radius: 50%; font-size: 18px; text-align: center; } #${panelId} .panel-inner { opacity: 1; transition: opacity 0.5s ease; } #${panelId}.collapsed { width: 36px !important; height: 36px !important; padding: 0 !important; overflow: hidden; } #${panelId}.collapsed .panel-inner { pointer-events: none !important; height: 0 !important; opacity: 0 !important; } #${panelId}.collapsed #${toggleBtnId} { top: 0 !important; } @media (max-width: 768px) { #${panelId} { width: calc(100vw - 30px); left: 15px; bottom: 10px; } } ` document.head.appendChild(style) // === Elements === const urlInput = panel.querySelector('#mock-url') const opacityInput = panel.querySelector('#mock-opacity') const offsetXInput = panel.querySelector('#offset-x') const offsetYInput = panel.querySelector('#offset-y') const coordGrid = panel.querySelector('#coordinate-grid') const toggleOverlayBtn = panel.querySelector('#toggle-overlay') const togglePanelBtn = panel.querySelector(`#${toggleBtnId}`) // === Events === let focusedInput = null offsetXInput.addEventListener('focus', () => (focusedInput = 'x')) offsetYInput.addEventListener('focus', () => (focusedInput = 'y')) offsetXInput.addEventListener('blur', () => (focusedInput = null)) offsetYInput.addEventListener('blur', () => (focusedInput = null)) urlInput.addEventListener('input', () => { urlImage = urlInput.value if (urlImage) dragImage = '' updateOverlayBackground() saveSettings({ urlImage, dragImage }) }) opacityInput.addEventListener('input', () => { opacity = parseFloat(opacityInput.value) overlay.style.opacity = opacity saveSettings({ opacity }) }) offsetXInput.addEventListener('input', () => { offsetX = parseInt(offsetXInput.value) || 0 applyOffset() }) offsetYInput.addEventListener('input', () => { offsetY = parseInt(offsetYInput.value) || 0 applyOffset() }) toggleOverlayBtn.addEventListener('click', () => { overlay.style.display = overlay.style.display === 'none' ? 'block' : 'none' saveSettings({ display: overlay.style.display }) }) togglePanelBtn.addEventListener('click', () => { panel.classList.toggle('collapsed') panelVisible = !panel.classList.contains('collapsed') saveSettings({ panelVisible }) }) document.addEventListener('keydown', (e) => { const step = e.shiftKey ? 10 : 1 if (focusedInput === 'x') { if (e.key === 'ArrowLeft') offsetX -= step if (e.key === 'ArrowRight') offsetX += step } else if (focusedInput === 'y') { if (e.key === 'ArrowUp') offsetY -= step if (e.key === 'ArrowDown') offsetY += step } else { if (e.key === 'ArrowLeft') offsetX -= step if (e.key === 'ArrowRight') offsetX += step if (e.key === 'ArrowUp') offsetY -= step if (e.key === 'ArrowDown') offsetY += step } applyOffset() }) document.addEventListener('dragover', (e) => e.preventDefault()) document.addEventListener('drop', (e) => { e.preventDefault() const file = e.dataTransfer.files[0] if (!file || !file.type.startsWith('image/')) return const reader = new FileReader() reader.onload = function (event) { dragImage = event.target.result urlImage = '' urlInput.value = '' updateOverlayBackground() saveSettings({ dragImage, urlImage }) } reader.readAsDataURL(file) }) // === Functions === function updateOverlayBackground() { overlay.style.backgroundImage = urlImage ? `url("${urlImage}")` : dragImage ? `url("${dragImage}")` : 'none' } function applyOffset() { overlay.style.left = `${offsetX}px` overlay.style.top = `${offsetY}px` offsetXInput.value = offsetX offsetYInput.value = offsetY coordGrid.textContent = `Смещение: X: ${offsetX}px, Y: ${offsetY}px` saveSettings({ offsetX, offsetY }) } function saveSettings(updates) { const current = JSON.parse(localStorage.getItem('pixelPerfectSettings') || '{}') const merged = { ...current, ...updates } localStorage.setItem('pixelPerfectSettings', JSON.stringify(merged)) } })()