この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
リリィ・シュシュのすべて という映画をご存知でしょうか?2001年に公開された岩井俊二監督の作品で、当時は結構好きで何度も観返したものでした。
劇中にて文字がカシャカシャとランダムに動きながら文章が表示されていくエフェクトがあるのですが、これを JavaScript で作ってみました。Flash 全盛のころから Web コンテンツにはよく取り入れられてるエフェクトではありますが、使い所さえ間違えなければ今でも充分に通用するカッコよさを持っています。
今回は jQuery を使わずに素のJavaScript (Vanilla JavaScript) だけでプラグインを作りました。このシリーズの趣旨から大きく外れてますが、そこは仕方ないね。
LetterTicker - 文字をランダムでカシャカシャ動かしながらテキストを表示させる
Step.1 | とりあえず動くものを作ってみる
まずは動くものから作っていきます。最終的にはクラス化してオプションパラメータ等を設定できるようにしますが、まずはベタ書きでもいいので動くところまでやってみるとします。
<p id="aletter">Hello, world!</p>
(function() {
var node = document.querySelector('#letter');
var str = node.textContent.split('');
var types = [];
var step = 8;
var fps = 24;
for (var i=0; i<str.length; i++) {
var ch = str[i];
if (ch === ' ') {
types[i] = 'space';
} else {
types[i] = 'symbol';
}
}
node.textContent = '';
// ランダム文字を織り交ぜながら一文字ずつ表示させる
var ticker = function(start) {
var strClone = str.slice(0);
if (start > str.length) {
// 全ての文字を表示したので終了
return;
}
for (var i=Math.max(start, 0); i<str.length; i++) {
if (i < start + step) {
strClone[i] = randomChar(types[i]);
} else {
strClone[i] = '';
}
}
node.textContent = strClone.join('');
return setTimeout(function() {
ticker(start + 1);
}, 1000 / fps);
};
// ランダム文字を生成して返す
// 表示する文字がスペースの時は何も返さない
var randomChar = function(type) {
var pool;
switch (type) {
case 'symbol':
pool = ',.?/\\(^)![]{}*&^%$#\'"';
break;
default:
pool = '';
}
var arr = pool.split('');
return arr[Math.floor(Math.random() * arr.length)];
};
ticker(step * -1);
}).call(this);
[/javascript]
<ol>
<li>表示したい文章を一文字単位の配列にします <em>(※ 4行目)</em></li>
<li>その配列をループ処理にかけて格納されてる文字が半角スペースか否かをもう一つの配列で管理します。 <em>(※ 9 ~ 16行目)</em></li>
</ol>
<p>ここまでで下準備が出来ました。</p>
<p>ランダムな文字列を織り交ぜながら一文字ずつ正しい文字を表示していきます。表示する文章の内容は <tt>str</tt> という変数に格納されていますが、実際に画面上に表示するのは22行目で作成した <tt>strClone</tt> という str を複製して作られた変数の中身です。strClone には画面に表示したい文章が一文字単位の配列として格納されています。この配列の一部分を対してランダムな文字列で上書きしていき、ランダム文字から後ろは空欄で消し、最後にその内容を画面に表示します。</p>
<p>ランダム文字での上書きは配列の先頭から行いますが、 <tt>ticker</tt>メソッドを再帰的に呼び出す度に上書き開始位置をひとつずつ後ろにずらしていくことで、あたかも一文字ずつ画面に表示されていくように見せることが出来ます。</p>
<h3>Step.2 | プラグイン(クラス)化してみる</h3>
<p>基本ロジックは出来たので、クラス化してオプションパラメータを渡せるようにしてみます。</p>
<h5>LetterTicker.js</h5>
<p>ランダム表示する文字列の長さと FPS だけではつまらないので、表示する文章をターゲットとなるDOMに予め記述されたものだけでなく、任意の文章で上書きできるように <tt>text</tt> を渡せるようにしました。さらによりプラグインらしくするために表示終了後に実行させたいコールバック関数も渡せるようにしました。</p>
<p>使い方は以下のとおりです。</p>
letterTicker = new LetterTicker('.letter', {
callback: function() {
return console.log(txt);
},
step: 8,
fps: 60,
text: 'Hello, world!!!'
});
製作自体は CoffeeScript で書いたので、そちらのコードも載せておきます。
LetterTicker.coffee
[coffee] class window.LetterTicker step: 8 fps: 24 text: '' callback: ()-> str: [] types: [] nodes: null
constructor: (selector, opts)-> @nodes = document.querySelectorAll selector unless @nodes then return
mergeOptions.call @, opts
for node in @nodes if @text @str = @text.split '' else if node.textContent @str = node.textContent.split '' else continue
@types = []
for i in [0...@str.length] ch = @str[i] if ch == ' ' @types[i] = 'space' else @types[i] = 'symbol'
node.innerHTML = '' @shuffle(node, @step * -1)
# private mergeOptions = (opts)-> for key, value of opts if value @[key] = value @
randomChar = (type)-> switch type when 'symbol' pool = ',.?/\\(^)![]{}*&^%$#\'"' else pool = ''
arr = pool.split '' arr[Math.floor(Math.random() * arr.length)]
# public shuffle: (node, start)-> len = @str.length strCopy = @str.slice 0
if start > len @callback.call node return
for i in [Math.max(start, 0)...len] if i < start+@step strCopy[i] = randomChar.call @, @types[i] else strCopy[i] = '' node.textContent = strCopy.join '' setTimeout => @shuffle node, start+1 return , 1000 / @fps @ [/coffee]
終わりに - 利用上の注意
文字列の長さの二乗 ぶんだけループ処理を行うのに加えて、文字列の長さの回数だけ画面に描画処理を行うため、結構な負荷がかかるのは言うまでもありません。JavaScript の処理の中でも DOM の更新は飛び抜けて高負荷なので、ページの見出しなど文字数が少ない箇所への利用にとどめておくのが良いでしょう。