2013-12-25 3 views
1

Im пытается переместить коробку на холсте, когда игрок перемещается, но не может понять правильные математические данные, чтобы сделать это, пытаясь сделать что-то симулятивное для перетаскивания функции jquery, за исключением холста.Move Box с курсором мыши

Вот мой код до сих пор:

var _proto = {left:0,top:0,left2:0,top2:0}; 
var _isClicked = false; 

$("canvas").on("mousedown", function(e) { 
    var offset = $(this).offset(); 
    _proto.left = e.pageX-offset.left; //left of screen works fine,mouse 
    _proto.top = e.pageY-offset.top; //top of screen works fine,mouse 
    _isClicked = true; 

    $(this).on("mousemove", function(e) { 

     _proto.left2 = (e.pageX-offset.left); //get new pos mouse, works fine 
     _proto.top2 = (e.pageY-offset.top); //get new pos mouse, works fine 

     //Obj is an array of proto's objects, 
     // It moves the box to quick and incorrect 
     _objects[0].left = _proto.left2-(_proto.left-_objects[0].left); 
     _objects[0].top = _proto.top2-(_proto.top-_objects[0].top); 

     if(_isClicked == false) $(this).off("mousemove");   
    }); 

}).on("mouseup", function(e) { 
    _isClicked = false; 
}); 

DEMO: http://jsfiddle.net/CezarisLT/tUXM3/

+1

Одно примечание: вместо привязки и открепления движением мыши свяжите один раз и оператор 'if', который возвращает, если' _isClicked' является 'false'. –

+1

Что такое переменная '_moveid'? И почему вы назначаете свойства left и top для '_objects [0]', используя текущее значение '_objects [_moveid]'? –

+1

В общем: 'element.style.left = element.offsetLeft + e.clientX - mx + 'px'', где' mx' - это предыдущее значение 'e.clientX'. См. [Эту скрипку] (http://jsfiddle.net/cww55/6/). Хотя скрипка написана с помощью ванильного JS, вы можете получить эту идею. – Teemu

ответ

1

Мне нравится использовать потоки событий для решения таких проблем, как эти. Что такое поток событий? Это поток событий. Итак, давайте создадим наш собственный 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 делает две вещи:

  1. Это позволяет вам подписаться на событие.
  2. Он позволяет обрабатывать поток событий, создавая совершенно новый поток событий.

Теперь давайте посмотрим на метод 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/


Так что же мы заключаем из этого? Потоки событий потрясающие, и вы должны использовать потоки вместо создания больших монолитных прослушивателей событий. Они делают ваш код более читабельным, понятным и поддерживаемым, и они упрощают жизнь каждого.

+0

Это отличный ответ, но можете ли вы объяснить, как isPointInPath используется для нескольких объектов? – Kivylius

+0

@CezarisLT Метод 'isPointInPath' принадлежит к API холста. См. Следующий лист обложки холста: http://www.nihilogic.dk/labs/canvas_sheet/HTML5_Canvas_Cheat_Sheet.png Метод isPointInPath используется для определения того, ограничены ли заданные координаты 'x' и' y 'активными в данный момент path: http://www.rgraph.net/blog/2013/february/an-example-of-the-html5-canvas-ispointinpath-function.html После того, как вы начнете путь, вы можете либо «заполнить» его, 'it,' clip' it или использовать 'isPointInPath'. В моем ответе я «привязываю» форму (т. Е. Начинаю путь). Тогда я просто использую 'isPointInPath' или что-то еще –

Смежные вопросы