Skip to content

Instantly share code, notes, and snippets.

@kawaz
Last active October 31, 2023 08:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kawaz/804ddbcc7ddc5033d09d4e72fdcd3bf5 to your computer and use it in GitHub Desktop.
Save kawaz/804ddbcc7ddc5033d09d4e72fdcd3bf5 to your computer and use it in GitHub Desktop.
よく使うJSの便利関数的な奴 tools.js
// タイムアウト付き setInterval
const setIntervalTimeout = (f, interval = 200, timeout = 5000, immediate = false) => {
immediate && setTimeout(f, 0)
const i = setInterval(f, interval)
const t = setTimeout(() => clearInterval(i), timeout)
return () => { clearInterval(i); clearTimeout(t) }
}
// タイムアウト付き setInterval の強化版(タブがバックグラウンドになってもサボらない)
const setIntervalTimeoutSuper = (f, interval = 200, timeout = 5000, immediate = false) => {
try {
const code = `addEventListener('message',()=>{${immediate}&&setTimeout(postMessage,0,null);setInterval(postMessage,${interval},null);setTimeout(close,${timeout})})`
const w = new Worker(`data:text/javascript;base64,${btoa(code)}`)
w.onmessage = f
w.postMessage(null)
return ()=>w.terminate()
} catch(_){
// 実装の問題またはCSPによる拒否などで Worker が使えなければ普通の setInterval を使う
return setIntervalTimeout(f, interval, immediate)
}
}
// f が truthy な値を返すまで繰り返し実行する。タイムアウトするとrejectされる。
const waitValue = (f, {interval=100, timeout=3000, immediate=true}={}) => new Promise(async(ok,ng)=>{
if(immediate){const r=await f();if(r)return ok(r)}
const i = setInterval(async(r)=>{if(r=await f())s(r)}, interval)
const t = setTimeout(s, timeout)
function s(r){clearTimeout(t);clearInterval(i);(r?ok:ng)(r)}
})
// querySelector のショートハンド
const query = (target, mapFn=v=>v) => mapFn(target instanceof Element ? target : typeof target === 'string' ? document.querySelector(target) : null)
const queryAll = (target, mapFn=v=>v) => (target instanceof Element ? [target] : typeof target === 'string' ? [...document.querySelectorAll(target)] : []).map(mapFn)
// shadowRoot を貫通して検索できるバージョン
// TODO: selector が子孫セレクタ(空白区切り)を含むケースに未対応(優先度=高)
// TODO: selector が複数セレクタ(カンマを含む)ケースに未対応(優先度=低)
const queryAllDeep = (selector, rootDocOrMapFn=document, mapFn=v=>v) => {
// rootDoc は省略可能とする
if(typeof rootDocOrMapFn === 'function') {
mapFn = rootDoc
rootDocOrMapFn = document
}
const docs = (node=rootDocOrMapFn, doc=rootDocOrMapFn) => ({node, doc, shadows: [...doc.querySelectorAll("*")].filter(n=>n.shadowRoot).map(n=>docs(n,n.shadowRoot))})
const allDocs = (d=docs())=>[d.doc, ...d.shadows.flatMap(s=>allDocs(s))]
return allDocs().flatMap(doc=>[...doc.querySelectorAll(selector)])
}
const myStyle = (id) => myStyle[`$${id}`] ?? (myStyle[`$${id}`] = (document.head.append(document.createElement('style')), document.styleSheets.item(document.styleSheets.length - 1)))
const newRule = () => (s => (s.addRule(), s.cssRules[s.cssRules.length - 1]))(myStyle())
// target が見つかるまで待つPromise
const ready = (target, interval = 200, timeout = 5000) => new Promise((resolve, reject) => {
const waiter = () => {
const el = query(target)
if (el) {
clearInterval(waiterId)
clearTimeout(timeoutId)
resolve(el)
}
}
const timeoutId = setTimeout(() => { clearInterval(waiterId); reject(new Error(`target ${target} was not ready`)); }, timeout)
const waiterId = setInterval(waiter, interval)
waiter()
})
//イベントが最後に発生してからdelay経過後に1度だけ実行されるようにする(キーボードイベントとかに使える)
const debounce = (f, delay=500) => {
let t;
return (...args) => {
clearTimeout(t)
t = setTimeout(()=>f(...args), delay)
}
}
//指定の時間間隔毎に最大一度だけ実行されるようにする(スクロールやリサイズなど継続的に発生するイベントに使える)
const throttle = (f, limit=500)=>{
let inThrottle = false;
return (...args) => {
if (!inThrottle) {
inThrottle = true
f(...args)
setTimeout(()=>inThrottle=false, limit)
}
};
}
// 配列がソート済みかチェックする
const isSorted = (arr, cmp=(a,b)=>a===b?0:a<b?1:-1) => arr.every((cur,i,a)=>i==0||0<=cmp(a[i-1],cur))
// DOMを作る
const createElement = (tag, props = {}, children = []) => {
const {classList=[], style={}, dataset={}, ...others} = props
const el = document.createElement(tag)
if(classList.length) {
el.classList.add(...classList)
}
Object.assign(el.style, style)
Object.assign(el.dataset, dataset)
Object.assign(el, others)
for(let child of children) {
if (child instanceof Element) {
el.appendChild(child)
} else {
el.appendChild(document.createTextNode(child))
}
}
return el
}
// フォーム要素の値を取得したりセットしたりする
const form = (target, ...values) => {
// get
if (values.length === 0) {
return query(target)?.value
}
// set
return [query(target)].map(e => {
const value = values.reduce((pre, value) => {
if (typeof value === "boolean" && e.tagName == "INPUT" && ["checkbox", "radio"].includes(e.type)) {
value = { checked: value }
} else if (["string", "number", "boolean"].includes(typeof value) || value === null) {
value = { value }
}
if (typeof value === "object") {
return Object.assign(pre, value)
}
}, {})
Object.assign(e, value)
return value
}).shift()
}
// ワイルドカードを正規表現化する
const wc = str => new RegExp(`^${str.replace(/[^a-z0-9_]/gi, "\\$&").replace(/\\\*/g, ".*")}$`)
// urlにマッチしたらcb(url)を実行
const urlmatch = (url, cb = url => console.log(`urlmatch: ${url}`)) =>
(wc(url)?.test(location.href) || (location.hash == "" && wc(url)?.test(location.href.replace(/#$/, "")))) && cb(url)
@kawaz
Copy link
Author

kawaz commented Jul 27, 2021

ユーザスクリプトのコメントに以下を追記すると使えるようになる

// @require      https://gist.githubusercontent.com/kawaz/804ddbcc7ddc5033d09d4e72fdcd3bf5/raw/tools.js?v=1

クエリの ?v=1 はキャッシュクリア用のパラメータ。gist更新後にすぐ使いたい場合はユーザスクリプト側の @require のクエリを変えればよい。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment