// ==UserScript==
// @name 自动点击菜单
// @namespace http://tampermonkey.net/
// @version 1.47.2
// @description 自动点击菜单,支持按ID、类名、文本、位置自动点击,可设定执行次数
// @author YuoHira
// @license MIT
// @match *://*/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=github.io
// @grant GM_setValue
// @grant GM_getValue
// @require https://cdn.jsdelivr.net/npm/[email protected]/lib/anime.min.js
// ==/UserScript==
// --- css.js ---
const AUTO_CLICK_MENU_CSS = `
/* ====== 主题变量 ====== */
:root {
--macaron-blue1: #7ecfff; /* 马卡龙主蓝 */
--macaron-blue2: #aee2ff; /* 马卡龙浅蓝 */
--macaron-bg: #fafdff; /* 背景淡蓝 */
--macaron-border: #e0e6ed;/* 边框灰蓝 */
--macaron-text: #2d3a4a; /* 主要字体色 */
--macaron-shadow: 0 4px 24px 0 rgba(126,207,255,0.18); /* 柔和阴影 */
}
/* ====== 菜单主容器 ====== */
.yuohira-container {
/* 渐变淡蓝背景,圆角,柔和阴影 */
background: linear-gradient(135deg, #eaf6ff 0%, #fafdff 100%);
border: none;
border-radius: 16px;
padding: 22px 24px 18px 24px;
box-shadow: var(--macaron-shadow);
display: flex;
flex-direction: column;
align-items: center;
min-width: 320px;
max-width: 95vw;
transition: box-shadow 0.2s, transform 0.15s;
}
.yuohira-container:hover {
box-shadow: 0 8px 32px 0 rgba(126,207,255,0.28);
transform: scale(1.015);
}
/* ====== 菜单标题 ====== */
.yuohira-title {
color: var(--macaron-blue1);
font-family: 'Segoe UI', 'PingFang SC', 'Arial', sans-serif;
font-size: 21px;
font-weight: bold;
margin-bottom: 18px;
letter-spacing: 1px;
}
/* ====== 通用按钮 ====== */
.yuohira-button {
/* 渐变蓝背景,圆角,阴影,动效 */
background: linear-gradient(90deg, var(--macaron-blue1) 0%, var(--macaron-blue2) 100%);
border: none;
color: #fff;
border-radius: 9px;
padding: 8px 22px;
cursor: pointer;
font-size: 16px;
margin: 7px 10px;
font-weight: 500;
box-shadow: 0 2px 8px 0 rgba(126,207,255,0.13);
transition: background 0.2s, color 0.2s, filter 0.2s, transform 0.13s;
outline: none;
}
.yuohira-button:hover {
filter: brightness(1.09);
transform: scale(1.06);
}
.yuohira-button:active {
filter: brightness(0.98);
transform: scale(0.96);
}
/* ====== 右上角小圆球(菜单开关) ====== */
.yuohira-toggle-button {
background: linear-gradient(135deg, var(--macaron-blue1) 60%, var(--macaron-blue2) 100%);
border: none;
color: #fff;
border-radius: 50%;
padding: 0;
cursor: pointer;
font-size: 20px;
width: 38px;
height: 38px;
position: fixed;
top: 18px;
right: 18px;
z-index: 10001;
opacity: 0.85;
box-shadow: 0 2px 8px 0 rgba(126,207,255,0.18);
display: flex;
align-items: center;
justify-content: center;
transition: opacity 0.3s, background 0.2s, transform 0.13s;
}
.yuohira-toggle-button:hover {
opacity: 1;
transform: scale(1.12);
}
.yuohira-toggle-button:active {
transform: scale(0.92);
}
/* ====== 输入框和下拉框 ====== */
.yuohira-input {
/* 渐变淡蓝背景,圆角,阴影,动效 */
border: 1.5px solid var(--macaron-blue1);
border-radius: 8px;
padding: 7px 12px;
margin: 7px 10px;
background: linear-gradient(90deg, #fafdff 60%, #eaf6ff 100%);
color: var(--macaron-text);
font-size: 15px;
outline: none;
box-shadow: 0 1.5px 6px 0 rgba(126,207,255,0.13);
transition: border 0.2s, box-shadow 0.2s, transform 0.13s;
appearance: none;
-webkit-appearance: none;
-moz-appearance: none;
position: relative;
}
.yuohira-input:focus, .yuohira-input:hover {
/* 深蓝高亮描边,阴影增强,缩放 */
border-color: #3fa6e8;
box-shadow: 0 3px 14px 0 rgba(126,207,255,0.22);
transform: scale(1.045);
}
/* 下拉框专属美化,自定义蓝色箭头 */
select.yuohira-input {
background: #fff url('data:image/svg+xml;utf8,<svg fill="%237ecfff" height="20" viewBox="0 0 20 20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M7.293 7.293a1 1 0 011.414 0L10 8.586l1.293-1.293a1 1 0 111.414 1.414l-2 2a1 1 0 01-1.414 0l-2-2a1 1 0 010-1.414z"/></svg>') no-repeat right 10px center/18px 18px;
padding-right: 36px;
cursor: pointer;
min-width: 80px;
}
select.yuohira-input::-ms-expand {
display: none;
}
/* ====== 输入项整体包裹 ====== */
.yuohira-input-wrapper {
display: flex;
align-items: center;
margin-bottom: 10px;
position: relative;
padding-bottom: 18px;
background: none;
border-radius: 0;
}
/* ====== 进度条 ====== */
.yuohira-progress-bar {
height: 5px;
position: absolute;
bottom: 0;
left: 0;
background: linear-gradient(90deg, var(--macaron-blue1), var(--macaron-blue2));
border-radius: 2.5px;
transition: width 0.3s;
}
/* ====== 警告提示 ====== */
.yuohira-warning {
color: #3fa6e8;
font-size: 12px;
position: absolute;
left: 0;
bottom: -13px;
width: 100%;
text-align: left;
z-index: 2;
pointer-events: none;
font-weight: 500;
}
/* ====== 屏幕取点遮罩 ====== */
.yuohira-crosshair-overlay {
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
z-index: 99999;
pointer-events: auto;
background: rgba(126,207,255, 0.08);
}
.yuohira-crosshair-line {
position: absolute;
background: var(--macaron-blue2);
z-index: 999999;
}
.yuohira-crosshair-label {
position: absolute;
background: var(--macaron-blue1);
color: #fff;
font-size: 13px;
padding: 3px 8px;
border-radius: 4px;
z-index: 999999;
pointer-events: none;
transform: translateY(-150%);
font-weight: 500;
}
`;
(function(){
// --- AutoClickMenu.js ---
// == 自动点击菜单主控类 ==
// 负责菜单的创建、样式注入、数据持久化、菜单项管理、自动点击主循环、动画、窗口拖动与位置保存等
class AutoClickMenu {
/**
* 构造函数,初始化主流程
* - 记录当前页面URL
* - 读取自动点击开关状态
* - 初始化菜单项列表
* - 启动初始化流程
*/
constructor() {
this.currentUrl = window.location.origin; // 当前页面域名,用于数据隔离
this.autoClickEnabled = GM_getValue(`${this.currentUrl}_autoClickEnabled`, false); // 自动点击开关
this.lastUpdateTime = new Map(); // 记录每个菜单项的上次点击时间
this.menuItems = []; // 菜单项对象列表
this.init();
}
/**
* 初始化主流程,页面加载后执行
* - 注入样式
* - 创建菜单容器和小圆球
* - 恢复上次保存的位置
* - 添加各类按钮和输入区
* - 加载本地保存的菜单项
* - 启动自动点击主循环
*/
init() {
window.onload = () => {
this.createStyles(); // 注入样式
this.menuContainer = this.createMenuContainer(); // 创建菜单主容器
this.toggleButton = new ToggleButton(this).createElement(); // 创建右上角小圆球
document.body.appendChild(this.menuContainer);
document.body.appendChild(this.toggleButton);
// 恢复上次保存的位置(如有)
const savedPos = GM_getValue('yuohira_menu_position', null);
if (savedPos && typeof savedPos.top === 'number' && typeof savedPos.right === 'number') {
this.menuContainer.style.top = savedPos.top + 'px';
this.menuContainer.style.right = savedPos.right + 'px';
this.toggleButton.style.top = savedPos.top + 'px';
this.toggleButton.style.right = savedPos.right + 'px';
}
this.addMenuTitle(this.menuContainer); // 添加标题
// 保存按钮,保存菜单项和位置
this.saveButton = this.addButton(this.menuContainer, '保存', 'yuohira-button', (e) => {
e.stopPropagation();
this.saveData();
// 保存当前位置
const top = parseInt(this.menuContainer.style.top) || 10;
const right = parseInt(this.menuContainer.style.right) || 10;
GM_setValue('yuohira_menu_position', { top, right });
});
// 重置位置按钮
this.resetButton = this.addButton(this.menuContainer, '重置位置', 'yuohira-button', (e) => {
e.stopPropagation();
this.menuContainer.style.top = '10px';
this.menuContainer.style.right = '10px';
this.toggleButton.style.top = '10px';
this.toggleButton.style.right = '10px';
});
// 新增菜单项按钮
this.addButtonElement = this.addButton(this.menuContainer, '+', 'yuohira-button', (e) => {
e.stopPropagation();
this.addInputField();
});
// 自动点击开关按钮
this.toggleAutoClickButton = this.addButton(this.menuContainer, this.autoClickEnabled ? '暂停' : '开始', 'yuohira-button', (e) => {
e.stopPropagation();
this.autoClickEnabled = !this.autoClickEnabled;
this.toggleAutoClickButton.innerText = this.autoClickEnabled ? '暂停' : '开始';
GM_setValue(`${this.currentUrl}_autoClickEnabled`, this.autoClickEnabled);
});
// 输入区容器
this.inputContainer = document.createElement('div');
this.menuContainer.appendChild(this.inputContainer);
this.loadSavedData(); // 加载本地保存的菜单项
this.applyAutoClick(); // 启动自动点击主循环
};
}
/**
* 注入样式到页面
*/
createStyles() {
const style = document.createElement('style');
style.innerHTML = AUTO_CLICK_MENU_CSS;
document.head.appendChild(style);
}
/**
* 创建菜单主容器
* @returns {HTMLDivElement} 菜单容器元素
*/
createMenuContainer() {
const menuContainer = document.createElement('div');
menuContainer.className = 'yuohira-container';
menuContainer.style.position = 'fixed';
menuContainer.style.top = '10px';
menuContainer.style.right = '10px';
menuContainer.style.zIndex = '10000';
menuContainer.style.display = 'none';
menuContainer.style.opacity = '0';
menuContainer.style.transform = 'translateY(-20px)';
document.body.appendChild(menuContainer);
// 阻止冒泡,防止误触页面其它元素
menuContainer.addEventListener('click', (e) => {
e.stopPropagation();
});
return menuContainer;
}
/**
* 添加菜单标题
* @param {HTMLElement} container 目标容器
*/
addMenuTitle(container) {
const menuTitle = document.createElement('h3');
menuTitle.innerText = '自动点击菜单';
menuTitle.className = 'yuohira-title';
container.appendChild(menuTitle);
}
/**
* 添加按钮
* @param {HTMLElement} container 按钮父容器
* @param {string} text 按钮文本
* @param {string} className 按钮样式类
* @param {function} onClick 点击回调
* @returns {HTMLButtonElement}
*/
addButton(container, text, className, onClick) {
const button = document.createElement('button');
button.innerText = text;
button.className = className;
button.addEventListener('click', onClick);
container.appendChild(button);
return button;
}
/**
* 加载本地保存的菜单项配置
*/
loadSavedData() {
const savedData = GM_getValue(this.currentUrl, []);
savedData.forEach(item => {
this.addInputField(item.type, item.value, item.enabled, item.interval, item.count);
});
}
/**
* 保存当前菜单项配置到本地
*/
saveData() {
const data = this.menuItems.map(item => item.getData());
GM_setValue(this.currentUrl, data);
}
/**
* 新增一个菜单项输入区
* @param {string} type 目标类型
* @param {string} value 目标值
* @param {boolean} enabled 是否启用
* @param {number} interval 间隔
* @param {number} count 执行次数
*/
addInputField(type = 'id', value = '', enabled = false, interval = 1000, count = -1) {
const menuItem = new MenuItem(type, value, enabled, interval, this, count);
this.menuItems.push(menuItem);
this.inputContainer.appendChild(menuItem.createElement());
}
/**
* 自动点击主循环,定时遍历所有启用的菜单项并执行点击
*/
applyAutoClick() {
const autoClick = () => {
if (this.autoClickEnabled && this.menuItems.some(item => item.isEnabled())) {
const currentTime = Date.now();
this.menuItems.forEach(item => item.autoClick(currentTime, this.lastUpdateTime));
}
requestAnimationFrame(autoClick);
};
requestAnimationFrame(autoClick);
}
/**
* 展开菜单,带动画
*/
showMenu() {
if (!this.menuContainer) return;
this.menuContainer.style.display = 'block';
this.menuContainer.style.overflow = 'hidden';
// 先测量内容高度
this.menuContainer.style.maxHeight = 'none';
const fullHeight = this.menuContainer.scrollHeight;
this.menuContainer.style.maxHeight = '0px';
anime({
targets: this.menuContainer,
opacity: [0, 1],
translateY: [-20, 0],
maxHeight: [0, fullHeight],
duration: 450,
easing: 'easeOutCubic',
update: anim => {
// 防止高度动画卡住
this.menuContainer.style.maxHeight = this.menuContainer.style.maxHeight;
},
complete: () => {
this.menuContainer.style.opacity = '1';
this.menuContainer.style.transform = 'translateY(0)';
this.menuContainer.style.maxHeight = 'none';
this.menuContainer.style.overflow = '';
}
});
}
/**
* 收起菜单,带动画
*/
hideMenu() {
if (!this.menuContainer) return;
const fullHeight = this.menuContainer.scrollHeight;
this.menuContainer.style.overflow = 'hidden';
this.menuContainer.style.maxHeight = fullHeight + 'px';
anime({
targets: this.menuContainer,
opacity: [1, 0],
translateY: [0, -20],
maxHeight: [fullHeight, 0],
duration: 350,
easing: 'easeInCubic',
complete: () => {
this.menuContainer.style.display = 'none';
this.menuContainer.style.opacity = '0';
this.menuContainer.style.transform = 'translateY(-20px)';
this.menuContainer.style.maxHeight = '0px';
this.menuContainer.style.overflow = '';
}
});
}
}
// --- MenuItem.js ---
// == 菜单项配置类(MenuItem)==
// 负责单个自动点击目标的输入、启用、间隔、次数、进度、选取等功能
class MenuItem {
/**
* 构造函数
* @param {string} type 目标类型(id/class/text/position)
* @param {string} value 目标值
* @param {boolean} enabled 是否启用
* @param {number} interval 间隔时间
* @param {AutoClickMenu} menu 主菜单实例
* @param {number} count 执行次数,-1为无限
*/
constructor(type, value, enabled, interval, menu, count = -1) {
this.type = type;
this.value = value;
this.enabled = enabled;
this.interval = interval;
this.menu = menu;
this.count = (typeof count === "number" ? count : -1);
}
/**
* 创建菜单项输入区 DOM 元素,包含类型选择、目标输入、启用/暂停、间隔、次数、进度、警告、删除等
* @returns {HTMLDivElement}
*/
createElement() {
const MIN_INTERVAL = 1;
const inputWrapper = document.createElement('div');
inputWrapper.className = 'yuohira-input-wrapper';
// 下拉选择目标类型
this.select = document.createElement('select');
const optionId = document.createElement('option');
optionId.value = 'id';
optionId.innerText = 'ID';
const optionClass = document.createElement('option');
optionClass.value = 'class';
optionClass.innerText = '类名';
const optionText = document.createElement('option');
optionText.value = 'text';
optionText.innerText = '文本';
const optionPosition = document.createElement('option');
optionPosition.value = 'position';
optionPosition.innerText = '位置';
this.select.appendChild(optionId);
this.select.appendChild(optionClass);
this.select.appendChild(optionText);
this.select.appendChild(optionPosition);
this.select.value = this.type;
this.select.className = 'yuohira-input';
inputWrapper.appendChild(this.select);
// 目标输入框
this.input = document.createElement('input');
this.input.type = 'text';
this.input.value = this.value;
this.input.className = 'yuohira-input';
this.input.placeholder = 'ID/类名/文本/坐标';
inputWrapper.appendChild(this.input);
// 选取按钮(支持屏幕取点/元素高亮)
this.selectButton = document.createElement('button');
this.selectButton.innerText = '选取';
this.selectButton.className = 'yuohira-button';
this.selectButton.addEventListener('click', (e) => this.selectElement(e));
inputWrapper.appendChild(this.selectButton);
// 类型切换时,动态调整输入提示和按钮状态
this.select.addEventListener('change', () => {
if (this.select.value === 'text') {
this.selectButton.disabled = true;
this.selectButton.style.backgroundColor = '#d3d3d3';
this.selectButton.style.borderColor = '#a9a9a9';
this.input.placeholder = '请输入文本';
} else if (this.select.value === 'position') {
this.selectButton.disabled = false;
this.selectButton.style.backgroundColor = '';
this.selectButton.style.borderColor = '';
this.input.placeholder = '点击"选取"后屏幕定位';
} else {
this.selectButton.disabled = false;
this.selectButton.style.backgroundColor = '';
this.selectButton.style.borderColor = '';
this.input.placeholder = '请输入ID/类名';
}
});
// 初始禁用选取按钮(文本模式)
if (this.type === 'text') {
this.selectButton.disabled = true;
this.selectButton.style.backgroundColor = '#d3d3d3';
this.selectButton.style.borderColor = '#a9a9a9';
}
// 启用/暂停按钮
this.toggleInputClickButton = document.createElement('button');
this.toggleInputClickButton.className = 'yuohira-button toggle-input-click-button';
this.toggleInputClickButton.innerText = this.enabled ? '暂停' : '开始';
this.toggleInputClickButton.setAttribute('data-enabled', this.enabled);
this.toggleInputClickButton.addEventListener('click', (e) => {
e.stopPropagation();
const isEnabled = this.toggleInputClickButton.innerText === '开始';
this.toggleInputClickButton.innerText = isEnabled ? '暂停' : '开始';
this.toggleInputClickButton.setAttribute('data-enabled', isEnabled);
this.warningMsg && (this.warningMsg.style.display = 'none');
});
inputWrapper.appendChild(this.toggleInputClickButton);
// 间隔输入区
const intervalWrapper = document.createElement('div');
intervalWrapper.style.position = 'relative';
intervalWrapper.style.display = 'inline-block';
intervalWrapper.style.width = '100px';
intervalWrapper.style.padding = '0px 140px 0px 0px';
this.intervalInput = document.createElement('input');
this.intervalInput.type = 'number';
this.intervalInput.value = this.interval;
this.intervalInput.className = 'yuohira-input';
this.intervalInput.placeholder = '间隔';
this.intervalInput.style.paddingRight = '10px';
this.intervalInput.style.width = '100px';
this.intervalInput.min = MIN_INTERVAL;
this.intervalInput.addEventListener('input', () => {
let val = parseInt(this.intervalInput.value, 10) || 0;
if (val < MIN_INTERVAL) {
val = MIN_INTERVAL;
this.intervalInput.value = MIN_INTERVAL;
}
this.interval = val;
});
intervalWrapper.appendChild(this.intervalInput);
// 间隔单位
const intervalSuffix = document.createElement('span');
intervalSuffix.innerText = 'ms';
intervalSuffix.style.color = '#0099cc';
intervalSuffix.style.position = 'absolute';
intervalSuffix.style.right = '2px';
intervalSuffix.style.top = '50%';
intervalSuffix.style.transform = 'translateY(-50%)';
intervalSuffix.style.pointerEvents = 'none';
intervalSuffix.style.zIndex = '1';
intervalWrapper.appendChild(intervalSuffix);
inputWrapper.appendChild(intervalWrapper);
// ==== 新增:执行次数输入框 ====
this.countInput = document.createElement('input');
this.countInput.type = 'number';
this.countInput.value = this.count;
this.countInput.className = 'yuohira-input';
this.countInput.style.width = '60px';
this.countInput.style.marginLeft = '8px';
this.countInput.placeholder = '-1为无限';
this.countInput.title = '执行次数,-1为无限';
this.countInput.addEventListener('input', () => {
let val = parseInt(this.countInput.value, 10);
if (isNaN(val)) val = -1;
this.count = val;
});
inputWrapper.appendChild(this.countInput);
// 次数单位
const countLabel = document.createElement('span');
countLabel.innerText = '次';
countLabel.style.color = '#0099cc';
countLabel.style.marginLeft = '2px';
inputWrapper.appendChild(countLabel);
// ==== 新增结束 ====
// 进度条
this.progressBar = document.createElement('div');
this.progressBar.className = 'yuohira-progress-bar';
inputWrapper.appendChild(this.progressBar);
// 警告信息
this.warningMsg = document.createElement('div');
this.warningMsg.className = 'yuohira-warning';
this.warningMsg.style.display = 'none';
inputWrapper.appendChild(this.warningMsg);
// 删除按钮
const removeButton = document.createElement('button');
removeButton.innerText = '-';
removeButton.className = 'yuohira-button';
removeButton.addEventListener('click', () => {
inputWrapper.remove();
this.menu.menuItems = this.menu.menuItems.filter(item => item !== this);
});
inputWrapper.appendChild(removeButton);
return inputWrapper;
}
/**
* 选取目标元素或屏幕坐标
* @param {Event} event
*/
selectElement(event) {
event.stopPropagation();
if (this.select.value === 'position') {
// 显示全屏十字准星
this.showCrosshairSelector();
return;
}
document.body.style.cursor = 'crosshair';
this.selectButton.disabled = true;
// 悬浮提示框
const hoverBox = document.createElement('div');
hoverBox.style.position = 'fixed';
hoverBox.style.backgroundColor = 'rgba(0, 0, 0, 0.7)';
hoverBox.style.color = 'white';
hoverBox.style.padding = '5px';
hoverBox.style.borderRadius = '5px';
hoverBox.style.pointerEvents = 'none';
hoverBox.style.zIndex = '10002';
document.body.appendChild(hoverBox);
// 鼠标移动时高亮元素并显示提示
const mouseMoveHandler = (e) => {
const elements = document.elementsFromPoint(e.clientX, e.clientY);
elements.forEach((el) => {
el.style.outline = '2px solid red';
});
document.addEventListener('mouseout', () => {
elements.forEach((el) => {
el.style.outline = '';
});
});
hoverBox.style.left = `${e.clientX + 10}px`;
hoverBox.style.top = `${e.clientY + 10}px`;
if (this.select.value === 'id' && elements[0].id) {
hoverBox.innerText = `ID: ${elements[0].id}`;
} else if (this.select.value === 'class' && elements[0].className) {
hoverBox.innerText = `Class: ${elements[0].className}`;
} else {
hoverBox.innerText = '无ID或类名';
}
};
// 点击选中目标
const clickHandler = (e) => {
e.stopPropagation();
e.preventDefault();
const selectedElement = e.target;
if (this.select.value === 'id' && selectedElement.id) {
this.input.value = selectedElement.id;
} else if (this.select.value === 'class' && selectedElement.className) {
this.input.value = selectedElement.className;
}
document.body.style.cursor = 'default';
document.removeEventListener('mousemove', mouseMoveHandler);
document.removeEventListener('click', clickHandler, true);
this.selectButton.disabled = false;
document.body.removeChild(hoverBox);
};
document.addEventListener('mousemove', mouseMoveHandler);
document.addEventListener('click', clickHandler, true);
}
/**
* 屏幕取点模式,显示全屏遮罩和十字准星,点击后写入坐标
*/
showCrosshairSelector() {
// 创建全屏遮罩和十字准星
const overlay = document.createElement('div');
overlay.className = 'yuohira-crosshair-overlay';
// 横线
const hLine = document.createElement('div');
hLine.className = 'yuohira-crosshair-line';
hLine.style.height = '1px';
hLine.style.width = '100vw';
hLine.style.top = '50%';
hLine.style.left = '0';
hLine.style.background = '#e74c3c';
// 竖线
const vLine = document.createElement('div');
vLine.className = 'yuohira-crosshair-line';
vLine.style.width = '1px';
vLine.style.height = '100vh';
vLine.style.left = '50%';
vLine.style.top = '0';
vLine.style.background = '#e74c3c';
// 坐标显示
const label = document.createElement('div');
label.className = 'yuohira-crosshair-label';
label.innerText = '点击以选取位置';
label.style.left = '50%';
label.style.top = '50%';
overlay.appendChild(hLine);
overlay.appendChild(vLine);
overlay.appendChild(label);
document.body.appendChild(overlay);
// 鼠标移动时更新准星位置和坐标
const moveHandler = (e) => {
hLine.style.top = `${e.clientY}px`;
vLine.style.left = `${e.clientX}px`;
label.style.left = `${e.clientX + 10}px`;
label.style.top = `${e.clientY + 10}px`;
label.innerText = `X: ${e.clientX}, Y: ${e.clientY}`;
};
overlay.addEventListener('mousemove', moveHandler);
// 点击写入坐标
const clickHandler = (e) => {
e.stopPropagation();
e.preventDefault();
this.input.value = `${e.clientX},${e.clientY}`;
document.body.removeChild(overlay);
overlay.removeEventListener('mousemove', moveHandler);
overlay.removeEventListener('click', clickHandler);
};
overlay.addEventListener('click', clickHandler);
}
/**
* 根据文本查找页面元素
* @param {string} text
* @returns {Element[]}
*/
findElementsByText(text) {
const elements = document.querySelectorAll('*');
const matchingElements = [];
elements.forEach(element => {
if (element.textContent.trim() === text) {
matchingElements.push(element);
}
});
return matchingElements;
}
/**
* 获取当前菜单项的所有配置数据
* @returns {Object}
*/
getData() {
return {
type: this.select.value,
value: this.input.value,
enabled: this.toggleInputClickButton.getAttribute('data-enabled') === 'true',
interval: parseInt(this.intervalInput.value, 10),
count: parseInt(this.countInput.value, 10)
};
}
/**
* 判断当前菜单项是否启用
* @returns {boolean}
*/
isEnabled() {
return this.toggleInputClickButton.getAttribute('data-enabled') === 'true';
}
/**
* 模拟鼠标点击目标元素
* @param {Element} element
*/
simulateMouseClick(element) {
const rect = element.getBoundingClientRect();
const x = rect.left + rect.width / 2;
const y = rect.top + rect.height / 2;
const opts = { bubbles: true, cancelable: true, clientX: x, clientY: y };
element.dispatchEvent(new PointerEvent('pointerdown', opts));
element.dispatchEvent(new MouseEvent('mousedown', opts));
element.dispatchEvent(new PointerEvent('pointerup', opts));
element.dispatchEvent(new MouseEvent('mouseup', opts));
element.dispatchEvent(new MouseEvent('click', opts));
}
/**
* 自动点击主逻辑,定时查找目标并点击,支持次数、进度、异常提示
* @param {number} currentTime 当前时间戳
* @param {Map} lastUpdateTime 上次更新时间Map
*/
autoClick(currentTime, lastUpdateTime) {
if (typeof this.count !== 'number') this.count = -1;
if (this.count === 0) return;
if (!this.isEnabled()) return;
const lastTime = lastUpdateTime.get(this) || 0;
const elapsedTime = currentTime - lastTime;
if (elapsedTime >= this.interval) {
let elements = [];
let inputVal = (this.input.value || '').trim();
let clicked = false;
if (this.select.value === 'id') {
if (inputVal) {
elements = Array.from(document.querySelectorAll(`#${CSS.escape(inputVal)}`));
}
} else if (this.select.value === 'class') {
if (inputVal) {
elements = Array.from(document.getElementsByClassName(inputVal));
}
} else if (this.select.value === 'text') {
if (inputVal) {
elements = this.findElementsByText(inputVal);
}
} else if (this.select.value === 'position') {
const pos = inputVal.split(',');
if (pos.length === 2) {
const x = parseInt(pos[0].trim(), 10);
const y = parseInt(pos[1].trim(), 10);
if (!isNaN(x) && !isNaN(y)) {
const el = document.elementFromPoint(x, y);
if (el && !this.menu.menuContainer.contains(el)) {
elements = [el];
}
}
}
}
if (this.select.value !== 'position') {
elements.forEach(element => {
if (!this.menu.menuContainer.contains(element)) {
this.simulateMouseClick(element);
clicked = true;
}
});
} else if (elements.length > 0) {
this.simulateMouseClick(elements[0]);
clicked = true;
}
// 点击成功后减少次数
if (clicked && this.count > 0) {
this.count--;
this.countInput.value = this.count;
}
// 异常提示处理
if (this.select.value !== 'position') {
if (inputVal && elements.length === 0) {
this.warningMsg.innerText = '未找到目标元素';
this.warningMsg.style.display = 'block';
} else {
this.warningMsg.style.display = 'none';
}
} else {
this.warningMsg.style.display = 'none';
}
this.progressBar.style.width = '100%';
lastUpdateTime.set(this, currentTime);
} else {
let percent = (1 - elapsedTime / this.interval) * 100;
if (percent < 0) percent = 0;
if (percent > 100) percent = 100;
this.progressBar.style.width = `${percent}%`;
}
}
}
// --- ToggleButton.js ---
// == 菜单右上角小圆球(ToggleButton)==
// 负责菜单的显示/隐藏切换、拖动窗口、动效等
class ToggleButton {
/**
* 构造函数
* @param {AutoClickMenu} menu 主菜单实例
*/
constructor(menu) {
this.menu = menu;
}
/**
* 创建小圆球 DOM 元素,并绑定点击/拖动等事件
* @returns {HTMLButtonElement}
*/
createElement() {
const toggleButton = document.createElement('button');
toggleButton.innerText = '>';
toggleButton.className = 'yuohira-toggle-button';
// 固定定位,初始位置与菜单一致
toggleButton.style.position = 'fixed';
toggleButton.style.top = this.menu.menuContainer.style.top || '10px';
toggleButton.style.right = this.menu.menuContainer.style.right || '10px';
toggleButton.style.zIndex = '10001';
toggleButton.style.width = '15px';
toggleButton.style.height = '15px';
toggleButton.style.fontSize = '10px';
toggleButton.style.textAlign = 'center';
toggleButton.style.lineHeight = '15px';
toggleButton.style.padding = '0';
toggleButton.style.boxSizing = 'border-box';
toggleButton.style.display = 'flex';
toggleButton.style.alignItems = 'center';
toggleButton.style.justifyContent = 'center';
document.body.appendChild(toggleButton);
// 点击切换菜单显隐,带缩放动画
toggleButton.addEventListener('click', (e) => {
e.stopPropagation();
// 按钮缩放动画(anime.js,更丝滑)
anime.remove(toggleButton);
anime({
targets: toggleButton,
scale: [1, 1.18, 0.95, 1],
duration: 320,
easing: 'easeInOutCubic'
});
if (this.menu.menuContainer.style.display === 'none') {
this.menu.showMenu();
toggleButton.innerText = '<';
} else {
this.menu.hideMenu();
toggleButton.innerText = '>';
}
});
// 拖动功能:按住小圆球可拖动菜单和小圆球整体
let isDragging = false;
let dragStartX = 0, dragStartY = 0;
let offsetToCenterX = 0, offsetToCenterY = 0;
let startTop = 0, startRight = 0;
const btnRect = () => toggleButton.getBoundingClientRect();
toggleButton.addEventListener('mousedown', (e) => {
if (e.button !== 0) return; // 只响应左键
isDragging = true;
document.body.style.userSelect = 'none';
toggleButton.style.cursor = 'move';
this.menu.menuContainer.style.transition = 'none';
toggleButton.style.transition = 'none';
// 记录鼠标到小圆球中心的偏移
const rect = btnRect();
offsetToCenterX = e.clientX - (rect.left + rect.width / 2);
offsetToCenterY = e.clientY - (rect.top + rect.height / 2);
// 记录当前 top/right
startTop = rect.top;
startRight = window.innerWidth - rect.right;
});
document.addEventListener('mousemove', (e) => {
if (!isDragging) return;
// 让小圆球中心跟随鼠标
let newCenterX = e.clientX - offsetToCenterX;
let newCenterY = e.clientY - offsetToCenterY;
// 计算新 top/right
let btnWidth = btnRect().width;
let btnHeight = btnRect().height;
let newTop = newCenterY - btnHeight / 2;
let newRight = window.innerWidth - (newCenterX + btnWidth / 2);
// 限制范围:上下左右都要有 10px 间距
const minTop = 10;
const minRight = 10;
const maxTop = window.innerHeight - this.menu.menuContainer.offsetHeight - 10;
const maxRight = window.innerWidth - 60 - 10;
newTop = Math.max(minTop, Math.min(maxTop, newTop));
newRight = Math.max(minRight, Math.min(maxRight, newRight));
this.menu.menuContainer.style.top = newTop + 'px';
this.menu.menuContainer.style.right = newRight + 'px';
toggleButton.style.top = newTop + 'px';
toggleButton.style.right = newRight + 'px';
});
document.addEventListener('mouseup', () => {
if (isDragging) {
isDragging = false;
document.body.style.userSelect = '';
toggleButton.style.cursor = '';
this.menu.menuContainer.style.transition = '';
toggleButton.style.transition = '';
}
});
return toggleButton;
}
}
// --- index.js ---
// == 自动点击菜单入口 ==
// 仅负责实例化主控类,启动脚本
'use strict';
new AutoClickMenu();
})();