JavaScript の EventListener における this について

Firefox 3 にて検証。

あるオブジェクトに属するメソッド(関数オブジェクト)をイベントリスナーとして登録した場合、その登録の方法によってメソッド内で使用される this の意味が違ってくるよという話。

まずはこんなオブジェクトをつくってみる

// MyEventListener オブジェクト作成
var MyEventListener = function() { return this}

// MyEventListener オブジェクトにonMouseClickメソッドを追加
MyEventListener.prototype = {

  onMouseClick: function() { alert(this); } //←ここの this が今回の主役

}

// MyEventListener から myEventListener オブジェクトを作成
var myEventListener = new MyEventListener();

そして、myEventListener.onMouseClick をイベントリスナーとして登録してみる

イベントリスナーとして素直に関数名を表記した場合

アラートには "[object HTMLParagraphElement]" と表示される。
この場合、 this = イベントリスナーが登録されたターゲット

var div = getElementById("div"); 
div.addEventListener("click", myEventListener.onMouseClick, false);
イベントリスナーとして無名関数を使用した場合

アラートには "[object Object]" と表示される。
この場合、 this = イベントリスナーオブジェクト

var div = getElementById("div"); 
div.addEventListener("click", function() { myEventListener.onMouseClick() }, false);

いったん無名関数を間にはさむので this の意味が違ってくるのは当然といえば当然なんだろうけど、注意しないといろいろと面倒なことになりそうなのでメモしておきます。

(追記) Mozilla Developer Center に関連情報が書いてあった

https://developer.mozilla.org/ja/DOM/element.addEventListener

メモリに関する問題

メモリ使用量が増大する問題を引き起こす方法に共通していることは、オブジェクトのメソッドをコールバックとして渡すことです。

setTimeout(obj.func, 1000); // |this| が間違って設定される!
document.addEventListener("load", obj.func, false); // |this| が間違って設定される!

単純な回避策としては、無名関数をコールバックとして渡す方法があります。

document.addEventListener("load", function(event) { obj.func(event); }, false);

注意すべきなのは、この無名関数はそれを内包する環境にある全ての変数にアクセスすることができ、それらの変数が使用するメモリは、その無名関数が使われる可能性がある限りは (つまりイベントリスナが登録されている限りは) JavaScript エンジンによって解放されないということです。この関数に対して維持される必要があるのは実際には obj だけなのに、JavaScript エンジンは他の変数も維持する事を選択し、その結果メモリの負荷が増大します。removeEventListener を呼び出すか無名関数への参照を全て削除すれば、変数の消費するメモリが解放されるようになります。なので忘れずにそれを行ない、使用されていないメモリが JavaScript エンジンによって効率的に解放されるようにして下さい。

JavaScript で SVG の角を丸める方法のメモ

二次スプライン曲線を利用して少ない手間で角を曲げる方法。
Firefox 3.0.5, Safari 3.2.1, Opera 9.63 にて動作確認済み。

ソース (roundEdge.xml)

<?xml version='1.0'?>
<html xmlns="http://www.w3.org/1999/xhtml"
	xmlns:svg="http://www.w3.org/2000/svg">

<head>
	<title>JavaScript で SVG の角を丸める</title>
	<script type="text/javascript">
		<![CDATA[
		
		function roundEdge(path, radius) {
			
			//パスをリセット
			resetPath();
		
			//各ポイントの取得
			var A = path.pathSegList.getItem(0); //左の点
			var B = path.pathSegList.getItem(1); //真ん中の点
			var C = path.pathSegList.getItem(2); //右の点
			
			//任意の2点から角度を割り出す
			var angle1 = Math.atan2(B.y - A.y, B.x - A.x); //A,B間の角度
			var angle2 = Math.atan2(C.y - B.y, C.x - B.x); //B,C間の角度
		
			//二次スプライン曲線用のポイントB1とB2を割り出す
			var B1x = B.x - (Math.cos(angle1) * radius);
			var B1y = B.y - (Math.sin(angle1) * radius);
		
			var B2x = B.x + (Math.cos(angle2) * radius);
			var B2y = B.y + (Math.sin(angle2) * radius);
		
			//二次スプライン曲線を利用して角を丸める
			path.setAttribute('d', 'M '+A.x+','+A.y+' L '+B1x+','+B1y+' Q '+B.x+','+B.y+' '+B2x+','+B2y+' L '+C.x+','+C.y);
				
		}
		
		function resetPath() {
			document.getElementById('path').setAttribute('d', 'M 73.574464,78.567376 L 172.96453,26.936173 L 509.85816,121.16312');
			return false;
		}
	
		function round() {
			
			var path = document.getElementById('path');
			var radius = Number(document.getElementById('radius').value);
			
			if(radius <= 0 || isNaN(radius)) {
				window.alert('正数を入力してください。');
				return false;
			}
			
			roundEdge(path, radius);
			return false;
		}
	
		]]>
	</script>	
</head>

<body>
  <h1>JavaScript で SVG の角を丸める</h1>
  <form action="./" onreset="return resetPath();" onsubmit="return round();">
  	<input type="text" id="radius" value="20" />
  	<input type="submit" name="submit" value="角を丸める" />
  	<input type="reset" value="リセット" />
  </form>
  <p>
    <svg:svg width="600" height="300">
     <svg:path
       d="M 73.574464,78.567376 L 172.96453,26.936173 L 509.85816,121.16312"
       id="path"
       style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
    </svg:svg>
  </p>

</body>
</html>

JavaScript で Mac OS X のバージョンを調べる方法


Safari 3 と Firefox 3 で確認済み。(Opera では動作しない*1 )

var _userAgent = navigator.userAgent; // UserAgent を取得

if(_userAgent.indexOf("Mac") != -1 ){ // Mac かどうか判断
	var _osVersionSplit = _userAgent.split(" ")[7].split(/[\._;]/);  //バージョン部分を分解 
	var _osVersion = _osVersionSplit[0] + '.' + _osVersionSplit[1]; //バージョンを再結合
}


// バージョン判断
if(_osVersion == "10.5") {
	alert("OS X 10.5 です。");
} else {
	alert("OS X 10.5 ではありません。");	
}


暫定で作ったので、もっとスマートな方法があったら教えてください。

*1:そもそも、Opera は OS のバージョン情報を返さないみたいです。

jsViz で画像表示

よしたんのブログ jsViz を試す を参考にしました。

よしたんのブログでは SVG で画像表示してましたが、htmlでの表示方法がわかったのでメモします。

まず JavaScript の変更

サンプルの html中にある JavaScript の以下の箇所を変更する。

nodeElement.style.backgroundImage = "url(http://kylescholz.com/cgi-bin/bubble.pl?title=&r=12&pt=8&b=888888&c=" + color + ")";

   ↓

nodeElement.style.backgroundImage = "url("+dataNode.image+")";

つぎに xml の変更

これはよしたんのブログに書いてあった方法そのまんま。
やることは、node要素にimageプロパティを追加するだけです。

<root root="true" fixed="true" image="./a.png" color="black">
  <node image="./b.png" color="black"></node>
  <node image="./b.png" color="black">
  	<node image="./b.png" color="black"></node>
  	<node image="./b.png" color="black"></node>
  </node>
  <node image="./b.png" color="black"></node>
</root>

結果

以上です。

JS LOOKBOOK ウィンドウのサイズに併せて拡大縮小するようにした

ブラウザウィンドウのサイズに併せて、ギャラリーが拡大縮小するようにしてみた。*1

http://ynakajima.net/jslookbook/
※Firefox2,IE6,Safari3,Opera9 で確認済み。*2

ウィンドウサイズ取得に関してのメモ

jQuery でウィンドウの高さを取得する場合 $(window).height() と書けばいいはず。
しかし、Firefox だと $(document).height() と同じ値が帰ってくるという困った現象に遭遇。

しょうがないので、windowサイズ取得部分は自力で書いてみた。

参考:ブラウザの表示領域のサイズを取得する方法。

if(jQuery.browser.opera) { //Opera (document.documentElement.clientHeightが存在するため先に処理)
	var _height = document.body.clientHeight;
} else if(typeof document.documentElement.clientHeight == 'number') { //Firefox IE Safari3
	var _height = document.documentElement.clientHeight;
} else if(typeof window.innerHeight == 'number') { //Safari2
	var _height = window.innerHeight;
} else { //その他のブラウザの場合
	var _height = 600;	
}

*1:ギャラリー以外は拡大縮小しない

*2:Safari2では動かない...

JS LOOKBOOK を IE に対応させてみた

昨日つくった、UNIQLO LOOKBOOKJavaScriptJS LOOKBOOKIE に対応させてみた。

http://ynakajima.net/jslookbook/

変更した箇所

  • スクロールする部分(#gallery)がはみ出て表示されてしまっていた
    • #gallery の親要素 #lookbook に overflow: hidden を指定していたのに反映されていない模様。
    • #gallery を div要素でラッピングして、ラッピングした div要素に overflow: hidden; position: relative; widh: 1020px を指定したら解消。
    • 参考 http://cssbug.at.infoseek.co.jp/detail/winie/b068.html

UNIQLO の LOOKBOOK を JavaScript で実装してみた

年末年始の課題の一つ、「Flash の勉強として UNIQLO LOOKBOOK をそっくりそのまま作ってみる」に着手する。

しかし、いきなり Flash でやるのも芸がないので使い慣れている JavaScript で試しに実装してみた。

http://ynakajima.net/jslookbook/


※使用している画像は flickr から持ってきた Creative Commons ライセンスのもの。

課題点

  • 結局5時間くらいかかってしまった。もうちょっとスピードアップしたい。
  • 画像を全部読み込むまで時間がかかるのを改善したい。
  • windowのサイズに合わせて画像サイズを変更できるようにしたい

ちなみに、Firefox でしか確認してないので IE だと見れないかも。
IE 対応しました。

さて、肝心の Flash のほうに取りかからねば。。。