文字毎に簡単なエフェクトを追加する方法 – No jQuery
テーマで簡単に設定できるようにテキストのエフェクトを追加できるようにしようと、試しでとりあえずこんな感じのエフェクトを1つ作成しましたので、備忘録として文字毎にエフェクトを与える方法を書いておきます。
jQueryだとプラグインが結構見つかるんですが、使わないようにしようとすると案外探すのが面倒になり、もうどうせなら自分で作ってみるかと。
とりあえずこんな感じです。
ちょっとゆっくり過ぎるかもしれません。
まずはHTMLを用意
個人的にはタグ内のHTMLを正規表現で変換してしまうのが一番手っ取り早かったので、今回はその方法で行きます。
<p class="text-spread-out">Testing <em>Paragraph</em> for Effect</p>
例えば、このようなHTMLタグがあったとします。
実際に書いてみると、エフェクトの付いたこの下にあるような状態になります。
Testing Paragraph for Effect
これを以下のように変換することで、各文字にエフェクトを与えることができるようになります。
<p class="text-spread-out">
<span ... class="with-animation-spread-out" data-test="">T</span>
<span ... class="with-animation-spread-out" data-test="">e</span>
<span ... class="with-animation-spread-out" data-test="">s</span>
<span ... class="with-animation-spread-out" data-test="">t</span>
...
</p>
包括するクラス「text-spread-out」を対象にして、テキストを文字毎にタグで囲ってやる必要がありますので、JavaScriptでその処理は行います。
各文字をタグで囲うには
JavaScriptで変換する処理です。
対象となるセレクタを使用して、まとめて取得して処理してしまいます。
const targets = document.querySelectorAll( '.text-spread-out' );
targets.forEach( ( el, index ) => {
var isNowInTag = false;
el.innerHTML = el.innerHTML.replace( /[^\s]/g, ( matched, p1, string ) => {
// HTMLタグはスキップ
if ( isNowInTag ) {
if ( '>' === matched ) {
isNowInTag = false;
return matched;
}
return matched;
}
if ( '<' === matched ) {
isNowInTag = true;
return matched;
}
// SPANタグでラッピング
var spanTag = document.createElement( 'span' );
spanTag.classList.add( 'with-animation-spread-out' );
const wrappedTag = this.wrapTextWithSpan( matched );
wrappedTag.innerText = matched;
return wrappedTag.outerHTML;
} );
} );
これで各文字をクラス「with-animation-spread-out」を持つSPANタグで囲われます。
実際はスペースの場合にスキップするなど、もうちょっと工夫した方がいいかもしれません。
ここまでやってしまうと、ホバーやクリック程度ならCSSだけでもある程度エフェクトがかけられます。
文字そのものを決まった法則に基づいて変換するにしても、SPANタグにデータ属性として別の文字を与えることもできますから、プロパティの関数を使ってデータを参照すればいいだけです。
では、今回のスクロールで出すようなエフェクトを操作する場合はどうでしょうか?
エフェクトのタイミングをJavaScriptでコントロール
文字毎にラッピングしたHTMLを用意した状態にしておきます。
<p class="text-spread-out">
<span ... class="with-animation-spread-out" data-test="">T</span>
<span ... class="with-animation-spread-out" data-test="">e</span>
<span ... class="with-animation-spread-out" data-test="">s</span>
<span ... class="with-animation-spread-out" data-test="">t</span>
...
</p>
イベントによりエフェクトがトリガーされるタイミングで良いので、まずはラッパーとなるタグを指定して、そこから文字をクラスなどで検索をかけて全部取得して処理します。
書くとこんな感じです。
const paragraph = document.querySelector( 'text-spread-out' );
const chars = paragraph.querySelectorAll( '.with-animation-spread-out' );
var key = 0;
var interval = 100; // ms
const execEachChar = function() {
// 文字が存在するかチェック
if ( ! char.hasOwnProperty( key ) ) {
return;
}
// 文字を取得
const char = chars[ key ];
/**
* charを使用してエフェクトを出すように調整。
**/
// 次の文字があるなら、intervalミリ秒ずらして次の文字へ
key++;
if ( char.hasOwnProperty( key ) ) {
setTimeout( execEachChar, interval );
}
};
あとはCSSアニメーションとsetTimeoutの遅延などでエフェクトのタイミングをずらしたり調整します。
上の例だと100ミリ秒遅らせながら処理しています。
エフェクトを出すように調整とありますが、具体的に僕の場合だと、CSSのプロパティ定義にトリガーされる条件となるクラスを2つにしております。
.with-animation-spread-out {
display: inline-block;
opacity: 0;
transform: ...;/* 自由に定義してください */
transition: opacity 2s ease 0s, transform 2s ease 0s;
}
.with-animation-spread-out.entered {
opacity: 1;
transform: none;
}
このようにして、クラス「entered」が追加された際にエフェクトと一緒にテキストが表示されるようにしています。
文字を散らすには…
デモとして紹介したようなエフェクトはJavaScriptで乱数を与える必要があります。
実際に使用しているCSSプロパティ「transform」に使用しているのは以下の3点。
- translate3d
- rotate3d
- scale
他のプロパティやtransformに他の変化も追加しても良いかもしれませんが、まぁ別に今のままでも良いかと妥協。
とりあえず以上の3点を使用するにあたり、必要なデータが8点。
- translateX
- translateY
- translateZ
- rotateX
- rotateY
- rotateZ
- rotateA
- scale
あとは乱数を使用して、これらのデータをランダムに生成するだけです。
とりあえず使用する関数を定義。
MDNでも紹介されている一般的な関数です。
乱数を取得
function getRandomArbitrary(min, max) {
return Math.random() * (max - min) + min;
}
端数処理
function round(number: number, precision: number) {
var shift = function (number, precision, reverseShift) {
if (reverseShift) {
precision = -precision;
}
var numArray = ("" + number).split("e");
return +(numArray[0] + "e" + (numArray[1] ? (+numArray[1] + precision) : precision));
};
return shift(Math.round(shift(number, precision, false)), precision, true);
}
では、文字をSPANタグでラッピングする際に、以下のようにしてプロパティに使用するデータを付与してみます。
// データの定義
const translateX = getRandomArbitrary( -100, 100 );
const translateY = getRandomArbitrary( -100, 100 );
const translateZ = getRandomArbitrary( -100, 100 );
const rotateX = round( getRandomArbitrary( -10, 10 ), 1 );
const rotateY = round( getRandomArbitrary( -10, 10 ), 1 );
const rotateZ = round( getRandomArbitrary( -10, 10 ), 1 );
const rotateA = getRandomArbitrary( 0, 360 );
const scaleChar = round( getRandomArbitrary( 0, 10 ), 1 );
// タグの生成
const spanTag = document.createElement( 'span' );
spanTag.classList.add( 'with-animation-spread-out' );
// データの付与
spanTag.style.setProperty('--translate-x', translateX + 'px');
spanTag.style.setProperty('--translate-y', translateY + 'px');
spanTag.style.setProperty('--translate-z', translateZ + 'px');
spanTag.style.setProperty('--rotate-x', rotateX.toString());
spanTag.style.setProperty('--rotate-y', rotateY.toString());
spanTag.style.setProperty('--rotate-z', rotateZ.toString());
spanTag.style.setProperty('--rotate-a', rotateA + 'deg');
spanTag.style.setProperty('--scale', scaleChar.toString());
// 他にも編集があれば、自由に加えられます。
spanTag.innerText = matched;
return spanTag.outerHTML;
こんな感じで、各文字のCSSプロパティの変数として定義していきます。
こうしておけば静的なCSSプロパティでも、「var(–translate-x)」のようにして、設定した変数を取得できます。
本当はデータセットとプロパティの関数「attr()」を使用したいところですが、文字列としてしか認識されないのでCSSプロパティとして機能するに至らず、結局そうした際はJavaScriptによりCSSプロパティをセットしなくちゃいけませんので、2度手間な気がしました。
あとは、すでに紹介した通りに、トリガーする条件となるクラスをJavaScriptで追加してやるだけで、文字毎のエフェクトが出るようになります。
ちなみに「transform」の値はこんな感じです。
transform: translate3d(var(--translate-x),var(--translate-y),var(--translate-z)) rotate3d(var(--rotate-x),var(--rotate-y),var(--rotate-z),var(--rotate-a)) scale(var(--scale));
プラグインを使用すると便利ですが、作り方を知っておくと役立つ事もあるかもです。
こういった処理を行ってくれるクラスを用意しておけば、ラッパーとなるクラスとエフェクトを指定するだけで簡単に任意のエフェクトがテキストに追加できちゃいます。