サイトマップ

JavaScriptコードスニペット:要素のサイズを自由に変更する

そういう要望は普通はあんまりないのですが、HTML要素をマウスで自由にリサイズできると便利なことがあります。典型的にはTEXTAREAをもっと拡げたいことがあるでしょう。FireFox 4ではブラウザの機能としてそういうことができるようになりましたが、やむを得ずそういう機能のないブラウザを使わざるを得ないときは、できるといいなと思ったりするものです。じゃあjQueryを使えばいいじゃないとか言わないでください。あれって巨大だし、bookmarkletにするのは難しいんじゃないでしょうか?

このプログラムは、もともとは画面内に配置したミニウインドウをリサイズできるようにするために作り始めたのですが、結構汎用的に使えるのではないかと思い、独立したものにしました。以下の例では@INCLUDE()という記述がありますが、サーバ側でその下のgeometry.jsファイルがその場に展開されるようにしています。

このプログラムの肝は、リサイズのためのハンドルの実現方法です。上下左右と角に8個の透明のdiv要素を配置し、それらをリサイズのためのハンドルとして使用します。煩雑に見えますが、似ているけど少しづつ異なるコードが並んでいるだけです。なお、IEには内部の要素より小さくできないバグがあるらしく、一方的に広がるばかりで気持ち悪いので、リサイズする前にリサイズ対象の要素の子要素を一旦display=noneにして消しています。もっとちらちらするかと思いましたが、影響はないようです。

[リサイズ開始]←クリックすると下の矩形領域の隅をドラッグしてサイズ変更ができるようになります。
var geometry = new @INCLUDE(geometry.js);
geometry.resizable(document.getElementById('rectangle'));

以下のプログラムは、関連度の高い『ウインドウのサイズを取得する』や『要素のページ内での絶対座標を取得する』を組み込んでクラス化しています。

geometry.js
/*
 * 要素の位置やサイズに関する情報を扱う
 */
function() {
	var bind = function(obj, fn) {
		return function() { return fn.apply(obj, arguments); }
	};
 
	this.styleSheets = [];
 
	/**
	 * 指定要素のページ内での絶対位置を取得する
	 */
	this.getGeometry = function(node) {
		var style = node.currentStyle || window.getComputedStyle(node);
		var result = {
			width : node.offsetWidth, height : node.offsetHeight,
			left : 0, top : 0,
			right : node.offsetWidth, bottom : node.offsetHeight,
			borderTop : parseInt(style.borderTopWidth),
			borderRight : parseInt(style.borderRightWidth),
			borderBottom : parseInt(style.borderBottomWidth),
			borderLeft : parseInt(style.borderLeftWidth),
			paddingTop : parseInt(style.paddingTop),
			paddingRight : parseInt(style.paddingRight),
			paddingBottom : parseInt(style.paddingBottom),
			paddingLeft : parseInt(style.paddingLeft)
		};
		var parent = node;
		while (parent) {
			result.left += parent.offsetLeft;
			result.top += parent.offsetTop;
			parent = parent.offsetParent;
		}
		if (document.all && document.compatMode == 'BackCompat') { //IE compat
			result.right += result.left;
			result.bottom += result.top;
		} else {
			result.right += result.left - result.borderLeft - result.borderRight;
			result.bottom += result.top - result.borderTop - result.borderBottom;
		}
		return result;
	};
 
	this.getWindowSize = function() {
		if (window.innerWidth)
			return { width : window.innerWidth, height : window.innerHeight };
		if (document.documentElement && document.documentElement.clientWidth)
			return { width : document.documentElement.clientWidth, height : document.documentElement.clientHeight };
 
		if (document.body.clientWidth)
			return { width : document.body.clientWidth, height : document.body.clientHeight };
	};
 
	/**
	 * 指定要素をリサイズ可能にする
	 * 依存するライブラリ:
	 *	event.js, style.js
	 * 影響を受けるプロパティ:
	 *	document.onmouseup
	 *	document.onmousemove
	 *	document.onselectstart
	 */
	this.resizable = function(node) {
		var HANDLE_SIZE = 7;
		var HANDLE_HALF = HANDLE_SIZE / 2;
		var geometry = this;
		var fixEvent = @INCLUDE(event.js);
		var selector = node.nodeName;
		if (node.id)
			selector += '#' + node.id;
		else if (node.className || node.styleClass)
			selector += '.' + (node.className || node.styleClass).split(/\s+/).join('.');
		geometry.styleSheets.push((@INCLUDE(style.js))(
			selector + " div.n-handle," +
			selector + " div.ne-handle," +
			selector + " div.e-handle," +
			selector + " div.se-handle," +
			selector + " div.s-handle," +
			selector + " div.sw-handle," +
			selector + " div.w-handle," +
			selector + " div.nw-handle {" +
			"	position:absolute;" +
			"	opacity:0;" + //set 0.5 on debug
			"	filter:alpha(opacity=0);" + //set 50 on debug
			"	line-height:0px;" +
			"}" +
			selector + " div.n-handle," +
			selector + " div.s-handle {" +
			"	width:100%;" +
			"	height:" + HANDLE_SIZE + "px;" +
			"	background-color:#FFAAAA;" +
			"	cursor:n-resize;" +
			"}" +
			selector + " div.e-handle," +
			selector + " div.w-handle {" +
			"	width:" + HANDLE_SIZE + "px;" +
			"	height:100%;" +
			"	background-color:#FFAAAA;" +
			"	cursor:e-resize;" +
			"}" +
			selector + " div.ne-handle," +
			selector + " div.se-handle," +
			selector + " div.sw-handle," +
			selector + " div.nw-handle {" +
			"	width:" + HANDLE_SIZE + "px;" +
			"	height:" + HANDLE_SIZE + "px;" +
			"	background-color:#AAAAFF;" +
			"}" +
			selector + " div.ne-handle { cursor:ne-resize; }" +
			selector + " div.se-handle { cursor:se-resize; }" +
			selector + " div.sw-handle { cursor:sw-resize; }" +
			selector + " div.nw-handle { cursor:nw-resize; }"
		));
 
		//resize handles
		var prepare_events = function() {
			document.onmouseup = function(evt) {
				evt = fixEvent(evt).stop();
				document.onmousemove = null;
				document.onmouseup = null;
			};
			document.onselectstart = function(evt) {
				evt = fixEvent(evt).stop();
			};
		};
		var new_handle = function(clazz) {
			var handle = node.appendChild(document.createElement('div'));
			handle.className = handle.styleClass = clazz;
			handle.innerHTML = ' ';
			return handle;
		};
		var handles = {
			n  : new_handle('n-handle'),
			e  : new_handle('e-handle'),
			s  : new_handle('s-handle'),
			w  : new_handle('w-handle'),
			ne : new_handle('ne-handle'),
			se : new_handle('se-handle'),
			sw : new_handle('sw-handle'),
			nw : new_handle('nw-handle')
		};
		handles.contains = function(el) {
			for (var key in this) {
				if (this[key] === el) return true;
			}
			return false;
		};
 
		handles['n'].onmousedown = bind(geometry, function(evt) {
			var geom = this.getGeometry(node);
			evt = fixEvent(evt).stop();
			var target = evt.target;
			target.offX = evt.pageX - geom.left;
			target.offY = evt.pageY - geom.top;
			document.onmousemove = bind(this, function(evt) {
				var geom = this.getGeometry(node);
				evt = fixEvent(evt).stop();
				if (evt.pageY - target.offY < geom.bottom) {
					prefit();
					node.style.top = (evt.pageY - target.offY) + 'px';
					node.style.height = (geom.bottom - this.getGeometry(node).top) + 'px';
					fit();
				}
			});
			prepare_events();
		});
 
		handles['ne'].onmousedown = bind(geometry, function(evt) {
			var geom = this.getGeometry(node);
			evt = fixEvent(evt).stop();
			var target = evt.target;
			target.offX = evt.offsetX;
			target.offY = evt.offsetY;
			document.onmousemove = bind(this, function(evt) {
				var geom = geometry.getGeometry(node);
				evt = fixEvent(evt).stop();
				if (evt.pageY - target.offY < geom.bottom &&
				    evt.pageX - target.offX > geom.left) {
					prefit();
					node.style.top = (evt.pageY) + 'px';
					node.style.width = (evt.pageX - geom.left) + 'px';
					node.style.height = (geom.bottom - this.getGeometry(node).top) + 'px';
					fit();
				}
			});
			prepare_events();
		});
 
		handles['e'].onmousedown = bind(geometry, function(evt) {
			evt = fixEvent(evt).stop();
			var target = evt.target;
			target.offX = evt.offsetX;
			target.offY = evt.offsetY;
			document.onmousemove = bind(this, function(evt) {
				var geom = geometry.getGeometry(node);
				evt = fixEvent(evt).stop();
				if (evt.pageX - target.offX > geom.left) {
					prefit();
					node.style.width = (evt.pageX - target.offX - geom.left) + 'px';
					fit();
				}
			});
			prepare_events();
		});
 
		handles['se'].onmousedown = bind(geometry, function(evt) {
			var geom = this.getGeometry(node);
			evt = fixEvent(evt).stop();
			var target = evt.target;
			target.offX = evt.offsetX;
			target.offY = evt.offsetY;
			document.onmousemove = bind(this, function(evt) {
				var geom = geometry.getGeometry(node);
				evt = fixEvent(evt).stop();
				if (evt.pageY - target.offY > geom.top &&
				    evt.pageX - target.offX > geom.left) {
					prefit();
					node.style.width = (evt.pageX - target.offX - geom.left) + 'px';
					node.style.height = (evt.pageY - target.offY - geom.top) + 'px';
					fit();
				}
			});
			prepare_events();
		});
 
		handles['s'].onmousedown = bind(geometry, function(evt) {
			evt = fixEvent(evt).stop();
			var geom = geometry.getGeometry(node);
			var target = evt.target;
			target.offX = evt.offsetX; //evt.pageX - geom.left;
			target.offY = evt.offsetY; //evt.pageY - geom.top + geom.height;
			document.onmousemove = bind(this, function(evt) {
				var geom = geometry.getGeometry(node);
				evt = fixEvent(evt).stop();
				if (evt.pageY - target.offY > geom.top) {
					prefit();
					node.style.height = (evt.pageY - geom.top - target.offY) + 'px';
					fit();
				}
			});
			prepare_events();
		});
 
		handles['sw'].onmousedown = bind(geometry, function(evt) {
			evt = fixEvent(evt).stop();
			var target = evt.target;
			target.offX = evt.offsetX;
			target.offY = evt.offsetY;
			document.onmousemove = bind(this, function(evt) {
				var geom = geometry.getGeometry(node);
				evt = fixEvent(evt).stop();
				if (evt.pageY - target.offY > geom.top &&
				    evt.pageX - target.offX < geom.right) {
					prefit();
					node.style.left = evt.pageX + 'px';
					var moved = geometry.getGeometry(node);
					if (moved.left != geom.left) {
						node.style.width = (geom.right - evt.pageX) + 'px';
					}
					node.style.height = (evt.pageY - target.offY - geom.top) + 'px';
					fit();
				}
			});
			prepare_events();
		});
 
		handles['w'].onmousedown = bind(geometry, function(evt) {
			evt = fixEvent(evt).stop();
			var target = evt.target;
			target.offX = evt.offsetX;
			target.offY = evt.offsetY;
			document.onmousemove = bind(this, function(evt) {
				var geom = geometry.getGeometry(node);
				evt = fixEvent(evt).stop();
				if (evt.pageX - target.offX < geom.right) {
					prefit();
					node.style.left = evt.pageX + 'px';
					var moved = geometry.getGeometry(node);
					if (moved.left != geom.left) {
						node.style.width = (geom.right - evt.pageX) + 'px';
					}
					fit();
				}
			});
			prepare_events();
		});
 
		handles['nw'].onmousedown = bind(geometry, function(evt) {
			evt = fixEvent(evt).stop();
			var target = evt.target;
			target.offX = evt.offsetX;
			target.offY = evt.offsetY;
			document.onmousemove = bind(this, function(evt) {
				var geom = geometry.getGeometry(node);
				evt = fixEvent(evt).stop();
				if (evt.pageY - target.offY < geom.bottom &&
				    evt.pageX - target.offX < geom.right) {
					prefit();
					node.style.left = evt.pageX + 'px';
					node.style.top = evt.pageY + 'px';
					var moved = geometry.getGeometry(node);
					if (moved.left != geom.left)
						node.style.width = (geom.right - evt.pageX) + 'px';
					if (moved.top != geom.top)
						node.style.height = (geom.bottom - evt.pageY) + 'px';
					fit();
				}
			});
			prepare_events();
		});
 
		var prefit = function() {
			var children = node.childNodes;
			for (var i = 0; i < children.length; i++) {
				var child = children.item(i);
				if (child.nodeType != 1) continue;
				if (handles.contains(child)) continue;
				//child.style['_display'] = child.style.display;
				child.style.display = 'none';
			}
		};
 
		var postfit = function() {
			var children = node.childNodes;
			for (var i = 0; i < children.length; i++) {
				var child = children.item(i);
				if (child.nodeType != 1) continue;
				if (handles.contains(child)) continue;
				//child.style.display = child.style['_display'];
				child.style.display = '';
				//delete child.style['_display'];
			}
		};
 
		var fit = function() {
			if (typeof node.onfit == 'function')
				node.onfit();
 
			var g = geometry.getGeometry(node);
			var off = geometry.getGeometry(handles['n'].offsetParent);
			g.left -= off.left;
			g.top -= off.top;
			if (document.all) { //IE
				g.borderTop = 0;
				g.borderRight = 0;
				g.borderBottom = 0;
				g.borderLeft = 0;
			}
			handles['n'].style.left = (g.left - g.borderLeft) + 'px';
			handles['n'].style.top = (g.top - g.borderTop - HANDLE_HALF) + 'px';
			handles['n'].style.width = g.width + 'px';
			handles['ne'].style.top = (g.top - g.borderTop - HANDLE_HALF) + 'px';
			handles['ne'].style.left = (g.left + g.width - g.borderLeft - g.borderRight - HANDLE_HALF) + 'px';
			handles['e'].style.left = (g.width + g.left - g.borderLeft - g.borderRight - HANDLE_HALF) + 'px';
			handles['e'].style.top = g.top + 'px';
			handles['e'].style.height = g.height + 'px';
			handles['se'].style.left = (g.width + g.left - g.borderLeft - HANDLE_HALF) + 'px';
			handles['se'].style.top = (g.top + g.height - g.borderTop - HANDLE_HALF) + 'px';
			handles['s'].style.left = (g.left - g.borderLeft - HANDLE_HALF) + 'px';
			handles['s'].style.top = (g.top + g.height - g.borderTop - HANDLE_HALF) + 'px';
			handles['s'].style.width = g.width + 'px';
			handles['sw'].style.left = (g.left - g.borderLeft - HANDLE_HALF) + 'px';
			handles['sw'].style.top = (g.top + g.height - g.borderTop - HANDLE_HALF) + 'px';
			handles['w'].style.left = g.left - g.borderLeft - HANDLE_HALF + 'px';
			handles['w'].style.top = g.top + 'px';
			handles['w'].style.height = g.height + 'px';
			handles['nw'].style.left = (g.left - g.borderLeft - HANDLE_HALF) + 'px';
			handles['nw'].style.top = (g.top - g.borderTop - HANDLE_HALF) + 'px';
			postfit();
		};
 
		fit();
	};
 
	this.finalize = function() {
		for (var i = 0; i < this.styleSheets.length; i++)
			this.styleSheets[i].parentNode.removeChild(this.styleSheets[i]);
	};
}

関連コンテンツ

 
research/1311297242.txt · 最終更新: 2011/07/24 09:18 by Kazuyuki Matsuda
特に明示されていない限り、本サイトの内容は次のライセンスに従います:Copyright(C) 2011 Shorindo, Inc. All Rights Reserved
Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki