JavaScript」タグアーカイブ

JavaScript, Python3 like format

JavaScriptの可変長引数: ぺるたごブログ

なんだ、JavaScript も可変長引数が使えたんだ。
arguments という特殊な変数を利用すればいいんだね。
って Gjs の /usr/share/gjs-1.0/format.js もそうやっていた。

それなら Python3 っぽい文字列成形が自作できるかも。
いや、すっかり Python3 に慣れてしまったので。

完全に同じでなくてもいいんだ、”{0}, {1}” みたいな手段って便利じゃん。
Gjs なら format.js でいいけど Web で使いたいし参考にして。

String.prototype.format = function() {
    var result = this;
    for (var i = 0; i < arguments.length; i++) {
        var s = "{" + i + "}";
        var r = String(arguments[i]); // not tostring();
        result = result.replace(s, r);
    }
    return result;
}

超手抜き、{0:.2f} みたいなのはできない!

それと文字列の tostring だとエラーになるけどキャストなら通るようだ。
これで文字列も整数も実数も対応できる、しかし本当にいいかげんな言語だ。

javascript_format

Fedora は最初から Gjs が入っているから確認が簡単でいいですよ。
SpiderMonkey ですからつまり Firefox でも使えるってことだし。

いや、Web てか iPhone で使えないと意味がない、確認だ。

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Python3 っぽい文字列の成形</title>
<!-- for Smart Phone -->
<meta name="viewport" content="
    width=device-width,
    initial-scale=1.0,
    minimum-scale=1.0,
    maximum-scale=1.0,
    user-scalable=no" />
<style>
body {
    -webkit-text-size-adjust: 100%;
}
</style>
<script type="text/javascript"><!--

    const STR = "Test : {0}, {1}, {2}";

    String.prototype.format = function() {
        var result = this;
        for (var i = 0; i < arguments.length; i++) {
            var s = "{" + i + "}";
            var r = String(arguments[i]); // not tostring();
            result = result.replace(s, r);
        }
        return result;
    }

    var init = function() {
        // Test
        var text = STR.format("str", 1, 0.5);
        document.getElementById("ID_TEXT").innerHTML = text;
    }
    //-->
</script>

</head>
<body onLoad="init()">

<p>const STR = "Test : {0}, {1}, {2}";<br />
var text = STR.format("str", 1, 0.5);<br />
document.getElementById("ID_TEXT").innerHTML = text;<br />
↓</p>

<p id="ID_TEXT"></p>

<p><a href=".">戻る</a></p>
</body>
</html>

Python3 っぽい文字列の成形

Safari, Google Chrome でも問題ないね、○E は知らないしどうでもいい。
ゲームの “Round {0} Start!” みたいな時に使うと便利かも。

JavaScript, Reverse the X and Y

今回は方向転換、弾いたボールが画面端に当たると跳ね返るように。

最初は +- で振り分けラジアン値を 90 度分回転させればいいだろうと考えた。
全然違った、10 度で右端にぶつけると 160 度で跳ね返らないとおかしいわけで。

次にまんまマイナスにするという思い付きをやってみた。
上下は上手くいくけど左右は吸い込まれる、なんだこれ?

他に何をやったか忘れたけど今度は上下だと当たった角度で戻って来る等々。
とりあえず X 軸、又は Y 軸で反転させればいいと思いついたけど処理方法が…

ということで [軸 反転 Math.PI] みたいなワードで検索検索!
コレもそのものズバリを見つけてしまった、言語は違うけど。

7. キャラクターの速度と進行方向を指定するようにする方法の実際のコードの質問。 – Yahoo!知恵袋

つまり X 軸と Y 軸で別々に処理する必要があるってことか。
そりゃそうだ、同時に当たる場合だってあるわけで。

// X軸反転
if (X座標 < 0 || X座標 > width)
    ラジアン = Math.PI - ラジアン;
// Y軸反転
if (Y座標 < 0 || Y座標 > height)
    ラジアン = -ラジアン;

三角関数はあんまり関係なかった…

しかしこれまた「ホンマカイナ」と思うほど単純。
実際にはどうなのか、オフセットがあるのでソレを考慮して。

どさくさで今後操作ツールバーを置く位置に戻るボタンを配置等して。
コードが更に長くなってワケワカになる前に定数を定義なんかもして、etc…
ということで、動作が判りやすいように5秒動くようにしたサンプル。

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>画面の隅で跳ね返る</title>
<!-- for Smart Phone -->
<meta name="viewport" content="
    width=device-width,
    initial-scale=1.0,
    minimum-scale=1.0,
    maximum-scale=1.0,
    user-scalable=no" />
<style>
body {
    margin: 0;
    overflow: hidden;
    -webkit-text-size-adjust: 100%;
}
#ID_CANVAS {
    background-color: #FF7700;
}
</style>
<script type="text/javascript"><!--

    var canvas = null;
    var context = null;
    var ball = null;
    // Constant
    const INIT_X = 160;  // X origin of the image
    const INIT_Y = 160;  // Y origin of the image
    const OFFSET = 24;   // Half of the image size
    const INTERVAL = 20; // n/1000 ms
    const LENGTH = 10;   // Distance the image moves
    const COUNT  = 250;  // INTERVAL*n ms
    // struct
    var ballData = function() {
        this.timer_id = -1;
        this.timer_count = 0;
        this.x = 0;
        this.y = 0;
        this.rotation = 0;
    }
    var BallData = new ballData();

    /*
     * Event Handler
    **/
    var onBallTouchStart = function(e) {
        e.preventDefault();
    }
    var onBallTouchMove = function(e) {
        if (BallData.timer_id == -1) {
            context.clearRect(0, 0, canvas.width, canvas.height);
            BallData.x = e.touches[0].pageX;
            BallData.y = e.touches[0].pageY;
            context.beginPath();
            context.moveTo(INIT_X, INIT_Y);
            context.lineTo(BallData.x, BallData.y);
            context.stroke();
            moveBall(BallData.x, BallData.y);
        }
    }
    var onBallTouchEnd = function(e) {
        if (BallData.timer_id == -1) {
            BallData.rotation = Math.atan2(BallData.y - INIT_Y, BallData.x - INIT_X)
            BallData.timer_count = COUNT;
            BallData.timer_id = setInterval(onTimer, INTERVAL);
        }
    }
    /*
     * Timer Handler
    **/
    var onTimer = function() {
        if (BallData.timer_count > 0) {
            // Change rotation
            if (BallData.x < OFFSET || BallData.x > canvas.width - OFFSET)
                BallData.rotation = Math.PI - BallData.rotation;
            if (BallData.y < OFFSET || BallData.y > canvas.height - OFFSET)
                BallData.rotation = -BallData.rotation;
            // Move Image
            BallData.x -= Math.cos(BallData.rotation) * LENGTH;
            BallData.y -= Math.sin(BallData.rotation) * LENGTH;
            moveBall(BallData.x, BallData.y);
            BallData.timer_count--;
        } else {
            clearInterval(BallData.timer_id);
            BallData.timer_id = -1;
            context.clearRect(0, 0, canvas.width, canvas.height);
            moveBall(INIT_X, INIT_Y);
        }
    }
    /*
     * Function
    **/
    var moveBall = function(x, y) {
        ball.style.left = x - OFFSET + "px";
        ball.style.top  = y - OFFSET + "px";
    }
    /*
     * Initialize
    **/
    var init = function() {
        if (window.TouchEvent) {
            canvas = document.getElementById("ID_CANVAS");
            canvas.width = window.innerWidth;
            canvas.height = window.innerHeight - 30;
            context = canvas.getContext("2d");
            ball = document.getElementById("ID_BALL");
            ball.addEventListener("touchstart",onBallTouchStart);
            ball.addEventListener("touchmove",onBallTouchMove);
            ball.addEventListener("touchend",onBallTouchEnd);
            moveBall(160, 160);
            // Back Button
            var back = document.getElementById("ID_BACK");
            back.style.top = canvas.height + "px";
            back.addEventListener("touchend", function() {
                document.location = ".";
            });
        }
    }
    //-->
</script>

</head>
<body onLoad="init()">

<canvas id="ID_CANVAS"></canvas>
<img id="ID_BALL" src="ball.png" style="position:absolute">
<img id="ID_BACK" src="back.png" style="position:absolute">

</body>
</html>

画面の隅で跳ね返る

本当にコレで想定どおりな動作になった。
次は途中に障害物を置いてソレに当たると破壊し跳ね返るように、かな。

おまけで、Gedit マジでコードが見易すぎて泣ける。
というか SyntaxHighlighter だとコメントになって見辛いよね。

gedit_color

HTML, CSS, JavaScript 部をデフォルトで完全分離し色分けするって凄いと思う。
iPhone 同様にカスタマイズの豊富さより作りの良さを求める人にはもってこい。
流石に -webkit-text-size-adjust 等は色分け登録されていないけどさ。

JavaScript, Moving at a constant speed

前回のコードに跳ね返る処理を入れるにも三角関数は必須だ。
ということで、とりあえずどの方向にも等速にボールを移動させる手段を探す。

Flashゲーム講座&ASサンプル集【三角関数を使った計算について】

‘どの方向にも一定の速度で移動するキャラクター’
というそのものズバリな解説を見つけた、よしコレでいこう。
ActionScript だけど基本計算は JavaScript と同一なはず。

endo blog: Javascript Math.atan2() X軸からポイントまでの角度を取得

って JavaScript には Math.atan2 という便利すぎ関数があるじゃないの。
HTML 中を引っ張った逆方向に移動するのだから

// 角度をラジアンで取得
ラジアン = Math.atan2(Y座標, X座標)
// 移動先の算出
X座標 -= Math.cos(ラジアン) * 移動ピクセル数
Y座標 -= Math.sin(ラジアン) * 移動ピクセル数

あれ、こんな冗談みたいに単純で本当にいいのかな?
試しに前回のを 20/1000 秒毎に 10px 移動する感じに書き換えてみる。

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>一定速で移動させる</title>
<!-- for Smart Phone -->
<meta name="viewport" content="
    width=device-width,
    initial-scale=1.0,
    minimum-scale=1.0,
    maximum-scale=1.0,
    user-scalable=no" />
<style>
body {
    margin: 0;
    overflow: hidden;
    -webkit-text-size-adjust: 100%;
}
#ID_CANVAS {
    background-color: #FFEE00;
}
</style>
<script type="text/javascript"><!--

    var canvas = null;
    var context = null;
    var ball = null;
    var offset = 24;
    // struct
    var ballData = function() {
        this.timer_id = -1;
        this.timer_count = 0;
        this.x = 0;
        this.y = 0;
        //this.move_x = 0;
        //this.move_y = 0;
        this.rotation = 0;
    }
    var BallData = new ballData();

    // Event Handler
    var onBallTouchStart = function(e) {
        e.preventDefault();
    }
    var onBallTouchMove = function(e) {
        if (BallData.timer_id == -1) {
            context.clearRect(0, 0, canvas.width, canvas.height);
            BallData.x = e.touches[0].pageX;
            BallData.y = e.touches[0].pageY;
            context.beginPath();
            context.moveTo(160, 160);
            context.lineTo(BallData.x, BallData.y);
            context.stroke();
            moveBall(BallData.x, BallData.y);
        }
    }
    var onBallTouchEnd = function(e) {
        if (BallData.timer_id == -1) {
            //BallData.move_x = (BallData.x - 160) / 5;
            //BallData.move_y = (BallData.y - 160) / 5;
            BallData.rotation = Math.atan2(BallData.y - 160, BallData.x - 160)
            BallData.timer_count = 20;
            BallData.timer_id = setInterval(onTimer, 20);
        }
    }
    // Timer Handler
    var onTimer = function() {
        if (BallData.timer_count > 0) {
            //BallData.x -= BallData.move_x;
            //BallData.y -= BallData.move_y;
            BallData.x -= Math.cos(BallData.rotation) * 10;
            BallData.y -= Math.sin(BallData.rotation) * 10;
            moveBall(BallData.x, BallData.y);
            BallData.timer_count--;
        } else {
            clearInterval(BallData.timer_id);
            BallData.timer_id = -1;
            context.clearRect(0, 0, canvas.width, canvas.height);
            moveBall(160, 160);
        }
    }
    // Func
    var moveBall = function(x, y) {
        ball.style.left = x - offset + "px";
        ball.style.top  = y - offset + "px";
    }
    // Connect
    var init = function() {
        if (window.TouchEvent) {
            canvas = document.getElementById("ID_CANVAS");
            canvas.width = window.innerWidth;
            canvas.height = window.innerHeight;// - 100;
            context = canvas.getContext("2d");
            ball = document.getElementById("ID_BALL");
            ball.addEventListener("touchstart",onBallTouchStart);
            ball.addEventListener("touchmove",onBallTouchMove);
            ball.addEventListener("touchend",onBallTouchEnd);
            moveBall(160, 160);
            var back = document.getElementById("ID_BACK");
            back.style.top = canvas.height - 30 + "px";
            back.addEventListener("touchstart", function() {
                document.location = ".";
            });
        }
    }
    //-->
</script>

</head>
<body onLoad="init()">

<canvas id="ID_CANVAS"></canvas>
<img id="ID_BALL" src="ball.png" style="position:absolute">
<img id="ID_BACK" src="back.png" style="position:absolute">
<!--<a id="ID_BACK" style="position:absolute" href=".">Back</a>-->

</body>
</html>

画像を引っ張って弾く

上下左右どのように引っ張っても本当にコレだけでイケた。
少しブレるかもと思ったけど一定速でスムースに動いてくれる。

目的どおりのものが少しずづできあがっていくのって楽しい!

JavaScript clearInterval

引っ張りゲーを実現するには指を放した後に引っ張った逆方向に一定時間画像を移動し続ける必要がある。
タイマーを利用するしかないだろう。

JavaScript では setInterval 関数を使う。
clearInterval していないサンプルコードが多い、動かしっぱなしはヤメようよ。

下記は 20/1000 秒毎に画像を移動、そして 1 秒でタイマーを停止してリセット。
タイマー毎に引っ張った距離の 1/5 ずつ戻す例にしてみた。
大抵のゲームは一定距離だけど、三角関数で計算するしかないだろうな…

それとタイマーを動かしている間は touchmove ハンドラ等を処理しないように。
ついポンポンとタッチすると酷いことになる。

記憶しておくデータが増えてとっ散らかるので構造体っぽくまとめている。
この記述方法は Python をやっている人ならすぐ解ると思う。
一つしか使わないなら static でもいいけど、その場合初期化されないので注意。
つまり下記では ballData.timer_id だと -1 にならないということです。

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>画像を引っ張って弾く</title>
<!-- for Smart Phone -->
<meta name="viewport" content="
    width=device-width,
    initial-scale=1.0,
    minimum-scale=1.0,
    maximum-scale=1.0,
    user-scalable=no" />
<style>
body {
    margin: 0;
    overflow: hidden;
    -webkit-text-size-adjust: 100%;
}
#ID_CANVAS {
    background-color: #FFEE00;
}
</style>
<script type="text/javascript"><!--

    var canvas = null;
    var context = null;
    var ball = null;
    var offset = 24;
    // struct
    var ballData = function() {
        this.timer_id = -1;
        this.timer_count = 0;
        this.x = 0;
        this.y = 0;
        this.move_x = 0;
        this.move_y = 0;
    }
    var BallData = new ballData();

    // Event Handler
    var onBallTouchStart = function(e) {
        e.preventDefault();
    }
    var onBallTouchMove = function(e) {
        if (BallData.timer_id == -1) {
            context.clearRect(0, 0, canvas.width, canvas.height);
            BallData.x = e.touches[0].pageX;
            BallData.y = e.touches[0].pageY;
            context.beginPath();
            context.moveTo(160, 160);
            context.lineTo(BallData.x, BallData.y);
            context.stroke();
            moveBall(BallData.x, BallData.y);
        }
    }
    var onBallTouchEnd = function(e) {
        if (BallData.timer_id == -1) {
            BallData.move_x = (BallData.x - 160) / 5;
            BallData.move_y = (BallData.y - 160) / 5;
            BallData.timer_count = 50;
            BallData.timer_id = setInterval(onTimer, 20);
        }
    }
    // Timer Handler
    var onTimer = function() {
        if (BallData.timer_count > 0) {
            BallData.x -= BallData.move_x;
            BallData.y -= BallData.move_y;
            moveBall(BallData.x, BallData.y);
            BallData.timer_count--;
        } else {
            clearInterval(BallData.timer_id);
            BallData.timer_id = -1;
            context.clearRect(0, 0, canvas.width, canvas.height);
            moveBall(160, 160);
        }
    }
    // Func
    var moveBall = function(x, y) {
        ball.style.left = x - offset + "px";
        ball.style.top  = y - offset + "px";
    }
    // Connect
    var init = function() {
        if (window.TouchEvent) {
            canvas = document.getElementById("ID_CANVAS");
            canvas.width = window.innerWidth;
            canvas.height = window.innerHeight - 100;
            context = canvas.getContext("2d");
            ball = document.getElementById("ID_BALL");
            ball.addEventListener("touchstart",onBallTouchStart);
            ball.addEventListener("touchmove",onBallTouchMove);
            ball.addEventListener("touchend",onBallTouchEnd);
            moveBall(160, 160);
        }
    }
    //-->
</script>

</head>
<body onLoad="init()">
<div>
<canvas id="ID_CANVAS"></canvas>
<img id="ID_BALL" src="ball.png" style="position:absolute">
</div>
<br />
<p><a href=".">Back</a></p>
</body>
</html>

画像を引っ張って弾く

jsconsole

JavaScript コンソールって便利だよね。
もちろん本物の iPhone でも動いたよ。

次は画面隅で跳ね返る手段の実装か、面倒くさそうだ。
しかしずっとウチ姫型で作っているけどモンスト型のほうが需要あるんだろうな。

JavaScript Move Image

JavaScript の Canvas は基本的にデバイスコンテキストと同じ。
Canvas 上の画像を移動する場合は当然一度塗りつぶして新たな位置に再描写。
コレが基本。

しかし JavaScript は進化していた、レイヤーが使える。
つまりは img タグで指定された画像位置を動的に変更することが可能。

img タグの style に position:absolute を指定。
後はスクリプトで style.top 等の値を代入するだけというお手軽さ。
png 画像の透過属性も適用されたまま移動できる。

以下は半透明なボールのような画像をタッチすると移動できる例。

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>画像をタッチして移動する</title>
<!-- for Smart Phone -->
<meta name="viewport" content="
    width=device-width,
    initial-scale=1.0,
    minimum-scale=1.0,
    maximum-scale=1.0,
    user-scalable=no" />
<style>
body {
    margin: 0;
    overflow: hidden;
    -webkit-text-size-adjust: 100%;
}
#ID_CANVAS {
    background-color: #FFEE00;
}
</style>
<script type="text/javascript"><!--

    var canvas = null;
    var context = null;
    var ball = null;
    var offset = 24;

    // Event Handler
    var onBallTouchStart = function(e) {
        e.preventDefault();
    }
    var onBallTouchMove = function(e) {
        context.clearRect(0, 0, canvas.width, canvas.height);
        var touch = e.touches[0];
        var x = touch.pageX;
        var y = touch.pageY;
        context.beginPath();
        context.moveTo(160, 160);
        context.lineTo(x, y);
        context.stroke();
        // Move Layer
        moveBall(x, y);
    }
    var onBallTouchEnd = function(e) {
        context.clearRect(0, 0, canvas.width, canvas.height);
        moveBall(160, 160);
    }
    var moveBall = function(x, y) {
        ball.style.left = x - offset + "px";
        ball.style.top  = y - offset + "px";
    }
    // Connect
    var init = function() {
        if (window.TouchEvent) {
            canvas = document.getElementById("ID_CANVAS");
            canvas.width = window.innerWidth;
            canvas.height = window.innerHeight - 100;
            context = canvas.getContext("2d");
            ball = document.getElementById("ID_BALL");
            ball.addEventListener("touchstart",onBallTouchStart);
            ball.addEventListener("touchmove",onBallTouchMove);
            ball.addEventListener("touchend",onBallTouchEnd);
            moveBall(160, 160);
        }
    }
    //-->
</script>

</head>
<body onLoad="init()">
<div>
<canvas id="ID_CANVAS"></canvas>
<img id="ID_BALL" src="ball.png" style="position:absolute">
</div>
<br />
<p><a href=".">Back</a></p>
</body>
</html>

画像をタッチして移動する

touch_move

パソコンとマウスでは JavaScript コンソールで iPhone Emulate で動いた。
まあ実際の端末のようにフリックでのアレはしませんけど。

touch 関連の addEventListener を img タグ位置にしてみた。
この方法なら画像の場所以外でのフリックは iPhone 標準になるね。
アレッと思う場合が減るのでこんな感じにすると良さげ。

透過属性が残ったままなのでラインが透けてみえるのが確認できる。
上手く利用すれば色々な Web アプリに応用できると思う。

注意しないといけないのは画像描写の原点は左上だということ。
上記のような場合は画像サイズの半分をオフセットして描写する。

なんだけど、何故か SO-02D では画像が中心に表示されないのだが。
オフセット無しなら中心に、iPhone 版 Chrome だと中心ってなんだこれ。
Android 全部でそうなのかは解らない、まだ調べること多いな…

追記: Ball を Bool と書いていた部分があったので書き換え、恥ずい…