我试图在播放器移动时在画布上移动一个框,但是无法找出正确的数学方法,因此尝试在画布上展示的类似于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;
});
演示:http://jsfiddle.net/CezarisLT/tUXM3/
最佳答案
我喜欢使用事件流来解决此类问题。什么是事件流?这是一连串的事件。因此,让我们创建自己的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
合并流的事件,以在用户拖动框时创建先前和当前鼠标位置的“属性事件流”。当用户拖动框时,我们对那些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/
那么我们可以从中得出什么结论呢?事件流很棒,您应该使用流而不是创建大型的整体事件侦听器。它们使您的代码更具可读性,可理解性和可维护性,并使每个人的生活变得更简单。
关于javascript - 用鼠标光标移动框,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20775530/