今回は、配置したテキストを画面の端から端まで無限ループで流し続けるアニメーションの実装方法をメモ。
特に難しい内容ではないですが、何度か実装する機会があったのでブログに書いておくことにしました。
デモサイトはこちら。
<div class="c-text">
<div class="c-text__item">NOTES BY SHARESL</div>
<div class="c-text__item">NOTES BY SHARESL</div>
</div>
サンプルとして「NOTES BY SHARESL」という文字列を無限ループさせるため、上記のようなHTMLを用意します。ポイントはループするテキストは複数用意すること。ここでは2つ同じ要素を用意していますが、もっと短い単語の場合は、テキストを横並びにしたときに表示したいエリアの横幅(ここでは画面の横幅)を超えるように複数設置する必要があります。
.c-text {
overflow: hidden;
display: flex;
width: 100vw;
margin-inline: calc(50% - 50vw);
}
.c-text__item {
flex-shrink: 0;
white-space: nowrap;
font-size: 120px;
&:nth-child(odd) {
animation: MoveLeft 24s -12s infinite linear; //24秒かけて-12秒後に無限ループさせる
}
&:nth-child(even) {
animation: MoveLeft2 24s infinite linear; //24秒かけて無限ループさせる
}
}
ラッパー要素にdisplay: flex;
、テキスト要素には改行したり潰れたりしないようにflex-shrink: 0;
とwhite-space: nowrap;
を指定してテキストを横並びにします。はみ出したテキストは表示しないようにラッパー要素にはoverflow: hidden;
も指定します。
nth-child(odd)
、nth-child(even)
で奇数番目・偶数番目の要素にそれぞれアニメーションを設定します。
流れるアニメーションの動きは下記のkeyframes
を使います。
@keyframes MoveLeft {
from {
transform: translateX(100%);
}
to {
transform: translateX(-100%);
}
}
@keyframes MoveLeft2 {
from {
transform: translateX(0);
}
to {
transform: translateX(-200%);
}
}
ここでのポイントは複数用意した要素のアニメーションをずらすことです。奇数番目と偶数番目でアニメーションの開始位置と開始時間をずらすことで、見切れずに無限に流れ続けるアニメーションを作ることができます。
ここまででとりあえず動くものを作ることはできました。
あとはJSで調整していきます。
具体的には、下記の内容になります。
CSSでもある程度は対応可能なのであくまで参考程度の情報になりますが、メモしておきます。
ここまでのサンプルでは「NOTES BY SHARESL」という文字だけで作っていたので気になりませんでしたが、他の文字列を使ったテキストアニメーションを追加した場合、同じCSSを使い回すと文字列の長さによってアニメーションの速度が変わってきます。
デモで見るとわかりやすいのでご覧ください。
複数の異なる長さの文章を交互にアニメーション(JS調整なし)
また、スマホとPCでも文字の大きさが変わる影響で流れる早さが変わってきます。
そういった場合に、アニメーションの早さを統一するようにJSを書いて調整してみました。
複数の異なる長さの文章を交互にアニメーション(JS調整あり)
上記のデモの内容を見ていきます。
まずCSS変数を使いたいので、CSSを下記のように調整します。
.c-text__item {
flex-shrink: 0;
white-space: nowrap;
font-size: 120px;
&:nth-child(odd) {
animation: MoveLeft var(--tick-duration, 24s) var(--tick-delay, -12s) infinite linear;
}
&:nth-child(even) {
animation: MoveLeft2 var(--tick-duration, 24s) infinite linear;
}
}
これで--tick-duration
・--tick-delay
の変数に値がある場合は変数の値が優先されるようになりました。
次にこの変数をJSから設定するためにHTMLにクラスを追加します。
<div class="c-text js-tick">
<div class="c-text__item js-tick-item">NOTES BY SHARESL</div>
<div class="c-text__item js-tick-item">NOTES BY SHARESL</div>
</div>
次に--tick-duration
・--tick-delay
をJSから設定します。
//アニメーションの速度を計算してCSS変数に
function calculateLoopAnimationSpeed() {
const targets = document.querySelectorAll('.js-tick');
if (!targets.length) {
return;
}
const distance = window.innerWidth;
const mql = window.matchMedia('(min-width: 801px)');
const time = mql.matches ? 18 : 9; //ここで時間を調整
const speed = distance / time;
targets.forEach((target) => {
const tickElems = target.querySelectorAll('.js-tick-item');
if (!tickElems.length) {
return;
}
const total = tickElems.length - 1;
tickElems.forEach((el, i) => {
const elWidth = el.clientWidth;
const elTime = Math.floor(elWidth / speed);
el.style.setProperty('--tick-duration', `${elTime}s`);
el.style.setProperty('--tick-delay', `${elTime / -2}s`);
});
});
}
テキスト要素にstyle.setProperty()
を使って計算したテキストアニメーションの速度を設定します。
計算方法は、下記のような感じです。
--tick-duration
= テキストの横幅 ÷ 早さ--tick-delay
= --tick-duration
の半分時間のところを任意で調整してあげれば、テキストアニメーションを複数設置して文字の長さがバラバラの場合でも、テキストアニメーションのスピードを一律にコントロールすることができます。ここでは800pxのブレイクポイントを設定して、801px以上は18秒、それ以下は半分の9秒に設定しました。
次に、画面リサイズによってCSS変数を再計算するようにしてあげます。
function resizeRefresh() {
const target = document.body;
const resizeObserver = new ResizeObserver((entries) => {
entries.forEach((entry) => {
calculateLoopAnimationSpeed();
});
});
resizeObserver.observe(target);
}
これはResizeObserverで簡単に書けますね。
動かしたいテキストを手動で複数追加するのはなんかめんどくさいのでJSで自動追加します。
function copyText() {
const targets = document.querySelectorAll('.js-tick');
if (!targets.length) {
return;
}
targets.forEach((target) => {
const tickElems = target.querySelectorAll('.js-tick-item');
if (!tickElems.length) {
return;
}
let length = 0;
tickElems.forEach((el) => {
length += el.clientWidth;
el.insertAdjacentHTML('afterend', el.outerHTML);
if (length > window.innerWidth) {
return;
}
});
});
}
要素が画面幅を超えるまで自動的にコピーして追加します。el.insertAdjacentHTML('afterend', el.outerHTML);
って、もっと効率良い書き方ありそうですがここではとりあえずこれで。。。
まとめると完成系は下記になります。
class Main {
constructor() {
this.init();
}
init() {
this.copyText();
this.calculateLoopAnimationSpeed();
this.resizeRefresh();
}
//リサイズ時にアニメーションの速度を再計算
resizeRefresh() {
const target = document.body;
const resizeObserver = new ResizeObserver((entries) => {
entries.forEach((entry) => {
this.calculateLoopAnimationSpeed();
});
});
resizeObserver.observe(target);
}
//アニメーションの速度を計算してCSS変数に
calculateLoopAnimationSpeed() {
const targets = document.querySelectorAll('.js-tick');
if (!targets.length) {
return;
}
const distance = window.innerWidth;
const mql = window.matchMedia('(min-width: 801px)');
const time = mql.matches ? 18 : 9;
const speed = distance / time;
targets.forEach((target) => {
const tickElems = target.querySelectorAll('.js-tick-item');
if (!tickElems.length) {
return;
}
const total = tickElems.length - 1;
tickElems.forEach((el, i) => {
const elWidth = el.clientWidth;
const elTime = Math.floor(elWidth / speed);
el.style.setProperty('--tick-duration', `${elTime}s`);
el.style.setProperty('--tick-delay', `${elTime / -2}s`);
if (i === total) {
el.parentNode.classList.remove('no-tick');
}
});
});
}
//テキストをコピーする
copyText() {
const targets = document.querySelectorAll('.js-tick');
if (!targets.length) {
return;
}
targets.forEach((target) => {
const tickElems = target.querySelectorAll('.js-tick-item');
if (!tickElems.length) {
return;
}
let length = 0;
tickElems.forEach((el) => {
length += el.clientWidth;
el.insertAdjacentHTML('afterend', el.outerHTML);
if (length > window.innerWidth) {
return;
}
});
});
}
}
new Main();
最終的にClassでまとめました。また、テキスト要素を複数コピーすると少しずつアニメーションの開始がずれる可能性があるので、.no-tick
というクラスを最初に付けておいて、.no-tick
が消えた場合にアニメーションをスタートする、という処理をcalculateLoopAnimationSpeed
関数に追加しています。
<div class="c-text no-tick js-tick">
<div class="c-text__item js-tick-item">NOTES BY SHARESL</div>
</div>
.c-text__item {
flex-shrink: 0;
white-space: nowrap;
font-size: 120px;
&:nth-child(odd) {
.c-text:not(.no-tick) & {
animation: MoveLeft var(--tick-duration, 24s) var(--tick-delay, -12s) infinite linear;
}
}
&:nth-child(even) {
.c-text:not(.no-tick) & {
animation: MoveLeft2 var(--tick-duration, 24s) infinite linear;
}
}
}
これで完成です。
複数の異なる長さの文章を交互にアニメーション(JS調整あり)
テキストの無限ループアニメーションについて書きました。
一度やってみれば簡単ですが、こういうのは忘れがちでしてほとんど自分用のメモです。
無限ループはSwiperなどで代用することも可能ですが、ループの最後でカクついたりしたのでCSSアニメーションで書く方が個人的には好きです。この記事ではテキストに限定しましたが、もちろん画像でもほとんど同じ書き方で使えます。