ネタバレ防止機能を修正アップデート

12/05/2020 0 Comments

ネタバレ防止機能とは

概要はこちらの記事を御覧ください。

修正箇所

1.CSSの修正

.ink-removed の背景を inherit にして、親要素(の背景色)をそのまま継承するように変更しました。お使いのCSSの該当箇所を、コピペで変更して下さい。

/*初めのbackgroundの色が海苔を剥がした際の背景色となるので、適宜書き換えてください*/
.ink-removed{ /*↓ココ*/
  background : inherit;
  display : inline
}
.inked{
  background : #000;
  filter : brightness(0%);
  display : inline;
  cursor : pointer
}
.inked *{
  filter : brightness(0%);
  pointer-events : none
 }

2. JavaScriptの修正

修正箇所は3点です。単にアップデートの必要な方はファイルを以下に差し替えて下さい。「//」で始まる行はただのコメントですので、削除していただいて構いません。

document.addEventListener('DOMContentLoaded', function(){
  const targetClass ='t_b';
  const regExp = /\|\|([\s\S]*?)\|\|/g;
//海苔範囲を正規表現で探して<div class="inked"></div>で覆う
  let postBodies = document.getElementsByClassName(targetClass);
  for (let postBody of postBodies){
    const txt = postBody.innerHTML.replace(regExp, ' <div class="inked">$1</div>');
    postBody.innerHTML = txt;
  }
//出来上がった海苔範囲に、クリックで剥がれるイベントリスナを追加
//具体的には <div> のクラスを ink-removed に変更
  let inkedDivs = document.getElementsByClassName('inked');
  for (let inkedDiv of inkedDivs){
    //inkedDiv.addEventListener('click', removeInk, false);
    //inkedDiv.setAttribute('onclick', 'removeInk();');
    inkedDiv.setAttribute('onclick', 'removeInk(this);');
  }
});
//function removeInk(){
function removeInk(elem){
//event.target.setAttribute('class','ink-removed');
//this.setAttribute('class','ink-removed');
  elem.setAttribute('class','ink-removed');
}

一点目は簡単。コード中にコメントした通り、イベントリスナの追加を複数行うために for 文を使うのですが、ココのループ内で

inkedDiv.addEventListener('click', function(){
  this.setAttribute('class','ink-removed');
}, false);

としていたものをひとまず、

inkedDiv.setAttribute('onclick', 'removeInk();'); 
…… 
function removeInk(){
  event.target.setAttribute('class','ink-removed');
}

のように書き換えました。
二点目は、イベントリスナの追加に関するこの部分です。

//他のスクリプトで innerHTML を書き換える場合に備えて、イベントリスナの追加方法を変更
//inkedDiv.addEventListener('click', removeInk, false);
inkedDiv.setAttribute('onclick', 'removeInk();');

一体何事か、順を追って説明しましょう。
私の作っているような「既存のHTMLをクライアント側で書き換えるスクリプト」では、 innerHTML= を使って要素を書き換えることがあるのですが、これによって既存の要素が破棄され、新たな要素が生成されます。このスクリプトで言えば、

……
  let postBody = document.getElementsByClassName(targetClass);
……
  const txt = postBody.innerHTML.replace(regExp, '<div class="inked">$1</div>');
  postBody.innerHTML = txt;

の部分で、 .replace() によって置換した文字列を .innerHTML= でdocumentの要素に押し込んでいます。これによって、代入前の要素は破棄され、新たに代入されたtxtを元に要素が生成されます。
と、この操作の前後で要素が不連続だというわけです。当然(?)、この操作以前に .addEventListner() で追加されたイベントリスナは失われてしまいます。
これを破壊しないためには textNode まで子孫を追っていって置換ということになりますが、要素を破棄させないままでの書き換えが正しく出来るのかは詰めて考えていません。出来ないことはないのでしょう。
なぜこんなことになるかといえば、.replace() が、置換の結果を返し、オブジェクトを変更しないメソッドであるためです。
「じぇいくえり(検索回避のための表記)」には要素内の文字列を変更する = オブジェクト自身を変更するメソッドがあるらしいですが、これが要素を破壊するか否かについては全く調べていません。何だか「じぇいくえり」自体を卒業?脱出?しとけって情報が多いので。
まあ要するに、他の .innerHTML= を用いるスクリプトを他にも書き、機能が異なるのだから別のスクリプトとして管理したいと思ったとき、 .addEventListner() (或いは .onclick() )ではあとに実行されたものしか正常に機能しないと思ったほうが楽なわけです。
で、何を使うかとなると、HTML自体を変更して、.innerHTML= に耐えてもらう方法しか思い付きませんでした。HTMLは文書構造を保持した文書、そこへ処理を組み込むな、というのはよく言われ、それには全く賛成です。今このスクリプト(及び、執筆中の他のスクリプトで)やっているのはクライアント側での最終処理であって、HTMLに処理を記述してプログラムから処理するのではなく、プログラムからHTMLを処理した結果として文書に処理命令に必要な情報が加えられただけなので、こういう思想を重んじる方々にも、きっとさほど怒られはしないでしょう。そうでないと、「スクリプトの処理に必要だから」以外の理由を持たずに与えられた、或いは与えられなかったclassやidなんかも全て怪しからん(googleIMEで変換できないのね……)ということになるはずです。
そんなわけで、

for (let postBody of postBodies){
  const txt = postBody.innerHTML.replace(regExp, ' <div class="inked" onclick="removeInk();">$1</div>');
  postBody.innerHTML = txt;
}

としてしまってもよいのですが、以前との比較をココで説明するため、.addEventListner() を書き換えたことを明示するために、敢えて上では以前と同じ流れのスクリプトを提示しています。
閑話休題。
三点目の変更点は変数宣言をにvarだけでなくlet、constを用いたり、for( let a of b )構文を用いたりと、古いブラウザを見捨てた書き方に改めたこと。後者に伴って、変数の命名も少々変更しました。

気になったこと

このスクリプトを修正していて気になったことがありました。誰かの役に立てばと思い、これも記しておきます。
次のスクリプトを見て下さい。右下の倍率は x0.5 をオススメします。

See the Pen
poEvRvy
by yume1000ya (@yume1000ya)
on CodePen.


fooとBar
“this”をStringとして表示させ、”this”の正体を見極める作業を、2つの実装方法を前後させて試しています。
何度かポチポチすれば、違いに気付くでしょう。始めの変更点を示す際にも、さり気なく、

inkedDiv.addEventListener('click', function(){
  this.setAttribute('class','ink-removed');
}, false);
……
  inkedDiv.setAttribute('onclick', 'removeInk();');
……
function removeInk(){
  event.target.setAttribute('class','ink-removed');
}

と、関数内で this を event.target で置き換えていました。

ただ、今度は event はあんまり触るな、廃止するとのお達しが目に止まりました。で、結局のところonclickで呼び出される関数には this としてイベントトリガを渡すことができるとのことで、

inkedDiv.setAttribute('onclick', 'removeInk(this);'); 
……
function removeInk(elem){
  elem.setAttribute('class','ink-removed');
}

と、こんな風に呼び出し、処理させることになりました。

さて、ここで何が起きているかというと、.addEventListner()はイベントリスナそのものを追加するのに対して、.setAttribete(‘onclick’,…)の形式では、イベントリスナ onclick に対してイベントハンドラを追加する、ということなのでしょう。一度イベントリスナを経由して呼び出されているために、呼び出し元の this が変化しているものと思われます。

最後に

以上、これから公開予定の他の機能を導入するでもなければほとんど必要のないプログラム修正とその解説でした。

お互いに、素敵なおーぷんまとめサイトの管理人ライフを。

それから、フラワーナイトガールを遊んでみることを断固!オススメします!

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です