連続したイベントをキャンセルし、一定時間が過ぎたらイベントを発火させる
寺尾で質問をした。
イベントを遅らせて発火させ、イベントオブジェクトから target 要素を取得したい
もうちょっと自身で精査すればよかったのに・・・自己解決した。
これで文字を入力するたびに処理しなくてもよくなった。
<!DOCTYPE html> <meta charset="UTF-8"> <title>イベントを遅らせられた</title> <body> <input type="text" name="inp"><br /> <input type="text" name="inp"><br /> <script> /***************************** 連続するイベントをキャンセルし、一定時間が経過したらイベントを発生させる const obj = EventDelay.create (eventType [, target[, delayTime[, useCapture]]]); ******************************/ class EventDelay { constructor (type, target, delayTime) { this.type = type; this.target = target; this.delayTime = delayTime; this.timerId = null; this.event = null; this.disabled = false; } dispatch () { this.timerOff (); //× this.target.dispatchEvent (this.event); if (! this.disabled) this.event.target.dispatchEvent (this.event);//イベントのターゲットが肝だった } timerOff () { if (this.timerId) clearTimeout (this.timerId); this.timerId = null; } timerOn () { if (this.disabled) return; let cbFunc = this.dispatch.bind (this); this.timerId = setTimeout (cbFunc, this.delayTime); } handleEvent (event) { this.timerOff (); if (this.disabled) return; if (event.isTrusted) { this.event = event; event.stopImmediatePropagation (); this.timerOn (); } } static create (type, target = document, delayTime = 333, useCapture = false) { if (1 > arguments.length || ! type) throw new Error ('イベントタイプが不正です'); let obj = new this (type, target, delayTime); target.addEventListener (type, obj, useCapture); target = null; return obj; } } //----------------------------------------------- //document に対する inputイベントを 333m/s 遅らせる EventDelay.create ('input'); //----------------------------------------------- document.addEventListener ('input', event=> { let e = event.target; console.log (event, e.value);// value値を取得 }, false); </script>
ちょっとした時間があり、全面的に見直した。
<!DOCTYPE html> <title></title> <meta charset="utf-8"> <style> </style> <body> <script> class DelayTask { #id = null; //setTimeout関数の戻り値に使用 constructor (callBackFunc, delayTime) { this.callBackFunc = callBackFunc; // func.bind (this) this.delayTime = delayTime; } execute (...args) { this.timer = null; this.callBackFunc (...args); } start (...args) { let cbFunc = this.execute.bind (this, ...args); this.timer = setTimeout (cbFunc, this.delayTime); return this; } stop () { this.timer = null; return this; } set timer (id) { if (this.#id) clearTimeout (this.#id); this.#id = id; } get timer () { return this.#id; } //__________________ static create (cbFunc, delayTime = 500) { if ('function' != typeof cbFunc) throw new Error ('関数ではありません'); return new this (cbFunc, delayTime); } } //_____________________________________________________ //遅延させてハンドラを呼び出す class DelayHandler extends DelayTask { constructor (handler, delayTime) { super (handler, delayTime); } handleEvent (event) { this.stop ().start (event); } static create (handler, delayTime = 300) { let cbFunc; //引数のチェック switch (Object.prototype.toString.call (handler)) { case '[object Function]' : cbFunc = handler; break; case '[object Object]' : if ('handleEvent' in handler) { if ('function' === typeof handler.handleEvent) cbFunc = handler.handleEvent.bind (handler); } //else Error !! default: throw new Error ('Not handleEvent'); break; } return new this (cbFunc, delayTime); } } //_____________________________________________________ function handler (event) { console.log(event); alert ('クリックされた'); } document.addEventListener ('click', DelayHandler.create (handler, 1000), false); </script>
WEB画面に枯葉が舞う
それにしても短い休日だった。
母さん、三途の川を渡ったころだろうか。
https://oshiete.goo.ne.jp/qa/12665779.html
枯葉よ~
<!DOCTYPE html> <meta charset="UTF-8"> <title>Test</title> <body> <img class="kareha" src="data:image/gif;base64,R0lGODlhLQAqAKIAAP7jRvTSMvfZO/veQuS8H+zFJ/DMLv///yH5BAEAAAcALAAAAAAtACoAAAP4eLrc/tCRGau1k97NTyZdWBWZaDaGQZ6sEqRFew7CK89CfYsAQO88HzDUEw43RYAEdGQkH59CbDioDqAkgrSQMtwCOQEks+0GzmBR6lzRltfosKB6cXvbbi4cnLMOng0fG3lmZ2F+SU8fTHVvBnF9VYlJgh2OkHOSk0lXIpeGkX+bgCGffJmioz0mpoeaqkqeUoWnqLCxpbO0rq+bJoR7coiTnSLAdwrCfsvFIW4PoLzLYkdotXLYR3uY2GxAXdvW4gFAjrTiXTt5uuDtXVMtZFvm4PM3E1nr8/tSjPGL8vZVAgKw4KImBhM2UZAQ4MIFDUs8DGSQRQIAOw=="> <img class="kareha" src="data:image/gif;base64,R0lGODlhMAAnAKIAAOlcNOM+E8cqB9RxW96dkfDUz8hPPP///yH5BAEAAAcALAAAAAAwACcAAAP/eLrc/qS8Sas9Q97NndFdeBGfaFbDcK5OYahsfBBCKZ/FEMC3qQeGnskVAOJixGLQRMisfkUBKJQiDA3F6DREinCzAOCWa3iNH8lAWGBlkcqGNoWwVgcEPNzgdR/IHQNrAGt/JySDa0sNBWF3dmwyOQYAgQI1W3RgSj0kRY19DJkCdkYhZzmNg1GKqqONUhUFsgRvhQpNqa1Lra15BbQpNcI1Xmgpqqp3Cq2PGr9YltGWcSOBuWsYmjUKOpaOd5Z5Fri5ygSOjTNR3+CQXIFgYX7fAgpq0VmjeIbMqt1hAM5lGRhNXAdUn/oxIxjtzMFf11wN9NTOBgtZED2lGkUQYJwtEb+OWaM4EdwLJxfJIUJU0pOfG88mrZwJMB8QlHoM1AAgc6UnOjQYGuTQxA+wPWV2KJEA4MArjtuGbJnlJwfKpgd0SrP0UciCAApCbnXn1QHWBVQHcC37AOwcRRUSAAA7"> <script> const {sin, cos, random, PI} = Math, DEG = PI / 180; //_________ class P2 { constructor (x = 0, y = 0) { this.x = x; this.y = y; } } //_________ class Status { constructor (...args) { this.reset (...args); } reset (fall = 0, swing = 0, spin = 0) { this.fall = fall; this.swing = swing; this.spin = spin; return this; } randomReset (max, min) { for (let name of Object.keys (max)) this[name] = rand (max[name], min[name]); return this; } } //_________ class Kareha { constructor (terms, target, position, character, spin, life) { this.terms = terms; this.target = target; this.position = position; this.character = character; this.life = life; this.spin = spin; } reset () { let t = this.terms; this.position = new P2 (rand (t.endArea.x, t.beginArea.x), 0); this.character.randomReset (t.maxStatus, t.minStatus); this.life = this.spin = 0; return this.disp (); } move () { if (null === this.life) this.reset (); let {x, y} = this.position; if (this.terms.endArea.y < y) return this.reset (); this.life++; this.spin += this.character.spin; y += this.character.fall + cos (this.spin * DEG); x += sin (this.spin * DEG) * this.character.swing; this.position.x = x; this.position.y = y; return this.disp (); } disp () { let style = this.target.style, {x, y} = this.position; if (null === this.life) style.opacity = 0 + ''; else { let n = this.spin |0; style.opacity = 1 + ''; style.left = (x |0)+ 'px'; style.top = (y |0)+ 'px'; style.transform = `rotate(${this.spin}deg)`; } return this; } static create (terms, target, position = new P2, character = new Status, spin = 0, life = null) { let obj = new this (terms, target, position, character, spin, life); target.style.position = 'absolute'; return obj.reset (); } } function rand (max = 100, min = 0) { let sa = max - min; return random () * sa + min; } //_________ { let img = document.querySelectorAll ('img.kareha'); for (let i = 0; i < 20; i++) { img[0].parentNode.appendChild (img[0].cloneNode (false)); img[1].parentNode.appendChild (img[1].cloneNode (false)); } } const TERMS = { minStatus: new Status (1, .5, .1), maxStatus: new Status (3, 3, 2), beginArea: new P2 (0, 0), endArea: new P2 (640, 800) }, KAREHA = [...document.querySelectorAll ('img.kareha')].map (e=> Kareha.create (TERMS, e)); (function loop () { KAREHA.forEach (obj=> obj.move ()); window.requestAnimationFrame (loop); }) (); </script>
ベンチを作った
開拓地に設置するベンチを作った
JavaScript エンターキーで移動する。その4
teratail で回答したものより更に加筆
https://teratail.com/questions/325107
- radio の checked は1つだけなので、最後に移動する。
- textarea 内の改行はオートインデントが可能。
- [Shift]+[Enter]は逆順に、[Ctrl]+[Enter]で checked の反転と textarea 内での改行
- datalist 内の summary に対応
- アンカータグの移動に対応
<!DOCTYPE html><title></title><meta charset="utf-8"> <style> a:focus, datalist:focus, input:focus { background: rgba(255,0,0,.2); } </style> <body> <nav> <p>最初に[Tab]でフォーカスを移動した場合テストできる<br> <a href="#C">ABC</a> <a href="#C">DEF</a> <a href="#A">GHI</a><br> </nav> <form> <p>フォーム内のアンカータグを移動する<br> <a href="#C">ABC</a> <a href="#C">DEF</a> <a href="#A">GHI</a> <p>通常の INPUT要素<br> 1.<input name="d0" value=""> 2.<input name="d1" value=""> <p>表示されていない要素<br> 1.<input type="hidden" name="d2"> 2.<input type="hidden" name="d2" style="display:none;"> 3.<input type="hidden" name="d2" style="visibility:hidden;"> 4.<input type="hidden" name="d2" style="opacity: 0;"> <p>INPUT[type=radio]要素の場合<br> <label>1.<input type="radio" name="d3" value="1">abc</label> <label>2.<input type="radio" name="d3" value="2">def</label> <label>3.<input type="radio" name="d3" value="3">ghi</label> <p>INPUT[type=checkbox]要素の場合<br> <label><input type="checkbox" name="d4" value="1">abc</label> <label><input type="checkbox" name="d4" value="2">def</label> <label><input type="checkbox" name="d4" value="3">ghi</label><br> <p>SELECT要素<br> <select name="d5"><option value="">-- <option value="1">abc <option value="2">def</select> <p>TEXTAREA要素<br> <textarea name="d6" cols="80" rows="5"> 改行すると、その行の最初のインデントが継続されます 改行せずに移動する場合は[Ctrl]+[Enter]、設定で逆の動作をします 改行すると、その行の最初のインデントが継続されます </textarea> <p>DATALIST要素にSUMMARYがある場合<br> <details> <summary>Summary OPEN1</summary> <ol><li>abc <li>def <li>ghi</ol> </details> <details open> <summary>Summary OPEN2</summary> <ol><li>abc <li>def <li>ghi</ol> </details> <p>INPUT[type=button] / BUTTON 要素の場合<br> <input type="button" value="フォームの移動を無効にする" onclick="F.disabled=true"> <input type="button" value="フォームの移動を有効にする" onclick="F.disabled=false"> </p> </form> <script> /* [Enter] 要素を次に移動する [Shift]+[Enter] 前に移動する [Ctrl]+[Enter]: 作用する */ //エンターキーで項目を移動する // // input 要素が1つの場合、機能しない。 // http://www.w3.org/TR/html5/constraints.html#implicit-submission class ExEnter { #root = null;//エンターキーで巡る親要素 #walker = null;//フォーカス要素の移動は treeWalker を利用する #imeFlag = null;//IME日本語入力中で未変換文字があり Enterが押されたことを感知するにはkeypress イベントがが実行されないことを利用する //________________________________________ constructor (root = document.documentElement, option = { }, disabled = false) { this.root = root;//setter 側で初期化処理を行う this.option = Object.assign ({ }, this.constructor.defaultOption, option); this.disabled = disabled; //true:機能停止 } // rootが指定されたら初期化する set root (root) { let handler = this.constructor.#handler; //まずイベントを取り外す if (this.#root) Object.keys (handler).forEach (eventType=> this.#root.removeEventListener (eventType, this, false) ); //イベントを登録する Object.keys (handler).forEach (eType=> root.addEventListener (eType, this, false) ); this.#root = root; this.#walker = root.ownerDocument.createTreeWalker ( root, NodeFilter.SHOW_ELEMENT, //要素ノードのみ this.constructor.#nodeFilter.bind (this) ); } get root () { return this.#root; } //ルートの中の対象となる最初の要素を返す get firstElement () { this.#walker.currentNode = this.root; return this.#walker.firstChild (); } //ルートの中の対象となる最後の要素を返す get lastElement () { this.#walker.currentNode = this.root; return this.#walker.lastChild (); } //ルートの中の対象となる要素のフォーカスを移動する direction= true:戻る /false:次へ move (direction = false, target = this.root.ownerDocument.activeElement) { if (this.disabled) return; if (! this.root.contains (target)) throw new Error ('ルート要素に含まれていない要素が指定されました'); const walker = this.#walker, isLoop = this.option.loop; walker.currentNode = target; let e = (direction) ? walker.previousNode () || (isLoop ? this.lastElement: null) : walker.nextNode () || (isLoop ? this.firstElement: null); if (e) e.focus (); } //対象となる要素にそこでの挙動を委ねる。戻り値で項目移動の判別 entrust (e, sw, prev) { //sw == ctrl, prev == shift if (this.disabled) return; let stay = false, tag = e.tagName; //stay == true 留まることを意味する if ('TEXTAREA' === tag && !prev) { //shiftキーが押されていない事が条件 if (sw ^ this.option.textarea) { //改行が行われる場合は留まる this.constructor.#insertCRLF (e, this.option.autoIndent); stay = true; } } else if (sw) { switch (tag) { case 'SUMMARY' : let parent = e.parentNode; parent.hasAttribute ('open') ? parent.removeAttribute ('open') : parent.setAttribute ('open', ''); stay = true; break; case 'A' : this.constructor.#fireEvent (e); stay = true; break; case 'INPUT' : switch (e.type) { case 'radio': if (e.checked = !e.checked) {//radio の checkedは選択肢が1つなので群の最後に移動 let radios = this.root.querySelectorAll (`input[type="radio"][name="${e.name}"]`); this.move (false, radios[radios.length-1]); } break; case 'checkbox' : e.checked = !e.checked; stay = true; break; case 'file' : case 'color' : case 'button' : case 'submit' : case 'reset' : this.constructor.#fireEvent (e); stay = true; break; } break; } } //値が有効? / summary & anchor has not checkValidity if (! stay && this.option.invalidStop && e.checkValidity) // stay = ! e.checkValidity (); return stay; } //イベントハンドラ(各typeによって分岐) handleEvent (event) { let handler = this.constructor.#handler[event.type]; if (handler) handler.call (this, event); } //________________________________________ //記載されたイベントは定義時に登録される {keyDown, keyup} static #handler = { 'keydown': function (event) { if ('Enter' === event.code) { let { target: { nodeName, type } } = event; if (/^(submit|reset|button|textarea|file|color)$/.test (type) || /^(SUMMARY|A)$/.test (nodeName)) event.preventDefault (); this.#imeFlag = event.isComposing;//IME変換中にEnterキーで確定、keyupでスルーするため } else this.#imeFlag = false; }, 'keyup': function (event) { if ('Enter' === event.code) { //IMEの動作で未変換中の文字があると判断してスルー if (this.#imeFlag) return event.preventDefault (); let { shiftKey, ctrlKey, target } = event, tmp; if (! (tmp = this.entrust (target, ctrlKey, shiftKey))) this.move (shiftKey, target); this.#imeFlag = false; } } }; //TreeWalker用のフィルター関数(要.bind(this)) static #nodeFilter (node) { const accept = NodeFilter.FILTER_ACCEPT, skip = NodeFilter.FILTER_SKIP, {option} = this; switch (node.nodeName) { case 'INPUT' : case 'TEXTAREA' : case 'SELECT' : case 'BUTTON' : if (node.disabled || node.readOnly) break; if (option.skip_tabIndex && ('-1' === node.getAttribute ('tabIndex'))) break; if (this.constructor.#isHide (node)) break; return accept; case 'A' : //href = '#...' で始まるもののみ対象とする if (option.anchor) return accept; break; case 'SUMMARY' : if (! option.details) break; return accept; } return skip; } //祖先の要素が隠された状態にあるか? static #isHide (node) { const chks = ['display', 'visibility', 'opacity', 'height', 'width']; for (let e = node; e !== document.body; e = e.parentNode) { let cs = getComputedStyle (e, null), [d, v, o, h, w] = chks.map (p=> cs.getPropertyValue (p));//それぞれchksの値 if ( 'none' === d || 'hidden' === v || 0 == parseFloat (o) || ! ('auto' === h || 0 < parseInt (h, 10)) || ! ('auto' === w || 0 < parseInt (w, 10)) ) return true; } return false; } //対象の要素にクリックイベントを発火させる static #fireEvent (target) { if (! target) throw new Error ('引数が無効!!'); let event = target.ownerDocument.createEvent ('MouseEvents'); event.initEvent ('click', false, true); target.dispatchEvent (event); return event; } //textarea要素に改行を挿入する static #insertCRLF (t, autoIndent = false, start = t.selectionStart || 0) { let str = '\n', value = t.value, first = value.slice (0, start), last = value.slice (start).replace (/^([\t\u3000\u0020]+)/, ''); if (autoIndent) { let spc = /(?:\n|^)([\t\u3000\u0020]+).*$/.exec (first); if (spc) str += spc[1]; } t.value = first + str + last; t.selectionStart = t.selectionEnd = start + str.length; } //________________________________________ //初期設定オプション値 static defaultOption = { loop: true, //要素を巡回する skip_tabIndex: true, //tabIndex属性が "-1" の場合フォーカスを飛ばす anchor: true, //アンカータグを有効にする(但し href属性の値が "#"から始まる場合) details: true, //details要素を巡回対象とする invalidStop:true,//要素の値が不正な場合でも移動する textarea: true, //true: 改行[Enter], 移動[CTRL]+[Enter], false: 移動[Enter], 改行[CTRL]+[Enter] autoIndent: true, //textarea要素内でのオートインデントを行う }; //インスタンスの作成 static create (root = document.documentElement, option = { }, disabled = false) { let obj = new this (root, option, disabled); return obj; } } const enter = ExEnter.create (TEST);
ISO 8601 覚書と正規表現
ISO 8601 覚書と正規表現
日付の表現
- 暦日付、年間通算日、週間日付の3種類だが、年間通算日と週間日付は除外する
暦日付
- 書式は YYYY-MM-DD または YYYYMMDDを許容する
- YYYYは4桁(0000-9999)、MMは2桁(01-12)、DDは2桁(01-31)とする
- 下位省略表記については、YYYY-MM、YYYY を採用する
let REG_YYYYMMDD = /^([0-9]{4})(?:\-?(0[1-9]|1[0-2])(?:\-?(0[1-9]|[12][0-9]|3[01]))?)?$/;
- 上位省略表記については、--MM-DD、--MM、---DDを許容する
let REG_YYYYMMDD = /^(?=(?:\d+(?:\-\d+(?:\-\d+)?)?|\-\-\d+?\-?\d+))([0-9]{4})?\D*(0[1-9]|1[0-2])?\D?(0[1-9]|[12][0-9]|3[01])?$/;
正規表現での考え方
前段階で2つの書式をチェックして、後半で数字をチェックする。
reg_exp.exec (string) => Array (4) [*****, YYYY, MM, DD]
更に拡張して元号年月日を許容する
- 文字列の先頭 "西暦|明治|大正|昭和|平成|令和|M|T|S|H|R" を識別する。西暦年は4桁、他の元号は2桁を許容する
- 明治初期は陰暦なのでその年代の使用には注意を要する。
- 漢字交じりの日付文字列に限り数値の前後の空白を許容する。
- 元号年は2桁の、"明治99年1月1日"を許容する。
- 上位省略表記については除外する
let REG_GYYYYMMDD = /^(?=(?:(?:[MmTtSsHhRr]?\d{1,2}|\d{4})(?:\-?\d+(?:\-?\d+)?)?|\s*(?:西暦\s*\d{4}|(?:明治|大正|昭和|平成|令和)\s*\d{1,2})\s*年\s*(?:\d+\s*月\s*(?:\d+\s*日\s*)?)?))\s*([^\d\s]+)?\s*([0-9]{1,4})\D*\s*(0?[1-9]|1[0-2])\D*\s*(0?[1-9]|[12][0-9]|3[01])\D*\s*$/; console.log (REG_GYYYYMMDD.exec ("西暦 2021 年 3 月 1 日")); //=> Array(5) ["西暦 2021 年 3 月 1 日", "西暦", "2021", "3", "1" ]
時刻の表現
- 書式は HH:MM:SS、HHMMSS、HH:MM、HHMM、-MM:SS、-MMSS、--SS を許容する