使用鼠标光标移动Box

时间:2013-12-25 18:11:59

标签: javascript math

我试图在玩家移动的同时在画布上移动一个方框,但是无法弄清楚正确的数学,我试图做一些类似于画布上的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;
});

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

1 个答案:

答案 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结束,filterscanmerge。这让生活更轻松。


首先让我们看一下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. 它允许您处理事件流,创建一个全新的事件流。
  3. 现在让我们看一下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方法接受两个事件流并将它们合并为一个事件流。要区分哪个事件流源自哪个事件,我们会将每个事件标记为leftright


    现在我们了解了事件流,让我们使用它们在画布上创建一个可拖动的框,看看它们如何让生活如此简单。

    我们要做的第一件事是设置画布:

    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个事件,因为用户应该能够将鼠标放在画布外面并将其释放。

    接下来,我们合并dragStartup个事件流,以创建一个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 dragStartStopmove流来创建最终的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 mousemovemousemove个事件,我们会看到之前和当前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/


    那么我们从中得出什么结论呢?事件流很棒,您应该使用流而不是创建大型单片事件侦听器。它们使您的代码更具可读性,可理解性和可维护性,并且使每个人的生活变得更加简单。