EmojiOne みたいな絵文字を画像に置換してくれるライブラリって便利ですよね。異なる環境でも同じように表示させるっていうのは理にかなっている考え方だと思います。
でも、絵文字はあくまで文字なんだから、そこを画像にしてしまうのはなんとなく癪に障ると思いました。私が勝手に1。で、なんとか Greasemonkey とかの UserScript で対処できないかなーって思ったので、JavaScript とかミリも知らないくせにコピペプログラミングにチャレンジしました。
使用する拡張機能は Tampermonkey、スクリプトは ECMAScript5、jQuery のバージョンは 3.3.1 でした。
ヤツは alt にいる
思いついたのはとある Mastodon インスタンスでのこと。とりあえず絵文字の入ったトゥートを選択して検証したら、該当部分する img 要素の alt 属性がまんま元の絵文字そのものでした。おっ、これは意外と楽にいけるのでは?
ざっとググってみると、いましたいました、img 要素を alt 属性のテキストに置換してくれるスクリプト。世の中なんでも先人がいるなあ2。
というわけでこれをいじいじしてこんな感じに。DOM 要素の生成だけ直感的にわからなかったのでここを見ながらいじりました。
$("img[alt][class*=emojione]").each(function() { // alt 属性を持ち class に emojione を持つ img 要素について
const elem = $(this);
const altAttr = elem.attr("alt");
if (!altAttr) {
return;
}
const replaceElem = $("<span></span>", {
class: "textizedEmoji",
text: altAttr
});
elem.replaceWith(replaceElem);
});
これを jsshell に流し込んでやると見事再置換に成功。でも Tampermonkey に移植するとなにも起こらず。しばらく🤔になりました3が、実行タイミング時点ではトゥートの読み込みとかまったくされてないわけなので当然といえば当然です。それに一度の実行では新しいトゥートに対応できません。つまりこれを DOM の変化ごとに回しつづける必要があります。なにそれ。。ぜんぜんゎかんなぃ。。もぅマヂ無理。マリカしょ。。。ぃま青コウラ飛んでった。ごぃのことゎもぅどぉでもぃぃんだって。。
MutationObserver is watching you
DOM の変化をどうやって捉えるのか、~~そもそも DOM とはなんなのか、~~その答えを確かめるべく我々は Qiita の奥地に向かった。
見つけた記事によると DOMNodeInserted というものを使う方法と MutationObserver というものを使う方法があるらしい。前者はなにやら非推奨らしいので後者を使うことにします。参考にするのはここ。
対象要素となにを監視するのかを指定するらしい。でも、どこになにが出てくるかなんてサイトごとに違う。さて、どうしよう。
私が出した答え: 文書構造本体である body 要素を指定し、その子孫を監視する。
もっといい方法があるかもしれませんが思いつきませんしこれで。
監視オプションには childList と subtree を指定。これで body の子孫要素(つまり文書すべて)が監視できます。まるでディストピアみたいだ(直喩)。
$(function() {
const observer = new MutationObserver(records => { //オブザーバー、変更があった時に処理を実行
// [code_1]
});
observer.observe(document.body, {
childList: true, // 対象ノードの子ノード(テキストノードも含む)に対する追加・削除を監視
subtree: true // 対象ノードとその子孫ノードに対する変更を監視
}); //監視開始
});
試しに実行してみたら見事うまくいきました。
しあげはおかあさん
これでほぼいい感じなんですが、2 点ほど改善の必要がありました。
まずひとつ。Mastodon にはカスタム絵文字というものがありまして、インスタンスごとに設定された画像をトゥートに組み込める機能があります4。が、これで表示された画像も絵文字と同じフォーマットなうえ、alt にはカスタム絵文字を表示させるためのショートコードもどきが入っています。つまりこのまま走らせるとせっかく鯖缶が実装してくれた絵文字が実装前と同じ表示になります。これはいけない。
ふたつ。ご存知 Twitter でも絵文字を画像化する機能があるんですがそのクラス名はちょっと違う5。これもなんとかしたい。
そんなわけで[code_1]を以下のように改変。
$("img[alt][class*=emoji i]").each(function() { // 大文字小文字を区別せず、class 属性の値に emoji を含む
const elem = $(this);
const altAttr = elem.attr("alt");
if (!altAttr || /:/.test(altAttr)) { // コロンが含まれていた場合も置換を実行しない
return;
}
const replaceElem = $("<span></span>", {
class: "textizedEmoji",
text: altAttr
});
elem.replaceWith(replaceElem);
});
ちなみに、ひとつ目の副産物で絵文字の代わりにショートコードが入ってるタイプ(あなたがまさにいま見ているサイトとか……)での置換を防げるようになりました。うーん、こっちもなんとかしたいけどリスト的ななにか(ふんわり)が必要になりそう。
余談
まーた青鳥は余計なことを
ツイート部分のはこれで再置換できるのですが、なんか名前のところとかだけ実装がちょっと違うタイプで私激おこ6。こっちは UserStyle で消し去ってやった。
span.Emoji{ /* 見てのとおり単に消している */
display: none;
}
span.Emoji + span.visuallyhidden{ /* 消すために行っている処置に対抗呪文 */
clip: auto;
position:static;
}
すべてのサイトで実行するには
「すべてのサイトで作用する UserScript」ってあんまり歓迎されてないのか知りませんが、全サイトを指定する方法がざっとググった程度では見つからず、色々試行錯誤する羽目になりました。結論から言うと、以下のようなメタデータブロックを書けばうまくいきました。
// ==UserScript==
// @name Sample Script
// @namespace http://example.com/
// @version 1.0
// @description Script Sample
// @include *://*/*
// ==/UserScript==
このワイルドカードらしき「*」の挙動がよくわからない。0 文字以上の任意の文字パターンってところでしょうか。ちなみにネタバレすると「*」一文字でも動きます。でもこの方が URL を示していることがわかりやすいので。
-
こういうのやりたいなら個人的には Web フォントで対処すべきだと思うのでいろいろなグループはいろいろ頑張れ。 ↩
-
alt 属性は代替文字列ですから、すでに誰かがやってる可能性は十分あると思ったんですよね。こういう役立つあれこれをたくさん残してくれるおかげで私みたいな出来の悪いコピペプログラマが育ってしまう。はい、ちゃんと勉強します。 ↩
-
🤔になる、個人的に結構使う表現です。絵文字の中でも顔、emoticon は文末につけて感情を示すのに使うことが多いと思うんですが、私はこうやって文中に使うほうが好きです。だからなおさら「文字」であってほしいんですよね……。 ↩
-
ところによってはすべてが同じ絵文字だったりする。した。 ↩
-
名前に@を挟む感覚で絵文字挟む人結構いますよね。そうでもないか? ↩