- // ==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/animejs@3.2.1/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();
-
- })();