A lightweight draggable library.
Bu script direkt olarak kurulamaz. Başka scriptler için bir kütüphanedir ve meta yönergeleri içerir // @require https://update.greasyfork.org/scripts/587662/1879553/Element%20Draggable.js
// ==UserScript==
// @name Element Draggable
// @name:zh-CN Element Draggable
// @namespace https://greasyfork.org/users/1570630
// @version 1.0
// @author ryxel
// @description A lightweight draggable library.
// @description:zh-CN 一个轻量级的元素拖拽库。
// @license MIT
// @grant GM_setValue
// @grant GM_getValue
// ==/UserScript==
var Draggable = (function() {
'use strict';
const getValue = typeof GM_getValue !== 'undefined' ? GM_getValue : (key) => JSON.parse(localStorage.getItem(key));
const setValue = typeof GM_setValue !== 'undefined' ? GM_setValue : (key, value) => localStorage.setItem(key, JSON.stringify(value));
class Draggable {
constructor(element, options = {}) {
if (!element) {
console.warn('Draggable: Element reference is null or undefined.');
return;
}
this.element = element;
this.handle = options.handle || element;
this.savePosition = options.savePosition || false;
this.id = options.id || null;
this.originalCursor = this.handle.style.cursor;
this.isMobile = /Android|iPhone|iPad|iPod|Mobile/i.test(navigator.userAgent);
this.isDragging = false;
this.rafId = null;
this.pointerX = 0;
this.pointerY = 0;
this.offsetX = 0;
this.offsetY = 0;
this.maxLeft = 0;
this.maxTop = 0;
this.midLeft = 0;
this.midTop = 0;
this.vw = 0;
this.vh = 0;
this.ew = 0;
this.eh = 0;
this.onStart = this.onStart.bind(this);
this.onMove = this.onMove.bind(this);
this.onEnd = this.onEnd.bind(this);
this.element.style.position = 'fixed';
this.handle.style.cursor = 'grab';
if (this.savePosition && this.id) {
this.loadStoredPosition();
}
this.handle.addEventListener('mousedown', this.onStart);
this.handle.addEventListener('touchstart', this.onStart, { passive: false });
}
updatePointerCoords(e) {
const source = (e.touches && e.touches[0]) || e;
this.pointerX = source.clientX;
this.pointerY = source.clientY;
}
cancelAnimation() {
if (this.rafId) {
cancelAnimationFrame(this.rafId);
this.rafId = null;
}
}
onStart(e) {
if (e.touches && e.touches.length > 1) return;
const tagName = e.target.tagName;
if (tagName === 'INPUT' || tagName === 'TEXTAREA' || tagName === 'BUTTON') {
return;
}
e.preventDefault();
this.updatePointerCoords(e);
const rect = this.element.getBoundingClientRect();
this.offsetX = this.pointerX - rect.left;
this.offsetY = this.pointerY - rect.top;
this.ew = rect.width;
this.eh = rect.height;
if (this.isMobile) {
this.vw = window.innerWidth;
this.vh = window.innerHeight;
} else {
this.vw = document.documentElement.clientWidth;
this.vh = document.documentElement.clientHeight;
}
this.maxLeft = Math.max(0, this.vw - this.ew);
this.maxTop = Math.max(0, this.vh - this.eh);
this.midLeft = this.maxLeft / 2;
this.midTop = this.maxTop / 2;
this.isDragging = true;
this.handle.style.cursor = 'grabbing';
document.addEventListener('mousemove', this.onMove);
document.addEventListener('touchmove', this.onMove, { passive: false });
document.addEventListener('mouseup', this.onEnd);
document.addEventListener('touchend', this.onEnd);
}
onMove(e) {
if (!this.isDragging) return;
e.preventDefault();
this.updatePointerCoords(e);
if (this.rafId) return;
this.rafId = requestAnimationFrame(() => {
this.rafId = null;
const left = Math.max(0, Math.min(this.pointerX - this.offsetX, this.maxLeft));
const top = Math.max(0, Math.min(this.pointerY - this.offsetY, this.maxTop));
if (left > this.midLeft) {
this.element.style.right = `${this.vw - left - this.ew}px`;
this.element.style.left = 'auto';
} else {
this.element.style.left = `${left}px`;
this.element.style.right = 'auto';
}
if (top > this.midTop) {
this.element.style.bottom = `${this.vh - top - this.eh}px`;
this.element.style.top = 'auto';
} else {
this.element.style.top = `${top}px`;
this.element.style.bottom = 'auto';
}
});
}
onEnd(e) {
if (!this.isDragging) return;
if (e.touches && e.touches.length > 0) return;
this.isDragging = false;
this.handle.style.cursor = 'grab';
this.cancelAnimation();
document.removeEventListener('mousemove', this.onMove);
document.removeEventListener('touchmove', this.onMove);
document.removeEventListener('mouseup', this.onEnd);
document.removeEventListener('touchend', this.onEnd);
if (this.savePosition && this.id) {
this.saveStoredPosition();
}
}
saveStoredPosition() {
try {
setValue(`draggable_pos_${this.id}`, {
left: this.element.style.left,
right: this.element.style.right,
top: this.element.style.top,
bottom: this.element.style.bottom
});
} catch (error) {
console.error('Failed to save position:', error);
}
}
loadStoredPosition() {
try {
const position = getValue(`draggable_pos_${this.id}`);
if (position) {
this.element.style.left = position.left;
this.element.style.right = position.right;
this.element.style.top = position.top;
this.element.style.bottom = position.bottom;
}
} catch (error) {
console.error('Failed to load position:', error);
}
}
destroy() {
this.cancelAnimation();
document.removeEventListener('mousemove', this.onMove);
document.removeEventListener('touchmove', this.onMove);
document.removeEventListener('mouseup', this.onEnd);
document.removeEventListener('touchend', this.onEnd);
if (this.handle) {
this.handle.removeEventListener('mousedown', this.onStart);
this.handle.removeEventListener('touchstart', this.onStart);
this.handle.style.cursor = this.originalCursor;
}
this.element = null;
this.handle = null;
}
}
return Draggable;
})();