幅が足りない時は文字をスクロールさせる。(マーキー) 電光掲示板のようなスクロール。局所的には使える。
試した方法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;
}
クラスt3
がrequestAnimationFrame
を使う場合で、クラスt4
がsetTimeout
を使う場合。どちらも同じだけど。
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);
DURATION
とPLAY_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 > p
のheight
指定は不要。
領域を指定する時は、.t5
のheight
をお好みの値に調整すればいい。
デモ
See the Pen marquee by ikapper (@ikapper) on CodePen.
おわりに
CSSで指定するのが無難なのかなという感想。文字列を動的に変更するならJavascript
を使っておくべきかもしれない。
まあでもマーキーはそこまで利用頻度が高いわけでないし、こういう表現方法もあるのだよくらいで頭の片隅にとどめておくのがいいのだろう。
広告