您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
Provides utility functions to get and wait for elements asyncronously that are not yet loaded or available on the page.
当前为
此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.greasyfork.icu/scripts/488161/1332704/wait-for-element.js
// ==UserScript== // @name wait-for-element // @description Provides utility functions to get and wait for elements asyncronously that are not yet loaded or available on the page. // @version 0.0.1 // @namespace owowed.moe // @author owowed <[email protected]> // @require https://update.greasyfork.icu/scripts/488160/1332703/make-mutation-observer.js // @license LGPL-3.0 // ==/UserScript== /** * @typedef WaitForElementOptions * @prop {string} [id] - when used, it will select element by ID, and it will use the `document` as the selector parent. * @prop {string | string[]} selector - the selector for the element. If `selector` is `string[]`, then `multiple` option is automatically enabled. Setting `multiple` option to `false` will not have effect at all. * @prop {ParentNode} [parent] - when used, it will instead use `parent` parent element as the selector parent. This option will specify/limit the scope of query selector from `parent` parent element. This may be useful for optimizing selecting element. * @prop {AbortSignal} [abortSignal] - when used, user may able to abort waiting for element by using `AbortSignal#abort()`. * @prop {boolean} [multiple] - when used, `waitForElement*` will act as `ParentNode#querySelectorAll()`. * @prop {number} [timeout] - will set wait for element timeout. Default timeout is 5 seconds. * @prop {boolean} [enableTimeout] - if timeout set by `timeout` reached, `waitForElement*` will throw `WaitForElementTimeoutError`. * @prop {number} [maxTries] - will set how many attempt `waitForElement*` will query select, and if it reached, it will throw `WaitForElementMaximumTriesError`. * @prop {boolean} [ensureDomContentLoaded] - ensure DOM content loaded by listening to `DOMContentLoad` event, and then execute by that, or checking by using `document.readyState`. * @prop {MutationObserverInit} [observerOptions] - set options for `MutationObserver` used in `waitForElement*`. * @prop {(elem: HTMLElement) => boolean} [filter] - filter multiple or single element before being returned. * @prop {(elem: HTMLElement) => HTMLElement} [transform] - transform or modify multiple or single element before being returned. */ /** * @typedef {Element[] | Element | null} WaitForElementReturnValue */ class WaitForElementError extends Error { name = this.constructor.name; } class WaitForElementTimeoutError extends WaitForElementError {} class WaitForElementMaximumTriesError extends WaitForElementError {} /** * Wait for element asyncronously until the element is available on the page. This function immediately accept `parent` as its first parameter. `parent` parameter will specify/limit the scope of query selector. This may be useful for optimizing selecting element. * @param {NonNullable<WaitForElementOptions["parent"]>} parent * @param {WaitForElementOptions["selector"]} selector * @param {WaitForElementOptions} options * @returns {WaitForElementReturnValue} */ function waitForElementByParent(parent, selector, options) { return waitForElementOptions({ selector, parent, ...options }); } /** * Wait for element asyncronously until the element is available on the page. This function immediately accept `selector` as its first parameter. * @param {WaitForElementOptions["selector"]} selector * @param {WaitForElementOptions} options * @returns {WaitForElementReturnValue} */ function waitForElement(selector, options) { return waitForElementOptions({ selector, ...options }); } /** * Wait for element asyncronously until the element is available on the page. * @param {WaitForElementOptions} options * @returns {WaitForElementReturnValue} */ function waitForElementOptions( { id, selector, parent = document.documentElement, abortSignal, // abort controller signal multiple = false, timeout = 5000, enableTimeout = true, maxTries = Number.MAX_SAFE_INTEGER, ensureDomContentLoaded = true, observerOptions = {}, filter, transform, throwError } = {}) { return new Promise((resolve, reject) => { let result, tries = 0; function* applyFilterTransform(result) { if (filter != undefined) { for (let elem of result) { if (filter(elem)) { if (transform) elem = transform(elem); yield elem; } } } else if (transform != undefined) { for (const elem of result) { yield transform(elem); } } } function tryQueryElement(observer) { abortSignal?.throwIfAborted(); if (multiple && id == undefined) { if (Array.isArray(selector)) { result = []; for (const sel of selector) { result = result.concat(Array.from(parent.querySelectorAll(sel))); } } else { result = Array.from(parent.querySelectorAll(selector)); } result = Array.from(applyFilterTransform(result)); } else { if (id) { result = document.getElementById(id); } else if (Array.isArray(selector)) { result = []; function* querySelectorIterator() { for (const sel of selector) { yield parent.querySelector(sel); } } result = Array.from(applyFilterTransform(querySelectorIterator())); } else { result = parent.querySelector(selector); } if (transform) result = transform(result); if (filter != undefined && !filter(result)) { return false; } } if (multiple ? result?.length > 0 : result) { observer?.disconnect(); resolve(result); return result; } tries++; if (tries >= maxTries) { observer?.disconnect(); if (throwError) { reject(new WaitForElementMaximumTriesError(`maximum number of tries (${maxTries}) reached waiting for element "${selector}"`)); } else { resolve(null); } return false; } } function startWaitForElement() { const firstResult = tryQueryElement(); if (firstResult) return; let observer = makeMutationObserver( { target: parent, childList: true, subtree: true, abortSignal, ...observerOptions }, () => tryQueryElement(observer)); let timeoutId = null; if (enableTimeout) { timeoutId = setTimeout(() => { observer.disconnect(); if (throwError) { reject(new WaitForElementTimeoutError(`timeout waiting for element "${selector}"`)); } else { resolve(null); } }, timeout); } abortSignal?.addEventListener("abort", () => { clearTimeout(timeoutId); observer.disconnect(); if (throwError) { reject(new DOMException(abortSignal.reason, "AbortError")); } else { resolve(null); } }); tryQueryElement(); } if (ensureDomContentLoaded && document.readyState == "loading") { document.addEventListener("DOMContentLoaded", () => { startWaitForElement(); }); } else { startWaitForElement(); } }); }