ホラー演出を CSS と JavaScript で作りたい。
この声にお答えします。
対象読者
- ホラー演出を実装したい人
- CSS・JavaScript による表現の幅を広げたい人
- 変わった演出が好きな人
実装したい人はもちろんですが、見て楽しい記事だと思うので、興味本位の読者も歓迎です。
コードはコピペしてもらっても構いません。
それではやっていきましょう。
マウスカーソルに合わせて移動するライトアップ
マウスカーソルが当たっている部分だけ明るくなります。
See the Pen spot-light by jun (@cpvhoorw-the-sasster) on CodePen.
コードの概要
document.addEventListener('DOMContentLoaded',...
ではDOM構築後に中の処理が実行されるようにしています。
kaibunsho.addEventListener('mouseleave',...
では怪文書のエリアからマウスカーソルが出た時に、スポットライトがなくなり、元の暗い背景色が全体にかかる処理を書いています。
コード:kaibunsho.addEventListener('mouseleave'...
// 怪文書のエリアからマウスカーソルが抜けた時にスポットライトを消す
kaibunsho.addEventListener('mouseleave', function (e) {
kaibunsho.setAttribute(
"style",
`background-color:rgba(0,0,0,0.9); `
);
})
kaibunsho.addEventListener('mousemove'...
では怪文書のエリアでマウスカーソルを動かした時に、スポットライトがマウスカーソルに合わせて移動する処理を書いています。
コード:kaibunsho.addEventListener('mousemove'...
// 怪文書エリアでマウスカーソルを動かすとスポットライトが動く
kaibunsho.addEventListener('mousemove', function (e) {
const rect = this.getBoundingClientRect();
// 要素の左上を原点(0,0)とした時のマウスカーソルの位置を設定
const x = e.clientX - rect.left;// 要素の左端からのマウスカーソルのX座標
const y = e.clientY - rect.top;// 要素の上端からのマウスカーソルのY座標
// マウスカーソルの位置にスポットライトをあてるCSSスタイルの設定
kaibunsho.setAttribute(
"style",
`background-image:
radial-gradient(circle at ${x}px ${y}px,
#fff 0px, #fff 30px, transparent 100px)`
);
});
コードの詳しい解説
マウスカーソルの座標の取得
<div class=”kaibunsho”>
)の左上を原点(0,0)とした時のマウスカーソルの座標を変数 x と変数 y
に代入しています。
コード:マウスカーソルの座標の取得
- javascript
-
const rect = this.getBoundingClientRect(); // 要素の左上を原点(0,0)とした時のマウスカーソルの位置を設定 const x = e.clientX - rect.left;// 要素の左端からのマウスカーソルのX座標 const y = e.clientY - rect.top;// 要素の上端からのマウスカーソルのY座標
コードの解説:マウスカーソルの座標の取得
clientX/Y
でビューポート(ブラウザの表示エリア)の左上を原点(0,0)とした時のマウスカーソルの座標を返しています。
getBoundingClientRect()
メソッドではビューポートの左上を原点(0,0)とした時の要素の位置を返しています。
つまり式を解読すると以下になります。
const x = e.clientX - rect.left;
→(要素の左端を座標 0 とした時のマウスカーソルの水平方向の座標) = (ビューボートでのマウスカーソルの水平方向の位置) – (ビューポートでの要素の左端の座標)
const y = e.clientY - rect.top;
→(要素の上端を座標 0 とした時のマウスカーソルの垂直方向の位置) = (マウスカーソルのビューボート内で垂直方向の位置) – (ビューポートでの要素の上端の座標)
スポットライトの実装
上記の「マウスカーソルの座標の取得」で代入した
x
, y
を background-image
で使用しています。これがスポットライトの実装になります。
コード:スポットライトの実装
- javascript
-
kaibunsho.setAttribute( "style", `background-image: radial-gradient(circle at ${x}px ${y}px, #fff 0px, #fff 30px, transparent 100px)` );
コードの解説:スポットライトの実装
コードを解読すると以下になります。
怪文書の要素(<div class="kaibunsho">
)の背景色をグラデーションで設定
グラデーションは円形
円の中心は(x,y)
0px
(円の中心)から 30px
にかけては色#fff
から #fff
のグラデーション(同じ色なので実質は #fff
一色)
30px
から 100px
にかけては #fff
から transparent
のグラデーション
100px 移以降は
transparent
( transparent
が設定されているところでは background-color: rgba(0, 0, 0, 0.9);
が適用される)。
補足
-
イベントについて
-
mouleave
- 要素からマウスカーソルが出た時にイベントが発行します
mousemove
- 要素内でマウスカーソルが移動するとイベントが発行します
-
座標位置を表すプロパティとメソッドについて
-
clientX/Y
- ビューポート(ブラウザの表示エリア)の左上の座標が原点(0,0)
screenX/Y
-
デバイスのディスプレイの左上の座標が原点(0,0)
もしウィンドウを移動していた場合はscreenX/Y
とpageX/Y
がずれることになる。 pageX/Y
-
ページの左上の座標が原点(0,0)
スクロールしていた場合は、原点がウィンドウ外にいく。その結果、pageX/Y
とclientX/Y
の値がずれる offsetX/Y
- 要素の左上の座標が(0,0)。
getBoundingClientRect()
-
要素の寸法とビューポートでの位置を返す
- 要素の位置を表すプロパティ
left
top
right
bottom
- 要素の寸法を表すプロパティ
width
height
要素のwidth(もしくはheight) + padding + border-width
の値と同じ
画面を埋め尽くすテキスト
画面にテキストが時間差で現れます。
テキストが現れる位置と大きく表示される一文字はランダムで選出します。
また、何個テキストを表示するのかは設定できます。
See the Pen text-emerge by jun (@cpvhoorw-the-sasster) on CodePen.
コードの概要
window.onload = startAnime
で、startAnime
関数が実行されます。
コード:startAnime
-
function startAnime() { // 追加するテキストを定義 let text1 = ここにいるよ"; let text2 = "ここにもいるよ"; let text3 = "どこに行くの"; let text4 = "終わらせない"; let textArray = new Array(text1, text2, text3, text4) // テキストを設定した要素を追加する回数 let textCount = 5; //テキストを挿入する要素の幅と高さを取得。あとでテキストの配置場所を指定する際に参照する。 let kabe = document.querySelector(".kabe"); let kabeHeight = kabe.offsetHeight; let kabeWidth = kabe.offsetWidth; //親要素にクラス名 play が付いてなければ処理をおこなう if (!kabe.classList.contains("play")) { emergeText(); }
- JavaScript
コードの解説:startAnime
追加されるテキストと追加するテキスト数を設定。
またテキストが表示される領域の<div class="kabe">
の幅と高さを取得しておく。
これは後にテキストの配置場所を指定する際に参照する。
emergeText
関数が実行されます
コード:emergeText
- javascript
-
// テキストを含む新しく要素を作成しテキストを配置する処理 function emergeText() { --textCount; kabe.classList.add("play"); // 新しくテキストを追加するために要素を作成 let newElement = document.createElement("span") newElement.classList.add("appearText", "text") // textArray からランダムにテキストを選択後、テキスト内の一文字にspanタグをかける let originalText = textArray[Math.floor(Math.random() * textArray.length)]; let rndIndex = Math.floor(Math.random() * originalText.length); let newText = originalText.replace(originalText[rndIndex], '<span class="big-font">' + originalText[rndIndex] + '</span>'); newElement.innerHTML = newText; // 新規作成したテキストの配置場所をランダムに指定 let rndPosY = Math.floor(Math.random() * kabeHeight); let rndPosX = Math.floor(Math.random() * kabeWidth); newElement.style.top = rndPosY + "px"; newElement.style.left = rndPosX + "px"; // 作成した要素を追加する kabe.appendChild(newElement); // 設定したテキスト数に達するまでテキスト作成の処理を実行する if (textCount == 0) { kabe.classList.remove("play");//なくなった場合は親要素のplayクラスを削除 } else { setTimeout(function () { emergeText(); }, 500); //0.5秒間隔でアニメーションをスタート } }
コードの解説:emergeText
テキストを追加するために新しく span
要素を作成し、クラス名、テキスト、配置位置を設定していく。
設定後に <div class="kabe">
に
kabe.appendChild(newElement)
で要素を挿入。
上記の span
要素作成し挿入する処理を textCount
回数分繰り返し行う。
setTimeout()
メソッドで 0.5 秒間間隔で関数 emergeText
を実行している。
コードの詳しい解説
テキストの文面の設定
コード:テキストの文面の設定
- JavaScript
-
function startAnime() { // 追加するテキストを定義 let text1 = "ここにいるよ"; let text2 = "ここにもいるよ"; let text3 = "どこに行くの"; let text4 = "終わらせない"; let textArray = new Array(text1, text2, text3, text4) ⋮ function emergeText() { ⋮ // 新しくテキストを追加するために要素を作成 let newElement = document.createElement("span") newElement.classList.add("appearText", "text") // textArray からランダムにテキストを選択後、テキスト内の一文字にspanタグをかける let originalText = textArray[Math.floor(Math.random() * textArray.length)]; let rndIndex = Math.floor(Math.random() * originalText.length); let newText = originalText.replace(originalText[rndIndex], '<span class="big-font">' + originalText[rndIndex] + '</span>'); newElement.innerHTML = newText;
- CSS
-
.big-font { font-size: 2rem; }
コードの解説:テキストの文面の設定
startAnime
関数の最初の方で四個テキストを設定して、配列 textArray
に格納しています。
抜粋した emergeText
関数のコードで以下を行なっています。
新しい span
要素の作成
新しい span
要素のクラスを appearText
と text
に指定
設定した四個のテキストからランダムに一個のテキストを選出し、テキストの中の一文字をランダムに選出し replace
メソッドで、<span class=”big-font”>[一文字のテキスト]</span>
に置換
CSSプロパティの設定でクラス
big-font
が指定されている要素のフォントサイズを大きくしています。
innerHTML
で作成した要素のテキストとして設定しています。
テキストの配置場所の指定
コード:テキストの配置場所の指定
- JavaScript
-
function startAnime() { ⋮ //テキストを挿入する要素の幅と高さを取得。あとでテキストの配置場所を指定する際に参照する。 let kabe = document.querySelector(".kabe"); let kabeHeight = kabe.offsetHeight; let kabeWidth = kabe.offsetWidth; ⋮ function emergeText() { ⋮ // 新規作成したテキストの配置場所をランダムに指定 let rndPosY = Math.floor(Math.random() * kabeHeight); let rndPosX = Math.floor(Math.random() * kabeWidth); newElement.style.top = rndPosY + "px"; newElement.style.left = rndPosX + "px";
- CSS
-
.kabe { width: 300px; height: 300px; ⋮ position: relative; ⋮ .text { ⋮ position: absolute;
コードの解説:テキストの配置場所の指定
変数 kabeHeight
、kabeWidth
に <div class=”kabe”>
の高さ、幅を代入しています。
emergeText
関数の中で rndPosY
、rndPosX
に <div class="kabe">
の高さ、幅を上限とした整数を代入し、
新しく作成した要素 newElement
のCSSプロパティ top
、left
に 数値が rndPos
、
rndPosY
の px
を指定しています。
テキストを含む span
要素に position:absolute
の親要素(<div class="kabe">
)に
position:relative
を指定しているので、
span
要素が <div class="kabe">
要素の左上を起点に、top
、left
で指定された位置に配置されます。
テキスト追加を複数回行う処理と回数の指定
コード:テキスト追加を複数回行う処理と回数の指定
function startAnime() {
⋮
// テキストを設定した要素を追加する回数
let textCount = 5;
⋮
// テキストを含む新しく要素を作成しテキストを配置する処理
function emergeText() {
--textCount;
⋮
// 作成した要素を追加する
kabe.appendChild(newElement);
// 設定したテキスト数に達するまでテキスト作成の処理を実行する
if (textCount == 0) {
kabe.classList.remove("play");//なくなった場合は親要素のplayクラスを削除
} else {
setTimeout(function () { emergeText(); }, 500); //0.5秒間隔でアニメーションをスタート
}
}
}
コードの解説:テキスト追加を複数回行う処理と回数の指定
変数 textCount
にテキストを設定した要素を追加する回数を代入してます。
emergeText
関数を実行するたびに textCount
をカウントダウンし、0
になるまで emergeText
を呼び出しています。
emergeText
関数を呼び出す時は、setTimeout
を使用して、0.5秒の間隔を置いています。
これでテキストが一つずつ時間を置いて現れるようになります。
テキストが徐々に現れるアニメーション
span
要素は追加直後は透明です。そこから透明度を徐々に下げることで、文字が徐々に現れるようなエフェクトをかけています。
コード:テキストが徐々に現れるアニメーション
- CSS
-
.text { ⋮ /* 要素にアニメーション text_anime_on を適用 */ animation-name: text_anime_on; /* アニメーション完了まで5s */ animation-duration: 5s; /* アニメーションの変化が最初ゆっくりで終了まで加速する */ animation-timing-function: ease-in; /* keyframesで最後に設定された値を保持する */ animation-fill-mode: forwards; } ⋮ @keyframes text_anime_on { 0% { opacity: 0; } 100% { opacity: 1; } }
コードの解説:テキストが徐々に現れるアニメーション
@keyframes
でアニメーションの動きを指定しています。
5秒間かけて文字が透明(opacity: 0;
)の状態から透明度を下げて表示されるように指定しています。
補足
-
Math.random()
メソッドの使い方について -
Math.random()
は0~0.999...
をランダムに返します。Math.floor()
は小数点を切り捨てます。
例えばMath.floor(1.2568)
では、1
となります。なので以下の式では
var
を最大値とした、整数がランダムで返されます。
Math.floor(Math.random() * var);
今回の実装では以下の式で使っています。
追加するテキストをランダムに選出
let originalText = textArray[Math.floor(Math.random() * textArray.length)];
選出したテキストの中の一文字をランダムに選出。(この一文字だけサイズを大きくする)
let rndIndex = Math.floor(Math.random() * originalText.length);
テキストの配置位置をランダムに選出。
let rndPosY = Math.floor(Math.random() * kabeHeight); let rndPosX = Math.floor(Math.random() * kabeWidth);
まとめ
ホラーエフェクトは実装していて楽しいです。
楽しい理由は「ユーザーを怖がらせる」という明確な目的があるからだと思います。
コーディングすること自体が目的でないコーディングを感じてもらって、少しでも楽しんでもらえたら嬉しいです。
コメント