テキストをスクロール表示させる
webengine
- TOP
- Old Archives
- テキストをスクロール表示させる
プログラムについて
諸兄も一度はYouTubveで見たことがあるのではないだろうか。
文字がただ流れるだけの動画を。
自分のペースで読めない上に内容に乏しく、非常にイライラするものだが、 作業ウィンドウ以外で流しておくことでウィンドウフォーカスを切り替えなくて済む、というメリットもある。
特にmpvを使うと、右クリックで再生/停止ができるし、再生速度もコントロールできてまあまあ便利だ。
ただ、検索性がないのでやっぱりストレスである。
そこで、テキストリーダーに自動スクロール機能が欲しい、と思った。
ありそうだが、見当たらなかった。
もちろん、一行単位であればsleep
とread
でも組み合わせればいいのだが、これはパッと動いてしまうため目の追従が難しく、読みづらい。
1ピクセルずつ動かしたいのだ。
xdotoolを使う、という方法も考えたのだが、意図したようにはうまくいかなかった。
JavaScriptを使えばすごく簡単なのに…というわけで、JavaScriptを使う方向でがんばることにした。
すごーーーーく単純に一時ファイルでHTMLを作ってQtWebengineで表示すればいいと思って、Pythonで作ったのだが…
…Surfでよかった。
まぁ、配布するならSurfみたいなマイナーなものは入れたくないなんていう天邪鬼さん1や、Surfは入っていないなんていう弱小ディストリさんはいっぱいいると思うので良しとしよう。
珍しく日本語READMEもある。 BGM機能があるのは、YouTubeでよく見るものをリスペクトしたものであり、背景画像がほしければCSS編集でOKだ。
やっていることはごく単純で、Pandocで処理することを前提として一時ファイルを活用している。
プレーンテキストの場合はsedを使ってラインブロック化している。
この場合改行されなくなってしまうので、pre-wrap
するようにデフォルトのスタイルシートで変更している。
スクロール制御はJavaScriptでものすごく入門的なコードだ。 右クリックでも再生・停止できるようにしているのは、コントロールをウィンドウフォーカスしてからでなくても行えるようにするためである。
ちなみに、今回のJavaScriptは互換性をあまり気にしない贅沢なコードでもある。 これはかなり新しいGtkWebkit、あるいはQtWebengineを使うという前提が成り立っているためだ。
解説
それでは恒例の初心者向けコード
JavaScript部分
せっかくなので、入門的なJavaScriptコードを解説しよう。
スクロール部分
まずスクロール自体はwindow.scroolBy
によって実現できる。
自動スクロールをするためにはこれを繰り返すようにしなくてはいけない。 もちろん、単純なループでもできるのだが、JavaScriptはシングルスレッドなので操作不能になってしまう。
このような反復はJavaScriptではタイマーイベントで行う。 タイマーにコールバック関数を登録することで、タイマー起動時にコールバック関数が実行される。 もし他の処理が実行中の場合は処理をキューに入れ、順番がきたら実行される。
反復の場合setInterval
のほうが簡単で、初心者向けの解説ではよくこちらが使われるが、
どちらかといえばタイマーイベントを都度登録するsetTimeout
のほうがコントロールしやすい。
関数オブジェクト、クロージャという考え方になれていないとそもそもコールバック関数が使えないので、ここはしっかりと理解しておく必要がある。
関数オブジェクトは実行可能なコード群をオブジェクト化したものである。 スイッチを押せば実行される物体になっているとでも思えばいい。 コールバック関数は、それを呼び出すタイミングでそのスイッチを押すように動作する。
setTimeout
は指定した時間が経過したときにコールバックを行う。
コールバック関数の中で自身をコールバック関数とする
setTimeout
を呼ぶことでループさせることができる。
「スクロールの開始」はその関数を呼べば良い。 setTimeout
で呼ばなくても一度呼べば setTimeout
によってループする。
「スクロールの停止」は setTimeout
をしなければ次回の実行がなされないため、停止する。
停止方法はタイマーをキャンセルするのではなく、次回のタイマーセットを行わないというものである。
このため、タイマー動作状態がonの場合のみ setTimeout
を行う。
setInterval
を使用した場合はタイマーをセット/キャンセルして制御する。
キーイベント部分
キーボードのキー入力は document
あるいは
window
に対してイベントリスナーを設定する。
DOM Level1 のonKeydown
を設定する場合の情報は割とあるのだが、 DOM Level3 の
addEventListener
を設定する情報はやや少ない。
コールバック関数の引数としては KeyboardEvent
オブジェクトが渡される。
特定のキーをキャプチャするわけではなく、キーボード入力全てをキャプチャしてコールバック関数が呼び出される。
コールバック関数内でキーを判別する。
なお、コールバック関数が時間がかかるとフリーズしていると感じることになるため注意が必要。
キーの種別を取るための方法は色々あるのだが、 code
が推奨される。 プリンタブルなキーに限定するのであれば key
でも良い。 char
や charCode
はあまりうまく動作しないし、 keyCode
や which
は廃止されている上に、プラットフォームに依存する。
それぞれどのような値になるのかは次のようなコードを書いて確認すれば良い。
document.addEventListener('keydown', (event) => {
alert('char:' + event.char)
alert('charCode:' + event.charCode)
alert('code:' + event.code)
alert('key:' + event.key)
alert('(obsolete) keycode:' + event.keyCode)
alert('(obsolete) which:' + event.which)
, false) }
元のキーの動作を無効にする場合は、第三引数を false
として先にキーを捕捉してから Event.preventDefault
によってバブリングを停止する。
右クリック禁止2、でお馴染み右クリックは
contextmenu
イベントになる3。
スクロールスピード
スクロールは
- スクロールする時間間隔が短くなると速くなる
- 一度にスクロールする量が増えると速くなる
量が増えるとスムーズさが書けるため、時間を短くするほうが優先。 間隔が1(1ミリ秒)になった場合、加速はスクロール量によって行う。
逆に遅くするときはスクロール量が増えているならそちらを先に減らす。スクロール量が1であれば時間間隔を増やす。
1ミリ秒刻みでは使いにくかろうと思ったので、5ミリ秒刻み、ただし値が小さいほうが変化量は大きいため、5ミリ秒からは1ミリ秒で調整されるようにしている。 (50ミリ秒から+5された場合は10%遅くなるが、10ミリ秒から-5された場合は100%速くなる)
割合で増減させてもいいのだが、どちらかといえばキーリピートが効くため一定間隔にしたほうが良いUIであると考えられる。
今回は50ミリ秒をデフォルトとしたため、10%の5ミリ秒を増減単位とした。 なお、この速度感は画面のピクセル数によって異なり、特にピクセル数が多く高精細なディスプレイの場合は遅く、ディスプレイサイズが大きくピクセル数が少ない場合は速く感じることになる。
設計
基本的には「得意なことは得意な方法で」だ。
このようにスクロールやキーイベントなどはウェブブラウザとJavaScriptが簡単に書ける。 だから無理せずウェブブラウザとJavaScriptで書こうと考えたわけだ。
ただし余計な機能や情報があると使いにくい。 最低限のウェブブラウザが欲しいのだが、既にそのようなものはSurfがあるのでこれを使う。
もっとも、レンダリング部分はQtwebengineやGtkWebkitがあるのだから書くのは非常に簡単である。
もちろん、そのためにはテキストをHTMLにする必要がある。
テキストを正確にHTMLで表現するのはちょっと大変だが、CSSで
white-space: pre-wrap
にしてしまえばタグ要素と
&
をエスケープしてしまえば元のテキストを表現できる。
このようなことはSedでもできる。 ただし、 &
を先にエスケープしなくてはならない。エスケープがエスケープされることを防ぐためだ。
テキスト処理するためのツールはLinux上に豊富にある。このようなことはシェルが得意とする部分だ。 文字列を埋め込むだけならば特別なツール(例えばeRuby)を使わなくても、ヒアドキュメントで十分だろう。 プログラムはシェルスクリプトで構築する、という方針は簡単に決められる。
中間的なファイルや生成に必要なファイルは、もちろん予め用意してリンクさせることもできるが、ディレクトリ設計に関する障害を増やすことになる。 今回はローカルディレクトリにインストールする、という前提を与えてはいるが、それでもできれば避けたいところだ。 それにバージョンアップの手間も考えれば、一時ファイルを作る方針にした。 これも簡単なものなのでヒアドキュメントで処理する。
ブラウザを作るにあたっては、私の得意なRubyではなく、割と苦手なPythonにした。 Rubyにもqml Rubyバインディングは存在するし、動作もするが、メンテナンスされておらず(3年間放置されている)、情報も非常に少ない。 また、ruby-qmlよりもpyqtのほうがqml自体もシンプルに書けるので、Pythonを採用した。
表示はウェブブラウザ(作ったもの的にはPython+qml+Webengine)で、スクロールはJavaScriptで、変換と橋渡しはシェルスクリプトで。 コンポーネントを分けて、それぞれが得意なことをシンプルに行う。 問題を簡単にし、ミスを減らすポイントでもある。