javascript - 用鼠标光标移动框

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




演示: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遍历,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方法有两件事:


它允许您订阅事件。
它允许您处理事件流,创建全新的事件流。


现在让我们看一下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方法采用两个事件流,并将它们合并为一个事件流。为了区分哪个事件流是哪个事件源,我们将每个事件标记为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事件进行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/

相关文章:

Javascript提示并提交

javascript - 如何使用 D3 js(v3) 中的循环在 SVG 元素中创建可变数量的图像或圆形元素

javascript - 使用javascript生成不超过整数的唯一id

javascript - 省略 a 元素中的 href 属性?

javascript - Openlayers WKT 返回太大的 GPS 坐标

ios - 圆角 : How to calculate Fillet radius?

python - 如何检查一个点是否在一条线下方?

java - 在Java深度生成列表n层的所有组合

math - 将一组共面的 3D 点映射到它们的平面 2D 坐标

java - 倒数计时器? (毫秒精度)?