これまで私はHTMLのページを作る上で、何度もQiitaの記事にお世話になってきました。それはこの記事を見ているあなたも(おそらく)同じでしょう。
この記事は、そんなQiita巡りの最中に私が出会った、「なくてはならない機能」「あったら便利な機能」を自分なりに書き直したものの保管庫です。まだ数は少ないですが、これから時間をかけて、もっともっと拡充していこうと思います。
jQueryが肌に合わない残念人間なので、元記事ではjQueryで書かれていたコードであっても、全てピュアJavaScriptに書き直されています。ご了承ください。
#ユーザーエージェント判別
参考:
使用してるブラウザを判定したい(@sakuraya さん)
UserAgentからOS/ブラウザなどの調べかたのまとめ(@nightyknite さん)
var _ua = (function () {
var u = window.navigator.userAgent;
return {
device: (function (){
for(var a of ["IEMobile","Android","iPod","iPad","iPhone"]) {
if(u.match(a)) return a;
}
return "PC";
})(),
browser: (function (){
var b = {
MSIE: {n: "IE", v: /rv:([0-9\.]+?)(?:[^0-9\.]|$)/},
Trident: {n: "IE", v: /rv:([0-9\.]+?)(?:[^0-9\.]|$)/},
Edge: {n: "Edge", v: /Edge\/([0-9\.]+?)(?: |$)/},
Edg: {n: "Edge Chromium", v: /Edg\/([0-9\.]+?)(?: |$)/},
Chrome: {n: "Chrome", v: /Chrome\/([0-9\.]+?)(?: |$)/},
Firefox: {n: "Firefox", v: /Firefox\/([0-9\.]+?)(?: |$)/},
Safari: {n: "Safari", v: /Version\/([0-9\.]+?)(?: |$)/},
Opera: {n: "Opera", v: /(?:OPR|Opera)(?: |\/)([0-9\.]+?)(?: |$)/}
}
for(var a in b) {
if(u.match(a)) return [b[a].n, (u.match(b[a].v) ? u.match(b[a].v)[1] : null)];
}
return undefined;
})()
}
})();
console.log(_ua);
// {device: "iPhone", browser: ["Safari", "13.1.1"]}
#textarea用補完機能(用ボックス)
参考: GitHubのようなtextareaの補完機能を実装する - カーソル位置の取得(@yuku_t さん)
下図の
border: solid 1px rgba(0, 0, 0, 0);
の末端にあるキャレットにピッタリ沿うようにして赤いボックスが現れる。
<div class="wrap">
<textarea></textarea>
</div>
.wrap {
position: relative;
}
/*
candidates (候補)のboxで"candy box"。
我ながらいいセンスだと思う。
*/
.wrap .candyBox {
position: absolute;
/*サンプル用スタイル*/
background:#b01;
height: 100px;
width: 100px;
}
candyBox();//textareaのoninputなどのイベントハンドラで呼び出し
function candyBox() {
var p = document.querySelector(".wrap");
var t = document.querySelector(".wrap textarea");
for(var a of document.querySelectorAll(".candyBox")) {
a.remove();
}
function getCaret(node) {
if(node.selectionStart) {
return node.selectionStart;
}else if (!document.selection) {
return 0;
}
var c = "\001",
sel = document.selection.createRange(),
dul = sel.duplicate(),
len = 0;
dul.moveToElementText(node);
sel.text = c;
len = dul.text.indexOf(c);
sel.moveStart('character',-1);
sel.text = "";
return len;
}
var index = getCaret(t);
//キャレット位置より前の文章。
var before = t.value.substring(0, index);
//キャレット位置から先の文章。今回は未使用。
var after = t.value.substring(index);
//textareaのイミテーション
var div = document.createElement("div");
//候補用ボックス
var box = document.createElement("div");
/*
候補をboxに入れる
候補はbeforeとafterに対しての正規表現マッチを元にリストを生成し、以下の作業をfor文で回す
@@@@ @@@@ @@@@ @@@@
候補となるaタグをcreateElementしてboxにappendChild
var a = document.createElement("a");
a.innerText = "候補の文字列";
box.appendChild(a);
作ったaタグにonclickイベントを適用
a.onclick = function(e) {
//候補をクリックされたら、textarea内を書き換え
//"選択された候補の文字列"はe.target.innerTextなどに置き換える
t.value = before + "選択された候補の文字列" + after;
//候補の挿入後のキャレットの位置の修正
var n = (before + "選択された候補の文字列").length;
t.setSelectionRange(n, n);
//一度フォーカスを外さないとスクロール位置が正常に修正されない
t.blur();
t.focus();
}
*/
box.classList.add("candyBox");
//textareaに適用されているスタイルをスタイルシートも含めて全て取得し、divにコピー
var t_style = window.getComputedStyle(t);
for(var k in t_style) {
div.style[k] = t_style[k];
}
var scale = t_style.transform.split(/[^\d\.]/).filter(function(v) {return v});
var scale_x = scale[0];
var scale_y = scale[3];
//キャレットの直前までのテキストを入力
div.innerHTML = before;
var span = document.createElement('span');
span.innerHTML = ' ';
div.scrollTop = div.scrollHeight;
div.appendChild(span);
document.body.appendChild(div);
var d = div.getBoundingClientRect();
var s = span.getBoundingClientRect();
var r = {top: s.top - d.top, left: s.left - d.left};
div.remove();
//注1
box.style.top = (r.top - t.scrollTop * (scale_y ? Number(scale_y) : 1)) + 'px';
box.style.left = (r.left - t.scrollLeft * (scale_x ? Number(scale_x) : 1)) + 'px';
p.appendChild(box);
}
注1: iOSにおけるフォーカス時の自動拡大防止のためなどでtextareaをCSSのtransform: scale()で拡大縮小している場合(iOSでinputのフォーカス時に画面がズームするのを防ぐ;@skwbr さん)は、(追記: 反映をオートにしました。)t.scrollTop
に拡大縮小比率を反映させてください。この値はtransformによる変更が適用されないためです。
#Cookie操作
参考: Cookieの情報を【取得/保存/削除】する(@mocha_xx さん)
var Cookie = {
cookie: function() {
var o = {};
if(document.cookie.length){
var tmp = document.cookie.split('; ');
for(var t of tmp){
var d = t.split('=');
o[d.splice(0,1)] = (function() {
var a = decodeURIComponent(d.join('='));
try {
var b = JSON.parse(a);
if(!Object.prototype.toString.call(b).match(/\[object (Array|Object)\]$/)) {
return a;
}
return b;
}catch(e) {
return a;
}
})();
}
}
return o;
},
get: function(k) {
return this.cookie()[k];
},
set: function(k, v, limit) {
document.cookie = k + "=" + encodeURIComponent(Object.prototype.toString.call(v).match(/\[object (Array|Object)\]$/) ? JSON.stringify(v) : v) + ";" + (limit ? " max-age=" + limit + "; " : " ") + "path=/";
return this;
},
remove: function(k) {
document.cookie = k + "=; max-age=0; path=/";
return this;
}
};
Cookie.cookie();
//{key1: "aaa", key2: 0, key3: {a: 1}}
Cookie.get("key1");
//"aaa"
Cookie.set("key4", "value");
Cookie.set("key4", "value").set("key5", [0, 1, 2, 3], 7*24*60*60);//第3引数は期限(秒)
Cookie.remove("key4");
Cookie.remove("key4").remove("key5");