CSS3
monaca
クソアプリ
MonacaDay 14

呪いの儀式もスマホでやる時代

先日、「呪い 方法」で検索していた時の話です。
誰にでもできる手軽な呪いとして、以下の方法が紹介されていました。

  1. 呪いたい人の名前を紙に書く
  2. nの初期値を1として、名前のうちn文字目までを「呪」に置き換えた文字列を書く
  3. n=文字数となるまで、nに1加算しながら2.の工程を繰り返す
  4. 最後に紙を赤く塗りつぶす

つまり仮に呪いたい人の名前が「長宗我部 寿限無長助」だった場合、以下のように紙に書くということです。

長宗我部寿限無長助
呪宗我部寿限無長助
呪呪我部寿限無長助
呪呪呪部寿限無長助
呪呪呪呪寿限無長助
呪呪呪呪呪限無長助
呪呪呪呪呪呪無長助
呪呪呪呪呪呪呪長助
呪呪呪呪呪呪呪呪助
呪呪呪呪呪呪呪呪呪

…面倒臭すぎません?
上記をコピペで書いただけでも面倒でした。手書きとなると面倒臭さはコピペの比じゃありません。
しかも最後に紙を赤く塗りつぶすっていうのも地味に大変ですね…。
刷毛と絵の具があれば楽に塗りつぶせそうですが、画材の準備も手間です。
全然手軽じゃありません。

そこで今回は、この呪いを手軽に実現するためのソリューションとなるスマホアプリをMonacaで作ってみました。

デモ動画

まずは以下の動画をご確認ください。

呪いの儀式もスマホでやる時代 pic.twitter.com/vJK1ISyOXO

— coboco (@_coboco) 2017年12月18日

(アプリ内で使用しているイラストは、ニコニ・コモンズのものを使用させて頂きました。ニコニ・コモンズIDは「nc153057」です)

それではソースコードを解説します。

ソースコード全文

index.html
<!DOCTYPE HTML>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
  <meta http-equiv="Content-Security-Policy" content="default-src * data: gap: https://ssl.gstatic.com; style-src * 'unsafe-inline'; script-src * 'unsafe-inline' 'unsafe-eval'">
  <script src="components/loader.js"></script>
  <script src="lib/onsenui/js/onsenui.min.js"></script>

  <link rel="stylesheet" href="components/loader.css">
  <link rel="stylesheet" href="lib/onsenui/css/onsenui.css">
  <link rel="stylesheet" href="lib/onsenui/css/onsen-css-components.css">

  <link rel="stylesheet" href="css/style.css">  
  <script src="js/app.js"></script>
</head>
<body>
  <ons-navigator id="navi" page="top.html"></ons-navigator>

  <!-- 名前入力画面 -->
  <template id="top.html">
    <ons-page id="topPage">
      <ons-toolbar>
        <div class="center">呪いのアプリ</div>
      </ons-toolbar>

      <div class="content">  
        <ons-input type="text" modifier="large underbar" placeholder="呪いたい人の名前" value="" id="name"></ons-input>
        <ons-button id="execButton" modifier="large" onclick="exec()">呪う!</ons-button>
      </div>
    </ons-page>
  </template>

  <!-- 呪い画面 -->
  <template id="curse.html">
    <ons-page id="cursePage">
      <p id="beforeName"></p>
      <p id="afterName"></p>
      <img id="image" src="img/nc153057.png"></img>
      <div id="curseArea"></div>
    </ons-page>
  </template>
</body>
</html>
css/style.css
/* TOP画面 */
#topPage .content{
  text-align: center;
  width: 70%;
  margin: 50px auto 0;
}

#topPage .content ons-input {
  display: block;
  margin: 0 auto 30px;
}

#topPage .content #execButton {
  margin-top: 50px;
}

/* 呪い画面 */
#cursePage {
  position:absolute;
  text-align:center;
}

#cursePage #image {
  width: 140px;
  margin: 80px 0 0 200px;
  opacity: 0;
}

#beforeName, #afterName{
  position:absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  margin: 80px auto;
  font-size: 50px;
}

/* 元の名前全体 */
#beforeName{
  color: black;
}

/* 呪文字列全体 */
#afterName{
  color: red;
}

/* 元の名前の中の、各1文字 */
#beforeName * {
  transition-property: opacity;
  transition-duration: 1s;
}

/* 画面全体を覆う赤いdiv要素 */
#curseArea{
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  width: 100%;
  height: 100%;
  background-color: red;
  opacity:0;
  transition-property: opacity;
  transition-duration: 5s;
}
js/app.js
// 呪い実行
function exec() {
  let name = document.querySelector('#name').value;
  let option = {
    animation: 'fade',
    data: { name: name }
  };
  // 呪い画面に遷移
  document.querySelector('#navi').pushPage('curse.html', option);
}

document.addEventListener('init', (event) => {
  let page = event.target;
  // 呪い画面を開いた時の処理
  if(page.id === 'cursePage') {
    // 名前の文字列を1文字ごとに分割し、span要素で囲む
    let nameString = page.data.name;
    let nameArray = nameString.split('');
    nameArray = nameArray.map((char, index) => {
      return `<span data="data-before-${index}" style="opacity:1">${char}</span>`;
    });
    document.querySelector('#beforeName').innerHTML = nameArray.join('');

    // 名前の文字数分「呪」の文字で構成された文字列を作る
    let curseArray = [];
    for(let i=0; i<nameString.length; i++) {
      curseArray.push(`<span data="data-after-${i}" style="opacity:0">呪</span>`);
    }
    document.querySelector('#afterName').innerHTML = curseArray.join('');

    // 1文字ずつ「呪」を表示、同時に元の名前を1文字ずつ非表示にする
    let lastindex;
    for(let i=0; i<nameString.length; i++) {
      setTimeout(() =>{
        // 元の名前を透明にする
        document.querySelector(`[data="data-before-${i}"]`).style['opacity'] = 0;
        // 「呪」を不透明にする
        document.querySelector(`[data="data-after-${i}"]`).style['opacity'] = 1;
      }, 2000 * (i+1)); 
      lastIndex = i+1;
    }

    // 赤く塗りつぶす処理
    setTimeout(() => {
      document.querySelector('#image').style['opacity'] = 1;
      setTimeout(() => {
        document.querySelector('#curseArea').style['opacity'] = 1;
      }, 4000);
    }, 2000 * (lastIndex) + 6000);

  }
});

このアプリのポイントは、CSS3によるアニメーション処理です。いい具合のタイミングでアニメーションがかかるように調整しながら書いていたら、setTimeout地獄になってしまいました…。本当はsetTimeoutを使わず全部CSS3で書いた方がスマートだとは思いますが、可読性重視ということでご容赦ください。

まず、元の名前と呪文字列は、同じ位置に表示されるようにCSSで調整しておきます。

name-area.png

各文字はspan要素で囲まれています。これにより、1文字ずつ表示/非表示の切り替え処理を行うことができます。
表示/非表示の切り替えはopacity(透明度)プロパティの操作によって実現しています。初期状態では元の名前は不透明(opacity:1)、呪文字列は透明(opacity:0)としておきます。

before-name.png

after-name.png

setTimeoutで時間差をつけながら、1文字ずつ元の名前を透明(opacity:0)に、呪文字列を不透明(opacity:1)にしていきます。

文字の表示状態切替
// 元の名前を透明にする
document.querySelector(`[data="data-before-${i}"]`).style['opacity'] = 0;
// 「呪」を不透明にする
document.querySelector(`[data="data-after-${i}"]`).style['opacity'] = 1;

このとき、元の名前は徐々に薄くなって消えるようなアニメーションをつけています。これはCSS3のtransitionによって実装しています。

元の名前を透明にしたときのアニメーション
#beforeName * {
  transition-property: opacity;
  transition-duration: 1s;
}

transition-propertyに指定したCSSプロパティが変更されたとき、変更が完了するまでの過程をtransition-durationに指定した時間をかけてアニメーション表示するという意味です。
文字がパッと切り替わるよりは、雰囲気が出るのではないでしょうか。
最初は「呪」を不透明にするときにもアニメーションをつけていたのですが、動かしてみると微妙だったのでやめました。なんでもかんでもアニメーションをつければ良いということではなく、メリハリが大事ですね。

最後に画面を赤くする処理も上記の処理と同じ仕組みで、画面全体を覆う赤いdiv要素をあらかじめ配置しておき、透明から不透明に変更しただけです。

これなら名前を入力すれば後は自動で呪いを実行してくれるので、とても手軽ですね。

注意事項

呪いを行ったことを相手に伝える行為は脅迫罪にあたります。
同時に、自分自身も呪いという行為を行うことで精神に支障をきたす恐れがあります。
ネガティブな感情を吐き出すことも時には大事ですが、呪いという手段を取る前に、まずは健全な手段を試してみてはいかがでしょうか。
「人を呪わば穴二つ」という言葉を聞いたことがあるかと思います。これは、人を呪うと必ずその報いを受けるという意味なのだそうです。
私は特別な訓練を受けているので大丈夫ですが、皆さんは安易に真似をしないようにしてください。