Мне нравится использовать потоки событий для решения таких проблем, как эти. Что такое поток событий? Это поток событий. Итак, давайте создадим наш собственный EventStream
конструктор:
function EventStream() {
var listeners = this.listeners = [];
return [this, function (event) {
return listeners.map(function (listener) {
return listener(event);
});
}];
}
Мы не будем использовать EventStream
конструктор напрямую. Вместо этого мы напишем функцию, которая создает поток событий, подписывается его в поток событий и возвращает поток:
function getEventStream(event, target) {
var pair = new EventStream;
target.addEventListener(event, pair[1]);
return pair[0];
}
Теперь мы можем создавать потоки событий следующим образом:
var move = getEventStream("mousemove", window);
Теперь мы имеют поток mousemove
событий, хранящихся в переменной move
. Итак, как мы его используем? Красота потоков событий заключается в том, что вы можете map
, filter
, scan
и merge
их. Это облегчает жизнь.
Сначала давайте посмотрим, метод map
:
EventStream.prototype.map = function (f) {
var pair = new EventStream;
var dispatch = pair[1];
this.listeners.push(function (x) {
return dispatch(f(x));
});
return pair[0];
};
Метод map
делает две вещи:
- Это позволяет вам подписаться на событие.
- Он позволяет обрабатывать поток событий, создавая совершенно новый поток событий.
Теперь давайте посмотрим на метод filter
:
EventStream.prototype.filter = function (f) {
var pair = new EventStream;
var dispatch = pair[1];
this.listeners.push(function (x) {
if (f(x)) return dispatch(x);
});
return pair[0];
};
filter
метод, как следует из названия, фильтрует события в потоке событий. Он возвращает совершенно новый поток событий с отфильтрованными событиями.
Далее вверх, scan
метод:
EventStream.prototype.scan = function (a, f) {
var pair = new EventStream;
var dispatch = pair[1];
dispatch(a);
this.listeners.push(function (x) {
return dispatch(a = f(a, x));
});
return pair[0];
};
Метод scan
позволяет создавать «свойства» которые меняют от случая к случаю, создавая новый «поток событий» свойство . Это очень полезная функция, которую я продемонстрирую, как использовать ниже.
Наконец мы имеем merge
метод:
EventStream.prototype.merge = function (stream) {
var pair = new EventStream;
var dispatch = pair[1];
this.listeners.push(function (x) {
return dispatch({left: x});
});
stream.listeners.push(function (x) {
return dispatch({right: x});
});
return pair[0];
};
Метод merge
принимает два события потоки и объединяет их в единый поток событий. Чтобы отличить, какой поток событий возник, какое событие мы помещаем каждое событие как left
или right
.
Теперь, когда мы узнали о событии потоков давайте использовать их, чтобы создать перемещаемую коробку на холст и посмотреть, как они делают жизнь так просто.
Первое, что мы сделать, это создать холст:
var canvas = document.querySelector("canvas");
var context = canvas.getContext("2d");
var width = canvas.width;
var height = canvas.height;
var position = getPosition(canvas);
var left = position.left;
var top = position.top;
getPosition
функция определяется следующим образом:
function getPosition(element) {
if (element) {
var position = getPosition(element.offsetParent);
return {
left: position.left + element.offsetLeft,
top: position.top + element.offsetTop
};
} else {
return {
left: 0,
top: 0
};
}
}
Далее мы создаем конструктор для Box
:
function Box(x, y, w, h) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
}
Box.prototype.bind = function (context) {
context.beginPath();
context.rect(this.x, this.y, this.w, this.h);
return context;
};
Затем мы создаем коробку и рисуем ее на экран:
var box = new Box(100, 100, 150, 150);
box.bind(context).fill();
Теперь нам нужно сделать его перетаскиваемым. Мы начинаем перетаскивать, удерживая нажатой кнопку мыши. Поэтому первое, что мы делаем, это создать поток в mousedown
событие:
var down = getEventStream("mousedown", canvas);
Мы хотим координаты mousedown
событий относительно холста. Кроме того, нам нужны только те события mousedown
, которые встречаются в верхней части окна. Это может быть легко обработаны с использованием потоков событий следующим образом:
var dragStart = down
.map(function (event) {
return {
x: event.clientX - left,
y: event.clientY - top
};
})
.filter(function (cursor) {
return box.bind(context).isPointInPath(cursor.x, cursor.y);
});
Теперь у вас есть поток mousedown
событий на верхней части коробки.
Далее мы получаем поток mouseup
событий, потому что перетаскивание останавливается, как только вы поднимаете палец с кнопки мыши:
var up = getEventStream("mouseup", window);
Мы получаем mouseup
события для всего окна, так как пользователь должен быть в состоянии мышей на мышь за пределами холста и отпустите его.
Далее мы объединяем потоки событий dragStart
и up
создать единый поток dragStartStop
событий:
var dragStartStop = dragStart.merge(up).map(function (x) {
return x.left;
});
События из потока в up
события не имеют никакой полезной информации. Они служат только для того, чтобы отметить, что пользователь перестал перетаскивать. Следовательно, мы заботимся только о событиях из потока событий left
.
Возвращаясь, чтобы на самом деле перетащить поле нам нужно mousemove
событий.Итак, давайте поток в mousemove
событие:
var move = getEventStream("mousemove", canvas).map(function (event) {
return {
x: event.clientX - left,
y: event.clientY - top
};
});
Как и с dragStart
потоком, мы только хотим координаты mousemove
событий относительно холста.
Теперь мы можем merge
в dragStartStop
и move
потоков для создания окончательного drag
потока:
var drag = dragStartStop.merge(move)
.scan(null, function (prev, event) {
if (event.hasOwnProperty("left")) {
var left = event.left;
return left && [left, left];
} else if (prev) return [prev[1], event.right];
})
.filter(function (x) {
return x;
})
.map(function (position) {
var prev = position[0];
var current = position[1];
return {
dx: current.x - prev.x,
dy: current.y - prev.y
};
});
Здесь мы scan
события объединяемых потоков, чтобы создать «поток событий свойства» из предыдущих и текущие позиции мыши, когда пользователь перетаскивает ящик. We filter
те mousemove
события, когда пользователь перетаскивает ящик, и мы получаем разницу в позициях между предыдущими и текущими событиями mousemove
.
Теперь мы можем сделать ящик тащат:
drag.map(function (position) {
box.x += position.dx;
box.y += position.dy;
context.clearRect(0, 0, width, height);
box.bind(context).fill();
});
Вот и все. Просто так? Смотрите демо: http://jsfiddle.net/PC3m8/
Так что же мы заключаем из этого? Потоки событий потрясающие, и вы должны использовать потоки вместо создания больших монолитных прослушивателей событий. Они делают ваш код более читабельным, понятным и поддерживаемым, и они упрощают жизнь каждого.
Одно примечание: вместо привязки и открепления движением мыши свяжите один раз и оператор 'if', который возвращает, если' _isClicked' является 'false'. –
Что такое переменная '_moveid'? И почему вы назначаете свойства left и top для '_objects [0]', используя текущее значение '_objects [_moveid]'? –
В общем: 'element.style.left = element.offsetLeft + e.clientX - mx + 'px'', где' mx' - это предыдущее значение 'e.clientX'. См. [Эту скрипку] (http://jsfiddle.net/cww55/6/). Хотя скрипка написана с помощью ванильного JS, вы можете получить эту идею. – Teemu