読者です 読者をやめる 読者になる 読者になる

銀の弾丸

プログラミングに関して、いろいろ書き残していければと思っております。

WEBページのフルスクリーンAPIを仕様通りに使えるモジュール「fullscrn」

WEBページ内の特定HTML要素を画面全体に広げられるフルスクリーンAPIのラッパーモジュールをnpmで公開しました。

WHATWGが策定しているフルスクリーンAPIは、現状(2017年5月現在)、多くのブラウザで、プリフィックス付きの実装(mozとかwebkitというアレですね)となっています。

なので、いろんなブラウザで動作させるには結構邪魔くさいことをするわけですが、このモジュールを使えば標準仕様と同じように利用できます。

f:id:takamints:20170528140557p:plain

目次

  1. リリースファイル
  2. サンプル
  3. API
    1. プロパティ
    2. メソッド
    3. イベント
  4. リンク
  5. あとがき

↓npm fullscrnはコチラです。

www.npmjs.com

HTML5 & CSS3 デザインレシピ集
狩野 祐東
技術評論社
売り上げランキング: 3,429
アッと驚く為五郎
アッと驚く為五郎
posted with amazlet at 17.05.28
EMIミュージック・ジャパン (2015-09-23)
売り上げランキング: 247,047

1. リリースファイル

  • fullscrn.js - SCRIPTタグで読み込む用。Browserifyでまとめているからちょっと読みにくいかも。
  • fullscrn.min.js - ↑をミニファイしたもの(uglify使用)
  • index.js - ソースファイル。npmでBrowserifyを使用してrequireする場合はコチラが使用されると思います。

2. サンプル

あまり意味のないサンプルですけどすみません。動きはわかると思います。

sample/injected.html

<body onload="main();">
    <button type="button" onclick="request1();"
    >Full&gt;&gt;</button>
    <span id="panel">
        <button type="button" onclick="request2();"
        >Full&gt;&gt;</button>
        <button type="button" id="exitButton"
        onclick="exit();">&lt;&lt;Exit</button>
    </span>
    <script src="fullscrn.js"></script> 
    <script>
        var panel = document.getElementById("panel");
        var exitButton = document.getElementById("exitButton");
        Fullscreen.debugMode(true);// Enables debug log
        function main() {
            // Handle change event 
            document.addEventListener("fullscreenchange",
                function() {
                    var fse = document.fullscreenElement;
                    console.log("FULLSCREEN CHANGE: " +
                        ((fse == null)? "(null)": "#" + fse.id));
                });
 
            // Handle error event 
            document.addEventListener("fullscreenerror",
                function() { console.log("FULLSCREEN ERROR"); });
 
            request1(); // This should be error 
        }
        function request1() {
            panel.requestFullscreen().then(function(){
                console.log("request1 done.");
            }).catch(function(err) {
                console.error(err.message);
            });
        }
        function request2() {
            exitButton.requestFullscreen().then(function(){
                console.log("request2 done.");
            }).catch(function(err) {
                console.error(err.message);
            });
        }
        function exit() {
            document.exitFullscreen()
            .then(function(){
                console.log("exit done.");
            }).catch(function(err) {
                console.error(err.message);
            });
        }
    </script> 
</body>

3. API

以降の各インターフェースは、標準仕様の通りに使用できるように、DOMの DocumentクラスやElementクラスのprototypeに、インジェクトしています。

ただし、完全一致を狙っているわけではないので、完全に同じ挙動をすることを保証しません。各社ブラウザ間でも挙動が微妙に違っていたりしますが、そこには全く触れていません。

また、2017-05-16にWHATWGの仕様が改定されており、本モジュール作成にあたっては、それ以前の仕様を参考にしていたため、違うところがあるかもしれません(未確認)。

※ 別途、独立したAPIもエクスポートしていますが、使う必要が無いのでここでは説明しません。

1) プロパティ

Document.fullscreenEnabled

フルスクリーンAPIが使用できるかどうかを示すbool型のプロパティ。スクリプトの読み込み時に決定します。

Document.fullscreenElement

全画面モードになっている要素を保持します。全画面モードでないときはnullです。

Document.fullscreen

参照時点で全画面モードかどうかを示すbool型のプロパティ(Document.fullscreenElement != null と同等)。

2) メソッド

以下、どちらも、操作の完了時に解決するPromiseを返します。まあ殆どの場合無視して構いません。

Element.requestFullscreen()

HTML要素で全画面モードを開始する要求を行います。

Document.exitFullscreen()

全画面モードを終了します。

3) イベント

全画面の状態が変化した場合や、エラー発生時のイベントです。

どちらも、document.addEventListenerイベントハンドラを登録して利用してください。 (Document.onfullscreenchange / Documentonfullscreenerror には未対応です)

Document “fullscreenchange”

全画面モードの状態変化時に発生します。

Document “fullscreenerror”

全画面モードに関するエラーがあった時に発生します。

4. リンク

5. あとがき

標準仕様と実装の混乱ぶり

現状、フルスクリーンAPIはプリフィックス付きで実装されているのに、既にObsoleteな仕様があったりしてカオスです。

さらに「Fullscreenなの?FullScreenなの?どっち?」という若干低レベルな混乱ぶりも垣間見れ、非常に使いづらい状況でした。

各社勝手に実装して、後から仕様をまとめるからこうなるんだろうなあ。知らんけど。

どうでもいいこと

このモジュール、ほとんどフルスクリーンAPIのPolyfillだと言えると思うのですが、npmでは既にfullscreenというモジュールが公開されていたため、仕方なくfullscrnという名になってます。

アチラはAPIのインターフェースがWHATWGの仕様に合致していないっぽいので、色んな意味でちょっと残念。 (# ̄З ̄)

しかしダウンロード数が遥かに多くて(約20倍!)、本家といえばアチラなのかも?

SVGの重なり順序を操作する「svg-z-order」

JavaScriptで、SVG要素の重なり順(Z-Order)を操作するモジュール svg-z-order をnpmで公開したのでご紹介。

SVGの図形の要素はドキュメント内での定義順に、奥から手前へ向けて描画されますが、 これを変更するには要素を並べ替えるしかありません。 HTML/CSSのz-indexスタイルのような方法が(今のところ?)無いのです。

並べ替えはSVGに限らず、DOMのNodeクラスinsertBeforeメソッドを使うのですが、数値指定の z-index ほど、お手軽ではありませんので、この(↓)モジュールを作りました。

f:id:takamints:20170423172653p:plain

使い方

特定要素を最前面へ持ってくるコードを、ちょっと冗長に書いてみた。

//モジュール取得
var svgz = require("svg-z-order");// (1)

//SVG要素 g#foo を最前面へ表示
var g = svg.getElementById("foo");
var svgzG = svgz.element(g); //(2)
svgzG.toTop(); //(3)
 
// D3.jsで使う(4)
var d3 = require("d3");
var d3g = d3.select("#foo");
svgz.element(d3g.node()).toTop();
  1. モジュールをインポートして、
  2. element メソッドで、DOM要素を参照するインスタンスを作り
  3. 最前面へ表示します。
  4. D3.jsを使っていますが、例として書いているだけで、依存関係はありません。

上の例では、Browserifyを使って、 var svgz = require("svg-z-orger");でモジュールを取り込んでいますが、 HTMLで<script src="svg-z-order.js"/>としている場合は、 (2)の行の svgz.element(g) を、 svgz_element(g) に変更して下さい。 requireできない環境ではグローバルスコープに定義します。

API

element(e:Element)

SVG要素を参照する SVGZElement クラスのインスタンスを返します。 Z-Orderを操作するAPIは SVGZElementクラスのメソッドです。

パラメータ

  • e:Element - 参照するDOM要素を指定します。

SVGZElementクラス

SVGZElement.toTop()

参照している要素を最前面に移動。

SVGZElement.toBottom()

参照している要素を最背面に移動。

SVGZElement.moveUp(e:Element / n:number)

参照している要素を上(前面)へ移動。

パラメータ

  • e:Element - この要素よりも上になるように移動します。
  • n:number - 要素の並びの中でこの回数分だけ上へ移動します。

要素を指定したときはその要素よりも上へ。数値指定した場合は、その回数だけ上へ移動。

SVGZElement.moveDown(e:Element / n:number)

参照している要素を下(背面)へ移動。

パラメータ

  • e:Element - この要素よりも下になるように移動します。
  • n:number - 要素の並びの中でこの回数分だけ下へ移動します。

要素を指定したときはその要素よりも下へ。数値指定の場合は、その回数だけ下へ移動。

サンプル

ドロップダウンでメソッドを選んで、円をクリックするとメソッドを適用します。

コードはこちら:/sample/web/index.js

リポジトリ

www.npmjs.com

github.com

いくつになってもお勉強

JavaScriptのプログラムからSVG要素を最前面へもってくる方法を検索したら、結構微妙な情報が出てきました。

曰く、

  1. SVG単体では不可能なので、HTMLのDIVにSVGを書いて重ね合わせて、DIVのz-indexで制御するしかありません。
  2. 対象のSVG要素をディープコピーして appendChild 。元要素は removeChild。

こういう情報を見て、当初「なるほどヤヤコシイなー」と思ったのですが、どうにも納得がいきませんで、いろいろやってみているとDOMの標準機能で可能と判明。

いくつになってもお勉強です。

微妙な説明を信じちゃうのは、Node.appendChildNode.insertBeforeは、既にDOMツリーにある要素を移動してくれるということをしっかり認識できていないということでしょう。自分も今まで「追加、挿入」ってイメージしか持っていませんでしたので。

D3.js v4 でドラッグするには d3.drag() で behavior を取得する

D3.jsでドラッグイベントを処理する必要があったのですよ。

ほぼ初めてのD3ですからグーグル先生にいろいろ聞いて、「ほうほうなるほど」と学習していたのですけど、 ドラッグに関して各所で示されていたサンプル通りにやってみたら、まさかのエラー。

結局は、大きな問題ではありませんで、ひと言で言ってしまえばバージョン違いだったのですが、どうにも日本語の情報が少ないようなので書いておきます。

f:id:takamints:20170418212231p:plain

英語版です。v4に関して日本語の書籍が見当たらないです。

D3.js/v3でのコード

「D3.js ドラッグ」などと検索して出てくるサンプルコードの多くが、D3.jsのv3(バージョン3)のために書かれたコードで、最新のv4では動かないんですね。

v3でドラッグするには、d3.behavior.drag()で、Drag Behavior を取得するのですが、v4ではこれが動かない。

// D3.js v3でDrag Behaviorを取得

var drag = d3.behavior.drag(); //V4ではエラー

D3.js/v4でのOKコード

じゃあどうやるんだって―と、v4以降では以下のように、d3.drag()とするのだそうだ。

// D3.js/V4 でDrag Behaviorを取得

var drag = d3.drag(); // これでOK

リンク

リポジトリCHANGES にはしっかり書いてありました。

CHANGES
github.com

Dragに関する詳細は以下に
github.com

いやしかし

メジャーバージョンが上がっているので、APIに互換性がなくなってても文句は言わない約束だけど、 レガシーコードには警告を出すとかって対応があればモアベターね。

JavaScriptのラムダ式(アロー関数)は丸括弧で括らなければ即時実行できませんのね

Node.jsで以下のようにラムダ式を即時実行していたのですが、ブラウザでは構文エラーとなって動かないんです。

(()=>{
    console.log("これ動きません");
}());

まさかコレが動かないとか思いもよらず。 どう見直しても問題があるとは思えなかったのだが動かないから仕方がない。

追記(2017-03-29)JavaScriptの言葉的には「ラムダ式」ではなく「アロー関数」のようですので、タイトルにのみ追記しました。

f:id:takamints:20170328172435p:plain
Link: Flickr PAGE - CC BY 2.0

色々やってみた結果、ブラウザで動作させるには、以下のようにラムダ式全体を丸括弧で括る必要があるようですね。 しかし、なんだかカッコが多すぎ。ラムダ的に台無し感がありますなー。

((()=>{
    console.log("これでヨシ。だがしかし・・・");
})());

丸括弧なしでは関数オブジェクトとして評価されていないようです。 アロー演算子=>)の優先度の問題かな?

そもそも、どちらが正しいのかわからなかったので、調べてみると、どうやらECMAScript6の仕様のバグだそうで、 Node.jsでは便利な様に解釈して実装されているのかも知れませんが、今の所ブラウザではどうにもならないようですね。

d.hatena.ne.jp

即時実行するときはラムダ式を使わないというのが安全・安心? 関数内のthisが定義時にバインドされちゃうようなので、単なる無名関数の構文糖として使うのはマズいらしいし・・・

JavaScriptでマイクロ秒単位の定期処理を実行する(npm fractional-timer)

JavaScriptで1ミリ秒より短いインターバルタイマー処理を提供するモジュールのご紹介。

実際のところ、精度はよくありませんので、クリティカルな用途には向きません。 単純な処理をなるべく高速にタイマーで実行したいけど、標準タイマーの1ミリ秒では遅すぎる・・・といった時に使える感じ。 処理が重い場合は、呼び出し間隔は長くなります。

Chrome と Node.Js で、正しく動いていることを確認していますが、 FirefoxやEdgeでは、まともに動いていなかったので、ご注意下さい。

f:id:takamints:20170327172258p:plain
Link: Flickr PAGE - CC BY-SA 2.0

使い方

使い方は遅延時間に実数を指定できる点を除けば、標準のsetIntervalclearIntervalと同じです。 遅延時間は標準と同じくミリ秒単位で指定します(0.1=100マイクロ秒)。

以下のように呼び出すことで、タイマーの数と遅延時間等を調整して、定期処理を実行します。

(function() {
    "use strict";
    let ft = require("fractional-timer");
    let ftid = ft.setInterval(
        function(){
            ft.clearInterval(ftid);
        },
        0.001);// 1 microseconds
}());

インストール

npmでインストールすればNode.jsから使えます。

> npm install fraction-timer

ブラウザのWEBアプリでは、browserifyrequire するか、 scriptタグで読み込んで、FractionalTimerクラスの静的メソッドsetInterval/clearIntervalを直接使ってくださいね。

リポジトリ

www.npmjs.com

github.com

その他、能書き

やっていることは単純です。複数のインターバルタイマーで一つの処理を呼び出しているだけです。

例えば、1マイクロ秒で実行したい場合、1ミリ秒のタイマーを1000個使って呼び出すということです。

実際には、指定された間隔から算出される単位時間あたりの実行回数を計算し、タイマーの数と間隔を自動的に算出、ベストエフォートで呼び出します。

MZ-700フルJavaScriptエミュレータ」で、エミュレーションをワーカースレッドの定期処理で実装していて、当初は単一のインターバルタイマーで、最も高速になるように調整していたのですが、ある時タイマーが二重に動いてしまったことがあって、速度がなんと2倍程度に跳ね上がっていたのです。

「うわあ!世紀の大発見!!!」と思ったけれど、単一スレッド内ではシーケンシャル動作が保証されているので、当然の挙動なんですよね。

ただ、こういうことをやってる人があまりいないところを見ると、需要はないみたいなんですけどね(笑)