Статьи

Сокровища Techy # 3: Когда мышка не мышка?

Некоторое время назад у меня был маленький гаджет в наборе инструментов, который я всегда нахожу бесценным, когда работаю с событиями мыши . Он оценивает две цели события, чтобы определить, содержит ли одна другую:

function contains(node1, node2)
{
	if(node2 == node1) { return true; }
	if(node2 == null) { return false; }
	else { return contains(node1, node2.parentNode); }
}

Благо и проклятие

Одна из самых красивых вещей в модели событий JavaScript — это всплывающее событие: если событие не перехватывается элементом, который его запускает, событие всплывает до его родительского элемента. Оттуда это может быть захвачено, или это может всплыть снова, и это продолжается до самого DOM , пока событие не будет захвачено или пока оно не вспыхнет сверху.

Однако, как бы это ни было элегантно и полезно, это делает события мыши более сложными; цель события может не быть элементом, к которому фактически был привязан прослушиватель событий, или события могут появляться в «неправильное» время. Типичным примером этого является перемещение мыши от элемента контейнера к элементу внутри него — событие элемента mouseout сработает в элементе контейнера, даже если мышь фактически не вышла за его пределы .

В большинстве современных браузеров мы можем различать, используя свойство события eventPhaseaddEventListenertrue Существуют также специализированные свойства, такие как originalTargetexplicitOriginalTarget

Но все это невозможно в Internet Explorer (по крайней мере, не в тех версиях, которые люди сейчас используют).

Найти что-то, что действительно работает

В Internet Explorer есть встроенный метод оценки цели событий, который называется… (как вы уже догадались)… contains() Итак, давайте сделаем что-то подобное, которое работает во всех браузерах. И таким образом мы сэкономим некоторую часть кода:

 container.onmouseout = function(e)
{
	var target = e ? e.relatedTarget : event.toElement;
	
	if(!contains(this, target))
	{
		//Mouse has left the container element
	}
	else
	{
		//Mouse is still inside
	}
};

Если бы мы пытались точно воссоздать метод IE, мы бы прототипировали наш метод для ObjectHTMLElement Но мы не должны создавать прототипы пользовательских методов для встроенных объектов , особенно в коде, с которым будут работать другие люди, потому что эти методы будут отображаться в перечислителях, что может вызвать серьезные проблемы для сценариев, которые их не ожидают.

Для интереса, хотя, если бы мы использовали его в качестве прототипа, ему потребовался бы только один аргумент, поскольку сам объект контейнера мог бы ссылаться на this

 Object.prototype.contains = function(node)
{
	if(node == this) { return true; }
	if(node == null) { return false; }
	else { return this.contains(node.parentNode); }
}

Во всяком случае, мы не собираемся делать это здесь. Помимо всего прочего, мы хотим, чтобы что-то работало во всех браузерах, включая Internet Explorer (который не поддерживает собственное прототипирование; и даже если бы он это делал, это могло бы перезаписать собственный метод).

Поэтому на практике я склонен использовать его в форме с двумя аргументами как метод любого основного объекта, с которым я работаю; Я нахожу это наиболее удобным, и он не перезаписывает никакие собственные методы:

 var myObject = {

	...,

	contains: function(node1, node2)
	{
		if(node2 == node1) { return true; }
		if(node2 == null) { return false; }
		else { return this.contains(node1, node2.parentNode); }
	},
	
	...

};

И вот, у вас это есть — короткий кусок кода, который противоречит его полезности (как это часто бывает с коротким кодом). Фактически, в той или иной форме я бы сказал, что этот код превратил примерно в четверть всех написанных мной сценариев! Так ура к этому; и благодарность Джейсону Дэвису , который много лет назад имел оригинальную идею прототипа Объекта — для ударов и хихиканья — который помог мне отучить себя от Netscape 4.