我试图在玩家移动的同时在画布上移动一个方框,但是无法弄清楚正确的数学,我试图做一些类似于画布上的jquery函数的可拖动的东西。
到目前为止我的代码:
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;
});
答案 0 :(得分: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
方法做了两件事:
现在让我们看一下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
方法允许我们创建“properties”,它会在不同事件之间进行更改,从而创建新的“属性事件流”。这是一个非常有用的功能,我将在下面演示如何使用。
最后我们有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
合并流的事件,以便在用户拖动框时创建先前和当前鼠标位置的“属性事件流”。当用户拖动框时,我们会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();
});
事件之间的位置差异。
现在我们可以绘制被拖动的框:
{{1}}
就是这样。简单吧?请参阅演示:http://jsfiddle.net/PC3m8/
那么我们从中得出什么结论呢?事件流很棒,您应该使用流而不是创建大型单片事件侦听器。它们使您的代码更具可读性,可理解性和可维护性,并且使每个人的生活变得更加简单。