3通りのmarquee

幅が足りない時は文字をスクロールさせる。(マーキー) 電光掲示板のようなスクロール。局所的には使える。

試した方法3つ

  • marqueeタグ(廃止)を利用
  • CSSのanimationを利用
  • Javascriptで実装

特に凝った動きをしないのであれば、真ん中のCSS利用が簡単。細かく指定したいならJavascriptがいいだろう。

最後に実装したデモを載せておく。

marqueeタグ(廃止)

<marquee>スクロールする文字列</marquee>とするだけ。 しかし、廃止されていていつブラウザで使用できなくなってもおかしくない。すでに使用できないブラウザもあるかもしれない。 一応スクロール方向や、跳ね返りのような動きも簡単に指定できる。

ブラウザによってはフレームレートが低く、カクついてみえることもある。この面でも使わない。

CSSのanimationを利用

animationを使えば割と簡単にできる。マーキー用のpタグをdivタグで囲み、CSSを指定する。

htmlはこうなる。

<div class="t2">
    <p>[css marquee] It's 9:30. If I have very long text..? It's 9:30. If I have very long text..?</p>
</div>

CSSはあまり長くならない。

.t2 {
  /* divから外に出た文字を隠すため */
  overflow: hidden;
}

.t2 > p {
  animation: 7s linear 0s infinite marquee;
  /* border: solid 1px black; */
  /* スクロールする文字列の改行を防ぐ */
  white-space: nowrap;
  width: fit-content;
}

@keyframes marquee {
  from {
    /* 決め打ち */
    transform: translate(50%);
  }
  to {
    /* 符号反転し決め打ち */
    transform: translate(-100%);
  }
}

このCSSでは、アニメーションにtransform: translate();を使っている。要素の幅分だけ右にシフトした状態から始まり、要素の幅分だけ左にはみ出たら最初に戻るという感じ。 なので、親要素の幅がこの要素の幅より広いとマーキーが正しく動かない。しかし、普通スクロールさせるのに親の幅が広すぎるなんてことは少ないと思うのでこのままとする。(position: absolute;leftとかを使えばなんとかなりそうな予感がする。)

さらにスクロールするテキストが短いとおかしくなると思う。穴だらけだな… しかし、この辺りはテキストに合わせて@keyframesの%を調整すれば済むはず。

うまく指定するにはテキストの幅を決め打ちしないといけない。javascriptを使えばできるが、ここではしないので決め打ちで済ます。

Javascriptで実装

requestAnimationFrameを使うかsetTimeoutを使うか。個人的には前者が好き。パフォーマンスなどの違いは調べてない。

描画間隔の指定は各環境ごとに動作具合が異なるので、その辺りを勝手に世話してくれる前者が便利だろう。前者は描画部分が隠れるとリクエストがされない場合がある点は考えておく必要がある。他のタブやウィンドウを見ている時、完全に隠れているとアニメーションが行われない。利点とも欠点とも言える。後者の隠れている状態の動きは知らない。

動的にサイズなどの情報を取得できるので、先ほどCSSでやっていた決め打ちの必要がなくなる。

どちらの場合も最低限のCSSは必要

はみ出し制御と改行防止と幅を正しく持たせるのにCSSはやはり必要になる。以下の2通りのどちらの場合も必要になるので、ここに載せる。

.t3, .t4 {
  overflow: hidden;
}

.t3 > p {
  white-space: nowrap;
  width: fit-content;
}

.t4 > p {
  white-space: nowrap;
  width: fit-content;
}

クラスt3requestAnimationFrameを使う場合で、クラスt4setTimeoutを使う場合。どちらも同じだけど。

requestAnimationFrameを利用する場合

htmlは変わらない。Javascript側で要素を発見しやすいようにidだけ設定した。

<div class="t3">
    <p id="marp">[JS marquee] Javascript keeps me moving :) (using requestAnimationFrame)</p>
</div>

Javascriptだと少し長くなる。仕方ないね。

// requestAnimationFrame version
const pelm = document.getElementById("marp");
const parent_pelm = document.getElementsByClassName("t3")[0];

const DURATION = 5;  //sec
const PLAY_COUNT = "inf";  // positive number or "inf"

let startTS = null;
let play_count = 1;
function marqueef(ts) {
  let needNextReq = false;
  if(startTS == null) startTS = ts;
  const pw = pelm.clientWidth;
  const ppw = parent_pelm.clientWidth;
  // 進捗を計算
  const progress = (ts - startTS) / (DURATION * 1000);
  if(progress > 1) {
    // 1ループ終了。続行するかどうかを判断。
    if(PLAY_COUNT === "inf" || PLAY_COUNT > play_count) {
      // 続行するなら開始時のタイムスタンプをリセット
      startTS = null;
      play_count++;
      needNextReq = true;
    }
  } else {
    needNextReq = true;
  }

  const newpos = (pw + ppw) * (1 - progress) - pw;
  pelm.style.transform = "translate(" + newpos + "px)";

  if(needNextReq) {
    requestAnimationFrame(marqueef);
  }
}

requestAnimationFrame(marqueef);

DURATIONPLAY_COUNTは自由に設定可能。それぞれ1ループにかける時間とループ回数。"inf"は無限に繰り返す。

requestAnimationFrameは関数を渡すとそれにタイムスタンプ(単位はミリ秒)を渡して実行してくれるもの。アニメーション開始時のタイムスタンプを保持することでアニメーションの進捗を管理できる。環境に合わせてフレームレートを調整してくれる。

needNextReqで次の描画が必要かどうかを決める。必要なループ回数を終えた後などは描画させないようにできる。

newposはスクロールする要素の新しいポジション。ここを自由にできるのがJavascriptのいいところ。ここでは、最初は文字列の左端を表示窓の右端に表示させ、最後には文字列の右端を表示窓の左端に表示させている、つまり、CSSなどの上2つの例と同じということ。

位置はtransformを使った。marginLeftでもいいかと思ったら次のループに移る時におかしなことになったので使わない。

setTimeoutを利用する場合

setTimeoutの場合も大体は同じ。まずはhtmlから。文言以外は同じになる。

<div class="t4">
    <p id="marp2">[JS marquee] Javascript keeps me moving :) (using setTimeout)</p>
</div>

Javascriptはタイムスタンプが与えられないので、工夫する必要がある。

// setTimeout version
const t4_pelm = document.getElementById("marp2");
const t4_parent_pelm = document.getElementsByClassName("t4")[0];

const DURATION = 5;  //sec
const PLAY_COUNT = "inf";  // positive number or "inf"

// 再描画間隔
const t4_INTERVAL = 1000/60;  //ms

let t4_play_count = 1;
let t4_frame_count = 0;  // 1ループ内での呼び出し回数
function t4_marqueef() {
  let needNextRender = false;
  const pw = t4_pelm.clientWidth;
  const ppw = t4_parent_pelm.clientWidth;
  const progress = (t4_frame_count * t4_INTERVAL) / (DURATION * 1000);
  if(progress > 1) {
    if(PLAY_COUNT === "inf" || PLAY_COUNT > t4_play_count) {
      t4_play_count++;
      t4_frame_count = 0;
      needNextRender = true;
    }
  } else {
    needNextRender = true;
    t4_frame_count++;
  }

  const newpos = (pw + ppw) * (1 - progress) - pw;
  t4_pelm.style.transform = "translate(" + newpos + "px"+")";
  if(needNextRender) {
    setTimeout(t4_marqueef, t4_INTERVAL);
  }
}

setTimeout(t4_marqueef, 0);

ほぼrequestAnimationFrameの場合と同じである。 異なるのは描画間隔を自前で決めないといけないところと、それに伴うprogress(進捗)の計算方法くらい。

描画間隔は、t4_INTERVALで決める。単位はミリ秒。上だと60fpsを目指す感じ。処理で時間がかかるので実際は少し遅くなる。 その辺を考慮して次のディレイを調整してもいいが、今回はしなかった。

進捗の計算は、1ループ中のどこら辺なのかを示す変数が必要。これはt4_frame_countで管理する。単純にsetTimeoutが1ループ中に何回呼ばれたのかを記録する。 これを利用して、1ループの開始から何秒経過したのかを判断する。これはt4_frame_count * t4_INTERVALミリ秒となる。これを1ループにかける時間(DURATION)で割れば進捗がわかる。

次のループへと移る時は、t4_frame_countを忘れずにリセットする。

描画間隔を正確にしたい時は、処理の開始時と次の描画予約前に、Data.now()で時間の差を出しておいて、t4_INETRVALから引いて置けばいいだろう。

最後のsetTimeout(t4_marqueef, 0);でディレイが0なのは、1フレーム目を描画するため。以降はインターバルごとに描画すればいい。

CSSで上下方向(縦方向)に動かす

ちょっと気になったので、これも実装してみる。Javascriptの場合は、translateY()を使って置けばいい。ぶっちゃけCSSもそうなる。 高さを指定する場合は指定方法を工夫しないと、うまくスクロールできない。(後述)

まずはhtmlから。

<div class="t5">
    <p>[CSS marquee] I move up and down.</p>
</div>

今までと同じなのでいいだろう。

次はCSS。

.t5 {
  overflow: scroll;
  height: 100px;
  border: solid 1px grey;
}

.t5 > p {
  margin: 0;
  white-space: nowrap;
  width: fit-content;
  height: 100%;
  animation: 3s linear 0s infinite marqueeUD;
}

@keyframes marqueeUD {
  from {
    transform: translateY(-100%);
  }
  to {
    transform: translateY(100%);
  }
}

スクロールさせる領域がp要素1行分であれば、.t5.t5 > pheight指定は不要。 領域を指定する時は、.t5heightをお好みの値に調整すればいい。

デモ

See the Pen marquee by ikapper (@ikapper) on CodePen.

おわりに

CSSで指定するのが無難なのかなという感想。文字列を動的に変更するならJavascriptを使っておくべきかもしれない。

まあでもマーキーはそこまで利用頻度が高いわけでないし、こういう表現方法もあるのだよくらいで頭の片隅にとどめておくのがいいのだろう。


広告

Bitly
タイトルとURLをコピーしました