IE6, Sortable и whatever:hover
Как подружить whatever:hover и Scriptaculous
Работая над одним сайтом, столкнулся с одной проблемой: в IE6 при использовании whatever:hover для выпадающего меню, построенного чисто на CSS, и Scriptaculous (а именно, Sortable, причем для элемента, не имеющего никакого отношения к меню) возникало непонятное мерцание.
Страница, на которой проявляется мерцание (только IE6).
То, что "виноват" именно Scriptaculous, доказывается очень легко: в исходном тексте test.html комментируется строка, создающая Draggable:
var id = 'user_' + user_id;
if ($(id)) {
Sortable.create(
$(id),
{
}
);
}
Результат можно увидеть здесь — мерцание исчезло.
Путём долгих и нудных экспериментов были найдены строки, вызывающие такое поведение:
drags: [],
observers: [],
register: function(draggable) {
if(this.drags.length == 0) {
this.eventMouseUp = this.endDrag.bindAsEventListener(this);
this.eventMouseMove = this.updateDrag.bindAsEventListener(this);
this.eventKeypress = this.keyPress.bindAsEventListener(this);
Event.observe(document, "mouseup", this.eventMouseUp);
Event.observe(document, "mousemove", this.eventMouseMove); // <---
Event.observe(document, "keypress", this.eventKeypress);
}
this.drags.push(draggable);
},
//...
};
Таким образом, если мы уберём Event.observe(document, "mousemove", this.eventMouseMove), то мерцание исчезнет. Естественно, это не выход, так как весь код, который прямо или косвенно использует Draggables, перестанет работать.
Попытаемся решить проблему (лезть в чужой код ой как не хочется). Одно ясно: вызов Event.observe(document, "mousemove", this.eventMouseMove) придется закомментировать. Добавлять обработчик события придется при активации draggable (в функции Draggables.activate), а убирать — в Draggables.deactivate() и Draggables.endDrag(). Возможно, хватит просто endDrag(), но ничего плохого не случится, если для подстраховки изменим оба метода.
Исходный код:
if(this.drags.length == 0) {
this.eventMouseUp = this.endDrag.bindAsEventListener(this);
this.eventMouseMove = this.updateDrag.bindAsEventListener(this);
this.eventKeypress = this.keyPress.bindAsEventListener(this);
Event.observe(document, "mouseup", this.eventMouseUp);
Event.observe(document, "mousemove", this.eventMouseMove); //<-- нужно закомментировать
Event.observe(document, "keypress", this.eventKeypress);
}
this.drags.push(draggable);
},
activate: function(draggable) {
if(draggable.options.delay) {
this._timeout = setTimeout(function() {
Draggables._timeout = null;
window.focus(); //<-- здесь нужно повесить обработчик события
Draggables.activeDraggable = draggable;
}.bind(this), draggable.options.delay);
} else {
window.focus(); // allows keypress events if window isn't currently focused, fails for Safari
//<-- здесь нужно повесить обработчик события
this.activeDraggable = draggable;
}
},
deactivate: function() {
this.activeDraggable = null; //<-- здесь нужно убрать обработчик события
},
endDrag: function(event) {
if(this._timeout) {
clearTimeout(this._timeout);
this._timeout = null;
}
if(!this.activeDraggable) return;
this._lastPointer = null;
this.activeDraggable.endDrag(event);
this.activeDraggable = null; //<-- здесь нужно убрать обработчик события
},
Окончательный результат:
if(this.drags.length == 0) {
this.eventMouseUp = this.endDrag.bindAsEventListener(this);
this.eventMouseMove = this.updateDrag.bindAsEventListener(this);
this.eventKeypress = this.keyPress.bindAsEventListener(this);
Event.observe(document, "mouseup", this.eventMouseUp);
Event.observe(document, "keypress", this.eventKeypress);
}
this.drags.push(draggable);
},
activate: function(draggable) {
if(draggable.options.delay) {
this._timeout = setTimeout(function() {
Draggables._timeout = null;
window.focus();
Event.observe(document, "mousemove", this.eventMouseMove);
Draggables.activeDraggable = draggable;
}.bind(this), draggable.options.delay);
} else {
window.focus(); // allows keypress events if window isn't currently focused, fails for Safari
if (draggable) {
Event.observe(document, "mousemove", this.eventMouseMove);
}
this.activeDraggable = draggable;
}
},
deactivate: function() {
this.activeDraggable = null;
Event.stopObserving(document, "mousemove", this.eventMouseMove);
},
endDrag: function(event) {
if(this._timeout) {
clearTimeout(this._timeout);
this._timeout = null;
}
if(!this.activeDraggable) return;
this._lastPointer = null;
this.activeDraggable.endDrag(event);
this.activeDraggable = null;
Event.stopObserving(document, "mousemove", this.eventMouseMove);
},
Всё работает!
Патч в формате unified diff.


Побочный эффект: элементы li теперь не перетаскиваются за bullet, только за содержимое (причем только в IE6; в остальных браузерах всё отлично работает).