DOM イベント処理 (2) mousemove
はじめに
この資料では前回の DOM イベント処理に引き続き、mousemove イベントを処理するサンプルを作り、解説します。
出来上がりは次のようになります。上の画像の上でマウスを動かしてみてください。下側にボインタで指した箇所の "拡大図" が表示されます。
さっそく解説に入ります。
1. 考え方
1-1. 画像のクリッピング
元画像とサムネイル画像の二枚の画像を準備します。今回はサムネイルは元画像の1/10の大きさとしています。そこで、サムネイル画像 上の mousemove イベントを処理し、サムネイル画像の左上部を原点とする座標系(下図x-y)における位置を求めます。位置の単位は px です。サムネイル画像と元画像の比率から、元画像における同様の座標がわかります(ここではそれぞれx10です)。元画像上の座標を中心として、上下 100px で囲まれた正方形の領域が "拡大図" に表示される領域です(下図の赤色の正方形)。
さて、ここで DIV 要素の background-position 属性を利用します。次のコードをみてください。
#viewer {
width: 200px;
height: 200px;
background: url("pic3.jpg");
background-position:
-600px -400px;
}
このコードにより、背景画像はx, y 座標それぞれ -600px, -400px だけずれた位置に移動します。表示される領域は x, y 共に 0 - 200px の矩形領域ですから、最終的に画像の (600px, 400px) から始まる 200px の正方形領域だけがクリッピングされることになります。
文章で書くと長ったらしいですが、単純な話です。
ポイントはここまでです。後は、スクリプトがさまざまな環境で実行されることに配慮して、feature sniffing を実施しています。
1-2. Feature sniffing
ここで feature sniffing について触れておきます。feature sniffing と対極にあるのは browser sniffing です。 browser sniffing はブラウザの判定を行うことによって、どの機能をサポートするか・どの機能が安全に実行可能か判別する方法です。それに対して、feature sniffing は直接的にその機能がサポートされているかどうかチェックしながらコードパスを割り振るものです。例えばイベントハンドラのセットアップ時に、 addEventListener メソッドがサポートされているかどうか確認するためには次の二通りの方法が考えられます。ひとつは 「ブラウザが FireFox であれば addEventListener をサポートするはずだ」 として 「addEventListener メソッドが利用可能かどうかをしるために、スクリプトの実行環境がFireFox かどうかをチェックする」 方法です。
<script
type="text/javascript">
// 0 - unknown
// 1 - FireFox
// 2 - IE
var browser = 0;
function foo()
{
alert( navigator.userAgent );
if (
navigator.userAgent.match(/Firefox/) ) {
browser =
1;
}
else if (
navigator.userAgent.match(/MSIE/) ) {
browser =
2;
}
else {
browser =
3;
}
alert ( browser );
}
</script>
<input type="button" onclick="foo();" >
二つ目は機能を直接調べる方法です。前回の記事で紹介した addEvent メソッドがこの例です。
function addEvent(element,
eventType, fn, useCapture) {
if (element.addEventListener)
{ // W3C DOM 標準 -
FireFox ...
element.addEventListener(eventType, fn, useCapture);
return
true;
}
else if (element.attachEvent)
{ // Internet
Explorer 専用
var r =
element.attachEvent('on' + eventType, fn);
return
r;
}
else {
element['on'+eventType] = fn;
}
}
element.addEventListener が null であるかどうかを直接チェックしています。このように機能を直接チェックする方法は feature sniffing と呼ばれています。
尚、feature sniffing をするにあたり、特にプロパティの有無を検出する際に気をつけなければならない点があります。それは値が 0 の場合は false と判定されるということです。例えば、foo.bar プロパティの有無を判定しようとして、メソッドの feature sniffing と同様に次のコードを書いたとします。
if (foo.bar) {
...
}
この場合、もし foo.bar の値が false や 0 の場合、foo が bar プロパティを持っていたとしても false として判定されてしまいます。このようなバグを避けるために、プロパティの場合は次のように判定します。
if ( typeof foo.bar != 'undefined' ) {
...
}
以上で前提知識は終了です。
2. リスト
気をつける点にコメントします。
///////////////////////////////////////////////////////////////////////////////
//
// Date: 1/8/2005
// Author: Keisuke
Oyama (keisrtp@gmail.com)
//
///////////////////////////////////////////////////////////////////////////////
var isIE
= false;
function initScript(e)
{
if ( document.getElementById ) {
var
thumbnail = document.getElementById('thumb');
addEvent(thumbnail, 'mousemove', onMouseMove, false);
}
if ( !window.opera &&
navigator.userAgent.indexOf('MSIE') != -1 ) {
isIE =
true;
}
}
///////////////////////////////////////////////////////////////////////////////
// addEvent については前回の記事を参照してく
ださい。
function addEvent(element,
eventType, fn, useCapture) {
if (element.addEventListener) { // W3C DOM 系 - FireFox ...
element.addEventListener(eventType, fn, useCapture);
return
true;
}
else if (element.attachEvent) { // Internet Explorer 系
var r =
element.attachEvent('on' + eventType, fn);
return
r;
}
else {
element['on'+eventType] = fn;
}
}
///////////////////////////////////////////////////////////////////////////////
function onMouseMove(e) {
var t = e.target ? e.target :
e.srcElement;
var x = 0, y = 0;
// サムネイル上での座標x-yを求めています
x = e.clientX - getPosX(t);
y = e.clientY - getPosY(t);
var ImgOx = (10 * x - 100)
* (-1); //
サムネイルの縮小率が 1/10 であることを既知としています
var ImgOy = (10 * y - 100)
* (-1);
var elmViewer =
document.getElementById('viewer');
elmViewer.style.backgroundPosition
= ImgOx + 'px ' + ImgOy + 'px'; //
ここがすべて
}
///////////////////////////////////////////////////////////////////////////////
function onMouseMove(ev)
{
var t = ev.target ? ev.target :
ev.srcElement;
var e = window.event ? window.event : ev;
var x = 0, y = 0;
var x0 = 0, y0 = 0;
if ( typeof e.pageX != 'undefined'
&& typeof e.pageY != 'undefined' ) {
x0 =
e.pageX;
y0 =
e.pageY;
}
else if ( typeof e.clientX !=
'undefined' && typeof e.clientY != 'undefined' ) {
x0 =
e.clientX;
y0 =
e.clientY;
if ( isIE
) { //
この補正は大変重要ですが他の資料で説明します
x0 +=
document.documentElement.scrollLeft;
y0 +=
document.documentElement.scrollTop;
}
}
//
サムネイル上での座標x-yを求めています
x = x0 - getPosX(t);
y = y0 - getPosY(t);
var ImgOx = (10 * x - 100) *
(-1); //
サムネイルの縮小率が 1/10 であることを既知としています
var ImgOy = (10 * y - 100) *
(-1);
var elmViewer =
document.getElementById('viewer');
elmViewer.style.backgroundPosition =
ImgOx + 'px ' + ImgOy + 'px';
}
///////////////////////////////////////////////////////////////////////////////
function getPosX
(elm) {
var posX = 0;
if ( typeof elm.offsetParent !=
'undefined' ) {
do {
posX += elm.offsetLeft;
} while (
elm = elm.offsetParent );
}
else if ( typeof elm.x != 'undefined' ) {
posX +=
elm.x;
}
return posX;
}
///////////////////////////////////////////////////////////////////////////////
function getPosY
(elm) {
var posY = 0;
if ( typeof elm.offsetParent !=
'undefined' ) {
do {
posY += elm.offsetTop;
} while
(elm = elm.offsetParent);
}
else if ( typeof obj.y != 'undefined' ) {
posY +=
elm.y;
}
return posY;
}
///////////////////////////////////////////////////////////////////////////////
addEvent(window,
'load', initScript, false);
HTML は次の通り。
<!DOCTYPE HTML PUBLIC "-//W3C//DTD
HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<style tyle="text/css">
#viewer {
width: 200px;
height: 200px;
background: url("pic3.jpg");
background-color: white;
background-repeat: no-repeat; <!-- 隅っこがわかるように
-->
border: 1px solid black;
}
</style>
<script type="text/javascript"
src="foo.js"></script>
</head>
<body>
<p>
<img id="thumb"
src="pic3s.jpg" alt="" />
<div id="viewer">
</div>
</body>
</html>