当前位置: 动力学知识库 > 问答 > 编程问答 >

javascript - Move Box with the mouse cursor

问题描述:

Im trying to move a box on canvas as the player moves but can't figure out the correct maths to do so im trying to do something simular to the draggable of the jquery function exept on canvas.

Here my code so far:

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/

网友答案:

I like to use event streams to solve problems like these. What is an event stream? It is a stream of events. So let's create our own EventStream constructor:

function EventStream() {
    var listeners = this.listeners = [];

    return [this, function (event) {
        return listeners.map(function (listener) {
            return listener(event);
        });
    }];
}

We won't be using the EventStream constructor directly. Instead we'll write a function which creates an event stream, subscribes it to a stream of events and returns the stream:

function getEventStream(event, target) {
    var pair = new EventStream;
    target.addEventListener(event, pair[1]);
    return pair[0];
}

Now we can create event streams as follows:

var move = getEventStream("mousemove", window);

Now we have a stream of mousemove events stored in the variable move. So how do we use it? The beauty of event streams is that you can map over, filter, scan and merge them. This makes life much easier.


First let's see the map method:

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];
};

The map method does two things:

  1. It allows you to subscribe to an event.
  2. It allows you to process an event stream, creating an entirely new event stream.

Now let's look at the filter method:

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];
};

The filter method, as the name implies, filters the events in an event stream. It returns an entirely new event stream with the filtered events.

Next up, the scan method:

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];
};

The scan method allows us to create "properties" which change from event to event, creating a new "property event stream". It's a very useful function which I'll demonstrate how to use below.

Finally we have the merge method:

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];
};

The merge method takes two events streams and merges them into a single event stream. To distinguish which event stream originated which event we tag each event as either left or right.


Now that we learned about event streams let's use them to create a draggable box on a canvas and see how they make life so simple.

The first thing we do is set up the canvas:

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;

The getPosition function is defined as follows:

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
        };
    }
}

Next we create a constructor for a 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;
};

Then we create a box and draw it to the screen:

var box = new Box(100, 100, 150, 150);
box.bind(context).fill();

Now we need to make it draggable. We start dragging by holding down the mouse button. So the first thing we do is create a mousedown event stream:

var down = getEventStream("mousedown", canvas);

We want the coordinates of the mousedown events relative to the canvas. In addition we only want those mousedown events which occur on top the box. This can easily be handled using event streams as follows:

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);
});

Now you have a stream of mousedown events on top of your box.

Next we get a stream of mouseup events because dragging stops once you pick up your finger from the mouse button:

var up = getEventStream("mouseup", window);

We get mouseup events for the entire window because the user should be able to mouse the mouse outside the canvas and release it.

Next we merge the dragStart and up event streams to create a single dragStartStop event stream:

var dragStartStop = dragStart.merge(up).map(function (x) {
    return x.left;
});

Events from the up event stream don't have any useful information. They only serve to mark that the user has stopped dragging. Hence we only care about events from the left event stream.

Coming back, to actually drag the box we need mousemove events. So let's get a mousemove event stream:

var move = getEventStream("mousemove", canvas).map(function (event) {
    return {
        x: event.clientX - left,
        y: event.clientY - top
    };
});

Like with the dragStart stream we only want the coordinates of mousemove events relative to the canvas.

Now we can merge the dragStartStop and the move streams to create the final drag stream:

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
    };
});

Here we scan the events of the merged streams to create a "property event stream" of previous and current mouse positions when the user is dragging the box. We filter those mousemove events when the user is dragging the box and we get the difference in positions between the previous and current mousemove events.

Now we can draw the box being dragged:

drag.map(function (position) {
    box.x += position.dx;
    box.y += position.dy;

    context.clearRect(0, 0, width, height);
    box.bind(context).fill();
});

That's it. Simple right? See the demo: http://jsfiddle.net/PC3m8/


So what do we conclude from this? Event streams are awesome and you should use streams instead of creating big monolithic event listeners. They make your code more readable, understandable and maintainable and they make everybody's life simpler.

分享给朋友:
您可能感兴趣的文章:
随机阅读: